Align up direction with normal while retaining look direction

I’m testing a small prototype where player (first person controller) can stick itself to different surfaces (similar to manifold garden / outer wilds gravity crystalls). After a quick google search, I decided to use Quaternion.FromToRotation(transform.up, normal) * transform.rotation with Quaterion.Slerp to smooth out the rotation. But it seems like FromToRotation can sometimes flip the player depending on the normal direction. What is the right way to compute this kind of rotation?
I’ve also attached a picture showing the issue.

I think your quaternion composition may be backwards?

I think it should be:

Quaternion targetRotation = transform.rotation * Quaternion.FromToRotation(transform.up, normal);

Because you’re starting at the object’s current rotation and adding the rotation that it takes to go from the current up direction to the surface normal direction.

Hmm, it works better now, but it still creates some weird rotations when you are not looking directly at the wall. I guess I should be using transform.forward somewhere as transform.up doesn’t provide enough info for Quaternion.FromToRotation, because I need to align both transform.up and transform.right/forward with the wall orientation.

What do you expect the new forward direction to be? Probably you can project the current forward direction on the wall to keep somewhat the same looking direction

// We need to check this cause we can't project a zero vector, that is when we're looking straight at the wall
bool areParallel = Mathf.Approximately(Mathf.Abs(Vector3.Dot(transform.forward, wallNormal)), 1f);
// If they are parallel we need to specify some arbitrary forward vector
Vector3 newForward = areParallel ? Vector3.up : Vector3.ProjectOnPlane(transform.forward, wallNormal).normalized;
Quaternion newRotation = Quaternion.LookRotation(newForward, wallNormal);
3 Likes

Thank you! It works great now. I had to set a lower threshold for parallelism check (about 0.95), as Mathf.Approximately was still giving false for really close numbers and also replaced Vector3.up with transform.up as player could be hanging upside down (while still looking directly at the wall). If you are curious, here is how it works now

2 Likes