Ver código fonte

Initial XRFaceTrackingProvider and XRFaceTracker work.

Updated to XRFaceModifier3D.
Malcolm Nixon 1 ano atrás
pai
commit
7d1a1abe76

+ 22 - 0
doc/classes/XRFaceModifier3D.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="XRFaceModifier3D" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+	<brief_description>
+		A node for driving standard face meshes from [XRFaceTracker] weights.
+	</brief_description>
+	<description>
+		This node applies weights from a [XRFaceTracker] to a mesh with supporting face blend shapes.
+		The [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/unified-blendshapes]Unified Expressions[/url] blend shapes are supported, as well as ARKit and SRanipal blend shapes.
+		The node attempts to identify blend shapes based on name matching. Blend shapes should match the names listed in the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/compatibility/overview]Unified Expressions Compatibility[/url] chart.
+	</description>
+	<tutorials>
+		<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
+	</tutorials>
+	<members>
+		<member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&amp;&quot;/user/head&quot;">
+			The [XRFaceTracker] path.
+		</member>
+		<member name="target" type="NodePath" setter="set_target" getter="get_target" default="NodePath(&quot;&quot;)">
+			The [NodePath] of the face [MeshInstance3D].
+		</member>
+	</members>
+</class>

+ 469 - 0
doc/classes/XRFaceTracker.xml

