Can anyone help me with this quaternion problem?

I’m trying to create my own custom rig for google daydream controller.

Daydream controller just gives you quaternion rotation of the controller in localspace of avatar.

I need to write function that takes that quaternion rotation and applies rotation to hand joint to make the controller model orient correctly.

I’m thinking the zero pose quaternions are important and so have stored them. But I can’t crack the problem.

I have done this function so far:

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //    SET CONTROLLER ROTATION
    public void SetControllerRotation(Quaternion qDesired)
    {
        tHand.localRotation = qDesired * Quaternion.Inverse(qZeroHand);
    }

I’ve illustrated the problem below, any help would be greatly appreciated.:slight_smile:

If its a humanoid avatar I would just use IK. Otherwise it sounds like you need might just simply need to add the delta.

Quaternion relative = Quaternion.Inverse(a) * b;
1 Like

Hi, Thanks for info

It’s a custom skeleton, various stuff atlased into one mesh and I eventually want to make custom IK whereby I control arm and hand rotations to simulate how person would move it based on their currently slected control scheme, there will be two control schemes.

So (a) is delta and (b) is passed quaternion?
That rotates hand in it’s space correctly, but I need to rotate controller in avatar space using just hand joint rotations.

Hmm. It looks like you need to rotate the Hand to make the finger reach a position, right? That means the rotation must be something like making the hand look at the goal with an offset. You could just offset the skeleton to do that. Otherwise it seems like a look at rotation with the delta(relative) between the hand and the finger endpoint added to it.

1 Like

Yes, that’s basically what I want. I need to rotate hand joint in order to have finger joint assume some desired rotation in avatar space.

Yes I thought about changing zero pose joint rotations in 3d tool to lign them up, but would prefer a general solution I can throw any model at in future.

Thanks, your thoughts here are a real help in me understanding this problem, I think I’ll read more about quaternions tonight, they’re my kryptonite at the moment!:smile:

Np! They’re great, but not as straightforward as vectors. I had to solve a few nasty hand alignment issues of my own with Mecanim and IK stuff, but I don’t remember a lot of the solution. It seems like your issue is more straightforward with simply adding the calculated delta rotation between two points. Here’s the whole code I wrote on finding rotations for IK if you feel adventurous.

