This is almost more a geometry/mathematics question than a game dev one, but I think I'll have a better chance actually understanding the answer if I ask here. Rather than try to explain it- because it's hard enough for me to wrap my head around it- I've put together a model to show my problem.

The Object

Model reference image

We have, basically, a rectangle. The four cubes on the end are to emphasize the corners visually. This all moves as one rigid object. This object has two axes running through it, shown as yellow and blue respectively, with each axis passing through two of the corners, and both intersecting in the center of the object. (You may need to expand the image to see the lines, because they're thin.)

The Goal

The goal is to move one of these axes into a desired position, solely by rotating the object around the other axis. That might sound simple on the surface, but it's complicated by two factors:

  1. The two axes are not orthogonal (they don't meet at right angles).
  2. The desired position of one axis is not relative to the other axis, but to something external.

For the purpose of this question, the yellow axis will be the one we're trying to get into position, and the blue axis will be the one around which the object rotates.

The "Desired Position"

The "desired position" of the yellow axis is an angle relative to the horizon. It's not a specific location, just an angle relative to the horizon. But it's specifically relative to where the horizon plane intersects with the plane of the axis being aligned. In case that sounds confusing (it does to me), here's another image to help:

Yellow axis plane

The yellow disc here shows the area that the yellow axis would rotate through if it were rotated around an orthogonal axis. The black line, marked with 0, shows the spot in that arc where the yellow axis would be level (even with the horizon). That's the line that the angle is measured relative to. The white arc and the white number show the current angle relative to that line.

The Rotaton

Here's what rotating around the blue axis looks like: Rotation

The two corners that the blue axis pass through stay in place, while the other corners move. You'll notice the black target line also moves. This is because is it relative to the yellow axis, and just like the yellow axis, it isn't orthogonal to the blue axis. It moves to stay within the arc of the yellow axis, while keeping at horizon level.

The Question

The question itself is somewhat simple if you can wrap your head around the above. Given a target angle for yellow, how many degrees around the blue axis do I need to rotate the object in order to achieve that angle?

The Code

To any brave, helpful souls willing to attempt a solution, I'm including here the code I used to create these images. This is the code that calculates the axes based of the positions of the four cubes, draws the lines and disks, and calculates the angles. The setup of the scene is very simple and should be understandable from the images provided, but do ask if you're unsure.

using UnityEditor;
using UnityEngine;

public class QuadFootAngleTester : MonoBehaviour
    public Transform FL, FR, RL, RR;
    public bool ShowFrontAngle;
    public RotationSide ActiveRotationAxisByFront;
    public float RotationVelocity;

    private void Update()
        Vector3 axis;
        if (ActiveRotationAxisByFront == RotationSide.Left)
            axis = RR.position - FL.position;
        else if (ActiveRotationAxisByFront == RotationSide.Right)
            axis = RL.position - FR.position;

        transform.Rotate(axis, RotationVelocity * Time.deltaTime, Space.World);

    private void OnDrawGizmos()
        gizmosForPair(FL, RR, Color.yellow, false, true);
        //discForPair(FR, RL, Color.blue);
        gizmosForPair(FR, RL, Color.blue, true, false);

        if (ShowFrontAngle)
            var frontPOS = Shared.FindAvergeCenter(new Vector3[] { FL.position, FR.position });
            var av = frontPOS - transform.position;
            var fv = av.Level();
            Handles.Label(frontPOS, (Vector3.Angle(fv, av) * Mathf.Sign(av.y)).ToString());


    private void gizmosForPair(Transform front, Transform rear, Color color, bool axisOnly = false, bool rotationPathDisc = false, bool rotationHandle = false)
        var v = front.position - transform.position;
        var side = Vector3.Cross(v, transform.up);
        var fwd = Vector3.Cross(v, side);
        var flat = Vector3.Cross(Vector3.up, side);
        var angle = Vector3.Angle(v, flat) * Mathf.Sign(v.y);

        Gizmos.color = color;
        Gizmos.DrawLine(rear.position, front.position);

        if (!axisOnly)
            Gizmos.DrawRay(transform.position, side);
            Gizmos.DrawRay(transform.position, fwd);

            Gizmos.color = Color.black;
            Gizmos.DrawRay(transform.position, flat);

            Handles.Label(transform.position + flat, flat.y.ToString());
            Handles.Label(front.position + (Vector3.down * .25F), angle.ToString());
            Handles.zTest = UnityEngine.Rendering.CompareFunction.Less;
            Handles.DrawSolidArc(transform.position, side, flat, angle, v.magnitude / 2);

        if (rotationPathDisc)
            var oc = Handles.color;
            var nc = color;
            nc.a = .25F;
            Handles.color = nc;
            Handles.DrawSolidDisc(transform.position, side, v.magnitude);
            Handles.color = oc;

        if (rotationHandle)
            Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
            Handles.RotationHandle(Quaternion.LookRotation(v, transform.up), transform.position);

    private void discForPair(Transform front, Transform rear, Color baseColor)
        var n = rear.position - front.position;
        var oc = Handles.color;
        var nc = baseColor;
        nc.a = .25F;
        Handles.color = nc;
        Handles.DrawSolidDisc(transform.position, n, n.magnitude / 2);
        Handles.color = oc;

    public enum RotationSide

public static class Ext
    /// <summary>
    /// Returns a copy of <paramref name="v"/> with <see cref="Vector3.y"/> set to 0.
    /// </summary>
    public static Vector3 Level(this Vector3 v)
        return new Vector3(v.x, 0, v.z);


You must log in to answer this question.

Browse other questions tagged .