Hi all,
I have been struggling with this one for a little while now and hope one of you guys can help me out.
The goal: Smoothly rotate a dial using a VR controller. Intuitively, I would have thought that could be achieved by having the dial’s local rotation (in my case on its forward axis) be offset the difference in the controller’s local forward rotation.
This doesn’t work because the controller can actually be oriented in many different ways, and still be made to rotate along the dial’s forward axis.
How do I calculate how much the controller has rotated in relation to the dial’s forward axis? It’s similar to SteamVR’s Circular Drive, only I need to do it in Unity XR Interaction Toolkit.
Here’s a drawing to help illustrate it:
My code is something like this:
private void CalculateRotation()
{
Quaternion projection = Quaternion.FromToRotation(controller.transform.forward, -dial.transform.forward);
Quaternion controllerOffset = Quaternion.Inverse(previousControllerRotation) * (controller.transform.rotation * projection);
previousControllerRotation = controller.transform.rotation;
var newTargetAngle = dial.transform.localRotation.z + controllerOffset.eulerAngles.z;
newTargetAngle = Mathf.Clamp(newTargetAngle, RotationMin, RotationMax);
targetAngle = newTargetAngle;
}
This is not working properly, although I think it is in the right direction (projecting the rotation delta onto the dial’s forward axis).
What do you think? Thanks in advance!
I see a lot of people are following the question, probably because it is hard to find a solution online.
I’m still trying to work it out. The question can be re-formulated like:
If I rotate my controller along any given axis, how many degrees did it move along my dial’s forward axis?
Will post the answer here if I figure it out before a kind stranger…
I ended up with a simpler solution: Calculate the rotational delta for the controller in Euler angles for the axis you’re interested in and add that onto the target angle of the dial.
The code:
private void ComputeTwistAngle()
{
// Calculate simple euler angle offset on desired local axis
var currentInteractorAngleOnAxis = followingInteractor.transform.localEulerAngles[axisOfRotation];
var interactorAngleDelta = previousInteractorAngleOnAxis - currentInteractorAngleOnAxis;
// Helper function from Unity Forums to wrap angle in a range
interactorAngleDelta = Wrap(interactorAngleDelta, maxRotationalDelta, -maxRotationalDelta);
// Clamp the angle within dial's rotation limits
targetAngle = Mathf.Clamp(targetAngle + interactorAngleDelta, MinAngle, MaxAngle);
previousInteractorAngleOnAxis = currentInteractorAngleOnAxis;
}
This works because the controller is mostly always facing the dial in such a way, that just getting the euler angle delta on the Z axis feels alright in-game.
It does feel a little hacky though (particularly with the angle wrapping), and does not cover edge cases where the controller is oriented differently, for example on its side.
For now, I am OK with this solution. If someone knows how to improve it, I would love to hear it!
@danielrothmann Hi, I know this post is old but there is a really great DialInteractable script in the VR sample project on Unity Learn, you can get it straight from the Unity Hub