how to do Quaternion.RotateTowards on single LOCAL axis?

57026-untitled2.png

I’ve found several solutions for achieving this goal when working from global axises (I hope that’s spelled right). But have a look at my picture and you’ll see that this wont work for me :\

I want that red character rotate towards the blue one back there. But I don’t want him to fall over or do other wiered stuff because of my FauxGravity to the sphere. (the FauxGravity script I use forces every linked body to rotate so it stands on the surface corectly)

EDIT: I visualized what i want to happen in a small clip:
http://gfycat.com/SingleSparseBarracuda

EDIT: Fixed some issues and reduced the math a bit.

There may be other ways to solve it but my math is a bit rusty. However, one approach would be something like this. Given the two dudes A and B (red and blue. image 1), we can make a vector C (green, image 2) which is A - B.

We can project C onto the plane of B to get a point D (pink, image 4).

This D point is in world space. It would be nice if it was in local space to B where rotation can be performed along Y axis, so let’s convert it (square, image 5). Now we can ignore Y and work with X and Z. From X, Z we can use simple trig like Mathf.Atan2 to get an angle. Now we have a single angle that defines Y rotation, so all we need to do is keep moving an angle toward the destination angle.

From this angle, we can make a quaternion that is a Y rotation.

Then we can apply the Y rotation to your existing rotation, to preserve your plants Up direction.

This all sounds crazy and stuff with math and ideas, so how do you do this in code?

Vector3 LookatXZ()
{
    Vector3 distance = lookat.position - transform.position;
    Vector3 direction = Vector3.ProjectOnPlane(distance, transform.up).normalized;
    return transform.InverseTransformVector(direction);
}

LookatXZ will perform the steps to get the direction in local space as described above. We can ignore the Y component and focus on X,Z, which will be the direction toward the target on our plane.

private static float AngleXZ(Vector3 direction)
{
    return Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
}

We can then take that direction and use Atan2 to get radians that describe our rotation. But quaterion methods expect degrees, not radians, so I convert the radians to degrees with Rad2Deg. What we get back now is an angle 0-360 degrees, which is the Y rotation that should be applied.

If we want to look toward a target gradually, we can use Mathf.MoveTowardsAngle which will take care of wrapping negative values for us (-180 is the same as 180 etc). Finally, we can create the quaternion based on the Y axis.

void LateUpdate()
{
    if (!lookat)
        return;

    float forwardAngle = AngleXZ(ForwardXZ());
    float lookatAngle = AngleXZ(LookatXZ());
    float speed = degreesPerSecond * Time.deltaTime;
    float angle = Mathf.MoveTowardsAngle(forwardAngle, lookatAngle, speed);
    transform.rotation *= Quaternion.AngleAxis(angle, Vector3.up);
}

I’ve got an example project (based on your previous question, but I disabled the line renderer) so you can test it out. Code file is linked too if you just want to set it up on your end. Note that rotation is not interactive in scene view in the example project. You have to press play and then drag the “Drag Me” around in the scene view. Both of them should be trying to look at each other.

Dragging is a little buggy so I advise you to change from local transform handles to world transform handles (toggle with X key)

EDIT: Updated links to point to new version with fixes.

Example project

SphericalLookTowards.cs