VR Challenge: Rotation issue when holding object with both hand controllers vertically

When working with two VR controllers, there is a use case where when gripping (we’ve tried our own code - below - and VRTK to do this and both have the same logic problem) and rotating an object with both controllers, at the vertical position of the controllers the object will start unnaturally rotating on the y-axis (World Space). This is problematic if you want to have an experience where users are using two hands to move and rotate objects naturally. Here’s a video of the issue in action.

Also the code that controls the rotation:

using UnityEngine;
using System.Collections;
using VRTK;
 
public class RotateControl : MonoBehaviour {
    VRTK_ControllerEvents events;
    VRTK_InteractGrab grab;
    public VRTK_InteractGrab analogGrab;
    GameObject lastTrigger;
 
    public bool rotating;
    Vector3 startPos;
    Vector3 startDiff;
    Vector3 diff;
    Vector3 d;
 
    // Use this for initialization
    void Start () {
        lastTrigger = gameObject;
        events = GetComponent<VRTK_ControllerEvents> ();
        grab = GetComponent<VRTK_InteractGrab> ();
        GetComponent<VRTK_ControllerEvents>().TriggerPressed += new ControllerInteractionEventHandler(DoTriggerPressed);
        GetComponent<VRTK_ControllerEvents>().TriggerReleased += new ControllerInteractionEventHandler(DoTriggerReleased);
 
    }
    private void DoTriggerPressed(object sender, ControllerInteractionEventArgs e)
    {
        if (GetComponent<VRTK_InteractTouch> ().touchedObject == null && analogGrab.GetGrabbedObject() != null) {
            startPos = transform.position;
            startDiff = analogGrab.transform.position - transform.position;
            rotating = true;
        }
    }
 