@@ -0,0 +1,469 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="XRFaceTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+	<brief_description>
+		A tracked face.
+	</brief_description>
+	<description>
+		An instance of this object represents a tracked face and its corresponding blend shapes. The blend shapes come from the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/unified-blendshapes]Unified Expressions[/url] standard, and contain extended details and visuals for each blend shape. Additionally the [url=https://docs.vrcft.io/docs/tutorial-avatars/tutorial-avatars-extras/compatibility/overview]Tracking Standard Comparison[/url] page documents the relationship between Unified Expressions and other standards.
+		As face trackers are turned on they are registered with the [XRServer].
+	</description>
+	<tutorials>
+		<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
+	</tutorials>
+	<methods>
+		<method name="get_blend_shape" qualifiers="const">
+			<return type="float" />
+			<param index="0" name="blend_shape" type="int" enum="XRFaceTracker.BlendShapeEntry" />
+			<description>
+				Returns the requested face blend shape weight.
+			</description>
+		</method>
+		<method name="set_blend_shape">
+			<return type="void" />
+			<param index="0" name="blend_shape" type="int" enum="XRFaceTracker.BlendShapeEntry" />
+			<param index="1" name="weight" type="float" />
+			<description>
+				Sets a face blend shape weight.
+			</description>
+		</method>
+	</methods>
+	<members>
+		<member name="blend_shapes" type="PackedFloat32Array" setter="set_blend_shapes" getter="get_blend_shapes" default="PackedFloat32Array()">
+			The array of face blend shape weights with indices corresponding to the [enum BlendShapeEntry] enum.
+		</member>
+	</members>
+	<constants>
+		<constant name="FT_EYE_LOOK_OUT_RIGHT" value="0" enum="BlendShapeEntry">
+			Right eye looks outwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_IN_RIGHT" value="1" enum="BlendShapeEntry">
+			Right eye looks inwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_UP_RIGHT" value="2" enum="BlendShapeEntry">
+			Right eye looks upwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_DOWN_RIGHT" value="3" enum="BlendShapeEntry">
+			Right eye looks downwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_OUT_LEFT" value="4" enum="BlendShapeEntry">
+			Left eye looks outwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_IN_LEFT" value="5" enum="BlendShapeEntry">
+			Left eye looks inwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_UP_LEFT" value="6" enum="BlendShapeEntry">
+			Left eye looks upwards.
+		</constant>
+		<constant name="FT_EYE_LOOK_DOWN_LEFT" value="7" enum="BlendShapeEntry">
+			Left eye looks downwards.
+		</constant>
+		<constant name="FT_EYE_CLOSED_RIGHT" value="8" enum="BlendShapeEntry">
+			Closes the right eyelid.
+		</constant>
+		<constant name="FT_EYE_CLOSED_LEFT" value="9" enum="BlendShapeEntry">
+			Closes the left eyelid.
+		</constant>
+		<constant name="FT_EYE_SQUINT_RIGHT" value="10" enum="BlendShapeEntry">
+			Squeezes the right eye socket muscles.
+		</constant>
+		<constant name="FT_EYE_SQUINT_LEFT" value="11" enum="BlendShapeEntry">
+			Squeezes the left eye socket muscles.
+		</constant>
+		<constant name="FT_EYE_WIDE_RIGHT" value="12" enum="BlendShapeEntry">
+			Right eyelid widens beyond relaxed.
+		</constant>
+		<constant name="FT_EYE_WIDE_LEFT" value="13" enum="BlendShapeEntry">
+			Left eyelid widens beyond relaxed.
+		</constant>
+		<constant name="FT_EYE_DILATION_RIGHT" value="14" enum="BlendShapeEntry">
+			Dilates the right eye pupil.
+		</constant>
+		<constant name="FT_EYE_DILATION_LEFT" value="15" enum="BlendShapeEntry">
+			Dilates the left eye pupil.
+		</constant>
+		<constant name="FT_EYE_CONSTRICT_RIGHT" value="16" enum="BlendShapeEntry">
+			Constricts the right eye pupil.
+		</constant>
+		<constant name="FT_EYE_CONSTRICT_LEFT" value="17" enum="BlendShapeEntry">
+			Constricts the left eye pupil.
+		</constant>
+		<constant name="FT_BROW_PINCH_RIGHT" value="18" enum="BlendShapeEntry">
+			Right eyebrow pinches in.
+		</constant>
+		<constant name="FT_BROW_PINCH_LEFT" value="19" enum="BlendShapeEntry">
+			Left eyebrow pinches in.
+		</constant>
+		<constant name="FT_BROW_LOWERER_RIGHT" value="20" enum="BlendShapeEntry">
+			Outer right eyebrow pulls down.
+		</constant>
+		<constant name="FT_BROW_LOWERER_LEFT" value="21" enum="BlendShapeEntry">
+			Outer left eyebrow pulls down.
+		</constant>
+		<constant name="FT_BROW_INNER_UP_RIGHT" value="22" enum="BlendShapeEntry">
+			Inner right eyebrow pulls up.
+		</constant>
+		<constant name="FT_BROW_INNER_UP_LEFT" value="23" enum="BlendShapeEntry">
+			Inner left eyebrow pulls up.
+		</constant>
+		<constant name="FT_BROW_OUTER_UP_RIGHT" value="24" enum="BlendShapeEntry">
+			Outer right eyebrow pulls up.
+		</constant>
+		<constant name="FT_BROW_OUTER_UP_LEFT" value="25" enum="BlendShapeEntry">
+			Outer left eyebrow pulls up.
+		</constant>
+		<constant name="FT_NOSE_SNEER_RIGHT" value="26" enum="BlendShapeEntry">
+			Right side face sneers.
+		</constant>
+		<constant name="FT_NOSE_SNEER_LEFT" value="27" enum="BlendShapeEntry">
+			Left side face sneers.
+		</constant>
+		<constant name="FT_NASAL_DILATION_RIGHT" value="28" enum="BlendShapeEntry">
+			Right side nose canal dilates.
+		</constant>
+		<constant name="FT_NASAL_DILATION_LEFT" value="29" enum="BlendShapeEntry">
+			Left side nose canal dilates.
+		</constant>
+		<constant name="FT_NASAL_CONSTRICT_RIGHT" value="30" enum="BlendShapeEntry">
+			Right side nose canal constricts.
+		</constant>
+		<constant name="FT_NASAL_CONSTRICT_LEFT" value="31" enum="BlendShapeEntry">
+			Left side nose canal constricts.
+		</constant>
+		<constant name="FT_CHEEK_SQUINT_RIGHT" value="32" enum="BlendShapeEntry">
+			Raises the right side cheek.
+		</constant>
+		<constant name="FT_CHEEK_SQUINT_LEFT" value="33" enum="BlendShapeEntry">
+			Raises the left side cheek.
+		</constant>
+		<constant name="FT_CHEEK_PUFF_RIGHT" value="34" enum="BlendShapeEntry">
+			Puffs the right side cheek.
+		</constant>
+		<constant name="FT_CHEEK_PUFF_LEFT" value="35" enum="BlendShapeEntry">
+			Puffs the left side cheek.
+		</constant>
+		<constant name="FT_CHEEK_SUCK_RIGHT" value="36" enum="BlendShapeEntry">
+			Sucks in the right side cheek.
+		</constant>
+		<constant name="FT_CHEEK_SUCK_LEFT" value="37" enum="BlendShapeEntry">
+			Sucks in the left side cheek.
+		</constant>
+		<constant name="FT_JAW_OPEN" value="38" enum="BlendShapeEntry">
+			Opens jawbone.
+		</constant>
+		<constant name="FT_MOUTH_CLOSED" value="39" enum="BlendShapeEntry">
+			Closes the mouth.
+		</constant>
+		<constant name="FT_JAW_RIGHT" value="40" enum="BlendShapeEntry">
+			Pushes jawbone right.
+		</constant>
+		<constant name="FT_JAW_LEFT" value="41" enum="BlendShapeEntry">
+			Pushes jawbone left.
+		</constant>
+		<constant name="FT_JAW_FORWARD" value="42" enum="BlendShapeEntry">
+			Pushes jawbone forward.
+		</constant>
+		<constant name="FT_JAW_BACKWARD" value="43" enum="BlendShapeEntry">
+			Pushes jawbone backward.
+		</constant>
+		<constant name="FT_JAW_CLENCH" value="44" enum="BlendShapeEntry">
+			Flexes jaw muscles.
+		</constant>
+		<constant name="FT_JAW_MANDIBLE_RAISE" value="45" enum="BlendShapeEntry">
+			Raises the jawbone.
+		</constant>
+		<constant name="FT_LIP_SUCK_UPPER_RIGHT" value="46" enum="BlendShapeEntry">
+			Upper right lip part tucks in the mouth.
+		</constant>
+		<constant name="FT_LIP_SUCK_UPPER_LEFT" value="47" enum="BlendShapeEntry">
+			Upper left lip part tucks in the mouth.
+		</constant>
+		<constant name="FT_LIP_SUCK_LOWER_RIGHT" value="48" enum="BlendShapeEntry">
+			Lower right lip part tucks in the mouth.
+		</constant>
+		<constant name="FT_LIP_SUCK_LOWER_LEFT" value="49" enum="BlendShapeEntry">
+			Lower left lip part tucks in the mouth.
+		</constant>
+		<constant name="FT_LIP_SUCK_CORNER_RIGHT" value="50" enum="BlendShapeEntry">
+			Right lip corner folds into the mouth.
+		</constant>
+		<constant name="FT_LIP_SUCK_CORNER_LEFT" value="51" enum="BlendShapeEntry">
+			Left lip corner folds into the mouth.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_UPPER_RIGHT" value="52" enum="BlendShapeEntry">
+			Upper right lip part pushes into a funnel.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_UPPER_LEFT" value="53" enum="BlendShapeEntry">
+			Upper left lip part pushes into a funnel.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_LOWER_RIGHT" value="54" enum="BlendShapeEntry">
+			Lower right lip part pushes into a funnel.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_LOWER_LEFT" value="55" enum="BlendShapeEntry">
+			Lower left lip part pushes into a funnel.
+		</constant>
+		<constant name="FT_LIP_PUCKER_UPPER_RIGHT" value="56" enum="BlendShapeEntry">
+			Upper right lip part pushes outwards.
+		</constant>
+		<constant name="FT_LIP_PUCKER_UPPER_LEFT" value="57" enum="BlendShapeEntry">
+			Upper left lip part pushes outwards.
+		</constant>
+		<constant name="FT_LIP_PUCKER_LOWER_RIGHT" value="58" enum="BlendShapeEntry">
+			Lower right lip part pushes outwards.
+		</constant>
+		<constant name="FT_LIP_PUCKER_LOWER_LEFT" value="59" enum="BlendShapeEntry">
+			Lower left lip part pushes outwards.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_UP_RIGHT" value="60" enum="BlendShapeEntry">
+			Upper right part of the lip pulls up.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_UP_LEFT" value="61" enum="BlendShapeEntry">
+			Upper left part of the lip pulls up.
+		</constant>
+		<constant name="FT_MOUTH_LOWER_DOWN_RIGHT" value="62" enum="BlendShapeEntry">
+			Lower right part of the lip pulls up.
+		</constant>
+		<constant name="FT_MOUTH_LOWER_DOWN_LEFT" value="63" enum="BlendShapeEntry">
+			Lower left part of the lip pulls up.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_DEEPEN_RIGHT" value="64" enum="BlendShapeEntry">
+			Upper right lip part pushes in the cheek.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_DEEPEN_LEFT" value="65" enum="BlendShapeEntry">
+			Upper left lip part pushes in the cheek.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_RIGHT" value="66" enum="BlendShapeEntry">
+			Moves upper lip right.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_LEFT" value="67" enum="BlendShapeEntry">
+			Moves upper lip left.
+		</constant>
+		<constant name="FT_MOUTH_LOWER_RIGHT" value="68" enum="BlendShapeEntry">
+			Moves lower lip right.
+		</constant>
+		<constant name="FT_MOUTH_LOWER_LEFT" value="69" enum="BlendShapeEntry">
+			Moves lower lip left.
+		</constant>
+		<constant name="FT_MOUTH_CORNER_PULL_RIGHT" value="70" enum="BlendShapeEntry">
+			Right lip corner pulls diagonally up and out.
+		</constant>
+		<constant name="FT_MOUTH_CORNER_PULL_LEFT" value="71" enum="BlendShapeEntry">
+			Left lip corner pulls diagonally up and out.
+		</constant>
+		<constant name="FT_MOUTH_CORNER_SLANT_RIGHT" value="72" enum="BlendShapeEntry">
+			Right corner lip slants up.
+		</constant>
+		<constant name="FT_MOUTH_CORNER_SLANT_LEFT" value="73" enum="BlendShapeEntry">
+			Left corner lip slants up.
+		</constant>
+		<constant name="FT_MOUTH_FROWN_RIGHT" value="74" enum="BlendShapeEntry">
+			Right corner lip pulls down.
+		</constant>
+		<constant name="FT_MOUTH_FROWN_LEFT" value="75" enum="BlendShapeEntry">
+			Left corner lip pulls down.
+		</constant>
+		<constant name="FT_MOUTH_STRETCH_RIGHT" value="76" enum="BlendShapeEntry">
+			Mouth corner lip pulls out and down.
+		</constant>
+		<constant name="FT_MOUTH_STRETCH_LEFT" value="77" enum="BlendShapeEntry">
+			Mouth corner lip pulls out and down.
+		</constant>
+		<constant name="FT_MOUTH_DIMPLE_RIGHT" value="78" enum="BlendShapeEntry">
+			Right lip corner is pushed backwards.
+		</constant>
+		<constant name="FT_MOUTH_DIMPLE_LEFT" value="79" enum="BlendShapeEntry">
+			Left lip corner is pushed backwards.
+		</constant>
+		<constant name="FT_MOUTH_RAISER_UPPER" value="80" enum="BlendShapeEntry">
+			Raises and slightly pushes out the upper mouth.
+		</constant>
+		<constant name="FT_MOUTH_RAISER_LOWER" value="81" enum="BlendShapeEntry">
+			Raises and slightly pushes out the lower mouth.
+		</constant>
+		<constant name="FT_MOUTH_PRESS_RIGHT" value="82" enum="BlendShapeEntry">
+			Right side lips press and flatten together vertically.
+		</constant>
+		<constant name="FT_MOUTH_PRESS_LEFT" value="83" enum="BlendShapeEntry">
+			Left side lips press and flatten together vertically.
+		</constant>
+		<constant name="FT_MOUTH_TIGHTENER_RIGHT" value="84" enum="BlendShapeEntry">
+			Right side lips squeeze together horizontally.
+		</constant>
+		<constant name="FT_MOUTH_TIGHTENER_LEFT" value="85" enum="BlendShapeEntry">
+			Left side lips squeeze together horizontally.
+		</constant>
+		<constant name="FT_TONGUE_OUT" value="86" enum="BlendShapeEntry">
+			Tongue visibly sticks out of the mouth.
+		</constant>
+		<constant name="FT_TONGUE_UP" value="87" enum="BlendShapeEntry">
+			Tongue points upwards.
+		</constant>
+		<constant name="FT_TONGUE_DOWN" value="88" enum="BlendShapeEntry">
+			Tongue points downwards.
+		</constant>
+		<constant name="FT_TONGUE_RIGHT" value="89" enum="BlendShapeEntry">
+			Tongue points right.
+		</constant>
+		<constant name="FT_TONGUE_LEFT" value="90" enum="BlendShapeEntry">
+			Tongue points left.
+		</constant>
+		<constant name="FT_TONGUE_ROLL" value="91" enum="BlendShapeEntry">
+			Sides of the tongue funnel, creating a roll.
+		</constant>
+		<constant name="FT_TONGUE_BLEND_DOWN" value="92" enum="BlendShapeEntry">
+			Tongue arches up then down inside the mouth.
+		</constant>
+		<constant name="FT_TONGUE_CURL_UP" value="93" enum="BlendShapeEntry">
+			Tongue arches down then up inside the mouth.
+		</constant>
+		<constant name="FT_TONGUE_SQUISH" value="94" enum="BlendShapeEntry">
+			Tongue squishes together and thickens.
+		</constant>
+		<constant name="FT_TONGUE_FLAT" value="95" enum="BlendShapeEntry">
+			Tongue flattens and thins out.
+		</constant>
+		<constant name="FT_TONGUE_TWIST_RIGHT" value="96" enum="BlendShapeEntry">
+			Tongue tip rotates clockwise, with the rest following gradually.
+		</constant>
+		<constant name="FT_TONGUE_TWIST_LEFT" value="97" enum="BlendShapeEntry">
+			Tongue tip rotates counter-clockwise, with the rest following gradually.
+		</constant>
+		<constant name="FT_SOFT_PALATE_CLOSE" value="98" enum="BlendShapeEntry">
+			Inner mouth throat closes.
+		</constant>
+		<constant name="FT_THROAT_SWALLOW" value="99" enum="BlendShapeEntry">
+			The Adam's apple visibly swallows.
+		</constant>
+		<constant name="FT_NECK_FLEX_RIGHT" value="100" enum="BlendShapeEntry">
+			Right side neck visibly flexes.
+		</constant>
+		<constant name="FT_NECK_FLEX_LEFT" value="101" enum="BlendShapeEntry">
+			Left side neck visibly flexes.
+		</constant>
+		<constant name="FT_EYE_CLOSED" value="102" enum="BlendShapeEntry">
+			Closes both eye lids.
+		</constant>
+		<constant name="FT_EYE_WIDE" value="103" enum="BlendShapeEntry">
+			Widens both eye lids.
+		</constant>
+		<constant name="FT_EYE_SQUINT" value="104" enum="BlendShapeEntry">
+			Squints both eye lids.
+		</constant>
+		<constant name="FT_EYE_DILATION" value="105" enum="BlendShapeEntry">
+			Dilates both pupils.
+		</constant>
+		<constant name="FT_EYE_CONSTRICT" value="106" enum="BlendShapeEntry">
+			Constricts both pupils.
+		</constant>
+		<constant name="FT_BROW_DOWN_RIGHT" value="107" enum="BlendShapeEntry">
+			Pulls the right eyebrow down and in.
+		</constant>
+		<constant name="FT_BROW_DOWN_LEFT" value="108" enum="BlendShapeEntry">
+			Pulls the left eyebrow down and in.
+		</constant>
+		<constant name="FT_BROW_DOWN" value="109" enum="BlendShapeEntry">
+			Pulls both eyebrows down and in.
+		</constant>
+		<constant name="FT_BROW_UP_RIGHT" value="110" enum="BlendShapeEntry">
+			Right brow appears worried.
+		</constant>
+		<constant name="FT_BROW_UP_LEFT" value="111" enum="BlendShapeEntry">
+			Left brow appears worried.
+		</constant>
+		<constant name="FT_BROW_UP" value="112" enum="BlendShapeEntry">
+			Both brows appear worried.
+		</constant>
+		<constant name="FT_NOSE_SNEER" value="113" enum="BlendShapeEntry">
+			Entire face sneers.
+		</constant>
+		<constant name="FT_NASAL_DILATION" value="114" enum="BlendShapeEntry">
+			Both nose canals dilate.
+		</constant>
+		<constant name="FT_NASAL_CONSTRICT" value="115" enum="BlendShapeEntry">
+			Both nose canals constrict.
+		</constant>
+		<constant name="FT_CHEEK_PUFF" value="116" enum="BlendShapeEntry">
+			Puffs both cheeks.
+		</constant>
+		<constant name="FT_CHEEK_SUCK" value="117" enum="BlendShapeEntry">
+			Sucks in both cheeks.
+		</constant>
+		<constant name="FT_CHEEK_SQUINT" value="118" enum="BlendShapeEntry">
+			Raises both cheeks.
+		</constant>
+		<constant name="FT_LIP_SUCK_UPPER" value="119" enum="BlendShapeEntry">
+			Tucks in the upper lips.
+		</constant>
+		<constant name="FT_LIP_SUCK_LOWER" value="120" enum="BlendShapeEntry">
+			Tucks in the lower lips.
+		</constant>
+		<constant name="FT_LIP_SUCK" value="121" enum="BlendShapeEntry">
+			Tucks in both lips.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_UPPER" value="122" enum="BlendShapeEntry">
+			Funnels in the upper lips.
+		</constant>
+		<constant name="FT_LIP_FUNNEL_LOWER" value="123" enum="BlendShapeEntry">
+			Funnels in the lower lips.
+		</constant>
+		<constant name="FT_LIP_FUNNEL" value="124" enum="BlendShapeEntry">
+			Funnels in both lips.
+		</constant>
+		<constant name="FT_LIP_PUCKER_UPPER" value="125" enum="BlendShapeEntry">
+			Upper lip part pushes outwards.
+		</constant>
+		<constant name="FT_LIP_PUCKER_LOWER" value="126" enum="BlendShapeEntry">
+			Lower lip part pushes outwards.
+		</constant>
+		<constant name="FT_LIP_PUCKER" value="127" enum="BlendShapeEntry">
+			Lips push outwards.
+		</constant>
+		<constant name="FT_MOUTH_UPPER_UP" value="128" enum="BlendShapeEntry">
+			Raises the upper lips.
+		</constant>
+		<constant name="FT_MOUTH_LOWER_DOWN" value="129" enum="BlendShapeEntry">
+			Lowers the lower lips.
+		</constant>
+		<constant name="FT_MOUTH_OPEN" value="130" enum="BlendShapeEntry">
+			Mouth opens, revealing teeth.
+		</constant>
+		<constant name="FT_MOUTH_RIGHT" value="131" enum="BlendShapeEntry">
+			Moves mouth right.
+		</constant>
+		<constant name="FT_MOUTH_LEFT" value="132" enum="BlendShapeEntry">
+			Moves mouth left.
+		</constant>
+		<constant name="FT_MOUTH_SMILE_RIGHT" value="133" enum="BlendShapeEntry">
+			Right side of the mouth smiles.
+		</constant>
+		<constant name="FT_MOUTH_SMILE_LEFT" value="134" enum="BlendShapeEntry">
+			Left side of the mouth smiles.
+		</constant>
+		<constant name="FT_MOUTH_SMILE" value="135" enum="BlendShapeEntry">
+			Mouth expresses a smile.
+		</constant>
+		<constant name="FT_MOUTH_SAD_RIGHT" value="136" enum="BlendShapeEntry">
+			Right side of the mouth expresses sadness.
+		</constant>
+		<constant name="FT_MOUTH_SAD_LEFT" value="137" enum="BlendShapeEntry">
+			Left side of the mouth expresses sadness.
+		</constant>
+		<constant name="FT_MOUTH_SAD" value="138" enum="BlendShapeEntry">
+			Mouth expresses sadness.
+		</constant>
+		<constant name="FT_MOUTH_STRETCH" value="139" enum="BlendShapeEntry">
+			Mouth stretches.
+		</constant>
+		<constant name="FT_MOUTH_DIMPLE" value="140" enum="BlendShapeEntry">
+			Lip corners dimple.
+		</constant>
+		<constant name="FT_MOUTH_TIGHTENER" value="141" enum="BlendShapeEntry">
+			Mouth tightens.
+		</constant>
+		<constant name="FT_MOUTH_PRESS" value="142" enum="BlendShapeEntry">
+			Mouth presses together.
+		</constant>
+		<constant name="FT_MAX" value="143" enum="BlendShapeEntry">
+			Represents the size of the [enum BlendShapeEntry] enum.
+		</constant>
+	</constants>
+</class>

+ 48 - 0
doc/classes/XRServer.xml

@@ -10,6 +10,14 @@
 		<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
 	</tutorials>
 	<methods>
+		<method name="add_face_tracker">
+			<return type="void" />
+			<param index="0" name="tracker_name" type="StringName" />
+			<param index="1" name="face_tracker" type="XRFaceTracker" />
+			<description>
+				Registers a new [XRFaceTracker] that tracks the blend shapes of a face.
+			</description>
+		</method>
 		<method name="add_interface">
 			<return type="void" />
 			<param index="0" name="interface" type="XRInterface" />
@@ -50,6 +58,19 @@
 				Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it.
 			</description>
 		</method>
+		<method name="get_face_tracker" qualifiers="const">
+			<return type="XRFaceTracker" />
+			<param index="0" name="tracker_name" type="StringName" />
+			<description>
+				Returns the [XRFaceTracker] with the given tracker name.
+			</description>
+		</method>
+		<method name="get_face_trackers" qualifiers="const">
+			<return type="Dictionary" />
+			<description>
+				Returns a dictionary of the registered face trackers. Each element of the dictionary is a tracker name mapping to the [XRFaceTracker] instance.
+			</description>
+		</method>
 		<method name="get_hmd_transform">
 			<return type="Transform3D" />
 			<description>
@@ -95,6 +116,13 @@
 				Returns a dictionary of trackers for [param tracker_types].
 			</description>
 		</method>
+		<method name="remove_face_tracker">
+			<return type="void" />
+			<param index="0" name="tracker_name" type="StringName" />
+			<description>
+				Removes a registered [XRFaceTracker].
+			</description>
+		</method>
 		<method name="remove_interface">
 			<return type="void" />
 			<param index="0" name="interface" type="XRInterface" />
@@ -123,6 +151,26 @@
 		</member>
 	</members>
 	<signals>
+		<signal name="face_tracker_added">
+			<param index="0" name="tracker_name" type="StringName" />
+			<param index="1" name="face_tracker" type="XRFaceTracker" />
+			<description>
+				Emitted when a new face tracker is added.
+			</description>
+		</signal>
+		<signal name="face_tracker_removed">
+			<param index="0" name="tracker_name" type="StringName" />
+			<description>
+				Emitted when a face tracker is removed.
+			</description>
+		</signal>
+		<signal name="face_tracker_updated">
+			<param index="0" name="tracker_name" type="StringName" />
+			<param index="1" name="face_tracker" type="XRFaceTracker" />
+			<description>
+				Emitted when an existing face tracker is updated.
+			</description>
+		</signal>
 		<signal name="interface_added">
 			<param index="0" name="interface_name" type="StringName" />
 			<description>

+ 615 - 0
scene/3d/xr_face_modifier_3d.cpp

@@ -0,0 +1,615 @@
+/**************************************************************************/
+/*  xr_face_modifier_3d.cpp                                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "xr_face_modifier_3d.h"
+
+#include "servers/xr/xr_face_tracker.h"
+#include "servers/xr_server.h"
+
+// This method takes the name of a mesh blend shape and returns the
+// corresponding XRFaceTracker blend shape. If no match is
+// found then the function returns -1.
+static int find_face_blend_shape(const StringName &p_name) {
+	// Entry for blend shape name table.
+	struct blend_map_entry {
+		int blend;
+		const char *name[4];
+	};
+
+	// Table of blend shape names.
+	//
+	// This table consists of the XRFaceTracker blend shape and
+	// the corresponding names (lowercase and no underscore) of:
+	// - The Unified Expression blend shape name.
+	// - The ARKit blend shape name (if present and different).
+	// - The SRanipal blend shape name (if present and different).
+	// - The Meta blend shape name (if present and different).
+	static constexpr blend_map_entry blend_map[] = {
+		{ XRFaceTracker::FT_EYE_LOOK_OUT_RIGHT,
+				{ "eyelookoutright", "eyerightright", "eyeslookoutr" } },
+		{ XRFaceTracker::FT_EYE_LOOK_IN_RIGHT,
+				{ "eyelookinright", "eyerightleft", "eyeslookinr" } },
+		{ XRFaceTracker::FT_EYE_LOOK_UP_RIGHT,
+				{ "eyelookupright", "eyerightlookup", "eyeslookupr" } },
+		{ XRFaceTracker::FT_EYE_LOOK_DOWN_RIGHT,
+				{ "eyelookdownright", "eyerightlookdown", "eyeslookdownr" } },
+		{ XRFaceTracker::FT_EYE_LOOK_OUT_LEFT,
+				{ "eyelookoutleft", "eyeleftleft", "eyeslookoutl" } },
+		{ XRFaceTracker::FT_EYE_LOOK_IN_LEFT,
+				{ "eyelookinleft", "eyeleftright", "eyeslookinl" } },
+		{ XRFaceTracker::FT_EYE_LOOK_UP_LEFT,
+				{ "eyelookupleft", "eyeleftlookup", "eyeslookupl" } },
+		{ XRFaceTracker::FT_EYE_LOOK_DOWN_LEFT,
+				{ "eyelookdownleft", "eyeleftlookdown", "eyeslookdownl" } },
+		{ XRFaceTracker::FT_EYE_CLOSED_RIGHT,
+				{ "eyeclosedright", "eyeblinkright", "eyerightblink", "eyesclosedr" } },
+		{ XRFaceTracker::FT_EYE_CLOSED_LEFT,
+				{ "eyeclosedleft", "eyeblinkleft", "eyeleftblink", "eyesclosedl" } },
+		{ XRFaceTracker::FT_EYE_SQUINT_RIGHT,
+				{ "eyesquintright", "eyessquintr" } },
+		{ XRFaceTracker::FT_EYE_SQUINT_LEFT,
+				{ "eyesquintleft", "eyessquintl" } },
+		{ XRFaceTracker::FT_EYE_WIDE_RIGHT,
+				{ "eyewideright", "eyerightwide", "eyeswidenr" } },
+		{ XRFaceTracker::FT_EYE_WIDE_LEFT,
+				{ "eyewideleft", "eyeleftwide", "eyeswidenl" } },
+		{ XRFaceTracker::FT_EYE_DILATION_RIGHT,
+				{ "eyedilationright", "eyerightdilation" } },
+		{ XRFaceTracker::FT_EYE_DILATION_LEFT,
+				{ "eyedilationleft", "eyeleftdilation" } },
+		{ XRFaceTracker::FT_EYE_CONSTRICT_RIGHT,
+				{ "eyeconstrictright", "eyerightconstrict" } },
+		{ XRFaceTracker::FT_EYE_CONSTRICT_LEFT,
+				{ "eyeconstrictleft", "eyeleftconstrict" } },
+		{ XRFaceTracker::FT_BROW_PINCH_RIGHT,
+				{ "browpinchright" } },
+		{ XRFaceTracker::FT_BROW_PINCH_LEFT,
+				{ "browpinchleft" } },
+		{ XRFaceTracker::FT_BROW_LOWERER_RIGHT,
+				{ "browlowererright" } },
+		{ XRFaceTracker::FT_BROW_LOWERER_LEFT,
+				{ "browlowererleft" } },
+		{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT,
+				{ "browinnerupright", "innerbrowraiserr" } },
+		{ XRFaceTracker::FT_BROW_INNER_UP_LEFT,
+				{ "browinnerupleft", "innerbrowraiserl" } },
+		{ XRFaceTracker::FT_BROW_OUTER_UP_RIGHT,
+				{ "browouterupright", "outerbrowraiserr" } },
+		{ XRFaceTracker::FT_BROW_OUTER_UP_LEFT,
+				{ "browouterupleft", "outerbrowraiserl" } },
+		{ XRFaceTracker::FT_NOSE_SNEER_RIGHT,
+				{ "nosesneerright", "nosewrinklerr" } },
+		{ XRFaceTracker::FT_NOSE_SNEER_LEFT,
+				{ "nosesneerleft", "nosewrinklerl" } },
+		{ XRFaceTracker::FT_NASAL_DILATION_RIGHT,
+				{ "nasaldilationright" } },
+		{ XRFaceTracker::FT_NASAL_DILATION_LEFT,
+				{ "nasaldilationleft" } },
+		{ XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT,
+				{ "nasalconstrictright" } },
+		{ XRFaceTracker::FT_NASAL_CONSTRICT_LEFT,
+				{ "nasalconstrictleft" } },
+		{ XRFaceTracker::FT_CHEEK_SQUINT_RIGHT,
+				{ "cheeksquintright", "cheekraiserr" } },
+		{ XRFaceTracker::FT_CHEEK_SQUINT_LEFT,
+				{ "cheeksquintleft", "cheekraiserl" } },
+		{ XRFaceTracker::FT_CHEEK_PUFF_RIGHT,
+				{ "cheekpuffright", "cheekpuffr" } },
+		{ XRFaceTracker::FT_CHEEK_PUFF_LEFT,
+				{ "cheekpuffleft", "cheekpuffl" } },
+		{ XRFaceTracker::FT_CHEEK_SUCK_RIGHT,
+				{ "cheeksuckright", "cheeksuckr" } },
+		{ XRFaceTracker::FT_CHEEK_SUCK_LEFT,
+				{ "cheeksuckleft", "cheeksuckl" } },
+		{ XRFaceTracker::FT_JAW_OPEN,
+				{ "jawopen", "jawdrop" } },
+		{ XRFaceTracker::FT_MOUTH_CLOSED,
+				{ "mouthclosed", "mouthclose", "mouthapeshape", "lipstoward" } },
+		{ XRFaceTracker::FT_JAW_RIGHT,
+				{ "jawright", "jawsidewaysright" } },
+		{ XRFaceTracker::FT_JAW_LEFT,
+				{ "jawleft", "jawsidewaysleft" } },
+		{ XRFaceTracker::FT_JAW_FORWARD,
+				{ "jawforward", "jawthrust" } },
+		{ XRFaceTracker::FT_JAW_BACKWARD,
+				{ "jawbackward" } },
+		{ XRFaceTracker::FT_JAW_CLENCH,
+				{ "jawclench" } },
+		{ XRFaceTracker::FT_JAW_MANDIBLE_RAISE,
+				{ "jawmandibleraise" } },
+		{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT,
+				{ "lipsuckupperright", "lipsuckrt" } },
+		{ XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT,
+				{ "lipsuckupperleft", "lipsucklt" } },
+		{ XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT,
+				{ "lipsucklowerright", "lipsuckrb" } },
+		{ XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT,
+				{ "lipsucklowerleft", "lipsucklb" } },
+		{ XRFaceTracker::FT_LIP_SUCK_CORNER_RIGHT,
+				{ "lipsuckcornerright" } },
+		{ XRFaceTracker::FT_LIP_SUCK_CORNER_LEFT,
+				{ "lipsuckcornerleft" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT,
+				{ "lipfunnelupperright", "lipfunnelerrt" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT,
+				{ "lipfunnelupperleft", "lipfunnelerlt" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT,
+				{ "lipfunnellowerright", "lipsuckrb" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT,
+				{ "lipfunnellowerleft", "lipsucklb" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT,
+				{ "lippuckerupperright" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT,
+				{ "lippuckerupperleft" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT,
+				{ "lippuckerlowerright" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT,
+				{ "lippuckerlowerleft" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT,
+				{ "mouthupperupright", "upperlipraiserr" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT,
+				{ "mouthupperupleft", "upperlipraiserl" } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT,
+				{ "mouthlowerdownright", "mouthlowerupright", "lowerlipdepressorr" } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT,
+				{ "mouthlowerdownleft", "mouthlowerupleft", "lowerlipdepressorl" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_RIGHT,
+				{ "mouthupperdeepenright" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_DEEPEN_LEFT,
+				{ "mouthupperdeepenleft" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_RIGHT,
+				{ "mouthupperright" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_LEFT,
+				{ "mouthupperleft" } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_RIGHT,
+				{ "mouthlowerright" } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_LEFT,
+				{ "mouthlowerleft" } },
+		{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT,
+				{ "mouthcornerpullright" } },
+		{ XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT,
+				{ "mouthcornerpullleft" } },
+		{ XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT,
+				{ "mouthcornerslantright" } },
+		{ XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT,
+				{ "mouthcornerslantleft" } },
+		{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT,
+				{ "mouthfrownright", "lipcornerdepressorr" } },
+		{ XRFaceTracker::FT_MOUTH_FROWN_LEFT,
+				{ "mouthfrownleft", "lipcornerdepressorl" } },
+		{ XRFaceTracker::FT_MOUTH_STRETCH_RIGHT,
+				{ "mouthstretchright", "lipstretcherr" } },
+		{ XRFaceTracker::FT_MOUTH_STRETCH_LEFT,
+				{ "mouthstretchleft", "lipstretcherl" } },
+		{ XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT,
+				{ "mouthdimplerright", "mouthdimpleright", "dimplerr" } },
+		{ XRFaceTracker::FT_MOUTH_DIMPLE_LEFT,
+				{ "mouthdimplerleft", "mouthdimpleleft", "dimplerl" } },
+		{ XRFaceTracker::FT_MOUTH_RAISER_UPPER,
+				{ "mouthraiserupper", "mouthshrugupper", "chinraisert" } },
+		{ XRFaceTracker::FT_MOUTH_RAISER_LOWER,
+				{ "mouthraiserlower", "mouthshruglower", "mouthloweroverlay", "chinraiserb" } },
+		{ XRFaceTracker::FT_MOUTH_PRESS_RIGHT,
+				{ "mouthpressright", "lippressorr" } },
+		{ XRFaceTracker::FT_MOUTH_PRESS_LEFT,
+				{ "mouthpressleft", "lippressorl" } },
+		{ XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT,
+				{ "mouthtightenerright", "liptightenerr" } },
+		{ XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT,
+				{ "mouthtightenerleft", "liptightenerl" } },
+		{ XRFaceTracker::FT_TONGUE_OUT,
+				{ "tongueout", "tonguelongstep2" } },
+		{ XRFaceTracker::FT_TONGUE_UP,
+				{ "tongueup" } },
+		{ XRFaceTracker::FT_TONGUE_DOWN,
+				{ "tonguedown" } },
+		{ XRFaceTracker::FT_TONGUE_RIGHT,
+				{ "tongueright" } },
+		{ XRFaceTracker::FT_TONGUE_LEFT,
+				{ "tongueleft" } },
+		{ XRFaceTracker::FT_TONGUE_ROLL,
+				{ "tongueroll" } },
+		{ XRFaceTracker::FT_TONGUE_BLEND_DOWN,
+				{ "tongueblenddown" } },
+		{ XRFaceTracker::FT_TONGUE_CURL_UP,
+				{ "tonguecurlup" } },
+		{ XRFaceTracker::FT_TONGUE_SQUISH,
+				{ "tonguesquish" } },
+		{ XRFaceTracker::FT_TONGUE_FLAT,
+				{ "tongueflat" } },
+		{ XRFaceTracker::FT_TONGUE_TWIST_RIGHT,
+				{ "tonguetwistright" } },
+		{ XRFaceTracker::FT_TONGUE_TWIST_LEFT,
+				{ "tonguetwistleft" } },
+		{ XRFaceTracker::FT_SOFT_PALATE_CLOSE,
+				{ "softpalateclose" } },
+		{ XRFaceTracker::FT_THROAT_SWALLOW,
+				{ "throatswallow" } },
+		{ XRFaceTracker::FT_NECK_FLEX_RIGHT,
+				{ "neckflexright" } },
+		{ XRFaceTracker::FT_NECK_FLEX_LEFT,
+				{ "neckflexleft" } },
+		{ XRFaceTracker::FT_EYE_CLOSED,
+				{ "eyeclosed" } },
+		{ XRFaceTracker::FT_EYE_WIDE,
+				{ "eyewide" } },
+		{ XRFaceTracker::FT_EYE_SQUINT,
+				{ "eyesquint" } },
+		{ XRFaceTracker::FT_EYE_DILATION,
+				{ "eyedilation" } },
+		{ XRFaceTracker::FT_EYE_CONSTRICT,
+				{ "eyeconstrict" } },
+		{ XRFaceTracker::FT_BROW_DOWN_RIGHT,
+				{ "browdownright", "browlowererr" } },
+		{ XRFaceTracker::FT_BROW_DOWN_LEFT,
+				{ "browdownleft", "browlowererl" } },
+		{ XRFaceTracker::FT_BROW_DOWN,
+				{ "browdown" } },
+		{ XRFaceTracker::FT_BROW_UP_RIGHT,
+				{ "browupright" } },
+		{ XRFaceTracker::FT_BROW_UP_LEFT,
+				{ "browupleft" } },
+		{ XRFaceTracker::FT_BROW_UP,
+				{ "browup" } },
+		{ XRFaceTracker::FT_NOSE_SNEER,
+				{ "nosesneer" } },
+		{ XRFaceTracker::FT_NASAL_DILATION,
+				{ "nasaldilation" } },
+		{ XRFaceTracker::FT_NASAL_CONSTRICT,
+				{ "nasalconstrict" } },
+		{ XRFaceTracker::FT_CHEEK_PUFF,
+				{ "cheekpuff" } },
+		{ XRFaceTracker::FT_CHEEK_SUCK,
+				{ "cheeksuck" } },
+		{ XRFaceTracker::FT_CHEEK_SQUINT,
+				{ "cheeksquint" } },
+		{ XRFaceTracker::FT_LIP_SUCK_UPPER,
+				{ "lipsuckupper", "mouthrollupper", "mouthupperinside" } },
+		{ XRFaceTracker::FT_LIP_SUCK_LOWER,
+				{ "lipsucklower", "mouthrolllower", "mouthlowerinside" } },
+		{ XRFaceTracker::FT_LIP_SUCK,
+				{ "lipsuck" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_UPPER,
+				{ "lipfunnelupper", "mouthupperoverturn" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_LOWER,
+				{ "lipfunnellower", "mouthloweroverturn" } },
+		{ XRFaceTracker::FT_LIP_FUNNEL,
+				{ "lipfunnel", "mouthfunnel" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_UPPER,
+				{ "lippuckerupper" } },
+		{ XRFaceTracker::FT_LIP_PUCKER_LOWER,
+				{ "lippuckerlower" } },
+		{ XRFaceTracker::FT_LIP_PUCKER,
+				{ "lippucker", "mouthpucker", "mouthpout" } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_UP,
+				{ "mouthupperup" } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_DOWN,
+				{ "mouthlowerdown" } },
+		{ XRFaceTracker::FT_MOUTH_OPEN,
+				{ "mouthopen" } },
+		{ XRFaceTracker::FT_MOUTH_RIGHT,
+				{ "mouthright" } },
+		{ XRFaceTracker::FT_MOUTH_LEFT,
+				{ "mouthleft" } },
+		{ XRFaceTracker::FT_MOUTH_SMILE_RIGHT,
+				{ "mouthsmileright", "lipcornerpullerr" } },
+		{ XRFaceTracker::FT_MOUTH_SMILE_LEFT,
+				{ "mouthsmileleft", "lipcornerpullerl" } },
+		{ XRFaceTracker::FT_MOUTH_SMILE,
+				{ "mouthsmile" } },
+		{ XRFaceTracker::FT_MOUTH_SAD_RIGHT,
+				{ "mouthsadright" } },
+		{ XRFaceTracker::FT_MOUTH_SAD_LEFT,
+				{ "mouthsadleft" } },
+		{ XRFaceTracker::FT_MOUTH_SAD,
+				{ "mouthsad" } },
+		{ XRFaceTracker::FT_MOUTH_STRETCH,
+				{ "mouthstretch" } },
+		{ XRFaceTracker::FT_MOUTH_DIMPLE,
+				{ "mouthdimple" } },
+		{ XRFaceTracker::FT_MOUTH_TIGHTENER,
+				{ "mouthtightener" } },
+		{ XRFaceTracker::FT_MOUTH_PRESS,
+				{ "mouthpress" } }
+	};
+
+	// Convert the name to lower-case and strip non-alphanumeric characters.
+	const String name = String(p_name).to_lower().replace("_", "");
+
+	// Iterate through the blend map.
+	for (const blend_map_entry &entry : blend_map) {
+		for (const char *n : entry.name) {
+			if (n == nullptr) {
+				break;
+			}
+
+			if (name == n) {
+				return entry.blend;
+			}
+		}
+	}
+
+	// Blend shape not found.
+	return -1;
+}
+
+// This method adds all the identified XRFaceTracker blend shapes of
+// the mesh to the p_blend_mapping map. The map is indexed by the
+// XRFaceTracker blend shape, and the value is the index of the mesh
+// blend shape.
+static void identify_face_blend_shapes(RBMap<int, int> &p_blend_mapping, const Ref<Mesh> &mesh) {
+	// Find all blend shapes.
+	const int count = mesh->get_blend_shape_count();
+	for (int i = 0; i < count; i++) {
+		const int blend = find_face_blend_shape(mesh->get_blend_shape_name(i));
+		if (blend >= 0) {
+			p_blend_mapping[blend] = i;
+		}
+	}
+}
+
+// This method removes any unified blend shapes from the p_blend_mapping map
+// if all the individual blend shapes are found and going to be driven.
+static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping) {
+	// Entry for unified blend table.
+	struct unified_blend_entry {
+		int unified;
+		int individual[4];
+	};
+
+	// Table of unified blend shapes.
+	//
+	// This table consists of:
+	// - The XRFaceTracker unified blend shape.
+	// - The individual blend shapes that make up the unified blend shape.
+	static constexpr unified_blend_entry unified_blends[] = {
+		{ XRFaceTracker::FT_EYE_CLOSED,
+				{ XRFaceTracker::FT_EYE_CLOSED_RIGHT, XRFaceTracker::FT_EYE_CLOSED_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_EYE_WIDE,
+				{ XRFaceTracker::FT_EYE_WIDE_RIGHT, XRFaceTracker::FT_EYE_WIDE_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_EYE_SQUINT,
+				{ XRFaceTracker::FT_EYE_SQUINT_RIGHT, XRFaceTracker::FT_EYE_SQUINT_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_EYE_DILATION,
+				{ XRFaceTracker::FT_EYE_DILATION_RIGHT, XRFaceTracker::FT_EYE_DILATION_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_EYE_CONSTRICT,
+				{ XRFaceTracker::FT_EYE_CONSTRICT_RIGHT, XRFaceTracker::FT_EYE_CONSTRICT_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_BROW_DOWN_RIGHT,
+				{ XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, -1, -1 } },
+		{ XRFaceTracker::FT_BROW_DOWN_LEFT,
+				{ XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_BROW_DOWN,
+				{ XRFaceTracker::FT_BROW_LOWERER_RIGHT, XRFaceTracker::FT_BROW_PINCH_RIGHT, XRFaceTracker::FT_BROW_LOWERER_LEFT, XRFaceTracker::FT_BROW_PINCH_LEFT } },
+		{ XRFaceTracker::FT_BROW_UP_RIGHT,
+				{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, -1, -1 } },
+		{ XRFaceTracker::FT_BROW_UP_LEFT,
+				{ XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_BROW_UP,
+				{ XRFaceTracker::FT_BROW_INNER_UP_RIGHT, XRFaceTracker::FT_BROW_OUTER_UP_RIGHT, XRFaceTracker::FT_BROW_INNER_UP_LEFT, XRFaceTracker::FT_BROW_OUTER_UP_LEFT } },
+		{ XRFaceTracker::FT_NOSE_SNEER,
+				{ XRFaceTracker::FT_NOSE_SNEER_RIGHT, XRFaceTracker::FT_NOSE_SNEER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_NASAL_DILATION,
+				{ XRFaceTracker::FT_NASAL_DILATION_RIGHT, XRFaceTracker::FT_NASAL_DILATION_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_NASAL_CONSTRICT,
+				{ XRFaceTracker::FT_NASAL_CONSTRICT_RIGHT, XRFaceTracker::FT_NASAL_CONSTRICT_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_CHEEK_PUFF,
+				{ XRFaceTracker::FT_CHEEK_PUFF_RIGHT, XRFaceTracker::FT_CHEEK_PUFF_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_CHEEK_SUCK,
+				{ XRFaceTracker::FT_CHEEK_SUCK_RIGHT, XRFaceTracker::FT_CHEEK_SUCK_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_CHEEK_SQUINT,
+				{ XRFaceTracker::FT_CHEEK_SQUINT_RIGHT, XRFaceTracker::FT_CHEEK_SQUINT_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_SUCK_UPPER,
+				{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_SUCK_LOWER,
+				{ XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_SUCK,
+				{ XRFaceTracker::FT_LIP_SUCK_UPPER_RIGHT, XRFaceTracker::FT_LIP_SUCK_UPPER_LEFT, XRFaceTracker::FT_LIP_SUCK_LOWER_RIGHT, XRFaceTracker::FT_LIP_SUCK_LOWER_LEFT } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_UPPER,
+				{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_FUNNEL_LOWER,
+				{ XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_FUNNEL,
+				{ XRFaceTracker::FT_LIP_FUNNEL_UPPER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_UPPER_LEFT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_RIGHT, XRFaceTracker::FT_LIP_FUNNEL_LOWER_LEFT } },
+		{ XRFaceTracker::FT_LIP_PUCKER_UPPER,
+				{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_PUCKER_LOWER,
+				{ XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_LIP_PUCKER,
+				{ XRFaceTracker::FT_LIP_PUCKER_UPPER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_UPPER_LEFT, XRFaceTracker::FT_LIP_PUCKER_LOWER_RIGHT, XRFaceTracker::FT_LIP_PUCKER_LOWER_LEFT } },
+		{ XRFaceTracker::FT_MOUTH_UPPER_UP,
+				{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_LOWER_DOWN,
+				{ XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_OPEN,
+				{ XRFaceTracker::FT_MOUTH_UPPER_UP_RIGHT, XRFaceTracker::FT_MOUTH_UPPER_UP_LEFT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_DOWN_LEFT } },
+		{ XRFaceTracker::FT_MOUTH_RIGHT,
+				{ XRFaceTracker::FT_MOUTH_UPPER_RIGHT, XRFaceTracker::FT_MOUTH_LOWER_RIGHT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_LEFT,
+				{ XRFaceTracker::FT_MOUTH_UPPER_LEFT, XRFaceTracker::FT_MOUTH_LOWER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_SMILE_RIGHT,
+				{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_SMILE_LEFT,
+				{ XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_SMILE,
+				{ XRFaceTracker::FT_MOUTH_CORNER_PULL_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_RIGHT, XRFaceTracker::FT_MOUTH_CORNER_PULL_LEFT, XRFaceTracker::FT_MOUTH_CORNER_SLANT_LEFT } },
+		{ XRFaceTracker::FT_MOUTH_SAD_RIGHT,
+				{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_SAD_LEFT,
+				{ XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_SAD,
+				{ XRFaceTracker::FT_MOUTH_FROWN_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_FROWN_LEFT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT } },
+		{ XRFaceTracker::FT_MOUTH_STRETCH,
+				{ XRFaceTracker::FT_MOUTH_STRETCH_RIGHT, XRFaceTracker::FT_MOUTH_STRETCH_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_DIMPLE,
+				{ XRFaceTracker::FT_MOUTH_DIMPLE_RIGHT, XRFaceTracker::FT_MOUTH_DIMPLE_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_TIGHTENER,
+				{ XRFaceTracker::FT_MOUTH_TIGHTENER_RIGHT, XRFaceTracker::FT_MOUTH_TIGHTENER_LEFT, -1, -1 } },
+		{ XRFaceTracker::FT_MOUTH_PRESS,
+				{ XRFaceTracker::FT_MOUTH_PRESS_RIGHT, XRFaceTracker::FT_MOUTH_PRESS_LEFT, -1, -1 } }
+	};
+
+	// Remove unified blend shapes if individual blend shapes are found.
+	for (const unified_blend_entry &entry : unified_blends) {
+		// Check if all individual blend shapes are found.
+		bool found = true;
+		for (const int i : entry.individual) {
+			if (i >= 0 && !p_blend_mapping.find(i)) {
+				found = false;
+				break;
+			}
+		}
+
+		// If all individual blend shapes are found then remove the unified blend shape.
+		if (found) {
+			p_blend_mapping.erase(entry.unified);
+		}
+	}
+}
+
+void XRFaceModifier3D::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker);
+	ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker);
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker");
+
+	ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target);
+	ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target);
+	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "MeshInstance3D"), "set_target", "get_target");
+}
+
+void XRFaceModifier3D::set_face_tracker(const StringName &p_tracker_name) {
+	tracker_name = p_tracker_name;
+}
+
+StringName XRFaceModifier3D::get_face_tracker() const {
+	return tracker_name;
+}
+
+void XRFaceModifier3D::set_target(const NodePath &p_target) {
+	target = p_target;
+
+	if (is_inside_tree()) {
+		_get_blend_data();
+	}
+}
+
+NodePath XRFaceModifier3D::get_target() const {
+	return target;
+}
+
+MeshInstance3D *XRFaceModifier3D::get_mesh_instance() const {
+	if (!has_node(target)) {
+		return nullptr;
+	}
+
+	Node *node = get_node(target);
+	if (!node) {
+		return nullptr;
+	}
+
+	return Object::cast_to<MeshInstance3D>(node);
+}
+
+void XRFaceModifier3D::_get_blend_data() {
+	// This method constructs the blend mapping from the XRFaceTracker
+	// blend shapes to the available blend shapes of the target mesh. It does this
+	// by:
+	//
+	// 1. Identifying the blend shapes of the target mesh and identifying what
+	//    XRFaceTracker blend shape they correspond to. The results are
+	//    placed in the blend_mapping map.
+	// 2. Prevent over-driving facial blend-shapes by removing any unified blend
+	//    shapes from the map if all the individual blend shapes are already
+	//    found and going to be driven.
+
+	blend_mapping.clear();
+
+	// Get the target MeshInstance3D.
+	const MeshInstance3D *mesh_instance = get_mesh_instance();
+	if (!mesh_instance) {
+		return;
+	}
+
+	// Get the mesh.
+	const Ref<Mesh> mesh = mesh_instance->get_mesh();
+	if (mesh.is_null()) {
+		return;
+	}
+
+	// Identify all face blend shapes and populate the map.
+	identify_face_blend_shapes(blend_mapping, mesh);
+
+	// Remove the unified blend shapes if all the individual blend shapes are found.
+	remove_driven_unified_blend_shapes(blend_mapping);
+}
+
+void XRFaceModifier3D::_update_face_blends() const {
+	// Get the XR Server.
+	const XRServer *xr_server = XRServer::get_singleton();
+	if (!xr_server) {
+		return;
+	}
+
+	// Get the face tracker.
+	const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name);
+	if (!p.is_valid()) {
+		return;
+	}
+
+	// Get the face mesh.
+	MeshInstance3D *mesh_instance = get_mesh_instance();
+	if (!mesh_instance) {
+		return;
+	}
+
+	// Get the blend weights.
+	const PackedFloat32Array weights = p->get_blend_shapes();
+
+	// Apply all the face blend weights to the mesh.
+	for (const KeyValue<int, int> &it : blend_mapping) {
+		mesh_instance->set_blend_shape_value(it.value, weights[it.key]);
+	}
+}
+
+void XRFaceModifier3D::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			_get_blend_data();
+			set_process_internal(true);
+		} break;
+		case NOTIFICATION_EXIT_TREE: {
+			set_process_internal(false);
+			blend_mapping.clear();
+		} break;
+		case NOTIFICATION_INTERNAL_PROCESS: {
+			_update_face_blends();
+		} break;
+		default: {
+		} break;
+	}
+}

+ 73 - 0
scene/3d/xr_face_modifier_3d.h

@@ -0,0 +1,73 @@
+/**************************************************************************/
+/*  xr_face_modifier_3d.h                                                 */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef XR_FACE_MODIFIER_3D_H
+#define XR_FACE_MODIFIER_3D_H
+
+#include "mesh_instance_3d.h"
+#include "scene/3d/node_3d.h"
+
+/**
+	The XRFaceModifier3D node drives the blend shapes of a MeshInstance3D
+	with facial expressions from an XRFaceTracking instance.
+
+	The blend shapes provided by the mesh are interrogated, and used to
+	deduce an optimal mapping from the Unified Expressions blend shapes
+	provided by the	XRFaceTracking instance to drive the face.
+ */
+
+class XRFaceModifier3D : public Node3D {
+	GDCLASS(XRFaceModifier3D, Node3D);
+
+private:
+	StringName tracker_name = "/user/head";
+	NodePath target;
+
+	// Map from XRFaceTracker blend shape index to mesh blend shape index.
+	RBMap<int, int> blend_mapping;
+
+	MeshInstance3D *get_mesh_instance() const;
+	void _get_blend_data();
+	void _update_face_blends() const;
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_face_tracker(const StringName &p_tracker_name);
+	StringName get_face_tracker() const;
+
+	void set_target(const NodePath &p_target);
+	NodePath get_target() const;
+
+	void _notification(int p_what);
+};
+
+#endif // XR_FACE_MODIFIER_3D_H

+ 2 - 0
scene/register_scene_types.cpp

@@ -271,6 +271,7 @@
 #include "scene/3d/visible_on_screen_notifier_3d.h"
 #include "scene/3d/voxel_gi.h"
 #include "scene/3d/world_environment.h"
+#include "scene/3d/xr_face_modifier_3d.h"
 #include "scene/3d/xr_nodes.h"
 #include "scene/animation/root_motion_view.h"
 #include "scene/resources/environment.h"
@@ -516,6 +517,7 @@ void register_scene_types() {
 	GDREGISTER_CLASS(XRController3D);
 	GDREGISTER_CLASS(XRAnchor3D);
 	GDREGISTER_CLASS(XROrigin3D);
+	GDREGISTER_CLASS(XRFaceModifier3D);
 	GDREGISTER_CLASS(MeshInstance3D);
 	GDREGISTER_CLASS(OccluderInstance3D);
 	GDREGISTER_ABSTRACT_CLASS(Occluder3D);

+ 2 - 0
servers/register_server_types.cpp

@@ -80,6 +80,7 @@
 #include "text/text_server_dummy.h"
 #include "text/text_server_extension.h"
 #include "text_server.h"
+#include "xr/xr_face_tracker.h"
 #include "xr/xr_interface.h"
 #include "xr/xr_interface_extension.h"
 #include "xr/xr_positional_tracker.h"
@@ -189,6 +190,7 @@ void register_server_types() {
 	GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions.
 	GDREGISTER_CLASS(XRPose);
 	GDREGISTER_CLASS(XRPositionalTracker);
+	GDREGISTER_CLASS(XRFaceTracker);
 
 	GDREGISTER_CLASS(AudioStream);
 	GDREGISTER_CLASS(AudioStreamPlayback);

+ 222 - 0
servers/xr/xr_face_tracker.cpp

@@ -0,0 +1,222 @@
+/**************************************************************************/
+/*  xr_face_tracker.cpp                                                   */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "xr_face_tracker.h"
+
+void XRFaceTracker::_bind_methods() {
+	// Base Shapes
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_OUT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_IN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_UP_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_DOWN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_OUT_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_IN_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_UP_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_LOOK_DOWN_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_CLOSED_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_CLOSED_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_SQUINT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_SQUINT_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_WIDE_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_WIDE_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_DILATION_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_DILATION_LEFT);
+	BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_PINCH_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_PINCH_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_LOWERER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_LOWERER_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_INNER_UP_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_INNER_UP_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_OUTER_UP_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_OUTER_UP_LEFT);
+	BIND_ENUM_CONSTANT(FT_NOSE_SNEER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_NOSE_SNEER_LEFT);
+	BIND_ENUM_CONSTANT(FT_NASAL_DILATION_RIGHT);
+	BIND_ENUM_CONSTANT(FT_NASAL_DILATION_LEFT);
+	BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT_LEFT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT_LEFT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_PUFF_RIGHT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_PUFF_LEFT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SUCK_RIGHT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SUCK_LEFT);
+	BIND_ENUM_CONSTANT(FT_JAW_OPEN);
+	BIND_ENUM_CONSTANT(FT_MOUTH_CLOSED);
+	BIND_ENUM_CONSTANT(FT_JAW_RIGHT);
+	BIND_ENUM_CONSTANT(FT_JAW_LEFT);
+	BIND_ENUM_CONSTANT(FT_JAW_FORWARD);
+	BIND_ENUM_CONSTANT(FT_JAW_BACKWARD);
+	BIND_ENUM_CONSTANT(FT_JAW_CLENCH);
+	BIND_ENUM_CONSTANT(FT_JAW_MANDIBLE_RAISE);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_CORNER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_CORNER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER_LEFT);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_DEEPEN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_DEEPEN_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_PULL_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_PULL_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_SLANT_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_CORNER_SLANT_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_FROWN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_FROWN_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_RAISER_UPPER);
+	BIND_ENUM_CONSTANT(FT_MOUTH_RAISER_LOWER);
+	BIND_ENUM_CONSTANT(FT_MOUTH_PRESS_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_PRESS_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER_LEFT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_OUT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_UP);
+	BIND_ENUM_CONSTANT(FT_TONGUE_DOWN);
+	BIND_ENUM_CONSTANT(FT_TONGUE_RIGHT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_LEFT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_ROLL);
+	BIND_ENUM_CONSTANT(FT_TONGUE_BLEND_DOWN);
+	BIND_ENUM_CONSTANT(FT_TONGUE_CURL_UP);
+	BIND_ENUM_CONSTANT(FT_TONGUE_SQUISH);
+	BIND_ENUM_CONSTANT(FT_TONGUE_FLAT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_TWIST_RIGHT);
+	BIND_ENUM_CONSTANT(FT_TONGUE_TWIST_LEFT);
+	BIND_ENUM_CONSTANT(FT_SOFT_PALATE_CLOSE);
+	BIND_ENUM_CONSTANT(FT_THROAT_SWALLOW);
+	BIND_ENUM_CONSTANT(FT_NECK_FLEX_RIGHT);
+	BIND_ENUM_CONSTANT(FT_NECK_FLEX_LEFT);
+	// Blended Shapes
+	BIND_ENUM_CONSTANT(FT_EYE_CLOSED);
+	BIND_ENUM_CONSTANT(FT_EYE_WIDE);
+	BIND_ENUM_CONSTANT(FT_EYE_SQUINT);
+	BIND_ENUM_CONSTANT(FT_EYE_DILATION);
+	BIND_ENUM_CONSTANT(FT_EYE_CONSTRICT);
+	BIND_ENUM_CONSTANT(FT_BROW_DOWN_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_DOWN_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_DOWN);
+	BIND_ENUM_CONSTANT(FT_BROW_UP_RIGHT);
+	BIND_ENUM_CONSTANT(FT_BROW_UP_LEFT);
+	BIND_ENUM_CONSTANT(FT_BROW_UP);
+	BIND_ENUM_CONSTANT(FT_NOSE_SNEER);
+	BIND_ENUM_CONSTANT(FT_NASAL_DILATION);
+	BIND_ENUM_CONSTANT(FT_NASAL_CONSTRICT);
+	BIND_ENUM_CONSTANT(FT_CHEEK_PUFF);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SUCK);
+	BIND_ENUM_CONSTANT(FT_CHEEK_SQUINT);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_UPPER);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK_LOWER);
+	BIND_ENUM_CONSTANT(FT_LIP_SUCK);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_UPPER);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL_LOWER);
+	BIND_ENUM_CONSTANT(FT_LIP_FUNNEL);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_UPPER);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER_LOWER);
+	BIND_ENUM_CONSTANT(FT_LIP_PUCKER);
+	BIND_ENUM_CONSTANT(FT_MOUTH_UPPER_UP);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LOWER_DOWN);
+	BIND_ENUM_CONSTANT(FT_MOUTH_OPEN);
+	BIND_ENUM_CONSTANT(FT_MOUTH_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SMILE_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SMILE_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SMILE);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SAD_RIGHT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SAD_LEFT);
+	BIND_ENUM_CONSTANT(FT_MOUTH_SAD);
+	BIND_ENUM_CONSTANT(FT_MOUTH_STRETCH);
+	BIND_ENUM_CONSTANT(FT_MOUTH_DIMPLE);
+	BIND_ENUM_CONSTANT(FT_MOUTH_TIGHTENER);
+	BIND_ENUM_CONSTANT(FT_MOUTH_PRESS);
+	BIND_ENUM_CONSTANT(FT_MAX);
+
+	ClassDB::bind_method(D_METHOD("get_blend_shape", "blend_shape"), &XRFaceTracker::get_blend_shape);
+	ClassDB::bind_method(D_METHOD("set_blend_shape", "blend_shape", "weight"), &XRFaceTracker::set_blend_shape);
+
+	ClassDB::bind_method(D_METHOD("get_blend_shapes"), &XRFaceTracker::get_blend_shapes);
+	ClassDB::bind_method(D_METHOD("set_blend_shapes", "weights"), &XRFaceTracker::set_blend_shapes);
+	ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "blend_shapes"), "set_blend_shapes", "get_blend_shapes");
+	ADD_PROPERTY_DEFAULT("blend_shapes", PackedFloat32Array()); // To prevent ludicrously large default values.
+}
+
+float XRFaceTracker::get_blend_shape(BlendShapeEntry p_blend_shape) const {
+	// Fail if the blend shape index is out of range.
+	ERR_FAIL_INDEX_V(p_blend_shape, FT_MAX, 0.0f);
+
+	// Return the blend shape value.
+	return blend_shape_values[p_blend_shape];
+}
+
+void XRFaceTracker::set_blend_shape(BlendShapeEntry p_blend_shape, float p_value) {
+	// Fail if the blend shape index is out of range.
+	ERR_FAIL_INDEX(p_blend_shape, FT_MAX);
+
+	// Save the new blend shape value.
+	blend_shape_values[p_blend_shape] = p_value;
+}
+
+PackedFloat32Array XRFaceTracker::get_blend_shapes() const {
+	// Create a packed float32 array and copy the blend shape values into it.
+	PackedFloat32Array data;
+	data.resize(FT_MAX);
+	memcpy(data.ptrw(), blend_shape_values, sizeof(blend_shape_values));
+
+	// Return the blend shape array.
+	return data;
+}
+
+void XRFaceTracker::set_blend_shapes(const PackedFloat32Array &p_blend_shapes) {
+	// Fail if the blend shape array is not the correct size.
+	ERR_FAIL_COND(p_blend_shapes.size() != FT_MAX);
+
+	// Copy the blend shape values into the blend shape array.
+	memcpy(blend_shape_values, p_blend_shapes.ptr(), sizeof(blend_shape_values));
+}

