using System.Collections;
using System.Collections.Generic;
using Microsoft.Azure.Kinect.BodyTracking;
public class PuppetAvatar : MonoBehaviour
public TrackerHandler KinectDevice;
Dictionary<JointId, Quaternion> absoluteOffsetMap;
public GameObject RootPosition;
public Transform CharacterRootTransform;
private Dictionary<JointId, Queue<Vector4>> jointRotationBuffers;
private int bufferSize = 60; // Adjust this depending on desired smoothing (e.g., 1 second of data at 60 FPS)
private float cutoffFrequency = 0.5f; // High-pass filter cutoff frequency in Hz
private float samplingFrequency = 60.0f; // Sampling frequency in Hz
private static HumanBodyBones MapKinectJoint(JointId joint)
// Mapping Kinect joints to Unity's Humanoid rig
case JointId.Pelvis: return HumanBodyBones.Hips;
case JointId.SpineNavel: return HumanBodyBones.Spine;
case JointId.SpineChest: return HumanBodyBones.Chest;
case JointId.Neck: return HumanBodyBones.Neck;
case JointId.Head: return HumanBodyBones.Head;
case JointId.HipLeft: return HumanBodyBones.LeftUpperLeg;
case JointId.KneeLeft: return HumanBodyBones.LeftLowerLeg;
case JointId.AnkleLeft: return HumanBodyBones.LeftFoot;
case JointId.FootLeft: return HumanBodyBones.LeftToes;
case JointId.HipRight: return HumanBodyBones.RightUpperLeg;
case JointId.KneeRight: return HumanBodyBones.RightLowerLeg;
case JointId.AnkleRight: return HumanBodyBones.RightFoot;
case JointId.FootRight: return HumanBodyBones.RightToes;
case JointId.ClavicleLeft: return HumanBodyBones.LeftShoulder;
case JointId.ShoulderLeft: return HumanBodyBones.LeftUpperArm;
case JointId.ElbowLeft: return HumanBodyBones.LeftLowerArm;
case JointId.WristLeft: return HumanBodyBones.LeftHand;
case JointId.ClavicleRight: return HumanBodyBones.RightShoulder;
case JointId.ShoulderRight: return HumanBodyBones.RightUpperArm;
case JointId.ElbowRight: return HumanBodyBones.RightLowerArm;
case JointId.WristRight: return HumanBodyBones.RightHand;
default: return HumanBodyBones.LastBone;
PuppetAnimator = GetComponent<Animator>();
Transform _rootJointTransform = CharacterRootTransform;
absoluteOffsetMap = new Dictionary<JointId, Quaternion>();
jointRotationBuffers = new Dictionary<JointId, Queue<Vector4>>();
for (int i = 0; i < (int)JointId.Count; i++)
HumanBodyBones hbb = MapKinectJoint((JointId)i);
if (hbb != HumanBodyBones.LastBone)
Transform transform = PuppetAnimator.GetBoneTransform(hbb);
Quaternion absOffset = GetSkeletonBone(PuppetAnimator, transform.name).rotation;
while (!ReferenceEquals(transform, _rootJointTransform))
transform = transform.parent;
absOffset = GetSkeletonBone(PuppetAnimator, transform.name).rotation * absOffset;
absoluteOffsetMap[(JointId)i] = absOffset;
// Initialize the joint rotation buffer
jointRotationBuffers[(JointId)i] = new Queue<Vector4>(new Vector4[bufferSize]);
private static SkeletonBone GetSkeletonBone(Animator animator, string boneName)
StringBuilder cloneName = new StringBuilder(boneName);
cloneName.Append("(Clone)");
foreach (SkeletonBone sb in animator.avatar.humanDescription.skeleton)
if (sb.name == boneName || sb.name == cloneName.ToString())
return animator.avatar.humanDescription.skeleton[count];
return new SkeletonBone();
private void LateUpdate()
for (int j = 0; j < (int)JointId.Count; j++)
JointId jointId = (JointId)j;
if (MapKinectJoint(jointId) != HumanBodyBones.LastBone && absoluteOffsetMap.ContainsKey(jointId))
// Get the current rotation and add it to the buffer
Quaternion currentRotation = KinectDevice.absoluteJointRotations[j];
Vector4 currentRotationVec = new Vector4(currentRotation.x, currentRotation.y, currentRotation.z, currentRotation.w);
Queue<Vector4> buffer = jointRotationBuffers[jointId];
if (buffer.Count >= bufferSize)
buffer.Enqueue(currentRotationVec);
// Apply Butterworth high-pass filter to the buffer
Vector4 filteredRotationVec = ApplyButterworthHighPassFilter(buffer.ToArray(), cutoffFrequency, samplingFrequency);
// Convert back to Quaternion
Quaternion filteredRotation = new Quaternion(filteredRotationVec.x, filteredRotationVec.y, filteredRotationVec.z, filteredRotationVec.w);
// Get the absolute offset
Quaternion absOffset = absoluteOffsetMap[jointId];
Transform finalJoint = PuppetAnimator.GetBoneTransform(MapKinectJoint(jointId));
// Apply the filtered rotation
finalJoint.rotation = absOffset * Quaternion.Inverse(absOffset) * filteredRotation * absOffset;
// Root joint - update position with offset
finalJoint.position = CharacterRootTransform.position +
new Vector3(RootPosition.transform.localPosition.x,
RootPosition.transform.localPosition.y + OffsetY,
RootPosition.transform.localPosition.z - OffsetZ);
private Vector4 ApplyButterworthHighPassFilter(Vector4[] buffer, float cutoffFrequency, float samplingFrequency)
Vector4 result = buffer[n - 1];
if (n < 2) return result;
// Butterworth filter parameters (second-order)
float omega = 2.0f * Mathf.PI * cutoffFrequency / samplingFrequency;
float tanOmega = Mathf.Tan(omega / 2.0f);
float a0 = 1.0f / (1.0f + Mathf.Sqrt(2.0f) * tanOmega + tanOmega * tanOmega);
float b1 = 2.0f * (tanOmega * tanOmega - 1.0f) * a0;
float b2 = (1.0f - Mathf.Sqrt(2.0f) * tanOmega + tanOmega * tanOmega) * a0;
Vector4 prevOutput = buffer[n - 2]; // Last output value (y[n-1])
Vector4 prevPrevOutput = buffer[n - 3 >= 0 ? n - 3 : n - 2]; // y[n-2], fallback if not enough data
Vector4 prevInput = buffer[n - 2]; // Last input value (x[n-1])
Vector4 prevPrevInput = buffer[n - 3 >= 0 ? n - 3 : n - 2]; // x[n-2]
// Apply Butterworth filter equation
result = a0 * result + a1 * prevInput + a2 * prevPrevInput + b1 * prevOutput + b2 * prevPrevOutput;