using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Unity.XR.PXR; using UnityEngine; #if AR_FOUNDATION_5||AR_FOUNDATION_6 using UnityEngine.XR.ARSubsystems; #endif using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #if UNITY_EDITOR using UnityEditor.XR.OpenXR.Features; #endif namespace Unity.XR.OpenXR.Features.PICOSupport { /// /// Body joint enumerations. /// * For leg tracking mode, joints numbered from 0 to 15 return data. /// * For full body tracking mode, all joints return data. /// public enum BodyTrackerRole { Pelvis = 0, LEFT_HIP = 1, RIGHT_HIP = 2, SPINE1 = 3, LEFT_KNEE = 4, RIGHT_KNEE = 5, SPINE2 = 6, LEFT_ANKLE = 7, RIGHT_ANKLE = 8, SPINE3 = 9, LEFT_FOOT = 10, RIGHT_FOOT = 11, NECK = 12, LEFT_COLLAR = 13, RIGHT_COLLAR = 14, HEAD = 15, LEFT_SHOULDER = 16, RIGHT_SHOULDER = 17, LEFT_ELBOW = 18, RIGHT_ELBOW = 19, LEFT_WRIST = 20, RIGHT_WRIST = 21, LEFT_HAND = 22, RIGHT_HAND = 23, NONE_ROLE = 24, // unvalid MIN_ROLE = 0, // min value MAX_ROLE = 23, // max value ROLE_NUM = 24, } /// /// The struct that defines the lengths (in centimeters) of different body parts of the avatar. /// public struct BodyTrackingBoneLength { /// /// The length of the head, which is from the top of the head to the upper area of the neck. /// public float headLen; /// /// The length of the neck, which is from the upper area of the neck to the lower area of the neck. /// public float neckLen; /// /// The length of the torso, which is from the lower area of the neck to the navel. /// public float torsoLen; /// /// The length of the hip, which is from the navel to the center of the upper area of the upper leg. /// public float hipLen; /// /// The length of the upper leg, which from the hip to the knee-joint. /// public float upperLegLen; /// /// The length of the lower leg, which is from the knee-joint to the ankle. /// public float lowerLegLen; /// /// The length of the foot, which is from the ankle to the tiptoe. /// public float footLen; /// /// The length of the shoulder, which is between the left and right shoulder joints. /// public float shoulderLen; /// /// The length of the upper arm, which is from the sholder joint to the elbow joint. /// public float upperArmLen; /// /// The length of the lower arm, which is from the elbow joint to the wrist. /// public float lowerArmLen; /// /// The length of the hand, which is from the wrist to the finger tip. /// public float handLen; } public enum BodyJointSet { BODY_JOINT_SET_BODY_START_WITHOUT_ARM = 1, //- For PICO Motion Tracker, nodes numbered 0 to 15 in `BodyTrackerRole` enum will return data. BODY_JOINT_SET_BODY_FULL_START = 2, /// - For PICO Motion Tracker, nodes numbered 0 to 23 in `BodyTrackerRole` enum will return data. } public struct BodyTrackingStartInfo { public BodyJointSet jointSet; public BodyTrackingBoneLength BoneLength; } /// Status code for body tracking data. public enum BodyTrackingStatusCode { /// There is no body tracking data. BT_INVALID = 0, /// There is body tracking data, and the data is accurate. BT_VALID = 1, /// There is body tracking data, but the data is not very accurate. BT_LIMITED = 2 } /// Error codes for body tracking. public enum BodyTrackingMessage { BT_MESSAGE_UNKNOWN = 0, /// PICO Motion Tracker not calibrated. BT_MESSAGE_TRACKER_NOT_CALIBRATED = 1, /// The number of connected PICO Motion Trackers is not enough. BT_MESSAGE_TRACKER_NUM_NOT_ENOUGH = 2, /// PICO Motion Tracker's status is abnormal. BT_MESSAGE_TRACKER_STATE_NOT_SATISFIED = 3, /// PICO Motion Tracker is always invisible. BT_MESSAGE_TRACKER_PERSISTENT_INVISIBILITY = 4, /// PICO Motion Tracker's data is abnormal. BT_MESSAGE_TRACKER_DATA_ERROR = 5, /// The user may have changed. BT_MESSAGE_USER_CHANGE = 6, /// The body tracking pose is abnormal. BT_MESSAGE_TRACKING_POSE_ERROR = 7 } /// Information about body tracking state. public unsafe struct BodyTrackingStatus { /// Status code for body tracking data. public BodyTrackingStatusCode stateCode; /// Body tracking error code. public BodyTrackingMessage message; public override string ToString() { string str = string.Format("stateCode:{0},errorCode:{1}\n", stateCode, message); return str; } } public struct BodyTrackingGetDataInfo { public long displayTime; } public enum BodyActionList { PxrTouchGround = 0x00000001, PxrKeepStatic = 0x00000002, PxrTouchGroundToe = 0x00000004, PxrFootDownAction = 0x00000008, } /// /// Contains data about the position and rotation of a body joint. /// public struct BodyTrackerTransPose { /// /// IMU timestamp. /// public Int64 TimeStamp; /// /// The joint's position on the X axis. /// public double PosX; /// /// The joint's position on the Y axis. /// public double PosY; /// /// The joint's position on the Z axis. /// public double PosZ; /// /// The joint's rotation on the X component of the Quaternion. /// public double RotQx; /// /// The joint's rotation on the Y component of the Quaternion. /// public double RotQy; /// /// The joint's rotation on the Z component of the Quaternion. /// public double RotQz; /// /// The joint's rotation on the W component of the Quaternion. /// public double RotQw; public override string ToString() { return string.Format("TimeStamp :{0}, PosX:{1}, PosY:{2}, PosZ:{3}, RotQx:{4}, RotQy:{5}, RotQz:{6}, RotQw:{7}\n", TimeStamp, PosX, PosY, PosZ, RotQx, RotQy, RotQz, RotQw); } } /// Information about the tracked bone node. public unsafe struct BodyTrackingRoleData { private int apiVersion; /// Bone name. if bone = `NONE_ROLE`, this bone is not calculated. public BodyTrackerRole role; /// Multiple actions can be supported at the same time by means of `OR BodyActionList`. public BodyActionList bodyAction; /// The bone's local transform. public BodyTrackerTransPose localPose; /// The bone's global transform. public BodyTrackerTransPose globalPose; /// The velocity of X, Y, and Z. public fixed double velo[3]; /// The acceleration of X, Y, and Z. public fixed double acce[3]; /// The angular velocity of X, Y, and Z. public fixed double wvelo[3]; /// The angular acceleration of X, Y, and Z. public fixed double wacce[3]; public override string ToString() { string str = string.Format("apiVersion :{0}, role:{1}, bodyAction:{2}, localPose:{3}, globalPose:{4}\n", apiVersion, role, bodyAction, localPose, globalPose); for (int i = 0; i < 3; i++) { str += string.Format(" velo[{0}]:{1}", i, velo[i].ToString("F6")); str += string.Format(" acce[{0}]:{1}", i, acce[i].ToString("F6")); str += string.Format(" wvelo[{0}]:{1}", i, wvelo[i].ToString("F6")); str += string.Format(" wacce[{0}]:{1}", i, wacce[i].ToString("F6")); str += "\n"; } return str; } } /// Body tracking data. public struct BodyTrackingData { private int apiVersion; /// Information about the tracked bone node. [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)BodyTrackerRole.NONE_ROLE)] public BodyTrackingRoleData[] roleDatas; public override string ToString() { string str = string.Format("apiVersion :{0}\n", apiVersion); for (int i = 0; i < (int)BodyTrackerRole.NONE_ROLE; i++) { str += string.Format(" roleData[{0}]:{1}", i, roleDatas[i].ToString()); } return str; } } #if UNITY_EDITOR [OpenXRFeature(UiName = "PICO Body Tracking", Hidden = false, BuildTargetGroups = new[] { UnityEditor.BuildTargetGroup.Android }, Company = "PICO", OpenxrExtensionStrings = extensionString, Version = "1.0.0", FeatureId = featureId)] #endif public class BodyTrackingFeature : OpenXRFeatureBase { public const string featureId = "com.pico.openxr.feature.PICO_BodyTracking"; public const string extensionString = "XR_BD_body_tracking XR_PICO_body_tracking2"; public const int XR_BODY_JOINT_COUNT_BD = 24; public static bool isEnable => OpenXRRuntime.IsExtensionEnabled("XR_BD_body_tracking"); public override string GetExtensionString() { return extensionString; } public override void Initialize(IntPtr intPtr) { Pxr_BodyTrackingEnable(isEnable); } [Obsolete("Please use StartBodyTracking(BodyJointSet JointSet, BodyTrackingBoneLength boneLength)")] public static bool StartBodyTracking(XrBodyJointSetBD Mode) { if (!isEnable) { return false; } BodyTrackingBoneLength boneLength=new BodyTrackingBoneLength(); return StartBodyTracking((BodyJointSet)Mode, boneLength)==0; } /// Starts body tracking. /// Specifies the body tracking mode (default or high-accuracy). /// Specifies lengths (unit: cm) for the bones of the avatar, which is only available for the `BTM_FULL_BODY_HIGH` mode. /// Bones that are not set lengths for will use the default values. /// /// /// - `0`: success /// - `1`: failure /// public static int StartBodyTracking(BodyJointSet JointSet, BodyTrackingBoneLength boneLength) { if (!isEnable) { return 1; } BodyTrackingStartInfo startInfo = new BodyTrackingStartInfo(); startInfo.jointSet = JointSet; startInfo.BoneLength = boneLength; return Pxr_StartBodyTracking(ref startInfo); } /// Launches the PICO Motion Tracker app to perform calibration. /// - For PICO Motion Tracker (Beta), the user needs to follow the instructions on the home of the PICO Motion Tracker app to complete calibration. /// - For PICO Motion Tracker (Official), "single-glance calibration" will be performed. When a user has a glance at the PICO Motion Tracker on their lower legs, calibration is completed. /// /// /// - `0`: success /// - `1`: failure /// public static int StartMotionTrackerCalibApp() { if (!isEnable) { return 1; } return Pxr_StartBodyTrackingCalibApp(); } public static bool IsBodyTrackingSupported() { if (!isEnable) { return false; } bool supported=false; Pxr_GetBodyTrackingSupported(ref supported); return supported; } /// /// Gets the data about the poses of body joints. /// /// Reserved parameter, pass `0`. /// Contains the data about the poses of body joints, including position, action, and more. [Obsolete("Please use GetBodyTrackingData")] public static bool GetBodyTrackingPose(ref BodyTrackerResult bodyTrackerResult) { if (!isEnable) { return false; } BodyTrackingGetDataInfo getInfo = new BodyTrackingGetDataInfo(); getInfo.displayTime = 0; BodyTrackingData data = new BodyTrackingData(); bool state = GetBodyTrackingData(ref getInfo, ref data)==0; for (int i = 0; i < XR_BODY_JOINT_COUNT_BD; i++) { bodyTrackerResult.trackingdata[i].pose.PosX =data.roleDatas[i].localPose.PosX ; bodyTrackerResult.trackingdata[i].pose.PosY = -data.roleDatas[i].localPose.PosZ; bodyTrackerResult.trackingdata[i].pose.PosZ = -data.roleDatas[i].localPose.PosZ; bodyTrackerResult.trackingdata[i].pose.RotQx = -data.roleDatas[i].localPose.RotQz; bodyTrackerResult.trackingdata[i].pose.RotQy = -data.roleDatas[i].localPose.RotQw; bodyTrackerResult.trackingdata[i].pose.RotQz = -data.roleDatas[i].localPose.RotQz; bodyTrackerResult.trackingdata[i].pose.RotQw = -data.roleDatas[i].localPose.RotQw; } return state; } /// Stops body tracking. /// /// - `0`: success /// - `1`: failure /// public static int StopBodyTracking() { return Pxr_StopBodyTracking(); } [Obsolete("Please use StopBodyTracking")] private void OnDestroy() { if (!isExtensionEnabled()) { return; } StopBodyTracking(); } [Obsolete("Please use StartMotionTrackerCalibApp")] public static void OpenFitnessBandCalibrationAPP() { StartMotionTrackerCalibApp(); } /// Gets body tracking data. /// Specifies the display time and the data filtering flags. /// For the display time, for example, when it is set to 0.1 second, it means predicting the pose of the tracked node 0.1 seconds ahead. /// /// Returns the array of data for all tracked nodes. /// /// - `0`: success /// - `1`: failure /// public unsafe static int GetBodyTrackingData(ref BodyTrackingGetDataInfo getInfo, ref BodyTrackingData data) { if (!isEnable) { return 1; } int val = -1; { val = Pxr_GetBodyTrackingData(ref getInfo, ref data); for (int i = 0; i < (int)BodyTrackerRole.ROLE_NUM; i++) { data.roleDatas[i].localPose.PosZ = -data.roleDatas[i].localPose.PosZ; data.roleDatas[i].localPose.RotQz = -data.roleDatas[i].localPose.RotQz; data.roleDatas[i].localPose.RotQw = -data.roleDatas[i].localPose.RotQw; data.roleDatas[i].velo[3] = -data.roleDatas[i].velo[3]; data.roleDatas[i].acce[3] = -data.roleDatas[i].acce[3]; data.roleDatas[i].wvelo[3] = -data.roleDatas[i].wvelo[3]; data.roleDatas[i].wacce[3] = -data.roleDatas[i].wacce[3]; } } return val; } /// Gets the state of PICO Motion Tracker and, if any, the reason for an exception. /// Indicates whether the PICO Motion Tracker is tracking normally: /// - `true`: is tracking /// - `false`: tracking lost /// /// Returns the information about body tracking state. /// /// - `0`: success /// - `1`: failure /// public static int GetBodyTrackingState(ref bool isTracking, ref BodyTrackingStatus state) { int val = -1; { val = Pxr_GetBodyTrackingState(ref isTracking, ref state); } return val; } #if AR_FOUNDATION_5||AR_FOUNDATION_6 public bool isBodyTracking=false; static List s_HumanBodyDescriptors = new List(); protected override void OnSubsystemCreate() { base.OnSubsystemCreate(); if (isBodyTracking) { CreateSubsystem( s_HumanBodyDescriptors, PXR_HumanBodySubsystem.k_SubsystemId); } } protected override void OnSubsystemStart() { if (isBodyTracking) { StartSubsystem(); } } protected override void OnSubsystemStop() { if (isBodyTracking) { StopSubsystem(); } } protected override void OnSubsystemDestroy() { if (isBodyTracking) { DestroySubsystem(); } } #endif private const string ExtLib = "openxr_pico"; [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern void Pxr_BodyTrackingEnable(bool enable); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_StartBodyTrackingCalibApp(); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_GetBodyTrackingSupported(ref bool supported); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_StartBodyTracking(ref BodyTrackingStartInfo startInfo); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_StopBodyTracking(); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_GetBodyTrackingState(ref bool isTracking, ref BodyTrackingStatus state); [DllImport(ExtLib, CallingConvention = CallingConvention.Cdecl)] private static extern int Pxr_GetBodyTrackingData(ref BodyTrackingGetDataInfo getInfo, ref BodyTrackingData data); } }