+ 213 - 0
servers/xr/xr_face_tracker.h

@@ -0,0 +1,213 @@
+/**************************************************************************/
+/*  xr_face_tracker.h                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef XR_FACE_TRACKER_H
+#define XR_FACE_TRACKER_H
+
+#include "core/object/ref_counted.h"
+
+/**
+	The XRFaceTracker class provides face blend shape weights.
+
+	The supported blend shapes are based on the Unified Expressions
+	standard, and as such have a well defined mapping to ARKit, SRanipal,
+	and Meta Movement standards.
+ */
+
+class XRFaceTracker : public RefCounted {
+	GDCLASS(XRFaceTracker, RefCounted);
+	_THREAD_SAFE_CLASS_
+
+public:
+	enum BlendShapeEntry {
+		// Base Shapes
+		FT_EYE_LOOK_OUT_RIGHT, // Right eye looks outwards.
+		FT_EYE_LOOK_IN_RIGHT, // Right eye looks inwards.
+		FT_EYE_LOOK_UP_RIGHT, // Right eye looks upwards.
+		FT_EYE_LOOK_DOWN_RIGHT, // Right eye looks downwards.
+		FT_EYE_LOOK_OUT_LEFT, // Left eye looks outwards.
+		FT_EYE_LOOK_IN_LEFT, // Left eye looks inwards.
+		FT_EYE_LOOK_UP_LEFT, // Left eye looks upwards.
+		FT_EYE_LOOK_DOWN_LEFT, // Left eye looks downwards.
+		FT_EYE_CLOSED_RIGHT, // Closes the right eyelid.
+		FT_EYE_CLOSED_LEFT, // Closes the left eyelid.
+		FT_EYE_SQUINT_RIGHT, // Squeezes the right eye socket muscles.
+		FT_EYE_SQUINT_LEFT, // Squeezes the left eye socket muscles.
+		FT_EYE_WIDE_RIGHT, // Right eyelid widens beyond relaxed.
+		FT_EYE_WIDE_LEFT, // Left eyelid widens beyond relaxed.
+		FT_EYE_DILATION_RIGHT, // Dilates the right eye pupil.
+		FT_EYE_DILATION_LEFT, // Dilates the left eye pupil.
+		FT_EYE_CONSTRICT_RIGHT, // Constricts the right eye pupil.
+		FT_EYE_CONSTRICT_LEFT, // Constricts the left eye pupil.
+		FT_BROW_PINCH_RIGHT, // Right eyebrow pinches in.
+		FT_BROW_PINCH_LEFT, // Left eyebrow pinches in.
+		FT_BROW_LOWERER_RIGHT, // Outer right eyebrow pulls down.
+		FT_BROW_LOWERER_LEFT, // Outer left eyebrow pulls down.
+		FT_BROW_INNER_UP_RIGHT, // Inner right eyebrow pulls up.
+		FT_BROW_INNER_UP_LEFT, // Inner left eyebrow pulls up.
+		FT_BROW_OUTER_UP_RIGHT, // Outer right eyebrow pulls up.
+		FT_BROW_OUTER_UP_LEFT, // Outer left eyebrow pulls up.
+		FT_NOSE_SNEER_RIGHT, // Right side face sneers.
+		FT_NOSE_SNEER_LEFT, // Left side face sneers.
+		FT_NASAL_DILATION_RIGHT, // Right side nose canal dilates.
+		FT_NASAL_DILATION_LEFT, // Left side nose canal dilates.
+		FT_NASAL_CONSTRICT_RIGHT, // Right side nose canal constricts.
+		FT_NASAL_CONSTRICT_LEFT, // Left side nose canal constricts.
+		FT_CHEEK_SQUINT_RIGHT, // Raises the right side cheek.
+		FT_CHEEK_SQUINT_LEFT, // Raises the left side cheek.
+		FT_CHEEK_PUFF_RIGHT, // Puffs the right side cheek.
+		FT_CHEEK_PUFF_LEFT, // Puffs the left side cheek.
+		FT_CHEEK_SUCK_RIGHT, // Sucks in the right side cheek.
+		FT_CHEEK_SUCK_LEFT, // Sucks in the left side cheek.
+		FT_JAW_OPEN, // Opens jawbone.
+		FT_MOUTH_CLOSED, // Closes the mouth.
+		FT_JAW_RIGHT, // Pushes jawbone right.
+		FT_JAW_LEFT, // Pushes jawbone left.
+		FT_JAW_FORWARD, // Pushes jawbone forward.
+		FT_JAW_BACKWARD, // Pushes jawbone backward.
+		FT_JAW_CLENCH, // Flexes jaw muscles.
+		FT_JAW_MANDIBLE_RAISE, // Raises the jawbone.
+		FT_LIP_SUCK_UPPER_RIGHT, // Upper right lip part tucks in the mouth.
+		FT_LIP_SUCK_UPPER_LEFT, // Upper left lip part tucks in the mouth.
+		FT_LIP_SUCK_LOWER_RIGHT, // Lower right lip part tucks in the mouth.
+		FT_LIP_SUCK_LOWER_LEFT, // Lower left lip part tucks in the mouth.
+		FT_LIP_SUCK_CORNER_RIGHT, // Right lip corner folds into the mouth.
+		FT_LIP_SUCK_CORNER_LEFT, // Left lip corner folds into the mouth.
+		FT_LIP_FUNNEL_UPPER_RIGHT, // Upper right lip part pushes into a funnel.
+		FT_LIP_FUNNEL_UPPER_LEFT, // Upper left lip part pushes into a funnel.
+		FT_LIP_FUNNEL_LOWER_RIGHT, // Lower right lip part pushes into a funnel.
+		FT_LIP_FUNNEL_LOWER_LEFT, // Lower left lip part pushes into a funnel.
+		FT_LIP_PUCKER_UPPER_RIGHT, // Upper right lip part pushes outwards.
+		FT_LIP_PUCKER_UPPER_LEFT, // Upper left lip part pushes outwards.
+		FT_LIP_PUCKER_LOWER_RIGHT, // Lower right lip part pushes outwards.
+		FT_LIP_PUCKER_LOWER_LEFT, // Lower left lip part pushes outwards.
+		FT_MOUTH_UPPER_UP_RIGHT, // Upper right part of the lip pulls up.
+		FT_MOUTH_UPPER_UP_LEFT, // Upper left part of the lip pulls up.
+		FT_MOUTH_LOWER_DOWN_RIGHT, // Lower right part of the lip pulls up.
+		FT_MOUTH_LOWER_DOWN_LEFT, // Lower left part of the lip pulls up.
+		FT_MOUTH_UPPER_DEEPEN_RIGHT, // Upper right lip part pushes in the cheek.
+		FT_MOUTH_UPPER_DEEPEN_LEFT, // Upper left lip part pushes in the cheek.
+		FT_MOUTH_UPPER_RIGHT, // Moves upper lip right.
+		FT_MOUTH_UPPER_LEFT, // Moves upper lip left.
+		FT_MOUTH_LOWER_RIGHT, // Moves lower lip right.
+		FT_MOUTH_LOWER_LEFT, // Moves lower lip left.
+		FT_MOUTH_CORNER_PULL_RIGHT, // Right lip corner pulls diagonally up and out.
+		FT_MOUTH_CORNER_PULL_LEFT, // Left lip corner pulls diagonally up and out.
+		FT_MOUTH_CORNER_SLANT_RIGHT, // Right corner lip slants up.
+		FT_MOUTH_CORNER_SLANT_LEFT, // Left corner lip slants up.
+		FT_MOUTH_FROWN_RIGHT, // Right corner lip pulls down.
+		FT_MOUTH_FROWN_LEFT, // Left corner lip pulls down.
+		FT_MOUTH_STRETCH_RIGHT, // Mouth corner lip pulls out and down.
+		FT_MOUTH_STRETCH_LEFT, // Mouth corner lip pulls out and down.
+		FT_MOUTH_DIMPLE_RIGHT, // Right lip corner is pushed backwards.
+		FT_MOUTH_DIMPLE_LEFT, // Left lip corner is pushed backwards.
+		FT_MOUTH_RAISER_UPPER, // Raises and slightly pushes out the upper mouth.
+		FT_MOUTH_RAISER_LOWER, // Raises and slightly pushes out the lower mouth.
+		FT_MOUTH_PRESS_RIGHT, // Right side lips press and flatten together vertically.
+		FT_MOUTH_PRESS_LEFT, // Left side lips press and flatten together vertically.
+		FT_MOUTH_TIGHTENER_RIGHT, // Right side lips squeeze together horizontally.
+		FT_MOUTH_TIGHTENER_LEFT, // Left side lips squeeze together horizontally.
+		FT_TONGUE_OUT, // Tongue visibly sticks out of the mouth.
+		FT_TONGUE_UP, // Tongue points upwards.
+		FT_TONGUE_DOWN, // Tongue points downwards.
+		FT_TONGUE_RIGHT, // Tongue points right.
+		FT_TONGUE_LEFT, // Tongue points left.
+		FT_TONGUE_ROLL, // Sides of the tongue funnel, creating a roll.
+		FT_TONGUE_BLEND_DOWN, // Tongue arches up then down inside the mouth.
+		FT_TONGUE_CURL_UP, // Tongue arches down then up inside the mouth.
+		FT_TONGUE_SQUISH, // Tongue squishes together and thickens.
+		FT_TONGUE_FLAT, // Tongue flattens and thins out.
+		FT_TONGUE_TWIST_RIGHT, // Tongue tip rotates clockwise, with the rest following gradually.
+		FT_TONGUE_TWIST_LEFT, // Tongue tip rotates counter-clockwise, with the rest following gradually.
+		FT_SOFT_PALATE_CLOSE, // Inner mouth throat closes.
+		FT_THROAT_SWALLOW, // The Adam's apple visibly swallows.
+		FT_NECK_FLEX_RIGHT, // Right side neck visibly flexes.
+		FT_NECK_FLEX_LEFT, // Left side neck visibly flexes.
+		// Blended Shapes
+		FT_EYE_CLOSED, // Closes both eye lids.
+		FT_EYE_WIDE, // Widens both eye lids.
+		FT_EYE_SQUINT, // Squints both eye lids.
+		FT_EYE_DILATION, // Dilates both pupils.
+		FT_EYE_CONSTRICT, // Constricts both pupils.
+		FT_BROW_DOWN_RIGHT, // Pulls the right eyebrow down and in.
+		FT_BROW_DOWN_LEFT, // Pulls the left eyebrow down and in.
+		FT_BROW_DOWN, // Pulls both eyebrows down and in.
+		FT_BROW_UP_RIGHT, // Right brow appears worried.
+		FT_BROW_UP_LEFT, // Left brow appears worried.
+		FT_BROW_UP, // Both brows appear worried.
+		FT_NOSE_SNEER, // Entire face sneers.
+		FT_NASAL_DILATION, // Both nose canals dilate.
+		FT_NASAL_CONSTRICT, // Both nose canals constrict.
+		FT_CHEEK_PUFF, // Puffs both cheeks.
+		FT_CHEEK_SUCK, // Sucks in both cheeks.
+		FT_CHEEK_SQUINT, // Raises both cheeks.
+		FT_LIP_SUCK_UPPER, // Tucks in the upper lips.
+		FT_LIP_SUCK_LOWER, // Tucks in the lower lips.
+		FT_LIP_SUCK, // Tucks in both lips.
+		FT_LIP_FUNNEL_UPPER, // Funnels in the upper lips.
+		FT_LIP_FUNNEL_LOWER, // Funnels in the lower lips.
+		FT_LIP_FUNNEL, // Funnels in both lips.
+		FT_LIP_PUCKER_UPPER, // Upper lip part pushes outwards.
+		FT_LIP_PUCKER_LOWER, // Lower lip part pushes outwards.
+		FT_LIP_PUCKER, // Lips push outwards.
+		FT_MOUTH_UPPER_UP, // Raises the upper lips.
+		FT_MOUTH_LOWER_DOWN, // Lowers the lower lips.
+		FT_MOUTH_OPEN, // Mouth opens, revealing teeth.
+		FT_MOUTH_RIGHT, // Moves mouth right.
+		FT_MOUTH_LEFT, // Moves mouth left.
+		FT_MOUTH_SMILE_RIGHT, // Right side of the mouth smiles.
+		FT_MOUTH_SMILE_LEFT, // Left side of the mouth smiles.
+		FT_MOUTH_SMILE, // Mouth expresses a smile.
+		FT_MOUTH_SAD_RIGHT, // Right side of the mouth expresses sadness.
+		FT_MOUTH_SAD_LEFT, // Left side of the mouth expresses sadness.
+		FT_MOUTH_SAD, // Mouth expresses sadness.
+		FT_MOUTH_STRETCH, // Mouth stretches.
+		FT_MOUTH_DIMPLE, // Lip corners dimple.
+		FT_MOUTH_TIGHTENER, // Mouth tightens.
+		FT_MOUTH_PRESS, // Mouth presses together.
+		FT_MAX // Maximum blend shape.
+	};
+
+	float get_blend_shape(BlendShapeEntry p_blend_shape) const;
+	void set_blend_shape(BlendShapeEntry p_blend_shape, float p_value);
+
+	PackedFloat32Array get_blend_shapes() const;
+	void set_blend_shapes(const PackedFloat32Array &p_blend_shapes);
+
+protected:
+	static void _bind_methods();
+
+private:
+	float blend_shape_values[FT_MAX] = {};
+};
+
+VARIANT_ENUM_CAST(XRFaceTracker::BlendShapeEntry);
+
+#endif // XR_FACE_TRACKER_H

