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; m01 = column1; m02 = column2;
m10 = column0; m11 = column1; m12 = column2;
m20 = column0; m21 = column1; m22 = column2;
}

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