I'm trying to simulate knobs in a virtual reality environment where the user can grab a knob and twist it with the controller. Essentially what I'm trying to do is translate the global rotation of the controller into an angle about the normal axis of the knob. I've got it working when the normal of the knob is along the z axis, as I can simply read the z rotation of the controller, but things get dicey on other axes. Part of the issue is that there are multiple Vector3 rotations to express any given orientation and the system will jump between them at spots. I'm really at a loss here, if anyone could point me in the right direction I'd be very grateful.
Let's construct a coordinate frame oriented along the axis of the knob:
Quaternion knobToWorld = Quaternion.LookRotation(knobAxis, knobZero);
...where "knobAxis" is the world space axis of rotation of the knob, pointing into the surface it's mounded on, and
knobZero is the direction of some reference point in the knob's range - like where the knob's default / zero setting should sit.
We can then take an arbitrary direction and map it into "knob space" with this:
Quaternion worldToKnob = Quaternion.Inverse(knobToWorld);
Vector3 directionInKnobSpace = worldToKnob * inputDirection;
This transforms a vector into one whose xy components correspond to the vector's projection into the knob's rotation plane.
Now you can take some direction from the controller, like its up vector, and project it into knob space. All that really matters here is that you pick a direction that won't be held parallel to the rotation axis. You can then find this vector's rotation clockwise from the direction you designated as "knob zero" with:
float angleFromZero = Mathf.Atan2(directionInKnobSpace.x, directionInKnobSpace.y) * Mathf.Rad2Deg;
You can record this angle when the user first grabs the knob, and then compute the angle difference on any subsequent frame to determine how far the knob has been twisted.