+ 48 - 0
servers/xr_server.cpp

@@ -30,6 +30,7 @@
 
 #include "xr_server.h"
 #include "core/config/project_settings.h"
+#include "xr/xr_face_tracker.h"
 #include "xr/xr_interface.h"
 #include "xr/xr_positional_tracker.h"
 
@@ -74,6 +75,11 @@ void XRServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers);
 	ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker);
 
+	ClassDB::bind_method(D_METHOD("add_face_tracker", "tracker_name", "face_tracker"), &XRServer::add_face_tracker);
+	ClassDB::bind_method(D_METHOD("remove_face_tracker", "tracker_name"), &XRServer::remove_face_tracker);
+	ClassDB::bind_method(D_METHOD("get_face_trackers"), &XRServer::get_face_trackers);
+	ClassDB::bind_method(D_METHOD("get_face_tracker", "tracker_name"), &XRServer::get_face_tracker);
+
 	ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface);
 	ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface);
 
@@ -97,6 +103,10 @@ void XRServer::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
 	ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
 	ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
+
+	ADD_SIGNAL(MethodInfo("face_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker")));
+	ADD_SIGNAL(MethodInfo("face_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker")));
+	ADD_SIGNAL(MethodInfo("face_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name")));
 };
 
 double XRServer::get_world_scale() const {
@@ -352,6 +362,44 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker
 	return arr;
 }
 
