5
\$\begingroup\$

I have a 4x4 transformation matrix, made only from successive rotations and translations, and I'd like to "snap" my object to the "nearest" of the 24 orthogonal (multiples of 90 degree) rotations.

I'm not sure how to think of the problem. Round each element of the upper 3x3 matrix to -1, 0, and +1 (cutting at .707)? I am pretty sure that would be too good to be true. Another possibility would be to convert to axis-and-rotation or quaternion, but would that make it simpler somehow? I'm not fluent in those manipulations.

Any guidance most welcome!

\$\endgroup\$
3

2 Answers 2

3
\$\begingroup\$

Not sure if this is the most performant, but it can be visualized easily:

//assuming you're starting with a relatively orthonormal matrix

1.) Take one of your matrix's 3 basis vectors and dot it against the 6 world orthogonal vectors and set it to the one whose result is closest to 1.0.

2.) Do the same for one of the 2 remaining basis vectors.

3.) Cross the two that you just reset to determine and reset the third basis vector.

\$\endgroup\$
7
  • 2
    \$\begingroup\$ You should probably guard against the second vector snapping to the same as the first (or the -ve of the first). This might happen e.g. if the original matrix was a rotation of 45 degrees around an axis. You can avoid this happening by excluding the first "snapped" vector from the candidates for the second one. \$\endgroup\$
    – GuyRT
    Sep 18, 2014 at 21:01
  • \$\begingroup\$ I am not quite convinced that this method will always yield the closest to the 24 candidate matrices in the sense of choosing the one which needs the smallest rotation to arrive at from the original. \$\endgroup\$
    – GuyRT
    Sep 18, 2014 at 21:05
  • \$\begingroup\$ ding! the light is beginning to show. i'll be trying this... (By which I mean, I'll be googling some of the terms next. Basis vector... description sounds to me maybe like, check where (1,0,0) is transformed to, what world orthogonal vector it becomes closest too, where (0,1,0) goes, and what's left for (0,0,1). Is that sort-of on target?) \$\endgroup\$ Sep 18, 2014 at 21:17
  • \$\begingroup\$ @GuyRT i see what you mean, steps as written might favor whichever vector you check first. Possibly check all 3 vectors up front, and sort from there... \$\endgroup\$ Sep 18, 2014 at 21:25
  • \$\begingroup\$ That's what I was thinking, but I'm not sure it matters. I'll +1 this answer because it's pretty easy to implement, intuitively yields the correct result and definitely worth trying. I can think of a method which is more exhaustive, but it's more effort to implement and more costly at runtime. \$\endgroup\$
    – GuyRT
    Sep 18, 2014 at 21:39
4
\$\begingroup\$

For anyone in the future looking for a solution to this using Unity/C#, here is an implementation I made based on Steve's answer:

//takes a rotation and returns the rotation that is the closest with all axes pointing at 90 degree intervals to the identity quaternion
Quaternion snapToNearestRightAngle(Quaternion currentRotation)
{
    Vector3 closestToForward = closestToAxis(currentRotation, Vector3.forward);
    Vector3 closestToUp = closestToAxis(currentRotation, Vector3.up);
    return Quaternion.LookRotation(closestToForward, closestToUp);
}

//finds the axis that is closest to the currentRotations local axis
Vector3 closestToAxis(Quaternion currentRotation, Vector3 axis)
{
    Vector3[] checkAxes = new Vector3[] { Vector3.forward, Vector3.right, Vector3.up, -Vector3.forward, -Vector3.right, -Vector3.up };
    Vector3 closestToAxis = Vector3.forward;
    float highestDot = -1;
    foreach (Vector3 checkAxis in checkAxes)
        check(ref highestDot, ref closestToAxis, currentRotation, axis, checkAxis);
    return closestToAxis;
}

//finds the closest axis to the input rotations specified axis
void check(ref float highestDot, ref Vector3 closest, Quaternion currentRotation, Vector3 axis, Vector3 checkDir)
{
    float dot = Vector3.Dot(currentRotation * axis, checkDir);
    if (dot > highestDot)
    {
        highestDot = dot;
        closest = checkDir;
    }
}

And here is an implementation based on this post which is much more efficient:

//takes a rotation and returns the rotation that is the closest with all axes pointing at 90 degree intervals to the identity quaternion
Quaternion snapToNearestRightAngle(Quaternion currentRotation)
{
    Vector3 closestToForward = SnappedToNearestAxis(currentRotation * Vector3.forward);
    Vector3 closestToUp = SnappedToNearestAxis(currentRotation * Vector3.up);
    return Quaternion.LookRotation(closestToForward, closestToUp);
}

//find the world axis that is closest to direction
Vector3 SnappedToNearestAxis(Vector3 direction)
{
    float x = Mathf.Abs(direction.x);
    float y = Mathf.Abs(direction.y);
    float z = Mathf.Abs(direction.z);
    if (x > y && x > z)
    {
        return new Vector3(Mathf.Sign(direction.x), 0, 0);
    }
    else if (y > x && y > z)
    {
        return new Vector3(0, Mathf.Sign(direction.y), 0);
    }
    else
    {
        return new Vector3(0, 0, Mathf.Sign(direction.z));
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ This second implementation is perfect. Thank you a bunch! \$\endgroup\$ Jan 16, 2022 at 18:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .