Cardinal Rotation III

Without further ado, here’s the Unity implementation of a rotation object.

Implementation

Math Helper

Several math/linear algebra helper methods will be needed, so let’s place these in a utility class:

using UnityEngine;

public static class MathHelper {

	public static int Sign(int x) {
		if (x < 0)
			return -1;

		if (x > 0)
			return 1;

		return 0;
	}

	public static Vector3Int Sign(Vector3Int vector) {
		return new Vector3Int {
			x = Sign(vector.x),
			y = Sign(vector.y),
			z = Sign(vector.z)
		};
	}

	public static Vector3Int Cross(Vector3Int a, Vector3Int b) {
		return new Vector3Int {
			x = a.y * b.z - a.z * b.y,
			y = a.z * b.x - a.x * b.z,
			z = a.x * b.y - a.y * b.x
		};
	}
}

Matrix3x3Int

We need an integer matrix to store $ M $ (see Definition 2.4), but Unity doesn’t provide a such type. Therefore, we must create our own:

using UnityEngine;

public class Matrix3x3Int {
	private int m00, m01, m02;
	private int m10, m11, m12;
	private int m20, m21, m22;

	private Matrix3x3Int() { }

	public Matrix3x3Int(Vector3Int column0, Vector3Int column1, Vector3Int column2) {
		m00 = column0[0]; m01 = column1[0]; m02 = column2[0];
		m10 = column0[1]; m11 = column1[1]; m12 = column2[1];
		m20 = column0[2]; m21 = column1[2]; m22 = column2[2];
	}

	public Vector3Int MultiplyPoint(Vector3Int vector) {
		return new Vector3Int {
			x = m00 * vector.x + m01 * vector.y + m02 * vector.z,
			y = m10 * vector.x + m11 * vector.y + m12 * vector.z,
			z = m20 * vector.x + m21 * vector.y + m22 * vector.z
		};
	}

	public static Matrix3x3Int operator *(Matrix3x3Int a, Matrix3x3Int b) {
		return new Matrix3x3Int {
			m00 = a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20,
			m01 = a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21,
			m02 = a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22,
			m10 = a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20,
			m11 = a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21,
			m12 = a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22,
			m20 = a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20,
			m21 = a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21,
			m22 = a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22
		};
	}
}

Rotation

Lastly, the rotation object. LookRotation methods are provided to easily build Rotation instances:

using UnityEngine;

public class Rotation {
	private Matrix3x3Int matrix;

	private Rotation(Matrix3x3Int matrix) => this.matrix = matrix;

	public static Rotation LookRotation(Vector3Int forward) {
		// Normalize inputs
		Vector3Int direction = MathHelper.Sign(forward);

		// Use "up" as the default "upwards" vector
		Vector3Int upwards = Vector3Int.up;
		// If direction is collinear with "up",
		// we must compute a different "upwards" vector
		if (direction == Vector3.up || direction == Vector3.down)
			upwards = MathHelper.Cross(direction, Vector3Int.right);

		return LookRotation(direction, upwards);
	}

	public static Rotation LookRotation(Vector3Int forward, Vector3Int upwards) {
		// Normalize inputs
		forward = MathHelper.Sign(forward);
		upwards = MathHelper.Sign(upwards);

		// Infer basis from the inputs
		Vector3Int b2 = forward;
		Vector3Int b0 = MathHelper.Cross(upwards, b2);
		b0 = MathHelper.Sign(b0);
		Vector3Int b1 = MathHelper.Cross(b2, b0);
		b1 = MathHelper.Sign(b1);

		return new Rotation(new Matrix3x3Int(b0, b1, b2));
	}

	public static Vector3Int operator *(Rotation rotation, Vector3Int vector) => rotation.matrix.MultiplyPoint(vector);

	public static Rotation operator *(Rotation lhs, Rotation rhs) => new Rotation(lhs.matrix * rhs.matrix);
}

Note: the arguments passed to Rotation.LookRotation must be converted from the integer space $ \mathbb{Z} ^ 3 $ to the sign domain $ \mathbb{S} ^ 3 $.

Usage

If you’ve worked much with Unity Quaternions, the syntax should be very similar. Use the * operator to apply rotations. For example, to rotate an integer vector:

Vector3Int direction = Vector3.back;
Rotation rotation = Rotation.LookRotation(direction);

Vector3Int point = new Vector3Int(10, 20, 30);
Vector3Int rotatedPoint = rotation * point;

To rotate an integer vector with a custom upwards:

Vector3Int direction = Vector3.back;
Vector3Int upwards = Vector3.down;
Rotation rotation = Rotation.LookRotation(direction, upwards);

Vector3Int point = new Vector3Int(10, 20, 30);
Vector3Int rotatedPoint = rotation * point;

To chain rotations:

Rotation a = Rotation.LookRotation(Vector3Int.Left);
Rotation b = Rotation.LookRotaton(Vector3Int.Down);

Rotation c = a * b;

Remember: rotation chaining is noncommutative. In general, a * b yields a different result than b * a.

Conclusion

This concludes the Cardinal Rotation series of posts. You can check out my full “Rotation” suite of files on GitHub.


719 Words

2021-02-10 10:22 +0000