/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
public partial class OVRUnityHumanoidSkeletonRetargeter
{
///
/// Skeleton meta data class, associated with each HumanyBodyBone enum.
///
protected class OVRSkeletonMetadata
{
///
/// Data associated per bone.
///
public class BoneData
{
///
/// Default, no-argument constructor.
///
public BoneData()
{
}
///
/// Copy constructor.
///
/// Other bone data to copy from.
public BoneData(BoneData otherBoneData)
{
OriginalJoint = otherBoneData.OriginalJoint;
FromPosition = otherBoneData.FromPosition;
ToPosition = otherBoneData.ToPosition;
JointPairStart = otherBoneData.JointPairStart;
JointPairEnd = otherBoneData.JointPairEnd;
JointPairOrientation = otherBoneData.JointPairOrientation;
CorrectionQuaternion = otherBoneData.CorrectionQuaternion;
ParentTransform = otherBoneData.ParentTransform;
DegenerateJoint = otherBoneData.DegenerateJoint;
}
///
/// Transform associated with joint.
///
public Transform OriginalJoint;
///
/// From position for joint pair, for debugging.
///
public Vector3 FromPosition;
///
/// To position for joint pair, for debugging.
///
public Vector3 ToPosition;
///
/// Start of joint pair (usually the original joint).
///
public Transform JointPairStart;
///
/// End of joint pair.
///
public Transform JointPairEnd;
///
/// Orientation or rotation corresponding to joint pair.
/// If multiplied by forward, produces a coordinate axis.
///
public Quaternion JointPairOrientation;
///
/// Offset quaternion, used for retargeting rotations.
///
public Quaternion? CorrectionQuaternion;
///
/// Parent transform of joint. This is defined in a special way for OVRSkeleton,
/// so we have to cache it ahead of time.
///
public Transform ParentTransform;
///
/// Some joints made have bad orientations due to faulty joint pairs.
///
public bool DegenerateJoint = false;
}
///
/// Human body bone enum to bone data mapping.
///
public Dictionary BodyToBoneData { get; } =
new Dictionary();
private readonly HumanBodyBones[] _boneEnumValues =
(HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones));
///
/// Constructor that copies another meta data class.
///
/// Other meta data to copy from.
public OVRSkeletonMetadata(OVRSkeletonMetadata otherSkeletonMetaData)
{
BodyToBoneData = new Dictionary();
foreach (var key in otherSkeletonMetaData.BodyToBoneData.Keys)
{
var value = otherSkeletonMetaData.BodyToBoneData[key];
BodyToBoneData[key] = new BoneData(value);
}
}
///
/// Main constructor.
///
/// Animator to build meta data from.
public OVRSkeletonMetadata(Animator animator)
{
BuildBoneData(animator);
}
///
/// Constructor for OVRSkeleton.
///
/// Skeleton to build meta data from.
/// Whether to use bind pose (T-pose) or not.
/// Custom bone ID to human body bone mapping.
public OVRSkeletonMetadata(OVRSkeleton skeleton, bool useBindPose,
Dictionary customBoneIdToHumanBodyBone)
{
BuildBoneDataSkeleton(skeleton, useBindPose, customBoneIdToHumanBodyBone);
}
///
/// Builds body to bone data with the OVRSkeleton.
///
/// The OVRSkeleton.
/// If true, use the bind pose.
/// Custom bone ID to human body bone mapping.
public void BuildBoneDataSkeleton(OVRSkeleton skeleton, bool useBindPose,
Dictionary customBoneIdToHumanBodyBone)
{
AssembleSkeleton(skeleton, useBindPose, customBoneIdToHumanBodyBone);
}
private void AssembleSkeleton(OVRSkeleton skeleton, bool useBindPose,
Dictionary customBoneIdToHumanBodyBone
)
{
if (BodyToBoneData.Count != 0)
{
BodyToBoneData.Clear();
}
var allBones = useBindPose ? skeleton.BindPoses : skeleton.Bones;
for (var i = 0; i < allBones.Count; i++)
{
var bone = allBones[i];
if (!customBoneIdToHumanBodyBone.ContainsKey(bone.Id))
{
continue;
}
var humanBodyBone = customBoneIdToHumanBodyBone[bone.Id];
var boneData = new BoneData();
boneData.OriginalJoint = bone.Transform;
if (!OVRHumanBodyBonesMappings.BoneIdToJointPair.ContainsKey(bone.Id))
{
Debug.LogError($"Can't find {bone.Id} in bone Id to joint pair map!");
continue;
}
var jointPair =
OVRHumanBodyBonesMappings.BoneIdToJointPair[bone.Id];
var startOfPair = jointPair.Item1;
var endofPair = jointPair.Item2;
// for some tip transforms, the start of pair starts with one joint before
boneData.JointPairStart = (startOfPair == bone.Id)
? bone.Transform
: FindBoneWithBoneId(allBones, startOfPair).Transform;
boneData.JointPairEnd = endofPair != OVRSkeleton.BoneId.Invalid
? FindBoneWithBoneId(allBones, endofPair).Transform
: boneData.JointPairStart;
boneData.ParentTransform = allBones[bone.ParentBoneIndex].Transform;
if (boneData.JointPairStart == null)
{
Debug.LogWarning($"{bone.Id} has invalid start joint.");
}
if (boneData.JointPairEnd == null)
{
Debug.LogWarning($"{bone.Id} has invalid end joint.");
}
BodyToBoneData.Add(humanBodyBone, boneData);
}
}
private static OVRBone FindBoneWithBoneId(IList bones, OVRSkeleton.BoneId boneId)
{
for (var i = 0; i < bones.Count; i++)
{
if (bones[i].Id == boneId)
{
return bones[i];
}
}
return null;
}
///
/// Builds body to bone data with an Animator.
///
///
private void BuildBoneData(Animator animator)
{
if (BodyToBoneData.Count != 0)
{
BodyToBoneData.Clear();
}
foreach (var humanBodyBone in _boneEnumValues)
{
if (humanBodyBone == HumanBodyBones.LastBone)
{
continue;
}
var currTransform = animator.GetBoneTransform(humanBodyBone);
if (currTransform == null)
{
continue;
}
var boneData = new BoneData();
boneData.OriginalJoint = currTransform;
BodyToBoneData.Add(humanBodyBone, boneData);
}
// Find paired joints after all transforms have been tracked.
// A joint start starts from a joints, ends at its child joint, and serves
// as the "axis" of the joint.
foreach (var key in BodyToBoneData.Keys)
{
var boneData = BodyToBoneData[key];
var jointPair = OVRHumanBodyBonesMappings.BoneToJointPair[key];
boneData.JointPairStart = jointPair.Item1 != HumanBodyBones.LastBone
? animator.GetBoneTransform(jointPair.Item1)
: boneData.OriginalJoint;
boneData.JointPairEnd = jointPair.Item2 != HumanBodyBones.LastBone
? animator.GetBoneTransform(jointPair.Item2)
: FindFirstChild(boneData.OriginalJoint, boneData.OriginalJoint);
boneData.ParentTransform = boneData.OriginalJoint.parent;
if (boneData.JointPairStart == null)
{
Debug.LogWarning($"{key} has invalid start joint.");
}
if (boneData.JointPairEnd == null)
{
Debug.LogWarning($"{key} has invalid end joint.");
}
}
}
///
/// Builds coordinate axes for all bones.
///
public void BuildCoordinateAxesForAllBones()
{
foreach (var key in BodyToBoneData.Keys)
{
var boneData = BodyToBoneData[key];
var jointPairStartPosition = boneData.JointPairStart.position;
Vector3 jointPairEndPosition;
// Edge case: joint pair end is null or same node. If that's the case,
// make joint pair end follow the axis from previous node.
if (boneData.JointPairEnd == null ||
boneData.JointPairEnd == boneData.JointPairStart ||
(boneData.JointPairEnd.position - boneData.JointPairStart.position).magnitude <
Mathf.Epsilon)
{
var node1 = boneData.ParentTransform;
var node2 = boneData.JointPairStart;
jointPairStartPosition = node1.position;
jointPairEndPosition = node2.position;
boneData.DegenerateJoint = true;
}
else
{
jointPairEndPosition = boneData.JointPairEnd.position;
boneData.DegenerateJoint = false;
}
// with some joints like hands, fix the right vector. that's because the hand is nice and
// flat, and the right vector should point to a thumb bone.
if (key == HumanBodyBones.LeftHand || key == HumanBodyBones.RightHand)
{
var jointToCreateRightVecWith = key == HumanBodyBones.LeftHand
? HumanBodyBones.LeftThumbIntermediate
: HumanBodyBones.RightThumbIntermediate;
var rightVec = BodyToBoneData[jointToCreateRightVecWith].OriginalJoint.position -
jointPairStartPosition;
boneData.JointPairOrientation =
CreateQuaternionForBoneDataWithRightVec(
jointPairStartPosition,
jointPairEndPosition,
rightVec);
}
else
{
boneData.JointPairOrientation =
CreateQuaternionForBoneData(
jointPairStartPosition,
jointPairEndPosition);
}
var position = boneData.OriginalJoint.position;
boneData.FromPosition = position;
boneData.ToPosition = position +
(jointPairEndPosition - jointPairStartPosition);
}
}
private static Transform FindFirstChild(
Transform startTransform,
Transform currTransform)
{
if (startTransform != currTransform)
{
return currTransform;
}
// Dead end.
if (currTransform.childCount == 0)
{
return null;
}
Transform foundChild = null;
for (var i = 0; i < currTransform.childCount; i++)
{
var currChild = FindFirstChild(startTransform,
currTransform.GetChild(i));
if (currChild != null)
{
foundChild = currChild;
break;
}
}
return foundChild;
}
private static Quaternion CreateQuaternionForBoneDataWithRightVec(
Vector3 fromPosition,
Vector3 toPosition,
Vector3 rightVector)
{
var forwardVec = (toPosition - fromPosition).normalized;
if (forwardVec.sqrMagnitude < Mathf.Epsilon)
{
forwardVec = Vector3.forward;
}
var upVector = Vector3.Cross(forwardVec, rightVector);
return Quaternion.LookRotation(forwardVec, upVector);
}
private static Quaternion CreateQuaternionForBoneData(
Vector3 fromPosition,
Vector3 toPosition)
{
var forwardVec = (toPosition - fromPosition).normalized;
if (forwardVec.sqrMagnitude < Mathf.Epsilon)
{
forwardVec = Vector3.forward;
}
return Quaternion.LookRotation(forwardVec);
}
}
}