Spelunx Cavern SDK
 
Loading...
Searching...
No Matches
BodyTracker.cs
Go to the documentation of this file.
1using System.Collections.Generic;
2using UnityEngine;
3using Microsoft.Azure.Kinect.BodyTracking;
4
5namespace Spelunx.Orbbec {
6 /// <summary>
7 /// BodyTracker represents the BodyData from the ORBBEC sensor as a skeleton.
8 /// Important: BodyTracker prefab's child hierachy must be in the same order as JointId.
9 /// </summary>
10 public class BodyTracker : MonoBehaviour {
11 [Header("References (Do NOT edit unless you know what you're doing!)")]
12 [SerializeField] private Transform rootJoint = null;
13
14 // Constants
15 readonly Quaternion Y_180_FLIP = new Quaternion(0.0f, 1.0f, 0.0f, 0.0f);
16 readonly Vector3 X_POSITIVE = Vector3.right; // Follow the Left-Hand Rule.
17 readonly Vector3 Y_POSITIVE = Vector3.up; // Follow the Left-Hand Rule.
18 readonly Vector3 Z_POSITIVE = Vector3.forward; // Follow the Left-Hand Rule.
19
20 // Internal variables.
21 [Header("Internal Variables (Exposed for debugging purposes.)")]
22 [SerializeField, Tooltip("Joint Positions")] private Vector3[] jointPositions = new Vector3[(int)JointId.Count];
23 [SerializeField, Tooltip("Absolute Joint Rotations")] private Quaternion[] absoluteJointRotations = new Quaternion[(int)JointId.Count];
24 private Dictionary<JointId, JointId> parentJointMap;
25 private Dictionary<JointId, Quaternion> basisJointMap;
26
27 private void Awake() {
28 InitParentJointMap();
29 InitBasisJointMap();
30 }
31
32 /// Sets the parent of every joint.
33 private void InitParentJointMap() {
34 parentJointMap = new Dictionary<JointId, JointId>();
35
36 parentJointMap[JointId.Pelvis] = JointId.Count; // Pelvis has no parent, so set it's parent to JointId.Count.
37 parentJointMap[JointId.SpineNavel] = JointId.Pelvis;
38 parentJointMap[JointId.SpineChest] = JointId.SpineNavel;
39 parentJointMap[JointId.Neck] = JointId.SpineChest;
40 parentJointMap[JointId.ClavicleLeft] = JointId.SpineChest;
41 parentJointMap[JointId.ShoulderLeft] = JointId.ClavicleLeft;
42 parentJointMap[JointId.ElbowLeft] = JointId.ShoulderLeft;
43 parentJointMap[JointId.WristLeft] = JointId.ElbowLeft;
44 parentJointMap[JointId.HandLeft] = JointId.WristLeft;
45 parentJointMap[JointId.HandTipLeft] = JointId.HandLeft;
46 parentJointMap[JointId.ThumbLeft] = JointId.HandLeft;
47 parentJointMap[JointId.ClavicleRight] = JointId.SpineChest;
48 parentJointMap[JointId.ShoulderRight] = JointId.ClavicleRight;
49 parentJointMap[JointId.ElbowRight] = JointId.ShoulderRight;
50 parentJointMap[JointId.WristRight] = JointId.ElbowRight;
51 parentJointMap[JointId.HandRight] = JointId.WristRight;
52 parentJointMap[JointId.HandTipRight] = JointId.HandRight;
53 parentJointMap[JointId.ThumbRight] = JointId.HandRight;
54 parentJointMap[JointId.HipLeft] = JointId.SpineNavel;
55 parentJointMap[JointId.KneeLeft] = JointId.HipLeft;
56 parentJointMap[JointId.AnkleLeft] = JointId.KneeLeft;
57 parentJointMap[JointId.FootLeft] = JointId.AnkleLeft;
58 parentJointMap[JointId.HipRight] = JointId.SpineNavel;
59 parentJointMap[JointId.KneeRight] = JointId.HipRight;
60 parentJointMap[JointId.AnkleRight] = JointId.KneeRight;
61 parentJointMap[JointId.FootRight] = JointId.AnkleRight;
62 parentJointMap[JointId.Head] = JointId.Pelvis;
63 parentJointMap[JointId.Nose] = JointId.Head;
64 parentJointMap[JointId.EyeLeft] = JointId.Head;
65 parentJointMap[JointId.EarLeft] = JointId.Head;
66 parentJointMap[JointId.EyeRight] = JointId.Head;
67 parentJointMap[JointId.EarRight] = JointId.Head;
68 }
69
70 /// Sets the rotation basis of every joint. The rotation basis are according to (https://learn.microsoft.com/en-us/previous-versions/azure/kinect-dk/body-joints).
71 private void InitBasisJointMap() {
72 // Spine and left hip share the same basis.
73 Quaternion leftHipBasis = Quaternion.LookRotation(X_POSITIVE, -Z_POSITIVE);
74 Quaternion spineHipBasis = Quaternion.LookRotation(X_POSITIVE, -Z_POSITIVE);
75 Quaternion rightHipBasis = Quaternion.LookRotation(X_POSITIVE, Z_POSITIVE);
76
77 // Arms and thumbs share the same basis.
78 Quaternion leftArmBasis = Quaternion.LookRotation(Y_POSITIVE, -Z_POSITIVE);
79 Quaternion rightArmBasis = Quaternion.LookRotation(-Y_POSITIVE, Z_POSITIVE);
80 Quaternion leftHandBasis = Quaternion.LookRotation(-Z_POSITIVE, -Y_POSITIVE);
81 Quaternion rightHandBasis = Quaternion.identity;
82 Quaternion leftFootBasis = Quaternion.LookRotation(X_POSITIVE, Y_POSITIVE);
83 Quaternion rightFootBasis = Quaternion.LookRotation(X_POSITIVE, -Y_POSITIVE);
84
85 basisJointMap = new Dictionary<JointId, Quaternion>();
86
87 // pelvis has no parent so set to count
88 basisJointMap[JointId.Pelvis] = spineHipBasis;
89 basisJointMap[JointId.SpineNavel] = spineHipBasis;
90 basisJointMap[JointId.SpineChest] = spineHipBasis;
91 basisJointMap[JointId.Neck] = spineHipBasis;
92 basisJointMap[JointId.ClavicleLeft] = leftArmBasis;
93 basisJointMap[JointId.ShoulderLeft] = leftArmBasis;
94 basisJointMap[JointId.ElbowLeft] = leftArmBasis;
95 basisJointMap[JointId.WristLeft] = leftHandBasis;
96 basisJointMap[JointId.HandLeft] = leftHandBasis;
97 basisJointMap[JointId.HandTipLeft] = leftHandBasis;
98 basisJointMap[JointId.ThumbLeft] = leftArmBasis;
99 basisJointMap[JointId.ClavicleRight] = rightArmBasis;
100 basisJointMap[JointId.ShoulderRight] = rightArmBasis;
101 basisJointMap[JointId.ElbowRight] = rightArmBasis;
102 basisJointMap[JointId.WristRight] = rightHandBasis;
103 basisJointMap[JointId.HandRight] = rightHandBasis;
104 basisJointMap[JointId.HandTipRight] = rightHandBasis;
105 basisJointMap[JointId.ThumbRight] = rightArmBasis;
106 basisJointMap[JointId.HipLeft] = leftHipBasis;
107 basisJointMap[JointId.KneeLeft] = leftHipBasis;
108 basisJointMap[JointId.AnkleLeft] = leftHipBasis;
109 basisJointMap[JointId.FootLeft] = leftFootBasis;
110 basisJointMap[JointId.HipRight] = rightHipBasis;
111 basisJointMap[JointId.KneeRight] = rightHipBasis;
112 basisJointMap[JointId.AnkleRight] = rightHipBasis;
113 basisJointMap[JointId.FootRight] = rightFootBasis;
114 basisJointMap[JointId.Head] = spineHipBasis;
115 basisJointMap[JointId.Nose] = spineHipBasis;
116 basisJointMap[JointId.EyeLeft] = spineHipBasis;
117 basisJointMap[JointId.EarLeft] = spineHipBasis;
118 basisJointMap[JointId.EyeRight] = spineHipBasis;
119 basisJointMap[JointId.EarRight] = spineHipBasis;
120 }
121
122 private int FindClosestTrackedBody(FrameData trackerFrameData) {
123 int closestBody = -1;
124 float minDistanceFromKinect = float.MaxValue;
125 for (int i = 0; i < (int)trackerFrameData.NumDetectedBodies; i++) {
126 var pelvisPosition = trackerFrameData.Bodies[i].JointPositions3D[(int)JointId.Pelvis];
127 Vector3 pelvisPos = new Vector3((float)pelvisPosition.X, (float)pelvisPosition.Y, (float)pelvisPosition.Z);
128 if (pelvisPos.magnitude < minDistanceFromKinect) {
129 closestBody = i;
130 minDistanceFromKinect = pelvisPos.magnitude;
131 }
132 }
133 return closestBody;
134 }
135
136 private Quaternion OrientateRotation(Quaternion rotation, SensorOrientation sensorOrientation) {
137 switch (sensorOrientation) {
138 case SensorOrientation.Clockwise90:
139 return Quaternion.AngleAxis(90.0f, Z_POSITIVE) * rotation;
140 case SensorOrientation.CounterClockwise90:
141 return Quaternion.AngleAxis(-90.0f, Z_POSITIVE) * rotation;
142 case SensorOrientation.Flip180:
143 return Quaternion.AngleAxis(180.0f, Z_POSITIVE) * rotation;
144 }
145 return rotation;
146 }
147
148 private Vector3 OrientatePosition(Vector3 position, SensorOrientation sensorOrientation) {
149 float rotationAngle = 0.0f;
150 switch (sensorOrientation) {
151 case SensorOrientation.Clockwise90:
152 // Clockwise 90 degrees means that we face the camera, and then rotate the camera 90 degrees relative to us.
153 // Intuitively, I know that if the camera is rotated clockwise 90 degrees, we should be rotating it anti-clockwise 90 degrees instead to compensate for it.
154 // But why are we doing the opposite? Not a damn clue, I figured it out via trial and error.
155 // It works, my semester is ending and I'm burnt out, and I'm not sure I really care that much right now.
156 rotationAngle = 90.0f;
157 break;
158 case SensorOrientation.CounterClockwise90:
159 rotationAngle = -90.0f;
160 break;
161 case SensorOrientation.Flip180:
162 rotationAngle = 180.0f;
163 break;
164 }
165
166 // Left-Hand Rule!
167 Matrix4x4 translationMatrix = Matrix4x4.Translate(position);
168 Matrix4x4 rotationMatrix = Matrix4x4.Rotate(Quaternion.AngleAxis(rotationAngle, Z_POSITIVE));
169 Matrix4x4 positionMatrix = rotationMatrix * translationMatrix;
170 return new Vector3(positionMatrix.m03, positionMatrix.m13, positionMatrix.m23);
171 }
172
173 private void SetBonesTransform(BodyData body, SensorOrientation sensorOrientation) {
174 for (int jointNum = 0; jointNum < (int)JointId.Count; jointNum++) {
175 // Calculate joint position.
176 Vector3 jointPos = OrientatePosition(
177 // Convert from System.Numerics.Vector3 to UnityEngine.Vector3.
178 new Vector3(body.JointPositions3D[jointNum].X,
179 -body.JointPositions3D[jointNum].Y,
180 body.JointPositions3D[jointNum].Z),
181 sensorOrientation);
182 jointPositions[jointNum] = jointPos;
183
184 // We have to convert from System.Numerics.Quaternion to UnityEngine.Quaternion.
185 Quaternion bodyJointRotation = new Quaternion(
186 body.JointRotations[jointNum].X,
187 body.JointRotations[jointNum].Y,
188 body.JointRotations[jointNum].Z,
189 body.JointRotations[jointNum].W);
190
191 // By rotating the inverse of a basis, we are bring a point from world space, into that basis' space.
192 Quaternion jointRot = OrientateRotation(Y_180_FLIP * bodyJointRotation * Quaternion.Inverse(basisJointMap[(JointId)jointNum]), sensorOrientation);
193 absoluteJointRotations[jointNum] = jointRot;
194
195 // These are absolute body space because each joint has the body root for a parent in the scene graph.
196 transform.GetChild(0).GetChild(jointNum).localPosition = jointPos;
197 transform.GetChild(0).GetChild(jointNum).localRotation = jointRot;
198
199 // Certain joints don't have a bone, so there's no need to render them.
200 if (parentJointMap[(JointId)jointNum] == JointId.Head ||
201 parentJointMap[(JointId)jointNum] == JointId.Count) {
202 transform.GetChild(0).GetChild(jointNum).GetChild(0).gameObject.SetActive(false);
203 continue;
204 }
205
206 // For the other joints, rotate and scale their bones so that they link up with the parent joint.
207 Vector3 parentTrackerSpacePosition = OrientatePosition(
208 new Vector3(body.JointPositions3D[(int)parentJointMap[(JointId)jointNum]].X,
209 -body.JointPositions3D[(int)parentJointMap[(JointId)jointNum]].Y,
210 body.JointPositions3D[(int)parentJointMap[(JointId)jointNum]].Z),
211 sensorOrientation);
212 Vector3 boneDirectionTrackerSpace = jointPos - parentTrackerSpacePosition;
213 Vector3 boneDirectionWorldSpace = transform.rotation * boneDirectionTrackerSpace;
214 Vector3 boneDirectionLocalSpace = Quaternion.Inverse(transform.GetChild(0).GetChild(jointNum).rotation) * Vector3.Normalize(boneDirectionWorldSpace);
215
216 // If the order of children in the scene hierachy ever changes, this will all be messed up.
217 transform.GetChild(0).GetChild(jointNum).GetChild(0).localScale = new Vector3(1, 20.0f * 0.5f * boneDirectionWorldSpace.magnitude, 1);
218 transform.GetChild(0).GetChild(jointNum).GetChild(0).localRotation = Quaternion.FromToRotation(Vector3.up, boneDirectionLocalSpace);
219 transform.GetChild(0).GetChild(jointNum).GetChild(0).position = transform.GetChild(0).GetChild(jointNum).position - 0.5f * boneDirectionWorldSpace;
220 }
221 }
222
223 /// <summary>
224 /// Show or hide the skeleton in the game scene.
225 /// </summary>
226 /// <param name="show">
227 /// Show the skeleton if true, else hide the skeleton.
228 /// </param>
229 public void ShowSkeleton(bool show) {
230 for (int jointNum = 0; jointNum < (int)JointId.Count; jointNum++) {
231 transform.GetChild(0).GetChild(jointNum).gameObject.GetComponent<MeshRenderer>().enabled = show;
232 transform.GetChild(0).GetChild(jointNum).GetChild(0).GetComponent<MeshRenderer>().enabled = show;
233 }
234 }
235
236 public Vector3 GetJointPosition(JointId jointId) { return jointPositions[(int)jointId]; }
237
238 /// <summary>
239 /// Get the rotation of a joint, relative to the root.
240 /// </summary>
241 /// <param name="jointId">
242 /// The ID of the joint which to get the rotation.
243 /// </param>
244 /// <returns>
245 /// The rotation of the joint of ID jointId, relative to the root.
246 /// </returns>
247 public Quaternion GetAbsoluteJointRotation(JointId jointId) { return absoluteJointRotations[(int)jointId]; }
248
249 /// <summary>
250 /// Get the rotation of a joint, relative to its parent.
251 /// </summary>
252 /// <param name="jointId">
253 /// The ID of the joint which to get the rotation.
254 /// </param>
255 /// <returns>
256 /// The rotation of the joint of ID jointId, relative to its parent.
257 /// </returns>
258 public Quaternion GetRelativeJointRotation(JointId jointId) {
259 JointId parent = parentJointMap[jointId];
260 Quaternion parentJointRotationBodySpace = Quaternion.identity;
261 if (parent == JointId.Count) {
262 parentJointRotationBodySpace = Y_180_FLIP;
263 } else {
264 parentJointRotationBodySpace = absoluteJointRotations[(int)parent];
265 }
266 Quaternion jointRotationBodySpace = absoluteJointRotations[(int)jointId];
267 Quaternion relativeRotation = Quaternion.Inverse(parentJointRotationBodySpace) * jointRotationBodySpace;
268
269 return relativeRotation;
270 }
271
272 /// <summary>
273 /// Update the skeleton based on frame data.
274 /// </summary>
275 /// <param name="frameData">
276 /// The frame data to update the skeleton.
277 /// </param>
278 /// The sensor orientation, used to rotate the skeleton is upright.
279 /// <param name="orientation"></param>
280 public void UpdateSkeleton(FrameData frameData, SensorOrientation orientation) {
281 //this is an array in case you want to get the n closest bodies
282 int closestBody = FindClosestTrackedBody(frameData);
283
284 // render the closest body
285 BodyData skeleton = frameData.Bodies[closestBody];
286 SetBonesTransform(skeleton, orientation);
287 }
288
289 public Transform GetRootJoint() { return rootJoint; }
290 }
291}
BodyTracker represents the BodyData from the ORBBEC sensor as a skeleton. Important: BodyTracker pref...
Definition: BodyTracker.cs:10
Quaternion GetAbsoluteJointRotation(JointId jointId)
Get the rotation of a joint, relative to the root.
Definition: BodyTracker.cs:247
void UpdateSkeleton(FrameData frameData, SensorOrientation orientation)
Update the skeleton based on frame data.
Definition: BodyTracker.cs:280
Vector3 GetJointPosition(JointId jointId)
Definition: BodyTracker.cs:236
void ShowSkeleton(bool show)
Show or hide the skeleton in the game scene.
Definition: BodyTracker.cs:229
Quaternion GetRelativeJointRotation(JointId jointId)
Get the rotation of a joint, relative to its parent.
Definition: BodyTracker.cs:258
BodyData[] Bodies
Array of bodies. Use NumDetectedBodies to determine how many bodies contain useful data.
Definition: FrameData.cs:21
System.Numerics.Quaternion[] JointRotations
Definition: BodyData.cs:18
System.Numerics.Vector3[] JointPositions3D
Definition: BodyData.cs:17