// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. using System; using Unity.Collections; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Jobs; using static Meta.XR.Movement.MSDKUtility; namespace Meta.XR.Movement.Retargeting { /// /// The joint index that is represented as a name by the character retargeter config. /// [Serializable] public struct TargetJointIndex : IComparable { /// /// The index of the target joint. /// [SerializeField] public int Index; /// /// Create a target joint index. /// /// The index value public TargetJointIndex(int index) { Index = index; } /// /// Compares this TargetJointIndex to another object for ordering. /// /// The object to compare with. /// A value indicating the relative order of the objects being compared. /// Thrown when the object is not a TargetJointIndex. public int CompareTo(object obj) { if (obj is TargetJointIndex other) { return Index.CompareTo(other.Index); } throw new ArgumentException("Object is not a JointIndex", nameof(obj)); } /// /// Determines whether this instance is equal to another TargetJointIndex. /// /// The TargetJointIndex to compare with this instance. /// True if the specified TargetJointIndex has the same Index as this instance; otherwise, false. public bool Equals(TargetJointIndex other) { return Index == other.Index; } /// /// Implicitly converts a TargetJointIndex to an integer. /// /// The TargetJointIndex to convert. /// The integer value of the TargetJointIndex's Index. public static implicit operator int(TargetJointIndex targetJointIndex) { return targetJointIndex.Index; } /// /// Implicitly converts an integer to a TargetJointIndex. /// /// The integer value to convert. /// A new TargetJointIndex with the specified index. public static implicit operator TargetJointIndex(int index) { return new TargetJointIndex(index); } } /// /// Indicates what kind of world space to use. /// public enum JointType { NoWorldSpace = 0, WorldSpaceRootOnly = 1, WorldSpaceAllJoints = 2 } /// /// Contain configuration information for retargeting poses. /// public class CharacterRetargeterConfig : MonoBehaviour { /// /// Store information about a pair of joint transforms. /// [Serializable] public struct JointPair { /// /// The joint transform. /// public Transform Joint; /// /// The parent joint transform. /// public Transform ParentJoint; } /// /// Represents shape pose data. /// [Serializable] public struct ShapePoseData { /// /// The skinned mesh renderer. /// public SkinnedMeshRenderer SkinnedMesh; /// /// The shape name. /// public string ShapeName; /// /// The shape index. /// public int ShapeIndex; /// /// Initializes the struct. /// /// The skinned mesh renderer. /// The shape name. /// The shape index. public ShapePoseData(SkinnedMeshRenderer skinnedMesh, string shapeName, int shapeIndex) { SkinnedMesh = skinnedMesh; ShapeName = shapeName; ShapeIndex = shapeIndex; } } /// /// Gets the configuration text. /// public string Config => _config != null ? _config.text : string.Empty; /// /// Getter and setter for the configuration asset. /// public TextAsset ConfigAsset { get => _config; set => _config = value; } /// /// Gets the number of joints in the configuration. /// public int NumberOfJoints => _jointPairs?.Length ?? 0; /// /// The joint pairs for the character retargeter corresponding to this object. /// public JointPair[] JointPairs => _jointPairs; /// /// Gets the number of shapes in the configuration. /// public int NumberOfShapes => _shapePoseData.Length; /// /// Joints used for retargeted character. /// public TransformAccessArray Joints => _joints; /// /// The configuration text asset containing retargeting data. /// [SerializeField] protected TextAsset _config; /// /// The joint pair data from the configuration, mapping joints to their parent joints. /// [SerializeField] protected JointPair[] _jointPairs; /// /// The shape pose data from the configuration, used for facial expressions and blendshapes. /// [SerializeField] protected ShapePoseData[] _shapePoseData; protected TransformAccessArray _joints; /// /// Initializes the TransformAccessArray with joint transforms when the component starts. /// public virtual void Start() { // Fill out joints and native information. var joints = new Transform[_jointPairs.Length]; for (var i = 0; i < _jointPairs.Length; i++) { joints[i] = _jointPairs[i].Joint; } _joints = new TransformAccessArray(joints); } /// /// Gets the current body pose. /// /// The joint type. /// public NativeArray GetCurrentBodyPose(JointType jointType) { var bodyPose = new NativeArray(_jointPairs.Length, Allocator.TempJob); var job = new SkeletonJobs.GetPoseJob { BodyPose = bodyPose, JobJointType = jointType }; job.Schedule(_joints).Complete(); return bodyPose; } /// /// Gets the current face pose. /// /// The current face pose. public NativeArray GetCurrentFacePose() { // create flattened array to be used for data serialization. int numShapesTotal = NumberOfShapes; var faceShapePoses = new NativeArray(numShapesTotal, Allocator.Temp, NativeArrayOptions.UninitializedMemory); int shapePoseIndex = 0; foreach (var shape in _shapePoseData) { Assert.IsNotNull(shape.SkinnedMesh, "Skinned mesh null."); faceShapePoses[shapePoseIndex++] = shape.SkinnedMesh.GetBlendShapeWeight(shape.ShapeIndex); } return faceShapePoses; } /// /// Applies the specified body pose to the character. /// /// The body pose to apply. /// The joint type. public void ApplyBodyPose(NativeArray bodyPose, JointType jointType) { for (var i = 0; i < _jointPairs.Length; i++) { var joint = _jointPairs[i].Joint; var pose = bodyPose[i]; var useWorldSpace = UseWorldSpace(i, jointType); if (useWorldSpace) { joint.SetPositionAndRotation(pose.Position, pose.Orientation); } else { joint.SetLocalPositionAndRotation(pose.Position, pose.Orientation); } } } /// /// Applies the given face pose to the character. /// /// The face pose to apply. public void ApplyFacePose(NativeArray facePose) { var shapePoseIndex = 0; foreach (var shape in _shapePoseData) { shape.SkinnedMesh.SetBlendShapeWeight( shape.ShapeIndex, facePose[shapePoseIndex++]); } } private bool UseWorldSpace(int jointIndex, JointType jointType) { bool isRoot = jointIndex == 0; bool useWorldSpace = false; if (jointType == JointType.WorldSpaceRootOnly) { useWorldSpace = isRoot; } else if (jointType == JointType.WorldSpaceAllJoints) { useWorldSpace = true; } return useWorldSpace; } } }