Last active
November 24, 2020 12:28
-
-
Save fishtopher/eae1ab55b24827660e4a299eecce70d6 to your computer and use it in GitHub Desktop.
Tween the Oculus Avatar hands to any custom pose you want
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using System; | |
using UnityEngine; | |
using Oculus.Avatar; | |
// Smoothly tween between the "real" hand pose that comes out of the | |
// Oculus Avatar SDK and a custom grip pose. | |
// | |
// ## Why? | |
// The SDK doesn't natively do this so we have to do it manually here. | |
// We can't just naively lerp from the current hand pose to our target because | |
// ovr doesn't update bones unless the C layer says they changed - so there's | |
// no consistent FROM state, and thus no real control over how far through | |
// the lerp you want to be | |
// (i.e. you can't say "lerp 50% please" because the next frame the from | |
// state will have moved to 50% so the request for 50% is now really for 75% | |
// and so you'll always tend towards the TO state no matter how much you | |
// actually want to lerp.) | |
// | |
// ## Overview | |
// Roughtly we: | |
// - Make a copy of the hand skeleton | |
// - When the OvrAvatar updates, update our copy to match. | |
// - Use our copy as the FROM version in a lerp TO our custom pose | |
// - Set the rendered skeleton to whatever our lerped versions are | |
// | |
// ## How | |
// To make a custom hand pose, follow Oculus' instructions: | |
// https://developer.oculus.com/documentation/avatarsdk/latest/concepts/avatars-sdk-unity/#avatars-sdk-unity-custom-grip-poses | |
// | |
// ## TODO / Known Issues | |
// - Not tested with OvrAvatarSkinnedMeshRenderPBSComponent | |
// - Multiple target poses or some sort of blend tree would be nice. | |
// - Limit tweens to specific fingers to allow it to play nice with other hand | |
// poses (e.g. index finger trigger should only effect index finger). | |
// | |
// ## Contact | |
// Any questions or bug reports - email me! | |
// Chris McLaughlin - chris@vitei.com | |
public class OVRAvatarHandPoseOverrider : MonoBehaviour { | |
[Range(0,1)] | |
public float m_tweenAmount; | |
public OvrAvatar m_avatar; | |
public enum Handedness { Right, Left } | |
public Handedness m_hand; | |
public Transform m_gripPose; | |
OvrAvatarHand m_ovrHand; | |
OvrAvatarRenderComponent m_ovrHandRenderer; | |
Transform[] m_bonesTruePose; | |
IntPtr m_ovrRenderPart = IntPtr.Zero; | |
[System.Serializable] | |
public class BoneSet { | |
public Transform m_rendered; | |
public Transform m_base; | |
public Transform m_target; | |
} | |
List<BoneSet> m_boneSets; | |
#if UNITY_EDITOR | |
Transform m_lastGripPose; | |
private void OnValidate() { | |
// if the game is runnnign and the user changes the pose in editor, | |
// then we want to make sure to update everything so that's what | |
// they see! | |
if(Application.isPlaying && m_lastGripPose != m_gripPose) { | |
SetupBoneSets(); | |
} | |
m_lastGripPose = m_gripPose; | |
} | |
#endif | |
protected virtual void Start() { | |
// Get all the relevant components that we're gonna need later on | |
m_avatar = GetComponent<OvrAvatar>(); | |
m_ovrHand = (m_hand == Handedness.Left) ? m_avatar.HandLeft : m_avatar.HandRight; | |
} | |
protected virtual void Update() { | |
// we need a renderpart before we can do anything else. This doesn't | |
// exist until the SDK tries to render the hand, so we have to keep | |
// testing here in update until we can get it. | |
if (m_ovrRenderPart == IntPtr.Zero){ | |
GetNativeRenderPartPtr(); | |
return; | |
} | |
// we need the bonesets setup otherwise there's nothing to actually do | |
// the work on. Again we need the rendered data before we can do | |
// this setup. | |
if (m_boneSets == null) { | |
SetupBoneSets(); | |
return; | |
} | |
// Use the renderpart to look into the avatar sdk adn find out what bones have changed. | |
// Update our TRUE bones to whatever the sdk says they should be | |
// We can't use the meshrenderer's bones as the FROM in our lerp | |
// because the SDK doesn't update them unless _it_ thinks that they | |
// changed. | |
ulong dirtyJoints = CAPI.ovrAvatarSkinnedMeshRender_GetDirtyJoints(m_ovrRenderPart); | |
for (UInt32 i = 0; i < m_bonesTruePose.Length; i++) { | |
UInt64 dirtyMask = (ulong)1 << (int)i; | |
// We need to make sure that we fully update the initial position of | |
// Skinned mesh renderers, then, thereafter, we can only update dirty joints | |
if ((dirtyMask & dirtyJoints) != 0) { | |
//This joint is dirty and needs to be updated | |
Transform targetBone = m_ovrHandRenderer.bones[i]; | |
if (m_bonesTruePose != null && m_bonesTruePose[i] != null) { | |
m_bonesTruePose[i].localPosition = targetBone.localPosition; | |
m_bonesTruePose[i].localRotation = targetBone.localRotation; | |
m_bonesTruePose[i].localScale = targetBone.localScale; | |
} | |
} | |
} | |
// Loop through all our bones based on how much the trigger is pulled and put them into the right positions! | |
for (int i = 0; i < m_boneSets.Count; i++) { | |
// Not lerping POSITION or SCALE because hand bones don't need to, but if you want to do that for some reason, then add it here! | |
// You could use a SLERP here, but I didn't see any difference in action, so went with the cheaper LERP instead. | |
m_boneSets[i].m_rendered.localRotation = Quaternion.Lerp(m_boneSets[i].m_base.localRotation, m_boneSets[i].m_target.localRotation, m_tweenAmount); | |
} | |
} | |
//------------------------------------------------------- | |
// Dive into the avatar sdk and pull out a reference to what I guess is | |
// the thing that renders a hand. we can then use it to look up how | |
// dirty it is later on. | |
void GetNativeRenderPartPtr() { | |
if (m_avatar.sdkAvatar != IntPtr.Zero) { // null check, otherwise the next line will hard crash the game. | |
UInt32 componentCount = CAPI.ovrAvatarComponent_Count(m_avatar.sdkAvatar); | |
for (UInt32 i = 0; i < componentCount; i++) { | |
IntPtr ptr = CAPI.ovrAvatarComponent_Get_Native(m_avatar.sdkAvatar, i); | |
ovrAvatarComponent component = (ovrAvatarComponent)System.Runtime.InteropServices.Marshal.PtrToStructure(ptr, typeof(ovrAvatarComponent)); | |
if (component.name == (m_hand == Handedness.Left ? "hand_left" : "hand_right")) { | |
m_ovrRenderPart = OvrAvatar.GetRenderPart(component, 0); //magic 0 | |
} | |
} | |
} | |
} | |
// Make a copy of all the bones in the OvrAvatarSkinnedMeshRenderComponent, these will represent the position as the avatar sdk thinks is should be | |
void CreateTrueBoneArray() { | |
Transform[] bones = m_ovrHandRenderer.bones; | |
m_bonesTruePose = new Transform[bones.Length]; | |
for (int i = 0; i < bones.Length; i++) { | |
m_bonesTruePose[i] = new GameObject(bones[i].name + " (true pose)").transform; | |
} | |
List<Transform> bonesAsList = new List<Transform>(bones); | |
for (int i = 0; i < bones.Length; i++) { | |
int parentIdx = bonesAsList.IndexOf(bones[i].parent); | |
if (parentIdx >= 0) { | |
m_bonesTruePose[i].parent = m_bonesTruePose[parentIdx]; | |
} | |
else { | |
m_bonesTruePose[i].parent = bones[i].parent; | |
} | |
m_bonesTruePose[i].transform.localPosition = bones[i].transform.localPosition; | |
m_bonesTruePose[i].transform.localRotation = bones[i].transform.localRotation; | |
m_bonesTruePose[i].transform.localScale = bones[i].transform.localScale; | |
} | |
} | |
// Make sets of corresponding bones that we'll lerp between. | |
void SetupBoneSets() { | |
if(m_ovrHand == null) { | |
return; | |
} | |
// Get the skinnedmeshrenderer because it has all the bone data we need | |
m_ovrHandRenderer = m_ovrHand.GetComponentInChildren<OvrAvatarRenderComponent>(); | |
if (m_ovrHandRenderer == null) { | |
return; | |
} | |
// Make our copy of the bones to use as the TRUTH | |
CreateTrueBoneArray(); | |
// Make our bonesets that allow us to lerp between TRUTH and m_gripPose; | |
m_boneSets = new List<BoneSet>(); | |
for (int i = 0; i < m_ovrHandRenderer.bones.Length; i++) { | |
BoneSet bs = new BoneSet(); | |
bs.m_rendered = m_ovrHandRenderer.bones[i]; | |
bs.m_base = m_bonesTruePose[i]; | |
bs.m_target = m_gripPose.FindChildRecursive(m_ovrHandRenderer.bones[i].name); | |
// don't add any that have incomplete data as it will cause errors later on ehwn we're interpolating | |
if (bs.m_rendered != null && bs.m_base != null && bs.m_target != null) { | |
m_boneSets.Add(bs); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
// Stripped this file down to just the fn required for the pose overrider | |
public static class TransformExtensions { | |
public static Transform FindChildRecursive(this Transform target, string name) { | |
if (target.name == name) | |
return target; | |
for (int i = 0; i < target.childCount; ++i) { | |
Transform result = FindChildRecursive(target.GetChild(i), name); | |
if (result != null) | |
return result; | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment