// Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Profiling; using Allocator = Unity.Collections.Allocator; using Assert = UnityEngine.Assertions.Assert; namespace Meta.XR.Movement { /// /// The native utility plugin containing extended Movement SDK functionality. /// This class provides a comprehensive set of tools for working with skeletal data, /// retargeting, serialization, and other Movement SDK operations. /// public abstract partial class MSDKUtility { public static class UnmanagedMarshalFunctions { public static IntPtr MarshalStringArrayToUnmanagedPtr(string[] managedStringArrayStrings) { if (managedStringArrayStrings == null || managedStringArrayStrings.Length <= 0) { return IntPtr.Zero; } // Allocate memory for the array of pointers IntPtr nativeStringArray = Marshal.AllocHGlobal(managedStringArrayStrings.Length * IntPtr.Size); try { // Iterate over each string in the array for (int i = 0; i < managedStringArrayStrings.Length; i++) { // Convert the string to a byte array byte[] bytes = System.Text.Encoding.UTF8.GetBytes(managedStringArrayStrings[i] == null ? "" : managedStringArrayStrings[i]); // Allocate memory for the current string IntPtr currentString = Marshal.AllocHGlobal(bytes.Length + 1); // +1 for null terminator try { // Copy the bytes into the allocated memory Marshal.Copy(bytes, 0, currentString, bytes.Length); // Set the last byte to zero (null terminator) Marshal.WriteByte(currentString, bytes.Length, 0); // Store the pointer to the current string in the array Marshal.WriteIntPtr(nativeStringArray, i * IntPtr.Size, currentString); } catch { // Free the memory for the current string if an exception occurs Marshal.FreeHGlobal(currentString); throw; } } } catch { Marshal.FreeHGlobal(nativeStringArray); return IntPtr.Zero; } return nativeStringArray; } public static bool FreeUnmanagedObject(ref IntPtr unmanagedObject) { if (unmanagedObject != IntPtr.Zero) { Marshal.FreeHGlobal(unmanagedObject); unmanagedObject = IntPtr.Zero; return true; } return false; } } /// /// Invalid Handle value. /// public const ulong INVALID_HANDLE = 0u; /// /// Invalid Joint index value. /// public const int INVALID_JOINT_INDEX = -1; /// /// Invalid BlendShape index value. /// public const int INVALID_BLENDSHAPE_INDEX = -1; /// /// Max Possible Snapshots in Serialization System /// public const int SERIALIZATION_MAX_POSSIBLE_SNAPSHOTS = 800; /// /// Min Possible Snapshots in Serialization System /// public const int SERIALIZATION_MIN_POSSIBLE_SNAPSHOTS = 254; /// /// Size in bytes for string fields in the serialization start header. /// public const int SERIALIZATION_START_HEADER_STRING_SIZE_BYTES = 32; /// /// Total size in bytes for the serialization start header. /// public const int SERIALIZATION_START_HEADER_SIZE_BYTES = 180; /// /// Total size in bytes for the serialization end header. /// public const int SERIALIZATION_END_HEADER_SIZE_BYTES = 8; /// /// Static DLL name. /// private const string DLL = "MetaMovementSDK_Utility"; /// /// Enum for native plugin results. Represents the possible outcomes of operations /// performed by the native plugin, including success and various failure modes. /// public enum Result { // Generic failure. Failure = 0, // Specific failures. Failure_ConfigNull = -1000, Failure_ConfigCannotParse = -1001, Failure_ConfigInvalid = -1002, Failure_HandleInvalid = -1003, Failure_Initialization = -1004, Failure_InsufficientSize = -1005, Failure_WriteOutputNull = -1006, Failure_RequiredParameterNull = -1007, Failure_InvalidData = -1008, // Success. Success = 1, } /// /// Enum for compression type used in serialization operations. /// Different compression types offer trade-offs between data size and precision. /// public enum SerializationCompressionType : byte { High = 0, // Compressed with joint lengths Medium = 1, // Joint compression, positions use less space Low = 2, // Joint compression, positions use more space } /// /// Option for APIs set/get attributes relative to a skeleton. /// Specifies whether operations should be performed on the source or target skeleton. /// public enum SkeletonType { /// /// Parameter for APIs set/get attributes relative to the source skeleton. /// SourceSkeleton = 0, /// /// Parameter for APIs set/get attributes relative to the target skeleton, /// TargetSkeleton = 1, } /// /// Parameter for APIs set/get a T-Pose type. /// Defines different reference poses that can be used for skeleton operations. /// public enum SkeletonTPoseType { /// /// Parameter for APIs set/get the current frame/state T-Pose. /// CurrentTPose = 0, /// /// Parameter for APIs set/get the source/target Minimum T-Pose. /// MinTPose = 1, /// /// Parameter for APIs set/get the source/target Maximum T-Pose. /// MaxTPose = 2, /// /// Parameter for APIs set/get the target Unscaled T-Pose. /// UnscaledTPose = 3, } /// /// Defines the coordinate space in which joint transforms are expressed. /// Parameter for Retargeting API - Returns Target Pose in Root Origin Relative Coordinates, /// Parameter for Retargeting API - Returns Target Pose in Local Coordinate space. /// public enum JointRelativeSpaceType { RootOriginRelativeSpace = 0, // Tracking Origin LocalSpace = 1, RootOriginRelativeWithJointScale = 2, LocalSpaceScaled = 3, } /// /// Defines how pose matching should prioritize scale versus orientation. /// Parameter Retargeting API - Matches the scale of the pose, preserves original orientation /// Parameter Retargeting API - Matches the orientation of the pose, preserves original scale /// public enum MatchPoseBehavior { MatchScale = 0, MatchOrientation = 1, } /// /// Defines the behavior type for joint mapping operations. /// Controls how joints are mapped between source and target skeletons. /// public enum JointMappingBehaviorType { /// /// Standard joint mapping behavior. /// Normal = 0, /// /// Joint mapping behavior with twist calculation. /// Aligned parent to twist joint for orientation /// Twist = 1, /// /// Joint mapping behavior with twist calculation /// aligned from the twist joint to it's children /// Used for joints that are mapped, but also twist /// based on orientation of the target rig. /// ChildAlignedTwist = 2, /// /// Editor-only placeholder value. /// Invalid = -1, } /// /// Flags that modify the behavior of the Alignment process. /// Parameter for Alignment API - Instructs Alignment function which operations to apply /// [Flags] public enum AlignmentFlags : byte { None = 0, /// /// Applies re-orientation of the skeletons to ensure the same facing /// ReorientToSourceFacing = 1 << 0, /// /// Re-orients Limbs to match source pose (Rotations only) /// LimbRotations = 1 << 1, /// /// Re-orients Hands and Fingers match source pose (Rotations only) /// HandAndFingerRotations = 1 << 2, /// /// Proportionally scales the character to align wrist height /// ProportionalScalingToHeight = 1 << 3, /// /// Scales/Stretches limbs to match the scale/proportion of the source rig (deformation) /// LimbDeformationMatchSourceProportion = 1 << 4, /// /// Scales/Stretches hands/finger to match the scale/proportion of the source rig (hand deformation) /// MatchHandAndFingerPoseWithDeformation = 1 << 5, /// /// Enum value composed of all flags set to true. /// All = ReorientToSourceFacing | LimbRotations | HandAndFingerRotations | ProportionalScalingToHeight | LimbDeformationMatchSourceProportion | MatchHandAndFingerPoseWithDeformation, } /// /// Flags that modify the behavior of the AutoMapping process. /// Parameter for Automapping API - Instructs AutoMapper to ignore TwistJoints and not process their mappings /// [Flags] public enum AutoMappingFlags : byte { EmptyFlag = 0, SkipTwistJoints = 1 << 0, } /// /// Parameter for APIs get a Joint by a KnownJoint ID. /// Represents common joints that exist in most humanoid skeletons. /// public enum KnownJointType { Unknown = -1, Root = 0, Hips = 1, RightUpperArm = 2, LeftUpperArm = 3, RightWrist = 4, LeftWrist = 5, Chest = 6, Neck = 7, RightUpperLeg = 8, LeftUpperLeg = 9, RightAnkle = 10, LeftAnkle = 11, KnownJointCount = 12, } /// /// Flags that modify the behavior of the retargeting process. /// Parameter for Retargeting API - Applies orientation fixup to joints to maintain child/parent orientation relationship. /// [Flags] public enum RetargetingBehaviorFlags : byte { None = 0, ApplyJointOrientationFixup = 1 << 0, } /// /// Defines the overall strategy for retargeting between skeletons. /// Parameter Retargeting API - Position & Rotation Retargeting (with Deformation) /// Parameter Retargeting API - Position & Rotation Retargeting (with Deformation) - Preserves Hand proportions /// Parameter Retargeting API - Rotation Only Retargeting w/ Uniform Scale - Preserves Target Model proportions /// Parameter Retargeting API - Rotation Only Retargeting - No Scaling - Preserves Target Model Scale & Proportions /// public enum RetargetingBehavior { RotationsAndPositions = 0, RotationsAndPositionsHandsRotationOnly = 1, RotationOnlyUniformScale = 2, RotationOnlyNoScaling = 3, } /// /// Controls how root motion is handled during retargeting. /// Parameter Retargeting API - Yaw and Flat Translation from Origin combined into Root Joint /// Parameter Retargeting API - Flat Translation from Origin in Root, Yaw in Hip (default MSDK OpenXR behavior) /// Parameter Retargeting API - Zero out translation and Yaw (Locks character to origin, facing forward) /// public enum RetargetingRootMotionBehavior { CombineHipRotationIntoRoot = 0, RootFlatTranslationFullHipRotation = 1, ZeroOutAllRootTranslationAndHipYaw = 2, } /// /// Defines the types of tracker joints available in the system. /// Tracker joint type; center eye, left input (hand/controller), /// or right input (hand/controller). /// public enum TrackerJointType { CenterEye = 0, LeftInput = 1, RightInput = 2 } /// /// RetargetingBehaviorInfo Struct. /// Used for communicating Retargeting modal data as an API Parameter /// [StructLayout(LayoutKind.Sequential), Serializable] public struct RetargetingBehaviorInfo { // Value determines how the joints are represented after // retargeted (Local vs Root Origin/Tracking releative) public JointRelativeSpaceType TargetOutputJointSpaceType; // Retargeting Behavior to apply (Positions/Rotations, scaling, etc) public RetargetingBehavior RetargetingBehavior; // Root Motion behavior (Where the linear/angular velocity // should be stored, and how) public RetargetingRootMotionBehavior RootMotionBehavior; // Complementary behaviors of the retargeter enabled by individual flags public RetargetingBehaviorFlags BehaviorFlags; /// /// Constructor for . /// /// /// /// /// public RetargetingBehaviorInfo( JointRelativeSpaceType jointSpaceType, RetargetingBehavior retargetingBehavior, RetargetingRootMotionBehavior rootMotionBehavior, RetargetingBehaviorFlags behaviorFlags) { TargetOutputJointSpaceType = jointSpaceType; RetargetingBehavior = retargetingBehavior; RootMotionBehavior = rootMotionBehavior; BehaviorFlags = behaviorFlags; } public override string ToString() { return $"JointRelativeSpaceType({TargetOutputJointSpaceType}) " + $"RetargetingBehavior({RetargetingBehavior}) " + $"RetargetingRootMotionBehavior({RootMotionBehavior})"; } public static RetargetingBehaviorInfo DefaultRetargetingSettings() { return new RetargetingBehaviorInfo( JointRelativeSpaceType.RootOriginRelativeSpace, RetargetingBehavior.RotationsAndPositions, RetargetingRootMotionBehavior.CombineHipRotationIntoRoot, RetargetingBehaviorFlags.ApplyJointOrientationFixup); } } /// /// SerializationSettings Struct. /// Used for communicating mutable/modifiable settings data /// to the Serialization component /// [StructLayout(LayoutKind.Sequential), Serializable] public struct SerializationSettings { /// /// The SerializationCompressionType type. /// public SerializationCompressionType CompressionType; /// /// The position threshold to use. /// public float PositionThreshold; /// /// The rotation angle threshold to use in Degrees. /// public float RotationAngleThresholdDegrees; /// /// The shape threshold /// public float ShapeThreshold; /// /// The number of snapshots /// public int NumberOfSnapshots; /// /// Constructor for . /// /// /// /// /// /// public SerializationSettings( SerializationCompressionType compressionType, float positionThreshold, float rotationAngleThresholdDegrees, float shapeThreshold, int numberOfSnapshots) { CompressionType = compressionType; PositionThreshold = positionThreshold; RotationAngleThresholdDegrees = rotationAngleThresholdDegrees; ShapeThreshold = shapeThreshold; NumberOfSnapshots = numberOfSnapshots; } /// /// String output for the struct. /// /// The string output for the SkeletonInfo struct. public override string ToString() { return $"CompressionType({CompressionType}) " + $"PositionThreshold({PositionThreshold}) " + $"RotationAngleThresholdDegrees({RotationAngleThresholdDegrees}) " + $"ShapeThreshold({ShapeThreshold}) " + $"NumberOfSnapshots({NumberOfSnapshots})"; } } /// /// Contains information about the skeleton type, number of joints, and number of blendshapes. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct SkeletonInfo { /// /// The type of skeleton. /// public SkeletonType Type; /// /// The number of joints. /// public int JointCount; /// /// The number of blendshapes. /// public int BlendShapeCount; /// /// Constructor for . /// /// /// /// public SkeletonInfo(SkeletonType type, int jointCount, int blendShapeCount) { Type = type; JointCount = jointCount; BlendShapeCount = blendShapeCount; } /// /// String output for the struct. /// /// The string output for the SkeletonInfo struct. public override string ToString() { return $"SkeletonType({Type}) " + $"JointCount({JointCount}) " + $"BlendShapeCount({BlendShapeCount})"; } } /// /// Contains extent data about a pose. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct Extents { /// /// The minimum x,y,z coordinates joints in the pose reach /// public Vector3 Min; /// /// The maximum x,y,z coordinates joints in the pose reach /// public Vector3 Max; /// /// Result of max - min for total coordinate space the pose occupies /// public Vector3 Range; /// /// String output for the struct. /// /// The string output for the Extents struct. public override string ToString() { return $"Min({Min}) " + $"Max({Max}) " + $"Range({Range})"; } } /// /// Contains information about a Skeleton Pose. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct PoseInfo { /// /// The Coordinate Space information for this pose. /// public CoordinateSpace CoordSpace; /// /// The extents of the Pose relative to the coordinate space /// public Extents Extents; /// /// The positions of all known joints relative to coordinate space (Vector3.Zero returned for unspecified known joints) /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)(KnownJointType.KnownJointCount))] public Vector3[] KnownJointPositions; /// /// String output for the struct. /// /// The string output for the PoseInfo struct. public override string ToString() { return $"CoordSpace({CoordSpace}) " + $"Extents({Extents}) " + $"KnownJointPositions({KnownJointPositions.ToString()})"; } } /// /// Represents a definition of a twist joint with the target jointIndex and the two joints used to influence the twist. /// Ratio defines the relative ratio of influence between the start and end influence joint index. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct TwistJointDefinition { /// /// The index of the target twist joint (in the target skeleton). /// public int TwistJointIndex; /// /// The index of source influence start index (either source or target skeleton depending on usage) /// public int InfluenceStartJointIndex; /// /// The index of source influence end index (either source or target skeleton depending on usage) /// Must be the same skeleton (source or target) as the Influence start joint index. /// public int InfluenceEndJointIndex; /// /// The ratio of influence between the start and end (ie - value we base our lerp(start, end, ratio). /// public float Ratio; public override string ToString() { return $"TwistJointIndex({TwistJointIndex}) " + $"InfluenceStartJointIndex({InfluenceStartJointIndex}) " + $"InfluenceEndJointIndex({InfluenceEndJointIndex}) " + $"Ratio({Ratio})"; } } /// /// Represents a single entry in a joint mapping, defining how a specific joint should be mapped. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct JointMappingEntry { /// /// The index of the joint in the skeleton. /// public int JointIndex; /// /// The weight applied to rotation mapping for this joint. /// public float RotationWeight; /// /// The weight applied to position mapping for this joint. /// public float PositionWeight; public override string ToString() { return $"JointIndex({JointIndex}) " + $"RotationWeight({RotationWeight}) " + $"PositionWeight({PositionWeight})"; } } /// /// Defines mapping information for a joint, including its index, type, behavior, and number of entries. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct JointMapping { /// /// The index of the joint in the skeleton. /// public int JointIndex; /// /// The type of skeleton. /// public SkeletonType Type; /// /// The behavior type for this joint mapping. /// public JointMappingBehaviorType Behavior; /// /// The number of entries in this joint mapping. /// public int EntriesCount; public override string ToString() { return $"JointIndex({JointIndex}) " + $"Type({Type}) " + $"Behavior({Behavior}) " + $"EntriesCount({EntriesCount})"; } } public struct JointMappingDefinition { public NativeArray Mappings; public NativeArray MappingEntries; public override string ToString() { return $"JointMappingDefinition: Mappings={Mappings.Length}, MappingEntries={MappingEntries.Length}"; } } [StructLayout(LayoutKind.Sequential), Serializable] private struct JointMappingDefinitionUnmanaged { public int MappingsCount; public int mappingEntryCount; public unsafe JointMapping* Mappings; public unsafe JointMappingEntry* MappingEntries; public unsafe JointMappingDefinitionUnmanaged(JointMappingDefinition safeParams) { MappingsCount = safeParams.Mappings.IsCreated ? safeParams.Mappings.Length : 0; mappingEntryCount = safeParams.MappingEntries.IsCreated ? safeParams.MappingEntries.Length : 0; Mappings = safeParams.Mappings.IsCreated ? safeParams.Mappings.GetPtr() : null; MappingEntries = safeParams.MappingEntries.IsCreated ? safeParams.MappingEntries.GetPtr() : null; } } /// /// Representation of a native transform, containing information about the orientation, /// position, and scale for a transform. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct NativeTransform : IEquatable { /// /// The transform orientation. /// public Quaternion Orientation; /// /// The transform position. /// public Vector3 Position; /// /// The transform scale. /// public Vector3 Scale; /// /// Constructor for . /// /// /// public NativeTransform(Quaternion orientation, Vector3 position) { Orientation = orientation; Position = position; Scale = Vector3.one; } /// /// Constructor for . /// /// /// /// public NativeTransform(Quaternion orientation, Vector3 position, Vector3 scale) { Orientation = orientation; Position = position; Scale = scale; } /// /// Constructor for . /// /// The pose to be converted. public NativeTransform(NativeTransform pose) { Orientation = pose.Orientation; Position = pose.Position; Scale = pose.Scale; } /// /// Constructor for . /// /// The pose to be converted. public NativeTransform(Pose pose) { Orientation = pose.rotation; Position = pose.position; Scale = Vector3.one; } /// /// Constructor for . /// /// The transform to be converted. public NativeTransform(Transform pose) { Orientation = pose.rotation; Position = pose.position; Scale = pose.localScale; } /// /// Implicit conversion from to . /// /// The pose to be converted. /// The native transform equivalent to the pose. public static implicit operator NativeTransform(Pose pose) { return new NativeTransform(pose); } /// /// The identity transform /// (orientation = Quaternion.identity, position = Vector3.zero, scale = Vector3.one). /// /// The identity transform. public static NativeTransform Identity() { return new NativeTransform(Quaternion.identity, Vector3.zero, Vector3.one); } /// /// String output for the struct. /// /// The string output for the struct. public override string ToString() { return $"Pos({Position.x:F3},{Position.y:F3},{Position.z:F3}), " + $"Rot({Orientation.x:F3},{Orientation.y:F3},{Orientation.z:F3},{Orientation.w:F3}), " + $"Scale({Scale.x:F3},{Scale.y:F3},{Scale.z:F3})"; } /// /// Equality operator for . /// /// The other operand. /// True if the two operands are equal; otherwise, false. public bool Equals(NativeTransform other) { return Orientation == other.Orientation && Position == other.Position && Scale == other.Scale; } public override bool Equals(object obj) { return obj is NativeTransform other && Equals(other); } public override int GetHashCode() { return HashCode.Combine(Orientation, Position, Scale); } /// /// Equality operator for . /// /// The left operand. /// The right operand. /// True if the two operands are equal; otherwise, false. public static bool operator ==(NativeTransform left, NativeTransform right) { return left.Equals(right); } /// /// Inequality operator for . /// /// The left operand. /// The right operand. /// True if the two operands are not equal; otherwise, false. public static bool operator !=(NativeTransform left, NativeTransform right) { return !left.Equals(right); } } /// /// Contains information about the coordinate space for a config. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct CoordinateSpace { /// /// The representation of the up vector in this coordinate space. /// public Vector3 Up; /// /// The representation of the forward vector in this coordinate space. /// public Vector3 Forward; /// /// The representation of the right vector in this coordinate space. /// public Vector3 Right; /// /// Constructor for . /// /// . /// /// public CoordinateSpace(Vector3 up, Vector3 forward, Vector3 right) { Up = up; Forward = forward; Right = right; } /// /// String output for the struct. /// /// String output. public override string ToString() { return $"Up({Up.x:F3},{Up.y:F3},{Up.z:F3}), " + $"Forward({Forward.x:F3},{Forward.y:F3},{Forward.z:F3}, " + $"Right({Right.x:F3},{Right.y:F3},{Right.z:F3})"; } } /// /// Contains Initialization Parameters for a Skeleton /// public struct SkeletonInitParams { public string[] BlendShapeNames; public string[] JointNames; public string[] ParentJointNames; public NativeArray MinTPose; public NativeArray MaxTPose; public NativeArray UnscaledTPose; public string[] OptionalKnownSourceJointNamesById; public string[] OptionalAutoMapExcludedJointNames; // Number of manifestations in the name and joint counts arrays public string[] OptionalManifestationNames; // Array of integers public int[] OptionalManifestationJointCounts; // Buffer of all Manifestation joint names in order of manifestations public string[] OptionalManifestationJointNames; public override string ToString() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.AppendLine($"SkeletonInitParams:"); sb.AppendLine($" BlendShapes: {BlendShapeNames?.Length ?? 0}"); sb.AppendLine($" Joints: {JointNames?.Length ?? 0}"); sb.AppendLine($" ParentJoints: {ParentJointNames?.Length ?? 0}"); sb.AppendLine($" MinTPose: {MinTPose.Length}"); sb.AppendLine($" MaxTPose: {MaxTPose.Length}"); sb.AppendLine($" UnscaledTPose: {UnscaledTPose.Length}"); sb.AppendLine($" Manifestations: {OptionalManifestationNames?.Length ?? 0}"); // Add joint names if (JointNames != null && JointNames.Length > 0) { sb.AppendLine("\nJoint Names:"); for (int i = 0; i < JointNames.Length; i++) { sb.AppendLine($" [{i}] {JointNames[i]}"); } } // Add parent joint names if (ParentJointNames != null && ParentJointNames.Length > 0) { sb.AppendLine("\nParent Joint Names:"); for (int i = 0; i < ParentJointNames.Length; i++) { sb.AppendLine($" [{i}] {ParentJointNames[i]}"); } } return sb.ToString(); } } /// /// Unmanaged Structure for containing Initialization Parameters for a Skeleton /// [StructLayout(LayoutKind.Sequential), Serializable] private struct SkeletonInitParamsUnmanaged : IDisposable { public int BlendShapeCount; public int JointCount; public IntPtr BlendShapeNames; public IntPtr JointNames; public IntPtr ParentJointNames; public unsafe NativeTransform* MinTPose; public unsafe NativeTransform* MaxTPose; public unsafe NativeTransform* UnscaledTPose; public IntPtr optional_KnownSourceJointNamesById; public int optional_AutoMapExcludedJointCount; public IntPtr optional_AutoMapExcludedJointNames; // Number of manifestations in the name and joint counts arrays public int optional_ManifestationCount; public IntPtr optional_ManifestationNames; // Array of integers public IntPtr optional_ManifestationJointCounts; // Buffer of all Manifestation joint names in order of manifestations public IntPtr optional_ManifestationJointNames; public unsafe SkeletonInitParamsUnmanaged(SkeletonInitParams safeParams) { BlendShapeCount = safeParams.BlendShapeNames?.Length ?? 0; JointCount = safeParams.JointNames?.Length ?? 0; BlendShapeNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams.BlendShapeNames); JointNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams.JointNames); ParentJointNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams.ParentJointNames); MinTPose = safeParams.MinTPose.IsCreated ? safeParams.MinTPose.GetPtr() : null; MaxTPose = safeParams.MaxTPose.IsCreated ? safeParams.MaxTPose.GetPtr() : null; UnscaledTPose = safeParams.UnscaledTPose.IsCreated ? safeParams.UnscaledTPose.GetPtr() : null; optional_KnownSourceJointNamesById = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams .OptionalKnownSourceJointNamesById); optional_AutoMapExcludedJointCount = safeParams.OptionalAutoMapExcludedJointNames?.Length ?? 0; optional_AutoMapExcludedJointNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams .OptionalAutoMapExcludedJointNames); optional_ManifestationCount = safeParams.OptionalManifestationNames?.Length ?? 0; optional_ManifestationNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams.OptionalManifestationNames); optional_ManifestationJointCounts = IntPtr.Zero; if (safeParams.OptionalManifestationJointCounts != null) { optional_ManifestationJointCounts = Marshal.AllocHGlobal(safeParams.OptionalManifestationJointCounts.Length * sizeof(int)); Marshal.Copy(safeParams.OptionalManifestationJointCounts, 0, optional_ManifestationJointCounts, safeParams.OptionalManifestationJointCounts.Length); } optional_ManifestationJointNames = UnmanagedMarshalFunctions.MarshalStringArrayToUnmanagedPtr(safeParams .OptionalManifestationJointNames); } public void Dispose() { UnmanagedMarshalFunctions.FreeUnmanagedObject(ref BlendShapeNames); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref JointNames); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref ParentJointNames); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref optional_KnownSourceJointNamesById); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref optional_AutoMapExcludedJointNames); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref optional_ManifestationNames); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref optional_ManifestationJointCounts); UnmanagedMarshalFunctions.FreeUnmanagedObject(ref optional_ManifestationJointNames); } } /// /// Contains Initialization Parameters for defining a configuration /// public struct ConfigInitParams { // Skeleton Data public SkeletonInitParams SourceSkeleton; public SkeletonInitParams TargetSkeleton; // Mapping Data public JointMappingDefinition MinMappings; public JointMappingDefinition MaxMappings; public override string ToString() { return "Source: " + SourceSkeleton + "\nTarget: " + TargetSkeleton + "\nMin Mappings: " + MinMappings + "\nMax Mappings:" + MaxMappings; } } /// /// Unmanaged Structure containing Parameters for defining a configuration /// [StructLayout(LayoutKind.Sequential), Serializable] private struct ConfigInitParamsUnmanaged : IDisposable { // Skeleton Data public SkeletonInitParamsUnmanaged SourceSkeleton; public SkeletonInitParamsUnmanaged TargetSkeleton; // Mapping Data public JointMappingDefinitionUnmanaged MinMappings; public JointMappingDefinitionUnmanaged MaxMappings; public ConfigInitParamsUnmanaged(ConfigInitParams safeParams) { SourceSkeleton = new SkeletonInitParamsUnmanaged(safeParams.SourceSkeleton); TargetSkeleton = new SkeletonInitParamsUnmanaged(safeParams.TargetSkeleton); MinMappings = new JointMappingDefinitionUnmanaged(safeParams.MinMappings); MaxMappings = new JointMappingDefinitionUnmanaged(safeParams.MaxMappings); } public void Dispose() { SourceSkeleton.Dispose(); TargetSkeleton.Dispose(); } } /// /// Profiler scope for measuring performance around a block of code. /// Used internally to track performance of various operations. /// private struct ProfilerScope : IDisposable { /// /// Constructor for . /// /// The name of the profiler sample. public ProfilerScope(string name) => Profiler.BeginSample(name); void IDisposable.Dispose() => Profiler.EndSample(); } /// /// Body tracking frame data that we should record. /// Contains information about the current tracking state, including joint positions, /// timestamps, confidence values, and other tracking metadata. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct FrameData { /// /// Main constructor for FrameData. /// Initializes a new instance with all tracking information. /// /// Body tracking fidelity. /// Timestamp. /// Valid state. /// Data confidence. /// Joint set. /// Calibration state. /// Skeleton change count. /// Is using hands (left). /// Is using hands (right). /// Left input. /// Right input. /// Center eye. public FrameData( byte bodyTrackingFidelity, double timestamp, bool isValid, float confidence, byte jointSet, byte calibrationState, uint skeletonChangeCount, bool isUsingHandsLeft, bool isUsingHandsRight, NativeTransform leftInput, NativeTransform rightInput, NativeTransform centerEye) { BodyTrackingFidelity = bodyTrackingFidelity; Timestamp = timestamp; IsValid = isValid; Confidence = confidence; JointSet = jointSet; CalibrationState = calibrationState; SkeletonChangeCount = skeletonChangeCount; IsUsingHandsLeft = isUsingHandsLeft; IsUsingHandsRight = isUsingHandsRight; LeftInput = leftInput; RightInput = rightInput; CenterEye = centerEye; } /// /// Body tracking fidelity level. /// Indicates the quality/detail level of the tracking data. /// public byte BodyTrackingFidelity; /// /// Timestamp of when this frame data was captured. /// Measured in seconds since an application-defined epoch. /// public double Timestamp; /// /// Indicates whether the tracking data in this frame is valid or not. /// Invalid data should not be used for animation or other purposes. /// public bool IsValid; /// /// Data confidence level between 0.0 and 1.0. /// Higher values indicate greater confidence in the tracking data's accuracy. /// public float Confidence; /// /// Tracking joint set identifier. /// Indicates which set of joints is being tracked in this frame. /// public byte JointSet; /// /// Body tracking calibration state. /// Indicates the current calibration status of the tracking system. /// public byte CalibrationState; /// /// True if left hand is based on hand tracking input (false if controllers). /// Determines whether the left hand data comes from hand tracking or controller tracking. /// [MarshalAs(UnmanagedType.U1)] public bool IsUsingHandsLeft; /// /// True if right hand is based on hand tracking input (false if controllers). /// Determines whether the right hand data comes from hand tracking or controller tracking. /// [MarshalAs(UnmanagedType.U1)] public bool IsUsingHandsRight; /// /// Skeleton change count. /// Increments whenever the skeleton configuration changes, allowing detection of structural changes. /// public uint SkeletonChangeCount; /// /// Left input (hand or controller) transform. /// Contains position, orientation, and scale information for the left hand or controller. /// public NativeTransform LeftInput; /// /// Right input (hand or controller) transform. /// Contains position, orientation, and scale information for the right hand or controller. /// public NativeTransform RightInput; /// /// Center eye transform. /// Contains position, orientation, and scale information for the center eye/head position. /// public NativeTransform CenterEye; public override string ToString() { return $"Fidelity: {BodyTrackingFidelity}, timestamp: {Timestamp}, valid: {IsValid}, " + $"Joint set: {JointSet}, calibration state: {CalibrationState}, " + $"skeleton change count: {SkeletonChangeCount}, " + $"is using hands left: {IsUsingHandsLeft}, " + $"is using hands right: {IsUsingHandsRight}, " + $"left input: {LeftInput.ToString()}, " + $"right input: {RightInput.ToString()}, " + $"center: {CenterEye.ToString()}."; } } /// /// Start header for serialization, containing metadata about the recording. /// This structure is serialized into native code and marks the beginning of a recording. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct StartHeader { /// /// Data version string. /// Identifies the version of the data format being used in the recording. /// [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SERIALIZATION_START_HEADER_STRING_SIZE_BYTES)] public string DataVersion; /// /// Operating system version string. /// Identifies the OS version where the recording was created. /// [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SERIALIZATION_START_HEADER_STRING_SIZE_BYTES)] public string OSVersion; /// /// Game engine version string. /// Identifies the game engine version used to create the recording. /// [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SERIALIZATION_START_HEADER_STRING_SIZE_BYTES)] public string GameEngineVersion; /// /// Application bundle ID string. /// Identifies the application that created the recording. /// [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SERIALIZATION_START_HEADER_STRING_SIZE_BYTES)] public string BundleID; /// /// Meta XR SDK version string. /// Identifies the version of the Meta XR SDK used to create the recording. /// [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SERIALIZATION_START_HEADER_STRING_SIZE_BYTES)] public string MetaXRSDKVersion; /// /// Recording UTC timestamp. /// The time when the recording was started, in UTC milliseconds since epoch. /// public long UTCTimestamp; /// /// Number of snapshots that exist in the recording. /// Each snapshot represents a frame of motion data. /// public int NumSnapshots; /// /// Total number of bytes used by all snapshots in the recording. /// Useful for memory allocation and storage planning. /// public int NumTotalSnapshotBytes; /// /// Start network time. /// The network time when the recording was started, used for synchronization. /// public double StartNetworkTime; /// /// Number of buffered snapshots. /// Indicates how many snapshots are being held in memory before writing to storage. /// public int NumBufferedSnapshots; /// /// Default start header constructor. /// Initializes a new StartHeader with all required metadata for a recording. /// /// Data version. /// OS version. /// Game engine version. /// Bundle ID. /// Meta XR SDK version. /// UTC timestamp. /// Num frames recorded. /// Num snapshot bytes recored. /// Start network time. /// Number of buffered snapshots. public StartHeader( string dataVersion, string osVersion, string gameEngineVersion, string bundleID, string metaXRSDKVersion, long utcTimeStamp, int numSnapshots, int numTotalSnapshotBytes, double startNetworkTime, int numberOfBufferedSnapshots) { DataVersion = dataVersion; OSVersion = osVersion; GameEngineVersion = gameEngineVersion; BundleID = bundleID; MetaXRSDKVersion = metaXRSDKVersion; UTCTimestamp = utcTimeStamp; NumSnapshots = numSnapshots; NumTotalSnapshotBytes = numTotalSnapshotBytes; StartNetworkTime = startNetworkTime; NumBufferedSnapshots = numberOfBufferedSnapshots; } /// public override string ToString() { return $"Data: {DataVersion}," + $"OS: {OSVersion}, " + $"Game Engine: {GameEngineVersion}, " + $"BundleID: {BundleID}," + $"MetaXRSDKVersion: {MetaXRSDKVersion}, " + $"UTCTimestamp: {UTCTimestamp}, " + $"NumSnapshots: {NumSnapshots}, " + $"NumSnapshotBytes: {NumTotalSnapshotBytes}, " + $"StartNetworkTime: {StartNetworkTime}, " + $" NumBufferedSnapshots: {NumBufferedSnapshots}."; } /// /// Creates a clone. /// /// A new clone of the current object. public StartHeader Clone() { return new StartHeader( dataVersion: DataVersion, osVersion: OSVersion, gameEngineVersion: GameEngineVersion, bundleID: BundleID, metaXRSDKVersion: MetaXRSDKVersion, utcTimeStamp: UTCTimestamp, NumSnapshots, NumTotalSnapshotBytes, StartNetworkTime, NumBufferedSnapshots); } } /// /// Container for start header bytes passed to native code. /// Provides a fixed-size byte array for serializing the StartHeader structure. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct StartHeaderSerializedBytes { /// /// Serialized start header bytes. /// The byte array containing the serialized StartHeader data. /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = SERIALIZATION_START_HEADER_SIZE_BYTES)] public byte[] SerializedBytes; /// /// Constructor with bytes to copy from. /// Initializes the structure with a copy of the provided byte array. /// /// Bytes to copy from. public StartHeaderSerializedBytes(byte[] bytes) { SerializedBytes = new byte[SERIALIZATION_START_HEADER_SIZE_BYTES]; Array.Copy(bytes, SerializedBytes, SERIALIZATION_START_HEADER_SIZE_BYTES); } } /// /// End header for serialization, containing metadata about the end of a recording. /// This structure is serialized into native code and marks the end of a recording. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct EndHeader { /// /// Recording end UTC timestamp. /// The time when the recording was completed, in UTC milliseconds since epoch. /// public long UTCTimestamp; public EndHeader(long utcTimestamp) { UTCTimestamp = utcTimestamp; } /// public override string ToString() { return $"UTCTimestamp: {UTCTimestamp}."; } } /// /// Container for end header bytes passed to native code. /// Provides a fixed-size byte array for serializing the EndHeader structure. /// [StructLayout(LayoutKind.Sequential), Serializable] public struct EndHeaderSerializedBytes { /// /// Serialized end header bytes. /// The byte array containing the serialized EndHeader data. /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = SERIALIZATION_END_HEADER_SIZE_BYTES)] public byte[] SerializedBytes; /// /// Constructor with bytes to copy from. /// Initializes the structure with a copy of the provided byte array. /// /// Bytes to copy from. public EndHeaderSerializedBytes(byte[] bytes) { SerializedBytes = new byte[SERIALIZATION_END_HEADER_SIZE_BYTES]; Array.Copy(bytes, SerializedBytes, SERIALIZATION_END_HEADER_SIZE_BYTES); } } /// /// Determines if the specified compression type uses joint length compression. /// Joint length compression provides higher compression ratios but may reduce precision. /// This method helps determine the appropriate decompression approach for serialized data. /// /// The compression type to check. /// True if the compression type uses joint lengths; otherwise, false. public static bool CompressionUsesJointLengths(SerializationCompressionType compressionType) { return compressionType == SerializationCompressionType.High; } /// /// Interface for DLL calls to the native Movement SDK Utility plugin. /// Contains all the P/Invoke method declarations for communicating with the native code. /// private abstract class Api { /********************************************************** * * Lifecycle Functions * **********************************************************/ [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_initialize(in CoordinateSpace coordinateSpace); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_createOrUpdateHandle(string config, out ulong handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_destroy(ulong handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_createOrUpdateSimpleUtilityConfig( string configName, SkeletonType skeletonType, ref SkeletonInitParamsUnmanaged initParamsUnmanaged, out ulong handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_createOrUpdateUtilityConfig( string configName, ref ConfigInitParamsUnmanaged initParamsUnmanaged, out ulong handle); /********************************************************** * * Query Functions * **********************************************************/ [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getCoordinateSpace(out CoordinateSpace coordinateSpace); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getConfigName( ulong handle, byte* outBuffer, out int inOutBufferSize); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getVersion(ulong handle, out double version); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getSkeletonInfo( ulong handle, SkeletonType skeletonType, out SkeletonInfo outSkeletonInfo); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getBlendShapeNames( ulong handle, SkeletonType skeletonType, byte* outBuffer, out int inOutBufferSize, void* outUnusedBlendShapeNames, out int inOutNumBlendShapeNames); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getJointNames( ulong handle, SkeletonType skeletonType, byte* outBuffer, out int inOutBufferSize, void* outUnusedJointNames, out int inOutNumJointNames); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getSkeletonTPose( ulong handle, SkeletonType skeletonType, SkeletonTPoseType tPoseType, JointRelativeSpaceType jointSpaceType, NativeTransform* outTransformData, out int inOutNumJoints); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_calculateSkeletonTPose( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float delta, NativeTransform* outTransformData, out int inOutNumJoints); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_calculateSkeletonTPoseAtHeight( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float height, NativeTransform* outTransformData, out int inOutNumJoints); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getSkeletonMappings( ulong handle, SkeletonTPoseType tPoseType, JointMapping* outMappingData, out int inOutNumMappings); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getSkeletonMappingEntries( ulong handle, SkeletonTPoseType tPoseType, JointMappingEntry* outMappingEntryData, out int inOutNumEntries); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getSkeletonMappingTargetJoints( ulong handle, int* outJointIndexList, out int inOutJointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getBlendShapeName( ulong handle, SkeletonType skeletonType, int blendShapeIndex, byte* outBuffer, out int inOutBufferSize); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getJointName( ulong handle, SkeletonType skeletonType, int jointIndex, byte* outBuffer, out int inOutBufferSize); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getJointIndex( ulong handle, SkeletonType skeletonType, string jointName, out int jointIndex); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getJointIndexByKnownJointType( ulong handle, SkeletonType skeletonType, KnownJointType knownJointType, out int jointIndex); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getParentJointIndex( ulong handle, SkeletonType skeletonType, int jointIndex, out int outParentJointIndex); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getParentJointIndexes( ulong handle, SkeletonType skeletonType, int* outJointIndexArray, out int inOutNumJoints); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getManifestationNames( ulong handle, SkeletonType skeletonType, byte* outBuffer, out int inOutBufferSize, void* outUnusedManifestationNames, out int inOutNumManifestationNames); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getJointsInManifestation( ulong handle, SkeletonType skeletonType, string manifestationName, int* outJointIndexList, out int inOutJointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getPoseInfo( ulong handle, SkeletonType skeletonType, SkeletonTPoseType poseType, out PoseInfo outPoseInfo); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getLengthBetweenJoints( ulong handle, SkeletonType skeletonType, SkeletonTPoseType poseType, int startJointIndex, int endJointIndex, out float outLength); /********************************************************** * * Retargeting Functions * **********************************************************/ [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_updateSourceReferenceTPose( ulong handle, NativeTransform* inTransformData, int inNumJoints, string manifestation); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_retargetFromSourceFrameData( ulong handle, RetargetingBehaviorInfo retargetingBehaviorInfo, NativeTransform* inSourceTransformData, int inNumSourceJoints, NativeTransform* outRetargetedTargetTransformData, out int inOutNumTargetJoints, string sourceManifestation, string targetOutputManifestation); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getLastProcessedFramePose( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType outJointSpaceType, NativeTransform* outTransformData, out int inOutNumJoints, string manifestation); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getLastRetargetedMappingData( ulong handle, int targetJointIndex, out SkeletonType outSourceSkeletonType, out NativeTransform outTPoseBlendedTransform, out NativeTransform outLastPoseBlendedTransform, int* outSourceJointIndexList, out int inOutSourceJointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_matchCurrentTPose( ulong handle, SkeletonType skeletonType, MatchPoseBehavior matchBehavior, JointRelativeSpaceType jointSpaceType, int jointCount, NativeTransform* inOutTransformData); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_matchPose( ulong handle, SkeletonType skeletonType, MatchPoseBehavior matchBehavior, JointRelativeSpaceType jointSpaceType, int jointCount, NativeTransform* inTransformSourcePose, NativeTransform* inOutTransformData); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_scalePoseToHeight( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float height, int jointCount, NativeTransform* inOutTransformData); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_alignTargetToSource( string configName, AlignmentFlags alignmentBehavior, ulong sourceConfigHandle, SkeletonType sourceSkeletonType, ulong targetConfigHandle, out ulong handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_generateMappings( ulong handle, AutoMappingFlags autoMappingBehaviorFlags); /********************************************************** * * Serialization Functions * **********************************************************/ // Configuration information. [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getSerializationSettings(ulong handle, out SerializationSettings outSerializationSettings); // Configuration information. [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_updateSerializationSettings(ulong handle, in SerializationSettings inMutableSettings); // Serialization. [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_createSnapshot( ulong handle, int baselineAck, double timestamp); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_snapshotSkeleton( ulong handle, NativeTransform* skeletonPose, int* skeletonIndices, int numberOfSkeletonIndices); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_snapshotFace( ulong handle, float* facePose, int* faceIndices, int numberOfFaceIndices); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_serializeSnapshot( ulong handle, void* outBuffer, out int inOutBufferSizeBytes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_provideFrameData(ulong handle, FrameData frameData); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_serializeStartHeader( in StartHeader startHeader, ref StartHeaderSerializedBytes headerBytes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_deserializeStartHeader( out StartHeader startHeader, in StartHeaderSerializedBytes headerBytes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_serializeEndHeader( in EndHeader endHeader, ref EndHeaderSerializedBytes headerBytes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_deserializeEndHeader( out EndHeader endHeader, in EndHeaderSerializedBytes headerBytes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_provideSkeletonPose( ulong handle, NativeTransform* skeletonPose, int* skeletonIndices, int numSkeletonIndices, double timestamp); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_deserializeSnapshotTimestamp( void* snapshotInBytes, out double outTimestamp); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_deserializeSnapshot( ulong handle, void* snapshotInBytes, out double timestamp, out SerializationCompressionType compressionType, out int ack, NativeTransform* skeletonPose, float* facePose, NativeTransform* btPose, ref FrameData frameData); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_resetInterpolators(ulong handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getInterpolatedSkeletonPose( ulong handle, SkeletonType skeletonType, double timestamp, NativeTransform* outSkeletonPose, out int inOutNumJoints); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_getInterpolatedFacePose( ulong handle, SkeletonType skeletonType, double timestamp, float* outFacePose, out int inOutNumShapes); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern Result metaMovementSDK_getInterpolatedTrackerJointPose( ulong handle, TrackerJointType trackerJointType, double timestamp, ref NativeTransform nativeTransform); /********************************************************** * * Tool and Data Functions * **********************************************************/ [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_writeConfigDataToJSON( ulong handle, CoordinateSpace* optionalCoordinateSpace, byte* outBuffer, out int inOutBufferSize); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_applyWorldSpaceCoordinateSpaceConversion( CoordinateSpace inCoordinateSpace, CoordinateSpace outCoordinateSpace, NativeTransform* transformData, int jointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_applyCoordinateSpaceConversion( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, CoordinateSpace inCoordinateSpace, CoordinateSpace outCoordinateSpace, NativeTransform* skeletonPose, int jointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_convertJointPose( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType inJointSpaceType, JointRelativeSpaceType outJointSpaceType, NativeTransform* skeletonPose, int jointCount); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern unsafe Result metaMovementSDK_calculatePoseExtents( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType inJointSpaceType, NativeTransform* skeletonPose, int jointCount, out Extents outExtents); } #region Unity API /********************************************************** * * Lifecycle Functions * **********************************************************/ /// /// Initialize the plugin to use a specific coordinate space. /// This must be called before any other operations to set up the coordinate system. /// The coordinate space defines how positions and orientations are interpreted. /// /// The coordinate space to use, defining up, forward, and right vectors. /// True if the function was successfully executed. public static bool Initialize(in CoordinateSpace coordinateSpace) { Result success; using (new ProfilerScope(nameof(Initialize))) { success = Api.metaMovementSDK_initialize(coordinateSpace); } return success == Result.Success; } /// /// Creates or updates a simple utility config and returns a handle for accessing the result. /// This is a simplified version of CreateOrUpdateUtilityConfig for cases where only one skeleton is needed. /// Use this method when you need to define a single skeleton without mapping to another skeleton. /// /// The name of the config. /// The type (Source or Target) of the skeleton to create in this config. /// Initialization parameters to define the skeleton. /// The handle that can be used for accessing the resulting config. /// True if the function was successfully executed. public static bool CreateOrUpdateSimpleUtilityConfig( string configName, SkeletonType skeletonType, SkeletonInitParams initParams, out ulong handle) { Result success; using (new ProfilerScope(nameof(CreateOrUpdateUtilityConfig))) { unsafe { SkeletonInitParamsUnmanaged initParamsUnmanaged = new SkeletonInitParamsUnmanaged(initParams); try { success = Api.metaMovementSDK_createOrUpdateSimpleUtilityConfig( configName, skeletonType, ref initParamsUnmanaged, out handle); } finally { initParamsUnmanaged.Dispose(); } } } return success == Result.Success; } /// /// Create or update a config and return a handle for accessing the result. /// This comprehensive method sets up both source and target skeletons with their respective /// blend shapes, joint hierarchies, and T-poses, along with the mapping between them. /// Use this method when you need to define a complete retargeting setup between two different skeletons. /// /// The name of the config. /// Initialization parameters to define the skeleton. /// The handle that can be used for accessing the resulting config. /// True if the function was successfully executed. public static bool CreateOrUpdateUtilityConfig(string configName, ConfigInitParams initParams, out ulong handle) { Result success; using (new ProfilerScope(nameof(CreateOrUpdateUtilityConfig))) { unsafe { ConfigInitParamsUnmanaged initParamsUnmanaged = new ConfigInitParamsUnmanaged(initParams); try { success = Api.metaMovementSDK_createOrUpdateUtilityConfig( configName, ref initParamsUnmanaged, out handle); } finally { initParamsUnmanaged.Dispose(); } } } return success == Result.Success; } /// /// Creates or updates a handle using a config string. /// This method allows for creating a configuration from a JSON string rather than /// specifying all parameters individually. /// Use this when you have a pre-defined configuration in JSON format, such as from a saved file. /// /// The contents of a config in JSON format. /// The handle that can be used for accessing the resulting config. /// True if the function was successfully executed. public static bool CreateOrUpdateHandle(string config, out ulong handle) { Result success; using (new ProfilerScope(nameof(CreateOrUpdateHandle))) { success = Api.metaMovementSDK_createOrUpdateHandle(config, out handle); } return success == Result.Success; } /// /// Destroy the specified handle instance. /// This releases all resources associated with the handle and should be called /// when the handle is no longer needed to prevent memory leaks. /// Always call this method when you're done with a handle to ensure proper cleanup. /// /// The handle to be destroyed. /// True if the function was successfully executed. public static bool DestroyHandle(ulong handle) { Result success; using (new ProfilerScope(nameof(DestroyHandle))) { success = Api.metaMovementSDK_destroy(handle); } return success == Result.Success; } /********************************************************** * * Query Functions * **********************************************************/ /// /// Get the coordinate space that the plugin is using. /// This retrieves the current coordinate space configuration that was set during initialization. /// The coordinate space defines the orientation of the up, forward, and right vectors. /// /// Output parameter that receives the coordinate space information. /// True if the function was successfully executed. public static bool GetCoordinateSpace(out CoordinateSpace coordinateSpace) { Result success; using (new ProfilerScope(nameof(GetCoordinateSpace))) { success = Api.metaMovementSDK_getCoordinateSpace(out coordinateSpace); } return success == Result.Success; } /// /// Get the name of the config for a handle. /// This retrieves the name that was assigned to the configuration when it was created. /// The config name can be used for identification and debugging purposes. /// /// The handle to get the config name from. /// Output parameter that receives the name of the config. /// True if the function was successfully executed. public static bool GetConfigName(ulong handle, out string configName) { Result success; configName = string.Empty; using (new ProfilerScope(nameof(GetConfigName))) { unsafe { success = Api.metaMovementSDK_getConfigName(handle, null, out var stringLength); if (success == Result.Success) { var nameBuffer = stackalloc byte[stringLength]; success = Api.metaMovementSDK_getConfigName(handle, nameBuffer, out stringLength); if (success == Result.Success) { configName = Marshal.PtrToStringAnsi((IntPtr)nameBuffer, stringLength); } } } } return success == Result.Success; } /// /// Gets the version number of the configuration. /// This can be used to check compatibility between different configurations. /// Version numbers help ensure that configurations are compatible with the current SDK. /// /// The handle to get the version from. /// Output parameter that receives the config version number. /// True if the function was successfully executed. public static bool GetVersion(ulong handle, out double version) { Result success; using (new ProfilerScope(nameof(GetConfigName))) { unsafe { success = Api.metaMovementSDK_getVersion(handle, out version); } } return success == Result.Success; } /// /// Get the skeleton info for a handle. /// This retrieves basic information about the skeleton structure, including the number of joints and blendshapes. /// Use this to understand the structure of a skeleton before performing operations on it. /// /// The handle to get the skeleton info from. /// The type of skeleton (source or target) to get info from. /// Output parameter that receives the skeleton information. /// True if the function was successfully executed. public static bool GetSkeletonInfo(ulong handle, SkeletonType skeletonType, out SkeletonInfo skeletonInfo) { Result success; using (new ProfilerScope(nameof(GetConfigName))) { success = Api.metaMovementSDK_getSkeletonInfo(handle, skeletonType, out skeletonInfo); } return success == Result.Success; } /// /// Get the names of all blendshapes for a skeleton type. /// Blendshapes are used for facial expressions and other deformations that aren't handled by the skeleton joints. /// This method returns an array of strings containing all blendshape names defined in the skeleton. /// /// The handle to get the blendshapes from. /// The type of skeleton to get the blendshape names from. /// Output array that receives the blendshape names. /// True if the function was successfully executed. public static bool GetBlendShapeNames(ulong handle, SkeletonType skeletonType, out string[] blendShapeNames) { Result success; // Assign an Empty String to the output array blendShapeNames = Array.Empty(); using (new ProfilerScope(nameof(GetBlendShapeNames))) { unsafe { success = Api.metaMovementSDK_getBlendShapeNames(handle, skeletonType, null, out var bufferSize, null, out var nameCount); if (success == Result.Success) { var buffer = new byte[bufferSize]; Span nameBuffer = buffer; fixed (byte* bytes = &nameBuffer.GetPinnableReference()) { success = Api.metaMovementSDK_getBlendShapeNames(handle, skeletonType, bytes, out bufferSize, null, out nameCount); if (success == Result.Success) { ConvertByteBufferToStringArray(bytes, bufferSize, nameCount, out blendShapeNames); } } } } } return success == Result.Success; } /// /// Get the name of a specific blendshape by its index. /// This method retrieves the name of a single blendshape identified by its index in the skeleton. /// Use this when you know the index but need the corresponding name for display or mapping purposes. /// /// The handle to get the blendshape from. /// The type of skeleton to get the blendshape name from. /// The index of the blendshape to get the name for. /// Output parameter that receives the blendshape name. /// True if the function was successfully executed. public static bool GetBlendShapeName(ulong handle, SkeletonType skeletonType, int blendShapeIndex, out string blendShapeName) { Result success; blendShapeName = string.Empty; using (new ProfilerScope(nameof(GetBlendShapeName))) { unsafe { success = Api.metaMovementSDK_getBlendShapeName(handle, skeletonType, blendShapeIndex, null, out var stringLength); if (success == Result.Success) { var nameBuffer = stackalloc byte[stringLength]; success = Api.metaMovementSDK_getBlendShapeName(handle, skeletonType, blendShapeIndex, nameBuffer, out stringLength); if (success == Result.Success) { blendShapeName = Marshal.PtrToStringAnsi((IntPtr)nameBuffer, stringLength); } } } } return success == Result.Success; } /// /// Get the names of all joints for a skeleton type. /// These names identify each bone in the skeleton hierarchy and are used for mapping between skeletons. /// Joint names are essential for identifying specific parts of the skeleton for retargeting and animation. /// /// The handle to get the joints from. /// The type of skeleton to get the joint names from. /// Output array that receives the joint names. /// True if the function was successfully executed. public static bool GetJointNames(ulong handle, SkeletonType skeletonType, out string[] jointNames) { Result success; jointNames = Array.Empty(); using (new ProfilerScope(nameof(GetJointNames))) { unsafe { success = Api.metaMovementSDK_getJointNames(handle, skeletonType, null, out var bufferSize, null, out var nameCount); if (success == Result.Success) { var buffer = new byte[bufferSize]; Span nameBuffer = buffer; fixed (byte* bytes = &nameBuffer.GetPinnableReference()) { success = Api.metaMovementSDK_getJointNames(handle, skeletonType, bytes, out bufferSize, null, out nameCount); if (success == Result.Success) { ConvertByteBufferToStringArray(bytes, bufferSize, nameCount, out jointNames); } } } } } return success == Result.Success; } /// /// Get the name of a specific joint by its index. /// This method retrieves the name of a single joint identified by its index in the skeleton. /// Use this when you know the index but need the corresponding name for display or mapping purposes. /// /// The handle to get the joint from. /// The type of skeleton to get the joint name from. /// The index of the joint to get the name for. /// Output parameter that receives the joint name. /// True if the function was successfully executed. public static bool GetJointName(ulong handle, SkeletonType skeletonType, int jointIndex, out string jointName) { Result success; jointName = string.Empty; using (new ProfilerScope(nameof(GetJointName))) { unsafe { success = Api.metaMovementSDK_getJointName(handle, skeletonType, jointIndex, null, out var stringLength); if (success == Result.Success) { var nameBuffer = stackalloc byte[stringLength]; success = Api.metaMovementSDK_getJointName(handle, skeletonType, jointIndex, nameBuffer, out stringLength); if (success == Result.Success) { jointName = Marshal.PtrToStringAnsi((IntPtr)nameBuffer, stringLength).TrimEnd('\0'); } } } } return success == Result.Success; } /// /// Get the index for the parent joint for each joint in the skeleton. /// This defines the hierarchical structure of the skeleton. /// Understanding the parent-child relationships is crucial for proper skeleton manipulation and retargeting. /// /// The handle to get the info from. /// The type of skeleton to get the info from. /// Reference to an array that will be filled with parent joint indices. /// For each joint at index i, jointIndexArray[i] will contain the index of its parent joint. /// True if the function was successfully executed. public static bool GetParentJointIndexesByRef( ulong handle, SkeletonType skeletonType, ref NativeArray jointIndexArray) { Result success; using (new ProfilerScope(nameof(GetParentJointIndexesByRef))) { unsafe { var jointIndexArrayLength = jointIndexArray.Length; success = Api.metaMovementSDK_getParentJointIndexes(handle, skeletonType, jointIndexArray.GetPtr(), out jointIndexArrayLength); } } return success == Result.Success; } /// /// Get the T-pose data for a skeleton. /// The T-pose is a reference pose used as a basis for retargeting and other operations. /// T-poses provide a standardized starting point for comparing and mapping between different skeletons. /// /// The handle to get the T-pose from. /// The type of skeleton to get the T-pose for. /// The specific T-pose type to retrieve (current, min, max, or unscaled). /// The coordinate space to express the transforms in. /// Reference to an array that will be filled with the T-pose transforms. /// True if the function was successfully executed. public static bool GetSkeletonTPoseByRef( ulong handle, SkeletonType skeletonType, SkeletonTPoseType tPoseType, JointRelativeSpaceType jointSpaceType, ref NativeArray transformArray) { Result success; using (new ProfilerScope(nameof(GetSkeletonTPoseByRef))) { unsafe { var transformArrayLength = transformArray.Length; success = Api.metaMovementSDK_getSkeletonTPose(handle, skeletonType, tPoseType, jointSpaceType, transformArray.GetPtr(), out transformArrayLength); } } return success == Result.Success; } /// /// Calculate a T-pose for a skeleton based on a blend factor between min and max T-poses. /// This allows for creating intermediate T-poses for different body proportions. /// The delta parameter controls the blend between minimum (0.0) and maximum (1.0) T-poses. /// /// The handle to calculate the T-pose for. /// The type of skeleton to calculate the T-pose for. /// The coordinate space to express the transforms in. /// The blend factor between min (0.0) and max (1.0) T-poses. /// Reference to an array that will be filled with the calculated T-pose transforms. /// True if the function was successfully executed. public static bool CalculateSkeletonTPoseByRef( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float delta, ref NativeArray transformArray) { Result success; using (new ProfilerScope(nameof(CalculateSkeletonTPoseByRef))) { unsafe { var transformArrayLength = transformArray.Length; success = Api.metaMovementSDK_calculateSkeletonTPose(handle, skeletonType, jointSpaceType, delta, transformArray.GetPtr(), out transformArrayLength); } } return success == Result.Success; } /// /// Calculate a T-pose for a skeleton scaled to a specific height. /// This is useful for adapting a skeleton to match a target character's height. /// The height parameter specifies the desired height in meters for the resulting T-pose. /// /// The handle to calculate the T-pose for. /// The type of skeleton to calculate the T-pose for. /// The coordinate space to express the transforms in. /// The target height in meters. /// Reference to an array that will be filled with the calculated T-pose transforms. /// True if the function was successfully executed. public static bool CalculateSkeletonTPoseAtHeightByRef( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float height, ref NativeArray transformArray) { Result success; using (new ProfilerScope(nameof(CalculateSkeletonTPoseAtHeightByRef))) { unsafe { var transformArrayLength = transformArray.Length; success = Api.metaMovementSDK_calculateSkeletonTPoseAtHeight(handle, skeletonType, jointSpaceType, height, transformArray.GetPtr(), out transformArrayLength); } } return success == Result.Success; } /// /// Get the joint mappings for a skeleton. /// Joint mappings define how joints in one skeleton correspond to joints in another skeleton. /// /// The handle to get the mappings from. /// The specific T-pose type to retrieve mappings for. /// Output array that receives the joint mappings. /// True if the function was successfully executed. public static bool GetSkeletonMappings( ulong handle, SkeletonTPoseType tPoseType, out NativeArray mappingsArray) { Result success; using (new ProfilerScope(nameof(GetSkeletonMappings))) { unsafe { success = Api.metaMovementSDK_getSkeletonMappings(handle, tPoseType, null, out var numMappings); if (success == Result.Success && numMappings > 0) { mappingsArray = new NativeArray(numMappings, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_getSkeletonMappings(handle, tPoseType, mappingsArray.GetPtr(), out numMappings); } else { mappingsArray = new NativeArray(0, Allocator.Temp, NativeArrayOptions.UninitializedMemory); } } } return success == Result.Success; } /// /// Get the list of target joint indices that are used in joint mappings. /// This identifies which joints in the target skeleton are affected by retargeting. /// /// The handle to get the target joints from. /// Output array that receives the joint indices. /// True if the function was successfully executed. public static bool GetSkeletonMappingTargetJoints(ulong handle, out NativeArray jointIndexList) { Result success; using (new ProfilerScope(nameof(GetSkeletonMappingTargetJoints))) { unsafe { int numTargetJoints = 0; success = Api.metaMovementSDK_getSkeletonMappingTargetJoints(handle, null, out numTargetJoints); if (success == Result.Success && numTargetJoints > 0) { jointIndexList = new NativeArray(numTargetJoints, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_getSkeletonMappingTargetJoints(handle, jointIndexList.GetPtr(), out numTargetJoints); } else { jointIndexList = new NativeArray(0, Allocator.Temp, NativeArrayOptions.UninitializedMemory); } } } return success == Result.Success; } /// /// Get the index of a joint by its name. /// This allows for looking up a joint's index in the skeleton hierarchy when you know its name. /// /// The handle to get the joint index from. /// The type of skeleton to get the joint index from. /// The name of the joint to find. /// Output parameter that receives the joint index. /// True if the function was successfully executed. public static bool GetJointIndex( ulong handle, SkeletonType skeletonType, string jointName, out int jointIndex) { Result result; using (new ProfilerScope(nameof(GetJointIndex))) { result = Api.metaMovementSDK_getJointIndex(handle, skeletonType, jointName, out jointIndex); } return result == Result.Success; } /// /// Get the index of a joint by its known joint type. /// This allows for looking up common joints (like hips, neck, etc.) without knowing their specific names. /// /// The handle to get the joint index from. /// The type of skeleton to get the joint index from. /// The known joint type to find. /// Output parameter that receives the joint index. /// True if the function was successfully executed. public static bool GetJointIndexByKnownJointType( ulong handle, SkeletonType skeletonType, KnownJointType knownJointType, out int jointIndex) { Result success; using (new ProfilerScope(nameof(GetParentJointIndex))) { success = Api.metaMovementSDK_getJointIndexByKnownJointType(handle, skeletonType, knownJointType, out jointIndex); } return success == Result.Success; } /// /// Get the index of a joint's parent. /// This allows for traversing up the skeleton hierarchy from a specific joint. /// /// The handle to get the parent joint index from. /// The type of skeleton to get the parent joint index from. /// The index of the joint to find the parent for. /// Output parameter that receives the parent joint index. /// True if the function was successfully executed. public static bool GetParentJointIndex( ulong handle, SkeletonType skeletonType, int jointIndex, out int parentJointIndex) { Result success; using (new ProfilerScope(nameof(GetParentJointIndex))) { success = Api.metaMovementSDK_getParentJointIndex(handle, skeletonType, jointIndex, out parentJointIndex); } return success == Result.Success; } /// /// Get the names of all manifestations for a skeleton type. /// Manifestations are subsets of a skeleton that can be used for specific purposes, /// such as only retargeting the upper body or only the hands. /// /// The handle to get the manifestations from. /// The type of skeleton to get the manifestation names from. /// Output array that receives the manifestation names. /// True if the function was successfully executed. public static bool GetManifestationNames( ulong handle, SkeletonType skeletonType, out string[] manifestationNames) { Result success; manifestationNames = Array.Empty(); using (new ProfilerScope(nameof(GetJointNames))) { unsafe { success = Api.metaMovementSDK_getManifestationNames(handle, skeletonType, null, out var bufferSize, null, out var nameCount); if (success == Result.Success) { var buffer = new byte[bufferSize]; Span nameBuffer = buffer; fixed (byte* bytes = &nameBuffer.GetPinnableReference()) { success = Api.metaMovementSDK_getManifestationNames(handle, skeletonType, bytes, out bufferSize, null, out nameCount); if (success == Result.Success) { ConvertByteBufferToStringArray(bytes, bufferSize, nameCount, out manifestationNames); } } } } } return success == Result.Success; } /// /// Get the joint indexes of all joints in a manifestation for a skeleton type. /// This identifies which joints are included in a specific manifestation subset. /// /// The handle to get the joints from. /// The type of skeleton to get the joint indices from. /// The name of the manifestation to get joints for. /// Output array that receives the joint indices in the manifestation. /// True if the function was successfully executed. public static bool GetJointsInManifestation( ulong handle, SkeletonType skeletonType, string manifestationName, out NativeArray jointIndexList) { Result success; using (new ProfilerScope(nameof(GetJointsInManifestation))) { unsafe { success = Api.metaMovementSDK_getJointsInManifestation( handle, skeletonType, manifestationName, null, out var jointIndexListSize); if (success == Result.Success && jointIndexListSize > 0) { jointIndexList = new NativeArray(jointIndexListSize, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_getJointsInManifestation( handle, skeletonType, manifestationName, jointIndexList.GetPtr(), out jointIndexListSize); } else { jointIndexList = new NativeArray(0, Allocator.Temp, NativeArrayOptions.UninitializedMemory); } } } return success == Result.Success; } /********************************************************** * * Retargeting Functions * **********************************************************/ /// /// Updates the source reference T-Pose to use for Retargeting. /// This allows for customizing the reference pose used as the basis for retargeting operations. /// /// The handle to update the reference T-pose for. /// An array of transforms composing a T-Pose for the source skeleton. /// Empty string or the name of a valid manifestation in the source skeleton. /// If specified, only updates the T-pose for joints in that manifestation. /// True if the function was successfully executed. public static bool UpdateSourceReferenceTPose( ulong handle, NativeArray sourceTPose, string manifestation = null) { Result success; using (new ProfilerScope(nameof(UpdateSourceReferenceTPose))) { unsafe { success = Api.metaMovementSDK_updateSourceReferenceTPose( handle, sourceTPose.GetPtr(), sourceTPose.Length, manifestation); } } return success == Result.Success; } /// /// Retargets a pose from the source skeleton to the target skeleton. /// This is the core function for transferring motion from one skeleton to another with different proportions. /// /// The handle to use for retargeting. /// A structure of settings that control how retargeting is performed. /// Array of transforms representing the current pose of the source skeleton. /// The number of source joints. This should be the length of the sourceFramePose, unless using manifestations. /// Reference to an array that will receive the retargeted pose for the target skeleton. /// Optional name of a manifestation in the source skeleton to retarget from. /// If null or empty, the entire skeleton is used. /// Optional name of a manifestation in the target skeleton to retarget to. /// If null or empty, the entire skeleton is used. /// True if the function was successfully executed. public static bool RetargetFromSourceFrameData( ulong handle, RetargetingBehaviorInfo retargetingBehaviorInfo, NativeArray sourceFramePose, ref NativeArray targetRetargetedPose, string sourceManifestation = null, string targetManifestation = null) { Result success = Result.Success; using (new ProfilerScope(nameof(RetargetFromSourceFrameData))) { unsafe { int numSourceJoints = sourceFramePose.Length; // If we have a manifestion, update our length to match // our manifestation joint count. if (sourceManifestation != null) { success = Api.metaMovementSDK_getJointsInManifestation( handle, SkeletonType.SourceSkeleton, sourceManifestation, null, out numSourceJoints); if (success == Result.Success && numSourceJoints > sourceFramePose.Length) { success = Result.Failure; } } if (success == Result.Success) { var numTargetJoints = targetRetargetedPose.Length; success = Api.metaMovementSDK_retargetFromSourceFrameData( handle, retargetingBehaviorInfo, sourceFramePose.GetPtr(), numSourceJoints, targetRetargetedPose.GetPtr(), out numTargetJoints, sourceManifestation, targetManifestation); } } } return success == Result.Success; } /// /// Gets the last processed frame pose for a skeleton. /// This retrieves the most recent pose that was processed by the retargeting system. /// /// The handle to get the pose from. /// The type of skeleton to get the pose for. /// The coordinate space to express the transforms in. /// Reference to an array that will be filled with the pose transforms. /// Optional name of a manifestation to get the pose for. /// If null or empty, the entire skeleton pose is returned. /// True if the function was successfully executed. public static bool GetLastProcessedFramePose( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, ref NativeArray transformArray, string manifestation = null) { Result success = Result.Failure; using (new ProfilerScope(nameof(GetLastProcessedFramePose))) { unsafe { var transformArrayLength = transformArray.Length; success = Api.metaMovementSDK_getLastProcessedFramePose( handle, skeletonType, jointSpaceType, transformArray.GetPtr(), out transformArrayLength, manifestation); } } return success == Result.Success; } /// /// Gets the mapping data used in the last retargeting operation for a specific target joint. /// This provides detailed information about how a target joint was influenced by source joints. /// /// The handle to get the mapping data from. /// The index of the target joint to get mapping data for. /// Output parameter that receives the source skeleton type. /// Output parameter that receives the blended T-pose transform. /// Output parameter that receives the blended last pose transform. /// Output array that receives the indices of source joints that influenced this target joint. /// True if the function was successfully executed. public static bool GetLastRetargetedMappingData( ulong handle, int targetJointIndex, out SkeletonType sourceSkeletonType, out NativeTransform tPoseBlendedTransform, out NativeTransform lastPoseBlendedTransform, out NativeArray sourceJointIndexList) { Result success; using (new ProfilerScope(nameof(GetLastRetargetedMappingData))) { unsafe { success = Api.metaMovementSDK_getLastRetargetedMappingData( handle, targetJointIndex, out sourceSkeletonType, out tPoseBlendedTransform, out lastPoseBlendedTransform, null, out var sourceJointIndexListSize); if (success == Result.Success && sourceJointIndexListSize > 0) { sourceJointIndexList = new NativeArray(sourceJointIndexListSize, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_getLastRetargetedMappingData(handle, targetJointIndex, out sourceSkeletonType, out tPoseBlendedTransform, out lastPoseBlendedTransform, sourceJointIndexList.GetPtr(), out sourceJointIndexListSize); } else { sourceJointIndexList = new NativeArray(0, Allocator.Temp, NativeArrayOptions.UninitializedMemory); } } } return success == Result.Success; } /// /// Matches a pose to the current T-pose of a skeleton. /// This adjusts the provided pose to match the scale or orientation of the current T-pose, /// depending on the specified match behavior. /// /// The handle to use for matching. /// The type of skeleton to match against. /// Controls whether to match scale or orientation. /// The coordinate space to express the transforms in. /// Reference to an array containing the pose to match, which will be updated with the matched pose. /// True if the function was successfully executed. public static bool MatchCurrentTPose( ulong handle, SkeletonType skeletonType, MatchPoseBehavior matchBehavior, JointRelativeSpaceType jointSpaceType, ref NativeArray inOutTransformData) { Result success; using (new ProfilerScope(nameof(MatchCurrentTPose))) { unsafe { success = Api.metaMovementSDK_matchCurrentTPose( handle, skeletonType, matchBehavior, jointSpaceType, inOutTransformData.Length, inOutTransformData.GetPtr()); } } return success == Result.Success; } /// /// Matches one pose to another pose. /// This adjusts the target pose to match the scale or orientation of the source pose, /// depending on the specified match behavior. /// /// The handle to use for matching. /// The type of skeleton the poses belong to. /// Controls whether to match scale or orientation. /// The coordinate space to express the transforms in. /// The source pose to match against. /// Reference to an array containing the pose to be modified, which will be updated with the matched pose. /// True if the function was successfully executed. public static bool MatchPose( ulong handle, SkeletonType skeletonType, MatchPoseBehavior matchBehavior, JointRelativeSpaceType jointSpaceType, ref NativeArray inTransformSourceData, ref NativeArray inOutTransformMutableData) { Result success; using (new ProfilerScope(nameof(MatchPose))) { unsafe { Assert.IsTrue(inTransformSourceData.Length == inOutTransformMutableData.Length); success = Api.metaMovementSDK_matchPose( handle, skeletonType, matchBehavior, jointSpaceType, inTransformSourceData.Length, inTransformSourceData.GetPtr(), inOutTransformMutableData.GetPtr()); } } return success == Result.Success; } /// /// Scales a pose to a specific height. /// This is useful for adapting a pose to match a target character's height. /// /// The handle to use for scaling. /// The type of skeleton the pose belongs to. /// The coordinate space to express the transforms in. /// The target height in meters. /// Reference to an array containing the pose to be scaled, which will be updated with the scaled pose. /// True if the function was successfully executed. public static bool ScalePoseToHeight( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, float height, ref NativeArray inOutTransformData) { Result success; using (new ProfilerScope(nameof(ScalePoseToHeight))) { unsafe { success = Api.metaMovementSDK_scalePoseToHeight( handle, skeletonType, jointSpaceType, height, inOutTransformData.Length, inOutTransformData.GetPtr()); } } return success == Result.Success; } /// /// Scales a pose to a specific height. /// This is useful for adapting a pose to match a target character's height. /// /// The name of the new config to be created /// Flags used to instruct operations to process. /// The config handle with the source skeleton definition /// Skeleton Type to pull the source skeleton from in the sourceConfigHandle data /// The config handle with the source skeleton definition (Assumed to be the Target skeleton) /// The out parameter used to communicate the new handle with the source skeleton aligned to the target skeleton. /// True if the function was successfully executed. public static bool AlignTargetToSource( string configName, AlignmentFlags alignmentBehavior, ulong sourceConfigHandle, SkeletonType sourceSkeletonType, ulong targetConfigHandle, out ulong handle) { Result success; using (new ProfilerScope(nameof(AlignTargetToSource))) { unsafe { success = Api.metaMovementSDK_alignTargetToSource( configName, alignmentBehavior, sourceConfigHandle, sourceSkeletonType, targetConfigHandle, out handle); } } return success == Result.Success; } /// /// Generates a joint mapping between the source and target skeletons defined in the config. /// /// A valid handle of a config containing a Target skeleton aligned to a source skeleton. /// Flags used to the automapper how to process the request. /// True if the function was successfully executed. public static bool GenerateMappings( ulong handle, AutoMappingFlags autoMappingBehavior) { Result success; using (new ProfilerScope(nameof(GenerateMappings))) { unsafe { success = Api.metaMovementSDK_generateMappings( handle, autoMappingBehavior); } } return success == Result.Success; } /// /// Gets information about a specific pose. /// This retrieves metadata about the pose, including its coordinate space, extents, and known joint positions. /// /// The handle to get the pose info from. /// The type of skeleton the pose belongs to. /// The specific pose type to get info for. /// Output parameter that receives the pose information. /// True if the function was successfully executed. public static bool GetPoseInfo( ulong handle, SkeletonType skeletonType, SkeletonTPoseType poseType, out PoseInfo poseInfo) { Result success; using (new ProfilerScope(nameof(GetPoseInfo))) { unsafe { success = Api.metaMovementSDK_getPoseInfo(handle, skeletonType, poseType, out poseInfo); } } return success == Result.Success; } /// /// Gets the distance between two joints in a pose. /// This calculates the direct linear distance between the specified joints. /// /// The handle to get the joint length from. /// The type of skeleton the joints belong to. /// The specific pose type to measure in. /// The index of the starting joint. /// The index of the ending joint. /// Output parameter that receives the distance between the joints in meters. /// True if the function was successfully executed. public static bool GetLengthBetweenJoints( ulong handle, SkeletonType skeletonType, SkeletonTPoseType poseType, int startJointIndex, int endJointIndex, out float length) { Result success; using (new ProfilerScope(nameof(GetLengthBetweenJoints))) { unsafe { success = Api.metaMovementSDK_getLengthBetweenJoints(handle, skeletonType, poseType, startJointIndex, endJointIndex, out length); } } return success == Result.Success; } /********************************************************** * * Serialization Functions * **********************************************************/ /// /// Get the serialization settings. /// /// The handle to get the configuration info. /// The serialization settings. /// True if the function was successfully executed. public static bool GetSerializationSettings(ulong handle, out SerializationSettings outSerializationSettings) { Result success; using (new ProfilerScope(nameof(GetSerializationSettings))) { success = Api.metaMovementSDK_getSerializationSettings(handle, out outSerializationSettings); } return success == Result.Success; } /// /// Update the serialization settings. /// /// The handle to update the configuration info. /// The serialization settings to update. /// True if the function was successfully executed. public static bool UpdateSerializationSettings(ulong handle, in SerializationSettings inMutableSettings) { Result success; using (new ProfilerScope(nameof(UpdateSerializationSettings))) { success = Api.metaMovementSDK_updateSerializationSettings(handle, in inMutableSettings); } return success == Result.Success; } /// /// Serializes body and face pose data into a compact byte array. /// This creates a snapshot of the current pose that can be stored or transmitted over a network. /// The serialization applies compression based on the current serialization settings. /// /// The handle to use for serialization. /// The timestamp to associate with this snapshot, in seconds. /// The body pose transforms to be serialized. /// The face pose blendshape weights to be serialized. /// The acknowledgement number for the data, used for synchronization. /// The indices of the joints in the body pose that should be serialized. /// The indices of the blendshapes in the face pose that should be serialized. /// Reference to a byte array that will be created and filled with the serialized data. /// True if serialization was successful. public static bool SerializeSkeletonAndFace( ulong handle, float timestamp, NativeArray bodyPose, NativeArray facePose, int ack, int[] bodyIndicesToSerialize, int[] faceIndicesToSerialize, ref NativeArray output) { using (new ProfilerScope(nameof(SerializeSkeletonAndFace))) { var bodyIndices = new NativeArray(bodyIndicesToSerialize.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); var faceIndices = new NativeArray(faceIndicesToSerialize.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); bodyIndices.CopyFrom(bodyIndicesToSerialize); faceIndices.CopyFrom(faceIndicesToSerialize); var success = Api.metaMovementSDK_createSnapshot(handle, ack, timestamp); if (success != Result.Success) { Debug.LogError("Could not create snapshot!"); return false; } unsafe { success = Api.metaMovementSDK_snapshotSkeleton( handle, bodyPose.GetPtr(), bodyIndices.GetPtr(), bodyIndices.Length); } if (success != Result.Success) { Debug.LogError("Could not snapshot current skeleton!"); return false; } unsafe { success = Api.metaMovementSDK_snapshotFace( handle, facePose.GetPtr(), faceIndices.GetPtr(), faceIndices.Length); } if (success != Result.Success) { Debug.LogError("Could not snapshot current skeleton!"); return false; } unsafe { success = Api.metaMovementSDK_serializeSnapshot(handle, null, out var bytes); if (success == Result.Success) { output = new NativeArray(bytes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_serializeSnapshot(handle, output.GetPtr(), out bytes); } } if (success == Result.Success) { return true; } Debug.LogError("Could not serialize snapshot!"); output.Dispose(); output = default; return false; } } /// /// Serializes a body skeleton into a snapshot byte array. /// This is a simplified version of SerializeSkeletonAndFace that only handles body pose data. /// Useful when you don't need to serialize facial expressions. /// /// The handle associated with the serialization. /// The timestamp to associate with this snapshot, in seconds. /// The acknowledgment index of the data, used for synchronization. /// The body pose transforms to serialize. /// The indices of the joints in the body pose that should be serialized. /// Reference to a byte array that will be created and filled with the serialized data. /// True if serialization was successful. public static bool SerializeBodySkeleton( ulong handle, float timestamp, int ack, NativeArray bodyTrackingPose, NativeArray bodyTrackingIndices, ref NativeArray output) { using (new ProfilerScope(nameof(SerializeBodySkeleton))) { var success = Api.metaMovementSDK_createSnapshot(handle, ack, timestamp); if (success != Result.Success) { Debug.LogError("Could not create snapshot!"); return false; } unsafe { success = Api.metaMovementSDK_provideSkeletonPose(handle, bodyTrackingPose.GetPtr(), bodyTrackingIndices.GetPtr(), bodyTrackingIndices.Length, timestamp); } if (success != Result.Success) { Debug.LogError("Could not provide source pose data!"); return false; } unsafe { success = Api.metaMovementSDK_serializeSnapshot(handle, null, out var bytes); if (success == Result.Success) { output = new NativeArray(bytes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); success = Api.metaMovementSDK_serializeSnapshot(handle, output.GetPtr(), out bytes); } } if (success == Result.Success) { return true; } Debug.LogError("Could not serialize snapshot!"); output.Dispose(); output = default; return false; } } /// /// Deserializes data into body and face pose data. /// This converts a serialized snapshot back into usable pose data for animation. /// /// The handle to use for deserialization. /// The serialized data to be deserialized. /// Output parameter that receives the timestamp of the snapshot. /// Output parameter that receives the compression type used in the snapshot. /// Output parameter that receives the acknowledgement number from the snapshot. /// Reference to an array that will be filled with the deserialized body pose transforms. /// Reference to an array that will be filled with the deserialized face pose blendshape weights. /// True if deserialization was successful. public static bool DeserializeSkeletonAndFace( ulong handle, NativeArray data, out double timestamp, out SerializationCompressionType compressionType, out int ack, ref NativeArray outputBodyPose, ref NativeArray outputFacePose) { Result success; using (new ProfilerScope(nameof(DeserializeSkeletonAndFace))) { unsafe { var frameData = new FrameData(); success = Api.metaMovementSDK_deserializeSnapshot(handle, data.GetPtr(), out timestamp, out compressionType, out ack, outputBodyPose.GetPtr(), outputFacePose.GetPtr(), null, ref frameData); } } return success == Result.Success; } /// /// Deserializes data into body and face pose data with additional tracking information. /// This extended version also extracts body tracking pose and frame metadata from the snapshot. /// /// The handle to use for deserialization. /// The serialized data to be deserialized. /// Output parameter that receives the timestamp of the snapshot. /// Output parameter that receives the compression type used in the snapshot. /// Output parameter that receives the acknowledgement number from the snapshot. /// Reference to an array that will be filled with the deserialized body pose transforms. /// Reference to an array that will be filled with the deserialized face pose blendshape weights. /// Reference to an array that will be filled with the deserialized body tracking pose transforms. /// Reference to a FrameData structure that will be filled with metadata about the frame. /// True if deserialization was successful. public static bool DeserializeSkeletonAndFace( ulong handle, NativeArray data, out double timestamp, out SerializationCompressionType compressionType, out int ack, ref NativeArray outputBodyPose, ref NativeArray outputFacePose, ref NativeArray outputBodyTrackingPose, ref FrameData frameData) { Result success; using (new ProfilerScope(nameof(DeserializeSkeletonAndFace))) { unsafe { success = Api.metaMovementSDK_deserializeSnapshot(handle, data.GetPtr(), out timestamp, out compressionType, out ack, outputBodyPose.GetPtr(), outputFacePose.GetPtr(), outputBodyTrackingPose.GetPtr(), ref frameData); } } return success == Result.Success; } /// /// Resets all interpolators used for deserialization. This should be done /// if interpolating back to an earlier snapshot. /// /// The handle to use for deserialization. /// True if the function was successfully executed. public static bool ResetInterpolators(ulong handle) { Result success; using (new ProfilerScope(nameof(DeserializeSkeletonAndFace))) { unsafe { success = Api.metaMovementSDK_resetInterpolators(handle); } } return success == Result.Success; } /// /// Get the interpolated body pose at a specific time. /// This is used for smooth playback of serialized animation data by interpolating between snapshots. /// /// The handle to get the body pose from. /// The type of skeleton to get the pose for. /// Reference to an array that will be filled with the interpolated body pose transforms. /// The timestamp to interpolate to, in seconds since the recording's epoch. /// True if the function was successfully executed. public static bool GetInterpolatedSkeleton( ulong handle, SkeletonType skeletonType, ref NativeArray interpolatedBodyPose, double time) { Result success; using (new ProfilerScope(nameof(GetInterpolatedSkeleton))) { unsafe { var interpolatedBodyPoseLength = interpolatedBodyPose.Length; success = Api.metaMovementSDK_getInterpolatedSkeletonPose( handle, skeletonType, time, interpolatedBodyPose.GetPtr(), out interpolatedBodyPoseLength); } } return success == Result.Success; } /// /// Gets the interpolated face pose at a specific time. /// This is used for smooth playback of serialized facial animation data by interpolating between snapshots. /// /// The handle to get the face pose from. /// The type of skeleton to get the face pose for. /// Reference to an array that will be filled with the interpolated blendshape weights. /// The timestamp to interpolate to, in seconds since the recording's epoch. /// True if the function was successfully executed. public static bool GetInterpolatedFace( ulong handle, SkeletonType skeletonType, ref NativeArray interpolatedFacePose, double time) { Result success; using (new ProfilerScope(nameof(GetInterpolatedFace))) { unsafe { var interpolatedFacePoseLength = interpolatedFacePose.Length; success = Api.metaMovementSDK_getInterpolatedFacePose( handle, skeletonType, time, interpolatedFacePose.GetPtr(), out interpolatedFacePoseLength); } } return success == Result.Success; } /// /// Gets the interpolated tracker joint pose at a specific time. /// This retrieves the interpolated position and orientation of a tracker joint (head or hands) for smooth playback. /// /// The handle to get the tracker pose from. /// The type of tracker joint to get the pose for (center eye, left input, or right input). /// Reference to a transform that will be filled with the interpolated tracker pose. /// The timestamp to interpolate to, in seconds since the recording's epoch. /// True if the function was successfully executed. public static bool GetInterpolatedJointPose( ulong handle, TrackerJointType trackerJointType, ref NativeTransform outputTransform, double time) { Result success; using (new ProfilerScope(nameof(GetInterpolatedJointPose))) { unsafe { success = Api.metaMovementSDK_getInterpolatedTrackerJointPose( handle, trackerJointType, time, ref outputTransform); } } return success == Result.Success; } /********************************************************** * * Tool and Data Functions * **********************************************************/ /// /// Writes the configuration data in a handle to a JSON string. /// This is useful for saving configurations to files or for debugging purposes. /// The JSON includes all skeleton definitions, joint mappings, and other configuration data. /// /// The handle containing the configuration to export. /// Output parameter that receives the JSON configuration data string. /// True if the function was successfully executed. public static bool WriteConfigDataToJson(ulong handle, out string jsonConfigData) { Result success; // Empty String jsonConfigData = ""; using (new ProfilerScope(nameof(WriteConfigDataToJson))) { unsafe { // NOTE: Second Parameter is an optional coordinate space. // null is passed so that the JSON config will be written in Unity // Coordinate Space (Z Forward, Y Up, LH) success = Api.metaMovementSDK_writeConfigDataToJSON(handle, null, null, out int bufferSize); if (success == Result.Success && bufferSize > 0) { var jsonBuffer = stackalloc byte[bufferSize]; success = Api.metaMovementSDK_writeConfigDataToJSON(handle, null, jsonBuffer, out bufferSize); if (success == Result.Success) { jsonConfigData = Marshal.PtrToStringAnsi((IntPtr)jsonBuffer, bufferSize); } } } } return success == Result.Success; } /// /// Converts an array of transforms from one Coordinate Space System to another. /// This allows converting from data in one coordinate space system to another (such as OpenXR to Unity, etc) /// Requires the transforms be in WorldSpace representation (or the same space) /// Does not require a handle or a valid skeleton to convert. /// /// The current Coordinate Space System of the Tranform Array. /// The target Coordinate Space System to convert the Transform Data to. /// Reference to an array containing the pose to be converted, which will be updated with the converted pose. /// True if the function was successfully executed. public static bool ApplyWorldSpaceCoordinateSpaceConversionByRef( CoordinateSpace inCoordinateSpace, CoordinateSpace outCoordinateSpace, ref NativeArray transformData) { Result success; using (new ProfilerScope(nameof(ApplyWorldSpaceCoordinateSpaceConversionByRef))) { unsafe { success = Api.metaMovementSDK_applyWorldSpaceCoordinateSpaceConversion( inCoordinateSpace, outCoordinateSpace, transformData.GetPtr(), transformData.Length); } } return success == Result.Success; } /// /// Converts a skeleton pose from one Coordinate Space System to another. /// This allows converting from data in one coordinate space system to another (such as OpenXR to Unity, etc) /// /// The handle containing the skeleton hierarchy information. /// The type of skeleton the pose belongs to. /// The current joint space format of the skeleton pose. /// The current Coordinate Space System of the skeleton pose. /// The target Coordinate Space System to convert the skeleton pose to. /// Reference to an array containing the pose to be converted, which will be updated with the converted pose. /// True if the function was successfully executed. public static bool ApplyCoordinateSpaceConversionByRef( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType jointSpaceType, CoordinateSpace inCoordinateSpace, CoordinateSpace outCoordinateSpace, ref NativeArray skeletonPose) { Result success; using (new ProfilerScope(nameof(ApplyCoordinateSpaceConversionByRef))) { unsafe { success = Api.metaMovementSDK_applyCoordinateSpaceConversion( handle, skeletonType, jointSpaceType, inCoordinateSpace, outCoordinateSpace, skeletonPose.GetPtr(), skeletonPose.Length); } } return success == Result.Success; } /// /// Converts a skeleton pose from one joint space format to another. /// This allows transforming between different coordinate representations, such as from local space /// (where each joint is relative to its parent) to root-relative space (where each joint is relative to the root). /// /// The handle containing the skeleton hierarchy information. /// The type of skeleton the pose belongs to. /// The current joint space format of the skeleton pose. /// The target joint space format to convert the skeleton pose to. /// Reference to an array containing the pose to be converted, which will be updated with the converted pose. /// True if the function was successfully executed. public static bool ConvertJointPoseByRef( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType inJointSpaceType, JointRelativeSpaceType outJointSpaceType, ref NativeArray skeletonPose) { Result success; using (new ProfilerScope(nameof(ConvertJointPoseByRef))) { unsafe { success = Api.metaMovementSDK_convertJointPose( handle, skeletonType, inJointSpaceType, outJointSpaceType, skeletonPose.GetPtr(), skeletonPose.Length); } } return success == Result.Success; } /// /// Calculates the spatial extents (min, max, and range) for a provided skeleton pose. /// This is useful for determining the bounding box of a pose, which can be used for /// collision detection, camera framing, or other spatial calculations. /// /// The handle containing the skeleton information. /// The type of skeleton the pose belongs to. /// The joint space format of the provided skeleton pose. /// Reference to an array containing the pose to calculate extents for. /// Output parameter that receives the Extents structure containing min, max, and range values. /// True if the function was successfully executed. public static bool CalculatePoseExtents( ulong handle, SkeletonType skeletonType, JointRelativeSpaceType inJointSpaceType, ref NativeArray skeletonPose, out Extents extentInfo) { Result success; using (new ProfilerScope(nameof(CalculatePoseExtents))) { unsafe { success = Api.metaMovementSDK_calculatePoseExtents( handle, skeletonType, inJointSpaceType, skeletonPose.GetPtr(), skeletonPose.Length, out extentInfo); } } return success == Result.Success; } /// /// Serializes a recording's into bytes. /// /// The to serialize. /// The serialized byes. /// True if the function was successfully executed. public static bool SerializeStartHeader( StartHeader startHeader, out byte[] headerBytes) { headerBytes = null; using (new ProfilerScope(nameof(SerializeStartHeader))) { var startHeaderArgument = new StartHeaderSerializedBytes { SerializedBytes = new byte[SERIALIZATION_START_HEADER_SIZE_BYTES] }; var success = Api.metaMovementSDK_serializeStartHeader(startHeader, ref startHeaderArgument); if (success != Result.Success) { Debug.LogError("Could not serialize start header!"); return false; } var serializedBytesCreated = startHeaderArgument.SerializedBytes; headerBytes = new byte[serializedBytesCreated.Length]; for (int i = 0; i < headerBytes.Length; i++) { headerBytes[i] = serializedBytesCreated[i]; } } return true; } /// /// Deserializes a recording's from bytes. /// /// The deserialized to update. /// The serialized bytes. /// True if the function was successfully executed. public static bool DeserializeStartHeader( byte[] headerBytes, out StartHeader startHeader) { if (headerBytes == null) { Debug.LogError("Can't deserialize start header if bytes array is null."); } if (headerBytes != null && headerBytes.Length != SERIALIZATION_START_HEADER_SIZE_BYTES) { Debug.LogError($"Can't deserialize start header if bytes array, " + $"length is {headerBytes.Length} bytes, " + $"expected: {SERIALIZATION_START_HEADER_SIZE_BYTES} bytes."); } using (new ProfilerScope(nameof(DeserializeStartHeader))) { unsafe { var headerBytesStruct = new StartHeaderSerializedBytes(headerBytes); var success = Api.metaMovementSDK_deserializeStartHeader(out startHeader, headerBytesStruct); if (success != Result.Success) { Debug.LogError("Could not serialize start header!"); return false; } } return true; } } /// /// Serializes a recording's into bytes. /// /// The to serialize. /// The serialized bytes to write. /// True if the function was successfully executed. public static bool SerializeEndHeader( EndHeader endHeader, out byte[] headerBytes) { headerBytes = null; using (new ProfilerScope(nameof(SerializeEndHeader))) { EndHeaderSerializedBytes endHeaderArgument = new EndHeaderSerializedBytes(); endHeaderArgument.SerializedBytes = new byte[SERIALIZATION_END_HEADER_SIZE_BYTES]; var success = Api.metaMovementSDK_serializeEndHeader(endHeader, ref endHeaderArgument); if (success != Result.Success) { Debug.LogError("Could not serialize the end header!"); return false; } else { var serializedBytesCreated = endHeaderArgument.SerializedBytes; headerBytes = new byte[serializedBytesCreated.Length]; for (int i = 0; i < headerBytes.Length; i++) { headerBytes[i] = serializedBytesCreated[i]; } } } return true; } /// /// Deserializes a recording's from bytes. /// /// The bytes provided as input. /// The deserialized to update. /// True if the function was successfully executed. public static bool DeserializeEndHeader( byte[] headerBytes, out EndHeader endHeader) { endHeader = new EndHeader(); using (new ProfilerScope(nameof(DeserializeEndHeader))) { if (headerBytes == null) { Debug.LogError("Can't deserialize end header if bytes array is null."); return false; } if (headerBytes.Length != SERIALIZATION_END_HEADER_SIZE_BYTES) { Debug.LogError($"Can't deserialize end header if bytes array, " + $"length is {headerBytes.Length} bytes, " + $"expected: {SERIALIZATION_END_HEADER_SIZE_BYTES} bytes."); } var headerBytesStruct = new EndHeaderSerializedBytes(headerBytes); var success = Api.metaMovementSDK_deserializeEndHeader(out endHeader, headerBytesStruct); if (success != Result.Success) { Debug.LogError("Could not deserialize end header!"); return false; } } return true; } /// /// Deserializes snapshot timestamp. /// /// The byte array to deserialize. /// The timestamp to return. /// True if the function was successfully executed. public static bool DeserializeSnapshotTimestamp( NativeArray data, out double timestamp) { using (new ProfilerScope(nameof(DeserializeSnapshotTimestamp))) { unsafe { var success = Api.metaMovementSDK_deserializeSnapshotTimestamp(data.GetPtr(), out timestamp); if (success != Result.Success) { Debug.LogError("Could not get snapshot timestamp!"); return false; } } } return true; } /********************************************************** * * Helper Functions * **********************************************************/ private static unsafe bool ConvertByteBufferToStringArray(byte* stringBuffer, int bufferSize, int stringCount, out string[] stringArray) { int currentNameStartIndex = 0; int stringsFound = 0; stringArray = new string[stringCount]; for (int i = 0; i < bufferSize; i++) { if (stringBuffer[i] == '\0') { if (stringsFound >= stringCount) { // LOG A WARNING - This should NEVER happen return false; } // Found a Name int stringLength = i - currentNameStartIndex; if (stringLength > 0) { stringArray[stringsFound] = Marshal.PtrToStringAnsi((IntPtr)(stringBuffer + currentNameStartIndex), stringLength); } else { // Empty String stringArray[stringsFound] = ""; } stringsFound++; currentNameStartIndex = i + 1; } } return true; } #endregion } /// /// Helper methods for the retargeting plugin. /// public static class NativeUtilityPluginHelper { /// /// Get the unsafe pointer for a native array. /// /// The native array. /// The type. /// The unsafe pointer of the native array. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe T* GetPtr(in this NativeArray array) where T : unmanaged { return (T*)array.GetUnsafePtr(); } } }