+void XRServer::add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker) {
+	ERR_FAIL_COND(p_face_tracker.is_null());
+
+	if (!face_trackers.has(p_tracker_name)) {
+		// We don't have a tracker with this name, we're going to add it.
+		face_trackers[p_tracker_name] = p_face_tracker;
+		emit_signal(SNAME("face_tracker_added"), p_tracker_name, p_face_tracker);
+	} else if (face_trackers[p_tracker_name] != p_face_tracker) {
+		// We already have a tracker with this name, we're going to replace it.
+		face_trackers[p_tracker_name] = p_face_tracker;
+		emit_signal(SNAME("face_tracker_updated"), p_tracker_name, p_face_tracker);
+	}
+}
+
+void XRServer::remove_face_tracker(const StringName &p_tracker_name) {
+	// Skip if no face tracker is found.
+	if (!face_trackers.has(p_tracker_name)) {
+		return;
+	}
+
+	// Send the removed signal, then remove the face tracker.
+	emit_signal(SNAME("face_tracker_removed"), p_tracker_name);
+	face_trackers.erase(p_tracker_name);
+}
+
+Dictionary XRServer::get_face_trackers() const {
+	return face_trackers;
+}
+
+Ref<XRFaceTracker> XRServer::get_face_tracker(const StringName &p_tracker_name) const {
+	// Skip if no tracker is found.
+	if (!face_trackers.has(p_tracker_name)) {
+		return Ref<XRFaceTracker>();
+	}
+
+	return face_trackers[p_tracker_name];
+}
+
 void XRServer::_process() {
 	// called from our main game loop before we handle physics and game logic
 	// note that we can have multiple interfaces active if we have interfaces that purely handle tracking

+ 11 - 0
servers/xr_server.h

@@ -39,6 +39,7 @@
 
 class XRInterface;
 class XRPositionalTracker;
+class XRFaceTracker;
 
 /**
 	The XR server is a singleton object that gives access to the various
@@ -86,6 +87,8 @@ private:
 	Vector<Ref<XRInterface>> interfaces;
 	Dictionary trackers;
 
+	Dictionary face_trackers;
+
 	Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */
 
 	double world_scale; /* scale by which we multiply our tracker positions */
@@ -183,6 +186,14 @@ public:
 	PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const;
 	// Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE?
 
+	/*
+		Face trackers are objects that expose the tracked blend shapes of a face.
+	 */
+	void add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker);
+	void remove_face_tracker(const StringName &p_tracker_name);
+	Dictionary get_face_trackers() const;
+	Ref<XRFaceTracker> get_face_tracker(const StringName &p_tracker_name) const;
+
 	// Process is called before we handle our physics process and game process. This is where our interfaces will update controller data and such.
 	void _process();