    private void DoTriggerReleased(object sender, ControllerInteractionEventArgs e)
    {
        if(rotating)
            rotating = false;
    }
    void Update()
    {
        if (rotating) {
            diff = analogGrab.transform.position - transform.position;
            d = diff - startDiff;
            if (Abs(startDiff.z) >= Abs(startDiff.x) && Abs(startDiff.z) >= Abs(startDiff.y)) 
            {
                if (diff.z > 0 && d.z <= -1) 
                {
                    if (d.x >= 1) 
                    {
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 90);
                        Debug.Log ("zp - Dreapta,  1");
                        startDiff = diff;
                    }
                    else
                        if(d.x <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, -90);
                            Debug.Log ("zp - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.y >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, 90);
                                Debug.Log ("zp - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.y <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, -90);
                                    Debug.Log ("zp - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.z <= -2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 180);
                                        Debug.Log ("zp - 180,  5");
                                        startDiff = diff;
                                    }
                } 
                else if(diff.z < 0 && d.z >= 1)
                {
                    if (d.x >= 1) 
                    {
                        Debug.Log ("zn - Dreapta,  1");
                        startDiff = diff;
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, -90);
                    }
                    else
                        if(d.x <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 90);
                            Debug.Log ("zn - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.y >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, -90);
                                Debug.Log ("zn - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.y <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, 90);
                                    Debug.Log ("zn - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.z >= 2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 180);
                                        Debug.Log ("zn - 180,  5");
                                        startDiff = diff;
                                    }
                }
            }
            else if (Abs(startDiff.x) >= Abs(startDiff.z) && Abs(startDiff.x) >= Abs(startDiff.y)) 
            {
                if (diff.x > 0 && d.x <= -1) {
 
                    if (d.z >= 1) 
                    {
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 90);
                        Debug.Log ("xp - Dreapta,  1");
                        startDiff = diff;
                    }
                    else
                        if(d.z <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, -90);
                            Debug.Log ("xp - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.y >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, 90);
                                Debug.Log ("xp - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.y <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, -90);
                                    Debug.Log ("xp - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.x <= -2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, 180);
                                        Debug.Log ("xp - 180,  5");
                                        startDiff = diff;
                                    }
                } 
                else if(diff.x < 0 && d.x >= 1)
                {
                    if (d.z >= 1) 
                    {
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, -90);
                        Debug.Log ("xn - Dreapta,  1");
                        startDiff = diff;
                    }
                    else
                        if(d.z <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.up, 90);
                            Debug.Log ("xn - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.y >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, -90);
                                Debug.Log ("xn - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.y <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, 90);
                                    Debug.Log ("xn - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.x >= 2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, -180);
                                        Debug.Log ("xn - 180,  5");
                                        startDiff = diff;
                                    }
 
                }
            }
            else if (Abs(startDiff.y) >= Abs(startDiff.z) && Abs(startDiff.y) >= Abs(startDiff.x)) 
            {
                if (diff.y > 0 && d.y <= -1) {
                    if (d.x >= 1) 
                    {
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, -90);
                        Debug.Log ("yp - Dreapta,  1");
                        startDiff = diff;
                    }
                    else
                        if(d.x <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, 90);
                            Debug.Log ("yp - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.z >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, -90);
                                Debug.Log ("yp - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.z <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, 90);
                                    Debug.Log ("yp - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.y <= -2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, 180);
                                        Debug.Log ("yp - 180,  5");
                                        startDiff = diff;
                                    }
 
                } 
                else if(diff.y < 0 && d.y >= 1)
                {
                    if (d.x >= 1) 
                    {
                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, 90);
                        Debug.Log ("yn - Dreapta,  1");
                        startDiff = diff;
                    }
                    else
                        if(d.x <= -1)
                        {
                            analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.forward, -90);
                            Debug.Log ("yn - Stanga,  2");
                            startDiff = diff;
                        }
                        else
                            if(d.z >= 1)
                            {
                                analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, 90);
                                Debug.Log ("yn - Sus,  3");
                                startDiff = diff;
                            }
                            else
                                if(d.z <= -1)
                                {
                                    analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, -90);
                                    Debug.Log ("yn - Jos,  4");
                                    startDiff = diff;
                                }
                                else
                                    if(d.y >= 2 )
                                    {
                                        analogGrab.GetGrabbedObject().transform.RotateAround(transform.position,Vector3.right, -180);
                                        Debug.Log ("yn - 180,  5");
                                        startDiff = diff;
                                    }
 
                }
            }
 
             
        }
    }
    float Abs(float f)
    {
        return Mathf.Abs (f);
    }
 
    void OnTriggerEnter(Collider coll)
    {
        //if (!changed && coll.tag == "RotCollider")// && grab.GetGrabbedObject () == null && analogGrab.GetGrabbedObject() != null && events.grabPressed ) 
        //{
            //changed = true;
            //lastTrigger = coll.gameObject;
            //analogGrab.GetGrabbedObject ().transform.LookAt (lastTrigger.transform.position);     
        //}
    }
    void OnTriggerExit(Collider coll)
    {
        //if (!changed && coll.tag == "RotCollider") {
        //  changed = false;
        //}
    }
 
}

I got this working correctly and the trick is that you want to keep applying the most recent rotation delta from each frame to your object’s active transformation- NOT from your object’s initial transformation at the time of “capture”.

‘’

The fact that this is done in most two handed VR apps can be gleaned from testing rotations in a given order. Try it in your favorite VR app: start with two hands at the same Y some distance horizontally apart, then rotate them around Z 90 deg (as if you are turning a wheel) and then rotate them around X 90deg (as if you are wrapping something around your hands). Your resultant model orientation will be different than had you just rotated the model about Y 90 deg. This shows that the order of transformations matters, even if the final positions of your hands are at the same orientation from one another. This implies that you cannot create a single transformation algorithm that maps from initial positions & orientations to some active position & orientation.

‘’

I’ve found the solution requires applying three transformations:

‘’

First: multiply the rotation delta from the previous difference vector between the two hands and the new difference vector (in Unity, you can get this using Quaternion.FromToRotation). This will get you most of the way there.

‘’

Then, multiply your new orientation with two additional transformations representing the delta in rotation of each hand’s orientation. I’ve found that this additional transformation should be scaled by half to get a good result (you can use Slerp of 0.5f from the default identity rotation) This gives you a sort of averaging of the influence of each of the two hands’ orientations on the overall rotation.

‘’

Lastly, because the two hands orientation changes will modify the rotation angle of your model in a way that could “de-attach” the model from your hands, simply use a lookat function (in Unity, you can get this using Quaternion.LookRotation) with the resultant up vector of all these transforms as the up direction to get a final rotation (this essentially forces the rotation component applied from each hand to only influence the rotation about the axis between both hands).

‘’

This kind of algorithm will give you a realistic feeling rotation (with realistically changing up direction) and won’t ever “jump” to a wildly different rotation (because you’re constantly applying a rotation over time).

‘’

That said, you’ll find that most 2-handed free-rotation algorithms exhibit some kinds of behavior that you may not find suitable with some movements(even in your favorite VR app) so the ideal solution will probably require additional thought to how you finally place your objects (snapping?) and if you want to limit rotation to a specific axis (It is possible -and easier- to create a consistent, completely order-independent transformation algorithm if you lock rotation to a single axis, say, Y).

‘’

Good luck!

I can resolve it, this work with one direction between two references points, and mantain the orientation, copy all after update method and write the conditions to activate it.
Another thing, I don’t read your code but I bet that do this

TransformObjectToRotate.position = (TransformRightControl.position + TransformLeftControl.position) / 2;
TransformObjectToRotate.LookAt (TransformRightControl);

Good luck
,GitHub - alejo1099/Grabbing-Object-2-hands-VR: Development of grabbing object with 2 hands, in HTC VIVE with Unity
I can resolve it, this work with one direction between two references points, and mantain the orientation, copy all after update method and write the conditions to activate it.
Another thing, I don’t read your code but I bet that do this
TransformObjectToRotate.position = (TransformRightControl.position + TransformLeftControl.position) / 2;
TransformObjectToRotate.LookAt (TransformRightControl);
Good luck