dwelling act 4 (live motion cap w/ kinect azure)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

504 lines
20 KiB

1 year ago
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace com.rfilkov.kinect
{
/// <summary>
/// Kinect user manager is the component that tracks the users in front of the sensor.
/// </summary>
public class KinectUserManager : MonoBehaviour
{
[System.Serializable]
public class KinectUserEvent : UnityEvent<ulong, int> { }
/// <summary>
/// Fired when new user gets detected.
/// </summary>
[Tooltip("This event is fired, when new user gets detected.")]
//public event System.Action<ulong, int> OnUserAdded;
public KinectUserEvent OnUserAdded = new KinectUserEvent();
/// <summary>
/// Fired when user gets removed.
/// </summary>
[Tooltip("This event is fired, when user gets removed.")]
//public event System.Action<ulong, int> OnUserRemoved;
public KinectUserEvent OnUserRemoved = new KinectUserEvent();
// List of all users
internal List<ulong> alUserIds = new List<ulong>();
internal Dictionary<ulong, int> dictUserIdToIndex = new Dictionary<ulong, int>();
internal ulong[] aUserIndexIds = new ulong[KinectInterop.Constants.MaxBodyCount];
internal Dictionary<ulong, float> dictUserIdToTime = new Dictionary<ulong, float>();
// Primary (first or closest) user ID
internal ulong liPrimaryUserId = 0;
// Calibration gesture data for each player
internal Dictionary<ulong, KinectGestureManager.GestureData> playerCalibrationData = new Dictionary<ulong, KinectGestureManager.GestureData>();
// reference to KM
private KinectManager kinectManager = null;
// transformation matrix of the primary body sensor (used to apply the sensor orientation transformation)
//protected Matrix4x4 matBodySensor = Matrix4x4.identity;
// parameters used by the FixedStepIndices order
[Tooltip("Minimum (leftmost) position, used by the FixedStepIndices-order, in meters.")]
public float fixedPosMin = -2f; // minimum (leftmost) fixed position, in meters
[Tooltip("Maximum (rightmost) position, used by the FixedStepIndices-order, in meters.")]
public float fixedPosMax = 2f; // maximum (rightmost) fixed position, in meters
[Tooltip("Fixed step between indices, used by the FixedStepIndices-order, in meters.")]
public float fixedPosStep = 1f; // fixed step between indices, in meters
[Tooltip("Central position (in meters from the sensor), used by the Distance-user detection order.")]
public Vector3 centralPosition = Vector3.zero;
protected virtual void Start()
{
kinectManager = KinectManager.Instance;
}
// returns the fixed step user index, according to the user position in space.
private int GetFixedStepUserIndex(Vector3 userPos)
{
float xDist = Mathf.Clamp(userPos.x, fixedPosMin, fixedPosMax);
int userIndex = Mathf.FloorToInt((xDist - fixedPosMin) / fixedPosStep);
return userIndex;
}
/// <summary>
/// Updates the user indices as needed, according to the detection order
/// </summary>
/// <param name="userDetectionOrder"></param>
public virtual void UpdateUserIndices(KinectManager.UserDetectionOrder userDetectionOrder)
{
switch(userDetectionOrder)
{
case KinectManager.UserDetectionOrder.Appearance:
break;
case KinectManager.UserDetectionOrder.Distance:
case KinectManager.UserDetectionOrder.LeftToRight:
RearrangeUserIndices(userDetectionOrder);
break;
case KinectManager.UserDetectionOrder.FixedStepIndices:
// clear all indices
System.Array.Clear(aUserIndexIds, 0, aUserIndexIds.Length);
// set current user indices
for (int i = 0; i < alUserIds.Count; i++)
{
ulong userId = alUserIds[i];
if (userId != 0)
{
Vector3 userPos = kinectManager.GetUserPosition(userId);
int userIndex = GetFixedStepUserIndex(userPos);
//Debug.Log(string.Format(" UpdateUserIndices - UserId: {0}, xPos: {1:F2}, index: {2}", userId, userPos.x, userIndex));
aUserIndexIds[userIndex] = userId;
}
}
break;
}
}
/// <summary>
/// Rearranges the user indices, according to the current criteria
/// </summary>
public virtual void RearrangeUserIndices(KinectManager.UserDetectionOrder userDetectionOrder)
{
if (userDetectionOrder != KinectManager.UserDetectionOrder.Appearance &&
userDetectionOrder != KinectManager.UserDetectionOrder.FixedStepIndices)
{
// get current user positions
Vector3[] userPos = new Vector3[aUserIndexIds.Length];
for (int i = 0; i < aUserIndexIds.Length; i++)
{
ulong userId = aUserIndexIds[i];
userPos[i] = userId != 0 ? kinectManager.GetUserPosition(userId) : Vector3.zero;
}
// bubble sort
bool reorderDone = false;
for (int i = aUserIndexIds.Length - 1; i >= 1; i--)
{
bool switchDone = false;
for (int j = 0; j < i; j++)
{
if (userPos[j] == Vector3.zero || userPos[j + 1] == Vector3.zero)
continue;
float userDist1 = 0f;
if (userDetectionOrder == KinectManager.UserDetectionOrder.Distance)
userDist1 = Mathf.Abs(userPos[j].x - centralPosition.x) + Mathf.Abs(userPos[j].z - centralPosition.z);
else if (userDetectionOrder == KinectManager.UserDetectionOrder.LeftToRight)
userDist1 = userPos[j].x;
if (Mathf.Abs(userDist1) < 0.01f)
userDist1 = 1000f; // far away
float userDist2 = 0f;
if (userDetectionOrder == KinectManager.UserDetectionOrder.Distance)
userDist2 = Mathf.Abs(userPos[j + 1].x - centralPosition.x) + Mathf.Abs(userPos[j + 1].z - centralPosition.z);
else if (userDetectionOrder == KinectManager.UserDetectionOrder.LeftToRight)
userDist2 = userPos[j + 1].x;
if (Mathf.Abs(userDist2) < 0.01f)
userDist2 = 1000f; // far away
if (userDist1 > userDist2)
{
// switch them
ulong tmpUserId = aUserIndexIds[j];
aUserIndexIds[j] = aUserIndexIds[j + 1];
aUserIndexIds[j + 1] = tmpUserId;
reorderDone = switchDone = true;
}
}
if (!switchDone) // check for sorted array
break;
}
if (reorderDone)
{
//System.Text.StringBuilder sbUsersOrder = new System.Text.StringBuilder();
//sbUsersOrder.Append("Users rearranged: ");
//for (int i = 0; i < aUserIndexIds.Length; i++)
//{
// if (aUserIndexIds[i] != 0)
// {
// sbUsersOrder.Append(i).Append(":").Append(aUserIndexIds[i]).Append(" ");
// }
//}
//Debug.Log(sbUsersOrder.ToString());
}
}
}
// Returns empty user slot for the given user Id
protected virtual int GetEmptyUserSlot(ulong userId, int bodyIndex, ref KinectInterop.BodyData[] alTrackedBodies, KinectManager.UserDetectionOrder userDetectionOrder)
{
// rearrange current users
RearrangeUserIndices(userDetectionOrder);
int uidIndex = -1;
if(userDetectionOrder == KinectManager.UserDetectionOrder.FixedStepIndices)
{
Vector3 userPos = alTrackedBodies[bodyIndex].position;
int userIndex = GetFixedStepUserIndex(userPos);
//Debug.Log(string.Format("GetEmptyUserSlot - UserId: {0}, xPos: {1:F2}, index: {2}, IndexId: {3}", userId, userPos.x, userIndex, aUserIndexIds[userIndex]));
if (aUserIndexIds[userIndex] == 0)
{
uidIndex = userIndex;
}
}
else if (userDetectionOrder != KinectManager.UserDetectionOrder.Appearance)
{
// add the new user, depending on the distance
Vector3 userPos = alTrackedBodies[bodyIndex].position;
float userDist = 0f;
if (userDetectionOrder == KinectManager.UserDetectionOrder.Distance)
userDist = Mathf.Abs(userPos.x - centralPosition.x) + Mathf.Abs(userPos.z - centralPosition.z);
else if (userDetectionOrder == KinectManager.UserDetectionOrder.LeftToRight)
userDist = userPos.x;
for (int i = 0; i < aUserIndexIds.Length; i++)
{
if (aUserIndexIds[i] == 0)
{
// free user slot
uidIndex = i;
break;
}
else
{
ulong uidUserId = aUserIndexIds[i];
Vector3 uidUserPos = kinectManager.GetUserPosition(uidUserId);
float uidUserDist = 0;
if (userDetectionOrder == KinectManager.UserDetectionOrder.Distance)
uidUserDist = Mathf.Abs(uidUserPos.x - centralPosition.x) + Mathf.Abs(uidUserPos.z - centralPosition.z);
else if (userDetectionOrder == KinectManager.UserDetectionOrder.LeftToRight)
uidUserDist = uidUserPos.x;
if (userDist < uidUserDist)
{
// current user is left to the compared one
for (int u = (aUserIndexIds.Length - 2); u >= i; u--)
{
aUserIndexIds[u + 1] = aUserIndexIds[u];
if (aUserIndexIds[u] != 0)
{
Debug.Log(string.Format("Reindexing user {0} to {1}, ID: {2}, pos: {3}, other-pos: {4}.", u, u + 1, aUserIndexIds[u], userPos, uidUserPos));
}
}
aUserIndexIds[i] = 0; // cleanup current index
uidIndex = i;
break;
}
}
}
}
else
{
// look for the 1st available slot
for (int i = 0; i < aUserIndexIds.Length; i++)
{
if (aUserIndexIds[i] == 0)
{
uidIndex = i;
break;
}
}
}
return uidIndex;
}
// releases the user slot. rearranges the remaining users.
protected virtual void FreeEmptyUserSlot(int uidIndex, KinectManager.UserDetectionOrder userDetectionOrder)
{
aUserIndexIds[uidIndex] = 0;
if (userDetectionOrder != KinectManager.UserDetectionOrder.Appearance &&
userDetectionOrder != KinectManager.UserDetectionOrder.FixedStepIndices)
{
// rearrange the remaining users
for (int u = uidIndex; u < (aUserIndexIds.Length - 1); u++)
{
aUserIndexIds[u] = aUserIndexIds[u + 1];
if (aUserIndexIds[u + 1] != 0)
{
Debug.Log(string.Format("Reindexing user {0} to {1}, ID: {2}.", u + 1, u, aUserIndexIds[u + 1]));
}
}
// make sure the last slot is free
aUserIndexIds[aUserIndexIds.Length - 1] = 0;
}
// rearrange the remaining users
RearrangeUserIndices(userDetectionOrder);
}
// Adds UserId to the list of users
public virtual int CalibrateUser(ulong userId, int bodyIndex, ref KinectInterop.BodyData[] alTrackedBodies,
KinectManager.UserDetectionOrder userDetectionOrder, GestureType playerCalibrationPose, KinectGestureManager gestureManager)
{
if (!alUserIds.Contains(userId))
{
if (CheckForCalibrationPose(userId, bodyIndex, playerCalibrationPose, gestureManager, ref alTrackedBodies))
{
//int uidIndex = alUserIds.Count;
int uidIndex = GetEmptyUserSlot(userId, bodyIndex, ref alTrackedBodies, userDetectionOrder);
if (uidIndex >= 0)
{
aUserIndexIds[uidIndex] = userId;
}
else
{
// no empty user-index slot
return -1;
}
dictUserIdToIndex[userId] = bodyIndex;
dictUserIdToTime[userId] = Time.time;
alUserIds.Add(userId);
// set primary user-id, if there is none
if (liPrimaryUserId == 0 && aUserIndexIds.Length > 0)
{
liPrimaryUserId = aUserIndexIds[0]; // userId
}
return uidIndex;
}
}
return -1;
}
// fires the OnUserAdded-event
internal void FireOnUserAdded(ulong userId, int userIndex)
{
OnUserAdded?.Invoke(userId, userIndex);
}
// Remove a lost UserId
public virtual int RemoveUser(ulong userId, KinectManager.UserDetectionOrder userDetectionOrder)
{
//int uidIndex = alUserIds.IndexOf(userId);
int uidIndex = System.Array.IndexOf(aUserIndexIds, userId);
// clear calibration data for this user
if (playerCalibrationData.ContainsKey(userId))
{
playerCalibrationData.Remove(userId);
}
// clean up the outdated calibration data in the data dictionary
List<ulong> alCalDataKeys = new List<ulong>(playerCalibrationData.Keys);
foreach (ulong calUserID in alCalDataKeys)
{
KinectGestureManager.GestureData gestureData = playerCalibrationData[calUserID];
if ((gestureData.timestamp + 60f) < Time.realtimeSinceStartup)
{
playerCalibrationData.Remove(calUserID);
}
}
alCalDataKeys.Clear();
// remove user-id from the global users lists
dictUserIdToIndex.Remove(userId);
dictUserIdToTime.Remove(userId);
alUserIds.Remove(userId);
if (uidIndex >= 0)
{
FreeEmptyUserSlot(uidIndex, userDetectionOrder);
}
// if this was the primary user, update the primary user-id
if (liPrimaryUserId == userId)
{
if (aUserIndexIds.Length > 0)
{
liPrimaryUserId = aUserIndexIds[0];
}
else
{
liPrimaryUserId = 0;
}
}
if (alUserIds.Count == 0)
{
//Debug.Log("Waiting for users.");
}
return uidIndex;
}
// fires the OnUserRemoved-event
internal void FireOnUserRemoved(ulong userId, int userIndex)
{
OnUserRemoved?.Invoke(userId, userIndex);
}
// check if the calibration pose is complete for given user
protected virtual bool CheckForCalibrationPose(ulong UserId, int bodyIndex, GestureType calibrationGesture,
KinectGestureManager gestureManager, ref KinectInterop.BodyData[] alTrackedBodies)
{
if (calibrationGesture == GestureType.None)
return true;
if (!gestureManager)
return false;
KinectGestureManager.GestureData gestureData = playerCalibrationData.ContainsKey(UserId) ?
playerCalibrationData[UserId] : new KinectGestureManager.GestureData();
// init gesture data if needed
if (gestureData.userId != UserId)
{
gestureData.userId = UserId;
gestureData.gesture = calibrationGesture;
gestureData.state = 0;
gestureData.timestamp = Time.realtimeSinceStartup;
gestureData.joint = 0;
gestureData.progress = 0f;
gestureData.complete = false;
gestureData.cancelled = false;
}
// get joint positions and tracking
int iAllJointsCount = (int)KinectInterop.JointType.Count;
bool[] playerJointsTracked = new bool[iAllJointsCount];
Vector3[] playerJointsPos = new Vector3[iAllJointsCount];
int[] aiNeededJointIndexes = gestureManager.GetNeededJointIndexes();
int iNeededJointsCount = aiNeededJointIndexes.Length;
//// sensor orientation angle
//float orientAngle = kinectManager.GetPrimaryBodySensorOrientationAngle();
//Vector3 sensorOri = new Vector3(0f, 0f, orientAngle);
//matBodySensor.SetTRS(Vector3.zero, Quaternion.Euler(sensorOri), Vector3.one);
for (int i = 0; i < iNeededJointsCount; i++)
{
int joint = aiNeededJointIndexes[i];
if (joint >= 0)
{
KinectInterop.JointData jointData = alTrackedBodies[bodyIndex].joint[joint];
playerJointsTracked[joint] = jointData.trackingState != KinectInterop.TrackingState.NotTracked;
playerJointsPos[joint] = jointData.position; //.kinectPos;
//playerJointsPos[joint] = matBodySensor.MultiplyPoint3x4(playerJointsPos[joint]); // apply sensor orientation
if (!playerJointsTracked[joint] && (joint == (int)KinectInterop.JointType.Neck))
{
KinectInterop.JointData lShoulderData = alTrackedBodies[bodyIndex].joint[(int)KinectInterop.JointType.ShoulderLeft];
KinectInterop.JointData rShoulderData = alTrackedBodies[bodyIndex].joint[(int)KinectInterop.JointType.ShoulderRight];
if (lShoulderData.trackingState != KinectInterop.TrackingState.NotTracked && rShoulderData.trackingState != KinectInterop.TrackingState.NotTracked)
{
playerJointsTracked[joint] = true;
playerJointsPos[joint] = (lShoulderData.position + rShoulderData.position) / 2f;
}
}
}
}
// estimate the gesture progess
gestureManager.CheckForGesture(UserId, ref gestureData, Time.realtimeSinceStartup,
ref playerJointsPos, ref playerJointsTracked);
playerCalibrationData[UserId] = gestureData;
// check if gesture is complete
if (gestureData.complete)
{
gestureData.userId = 0;
playerCalibrationData[UserId] = gestureData;
return true;
}
return false;
}
}
}