// (c) Copyright Cleverous 2015. All rights reserved.
using System.Collections;
using UnityEngine;
namespace Deftly
{
    public class IkProxy : MonoBehaviour
    {
        // Subject Information
        public Transform SubjectTr;
        public Subject Subject;
        protected Animator Animator;
        protected bool UseLeftIk;
        protected bool UseRightIk;
        protected int RightHandLayer;
        protected int LeftHandLayer;
        protected float CharScaleMultiplier;
        // Weapon Information
        protected Weapon _weapon;
        protected Transform _weaponTransform;
        protected WeaponType _weaponType;
        protected MountPivot _weaponPivot;
        protected Transform _weaponOriginalNonDomGoal;
        protected Hand DominantHand = Hand.Right;
        protected bool _cantProcessIk;
        protected bool _doNewRecoil;
        protected Vector3 _localRecoil;
        protected float _recoilTimer;
        protected float _recoilRng1;
        protected float _recoilRng2;
        protected bool _transitioning;
        protected GameObject _nonDomWorldTarget;
        protected virtual void CreateNonDomWorldTarget()
        {
            _nonDomWorldTarget = new GameObject
            {
                hideFlags = HideFlags.HideInHierarchy,
                name = "_NonDomHandTarget"
            };
        }
        protected virtual void Awake()
        {
            Animator = GetComponent<Animator>();
        }
        protected virtual void OnEnable()
        {
            StartCoroutine(Recoil());
            CreateNonDomWorldTarget();
        }      
        protected virtual void OnDestroy()
        {
            Destroy(_nonDomWorldTarget); // cleanup
        }
        protected virtual void Start()
        {
            CharScaleMultiplier = Subject.Stats.CharacterScale;
            Subject.OnReload += DoReload;
            Subject.OnFire += DoRecoil;
        }
        protected virtual void DoRecoil(Subject x)
        {
            _recoilTimer = 0;
            _doNewRecoil = true;
        }
        protected virtual IEnumerator Recoil()
        {
            while (true)
            {
                if (_doNewRecoil)
                {
                    if (_recoilTimer < _weapon.Stats.TimeToCooldown)
                    {
                        _recoilTimer += Time.deltaTime;
                        _localRecoil = _weapon.Stats.Recoil * _weapon.Stats.RecoilCurve.Evaluate(_recoilTimer / _weapon.Stats.TimeToCooldown);
                    }
                    else
                    {
                        _recoilRng1 = Random.Range(-_weapon.Stats.RecoilChaos, 1 * _weapon.Stats.RecoilChaos);
                        _recoilRng2 = Random.Range(-_weapon.Stats.RecoilChaos, 1 * _weapon.Stats.RecoilChaos);
                        _doNewRecoil = false;
                    }
                }
                yield return null;
            }
        }
        public virtual void UseLeftHandIk(bool status) { UseLeftIk = status; }
        public virtual void UseRightHandIk(bool status) { UseRightIk = status; }
        public virtual void DisableHandIk()
        {
            UseLeftIk = false;
            UseRightIk = false;
        }
        public virtual void EnableHandIk()
        {
            UseLeftIk = true;
            UseRightIk = true;
        }
        public virtual void ResetHandIk()
        {
            UseLeftIk = Subject.Stats.UseLeftHandIk;
            UseRightIk = Subject.Stats.UseRightHandIk;
        }
        public virtual void SetupWeapon(GameObject weapon)
        {
            // TODO blend IK into position
            // Cache relevant stuff
            _weaponTransform = weapon.transform;
            _weapon = _weaponTransform.GetComponent<Weapon>();
            _weaponType = _weapon.Stats.WeaponType;
            _weaponPivot = _weapon.Stats.MountPivot;
            _weaponOriginalNonDomGoal = _weapon.Stats.NonDominantHandGoal;
            if (_weaponOriginalNonDomGoal && _nonDomWorldTarget == null) { CreateNonDomWorldTarget(); }
            // Set Weapon Parent
            _weaponTransform.SetParent(GetRightHandBone());
            // Set Weapon IN HAND Position and IN HAND Rotation
            _weaponTransform.localPosition = Vector3.zero + SubjectTr.TransformVector(Subject.WeaponPositionInHandCorrection);
            _weaponTransform.rotation = FindWeaponOrientation();
            // Turn on/off ik per the Subject
            ResetHandIk();
            // Get Layer Information
            LeftHandLayer = Subject.LeftHandIkLayer;
            RightHandLayer = Subject.RightHandIkLayer;
            // Tell the Animator its Type ID
            if (Subject.ControlStats.AnimatorWeaponType != "") Animator.SetInteger(Subject.ControlStats.AnimatorWeaponType, _weapon.Stats.TypeId);
            // Fire the event to do the swap animation.
            DoSwitchedWeapon();
        }
        public virtual void DoReload() { StartCoroutine(ReloadTransition()); }
        public virtual void DoSwitchedWeapon() { StartCoroutine(WeaponTransition()); }
        public virtual IEnumerator ReloadTransition()
        {
            if (Subject.LogDebug) Debug.Log("Reloading.");
            _transitioning = true;
            if (Subject.ControlStats.AnimatorReload != "") Animator.SetBool(Subject.ControlStats.AnimatorReload, true);
            yield return new WaitForSeconds(_weapon.Stats.ReloadTime);
            if (Subject.ControlStats.AnimatorReload != "") Animator.SetBool(Subject.ControlStats.AnimatorReload, false);
            _transitioning = false;
            if (Subject.LogDebug) Debug.Log("Done Reloading.");
        }
        public virtual IEnumerator WeaponTransition()
        {
            if (Subject.LogDebug) Debug.Log("Swapping.");
            _transitioning = true;
            if (Subject.ControlStats.AnimatorSwap != "") Animator.SetBool(Subject.ControlStats.AnimatorSwap, true);
            yield return new WaitForSeconds(_weapon.Stats.SwapTime);
            if (Subject.ControlStats.AnimatorSwap != "") Animator.SetBool(Subject.ControlStats.AnimatorSwap, false);
            _transitioning = false;
            if (Subject.LogDebug) Debug.Log("Done Swapping.");
        }
        protected virtual void Update()
        {
            _cantProcessIk = !_weapon || Subject.IsDead || _transitioning || _weaponType == WeaponType.Melee;
            if (_cantProcessIk) return;
            if (UseLeftIk && _weaponOriginalNonDomGoal != null)
            {
                _nonDomWorldTarget.transform.position = _weaponOriginalNonDomGoal.position;
                _nonDomWorldTarget.transform.rotation = _weaponOriginalNonDomGoal.rotation;
            }
        }
        protected virtual void OnAnimatorIK(int layerIndex)
        {
            if (_cantProcessIk) return;
            UseLeftIk = Subject.Stats.UseLeftHandIk;
            UseRightIk = Subject.Stats.UseRightHandIk;
            // _weaponTransform.rotation = FindWeaponOrientation();
            if (layerIndex == RightHandLayer && UseRightIk) ApplyRightIk();
            if (layerIndex == LeftHandLayer && UseLeftIk && _weaponOriginalNonDomGoal != null) ApplyLeftIk();
        }
        protected virtual void ApplyRightIk()
        {
            SetIkPositionWeight(AvatarIKGoal.RightHand, 1);
            SetIkPosition(AvatarIKGoal.RightHand, FindDominantHandPosition());
            if (_weapon.Stats.UseElbowHintR)
            {
                SetIkHintWeight(AvatarIKHint.RightElbow, 1);
                SetIkHintPosition(AvatarIKHint.RightElbow, FindDominantElbowHintPosition());
            }
        }
        protected virtual void ApplyLeftIk()
        {
            SetIkPositionWeight(AvatarIKGoal.LeftHand, 1);
            SetIkRotationWeight(AvatarIKGoal.LeftHand, 1);
            SetIkPosition(AvatarIKGoal.LeftHand, FindNonDominantHandPosition());
            SetIkRotation(AvatarIKGoal.LeftHand, FindNonDominantHandRotation());
            if (_weapon.Stats.UseElbowHintL)
            {
                SetIkHintWeight(AvatarIKHint.LeftElbow, 1);
                SetIkHintPosition(AvatarIKHint.LeftElbow, FindNonDominantElbowHintPosition());
            }
        }
        protected virtual void LateUpdate()
        {
            if (_cantProcessIk) return;
          
            // Fix the broken rotation
            if (UseRightIk) GetRightHandBone().rotation = FindDominantHandRotation();
            // cache the correct position non-dom goal data
            if (_nonDomWorldTarget && _weaponOriginalNonDomGoal)
            {
                _nonDomWorldTarget.transform.position = _weaponOriginalNonDomGoal.position;
                _nonDomWorldTarget.transform.rotation = _weaponOriginalNonDomGoal.rotation;
            }
            // show any debugs
            if (Subject.ShowGunDebug)
            {
                Debug.DrawRay(GetRightHandBone().position, Subject.InvertHandForward ? -GetRightHandBone().right*2 : GetRightHandBone().right, Color.red);
                Debug.DrawRay(GetRightHandBone().position, SubjectTr.forward * 0.5f, Color.green);
            }
        }
        protected virtual Quaternion FindWeaponOrientation()
        {
            Transform bone = GetRightHandBone();
            return Quaternion.LookRotation(
                bone.TransformDirection(Vector3.Cross(Subject.ThumbDirection, Subject.PalmDirection)),
                bone.TransformDirection(DominantHand == Hand.Right ? Subject.ThumbDirection : -Subject.ThumbDirection));
            /*
            return Quaternion.LookRotation(
                DominantHand == Hand.Right && !Subject.InvertHandForward ? bone.right : -bone.right,
                bone.TransformDirection(DominantHand == Hand.Right ? Subject.ThumbDirection : -Subject.ThumbDirection));
             */
        }
        protected virtual Vector3 FindDominantHandPosition()
        {
            Vector3 a = _weaponPivot == MountPivot.LowerSpine ? FindSpinePosition() : FindShoulderPosition();
            Vector3 b = SubjectTr.TransformVector(_weapon.Stats.PositionOffset + Subject.DominantHandPosCorrection + new Vector3(0, _localRecoil.y, _localRecoil.z) * CharScaleMultiplier);
            return a + b;
        }
        protected virtual Quaternion FindDominantHandRotation()
        {
            return Subject.DominantHandRotCorrection == Vector3.zero
                ? Quaternion.identity
                : Quaternion.LookRotation(SubjectTr.forward) * Quaternion.Euler(Subject.DominantHandRotCorrection) * Quaternion.Euler(new Vector3(_localRecoil.x * _recoilRng1, _localRecoil.x, -_localRecoil.x * _recoilRng2));
        }
        protected virtual Vector3 FindDominantElbowHintPosition()
        {
            Vector3 pos = Animator.GetBoneTransform(HumanBodyBones.RightUpperArm).position + SubjectTr.TransformVector(_weapon.Stats.DominantElbowOffset * CharScaleMultiplier);
#if UNITY_EDITOR
            Debug.DrawRay(pos, Vector3.up * 0.1f, Color.green);
            Debug.DrawRay(pos, Vector3.left * 0.1f, Color.green);
            Debug.DrawRay(pos, Vector3.right * 0.1f, Color.green);
            Debug.DrawRay(pos, Vector3.down * 0.1f, Color.green);
#endif
            return pos;
        }
        protected virtual Vector3 FindNonDominantHandPosition()
        {
            // The original goal will always be wrong until UT fixes the SetIKRotation() bug.
            // The Internal Animation pass is done and Mecanim gets the rotation wrong every time on the right hand. (which breaks the left hand)
            // I correct the rotation manually in LateUpdate() and cache the position for the LH Goal while its correct.
            // I use that goal here. So everything is 1 frame behind for the left hand.
            return _nonDomWorldTarget.transform.position + SubjectTr.InverseTransformVector(Subject.NonDominantHandPosCorrection * CharScaleMultiplier);
            /*
            Transform goal = CurrentWeapon.Stats.NonDominantHandGoal;
            if (goal)
            {
                //if (_nonDomCached)
                return goal.position; // + SubjectTr.TransformVector(Subject.NonDominantHandPosCorrection);
            }
            return CurrentWeaponTr.position + CurrentWeapon.Stats.NonDominantHandPos;
             */
        }
        protected virtual Quaternion FindNonDominantHandRotation()
        {
            return _nonDomWorldTarget.transform.rotation * Quaternion.Euler(Subject.NonDominantHandRotCorrection);
            /*
            Transform goal = CurrentWeapon.Stats.NonDominantHandGoal;
            if (goal)
            {
                //if (_nonDomCached)
                return Quaternion.LookRotation(goal.forward, goal.up) * Quaternion.Euler(Subject.NonDominantHandRotCorrection);
            }
            Quaternion a = CurrentWeaponTr.rotation;
            Quaternion b = Quaternion.Euler(CurrentWeapon.Stats.NonDominantHandRot);
            return a * b;
             */
        }
        protected virtual Vector3 FindNonDominantElbowHintPosition()
        {
            Vector3 pos = Animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).position + SubjectTr.TransformVector(_weapon.Stats.NonDominantElbowOffset * CharScaleMultiplier);
#if UNITY_EDITOR
            Debug.DrawRay(pos, Vector3.up * 0.1f, Color.magenta);
            Debug.DrawRay(pos, Vector3.left * 0.1f, Color.magenta);
            Debug.DrawRay(pos, Vector3.right * 0.1f, Color.magenta);
            Debug.DrawRay(pos, Vector3.down * 0.1f, Color.magenta);
#endif
            return pos;
        }
        protected virtual Vector3 FindSpinePosition() { return Animator.GetBoneTransform(HumanBodyBones.Spine).position; }
        protected virtual Vector3 FindShoulderPosition() { return Animator.GetBoneTransform(HumanBodyBones.RightUpperArm).position; }
        protected virtual Transform GetRightHandBone() { return Animator.GetBoneTransform(HumanBodyBones.RightHand); }
        protected virtual Transform GetLeftHandBone() { return Animator.GetBoneTransform(HumanBodyBones.LeftHand); }
        protected static Quaternion GetDelta(Quaternion targetRotation, Quaternion currentRotation)
        {
            return targetRotation*Quaternion.Inverse(currentRotation);
        }
        // You can call these, but Mecanim requires it to come from OnAnimatorIK()
        // They'll be overidden anyway, so I need some sort of queue system for handling overrides that is analyzed during OnAnimatorIK()
        // The issue with Mecanim's rotation errors is holding back proper implementation of this as well.

        public virtual void SetIkPositionWeight(AvatarIKGoal armature, float weight) { Animator.SetIKPositionWeight(armature, weight); }
        public virtual void SetIkRotationWeight(AvatarIKGoal armature, float weight) { Animator.SetIKRotationWeight(armature, weight); }
        public virtual void SetIkPosition(AvatarIKGoal armature, Vector3 position) { Animator.SetIKPosition(armature, position); }
        public virtual void SetIkRotation(AvatarIKGoal armature, Quaternion rotation) { Animator.SetIKRotation(armature, rotation); }
        public virtual void SetIkHintPosition(AvatarIKHint hint, Vector3 position) { Animator.SetIKHintPosition(hint, position);}
        public virtual void SetIkHintWeight(AvatarIKHint hint, float weight) { Animator.SetIKHintPositionWeight(hint, weight);}
    }
}
1 Like