Recursive AI

Vector and quaternion math in C#

All onemath recipes

OneMath has Vector2/3/4, Matrix3x3/4x4 and Quaternion types in float (F) and double (D), with the operations you expect and the same results on every machine. The program below takes a ship’s heading, turns it with a quaternion, blends two orientations and moves a point into world space.

Terminal
dotnet new console -o VectorMath && cd VectorMath
dotnet add package RecursiveAI.OneMath --version 1.0.0

Put this in Program.cs and run dotnet run:

C#
using RecursiveAI.OneMath;
 
var forward = new Vector3F(0, 0, 1);
var toTarget = new Vector3F(3, 0, 4);
 
// Vectors: length, normalize, dot and cross products, distance.
Vector3F dir = toTarget.Normalized();
Console.WriteLine($"length {toTarget.Length()}, direction {dir}");
Console.WriteLine($"dot {Vector3F.Dot(forward, dir)}");
Console.WriteLine($"cross {Vector3F.Cross(forward, dir)}");
Console.WriteLine($"distance {Vector3F.Distance(Vector3F.Zero, toTarget)}");
 
// Quaternions: turn 90 degrees about the up axis (counterclockwise seen from above).
QuaternionF turn = QuaternionF.CreateFromAxisAngle(Vector3F.UnitY, MathF.PI / 2);
Console.WriteLine($"forward turned: {turn.Rotate(forward)}");
 
// Blend two orientations: halfway along the shortest arc.
QuaternionF half = QuaternionF.Slerp(QuaternionF.Identity, turn, 0.5f);
Console.WriteLine($"halfway: {half.Rotate(forward)}");
 
// Combine rotations: a * b applies b first, then a.
QuaternionF twice = turn * turn;
Console.WriteLine($"turned twice: {twice.Rotate(forward)}");
 
// Matrices: rotate, then move 10 along x. Column vectors, so read right to left.
Matrix4x4F world = Matrix4x4F.CreateTranslation(new Vector3F(10, 0, 0))
                 * Matrix4x4F.CreateFromQuaternion(turn);
Console.WriteLine($"local (0, 0, 1) in world: {world.TransformPoint(forward)}");
Text
length 5, direction (0.6, 0, 0.8)
dot 0.8
cross (0, 0.6, 0)
distance 5
forward turned: (0.99999994, 0, 5.9604645E-08)
halfway: (0.70710695, 0, 0.7071067)
turned twice: (0, 0, -0.99999976)
local (0, 0, 1) in world: (11, 0, 5.9604645E-08)

0.99999994 and 5.9604645E-08 instead of 1 and 0 are float rounding: π/2 isn’t exact in a float, and neither are its sine and cosine. What OneMath promises is that every machine prints these same values.

The types

  • Vectors: Vector2F, Vector3F, Vector4F and their D forms, with + - *, Dot, Cross, Length, Normalized, Distance, Lerp, Min, Max and Abs.
  • Quaternions: CreateFromAxisAngle (the axis must be a unit vector), Rotate, Slerp, NLerp, Conjugated, Normalized and the Hamilton product a * b.
  • Matrices: Matrix2x2, Matrix3x3, Matrix4x4, with CreateTranslation, CreateRotationX/Y/Z, CreateFromQuaternion, TransformPoint and TransformVector; also Affine3, RigidTransform and Bounds2/3.
  • ToSystemNumerics() and FromSystemNumerics() convert to and from System.Numerics, for handing results to a renderer.

Limits

  • Column vectors. A * B applies B first, the opposite of System.Numerics and MonoGame. Moving existing code: Port System.Numerics code to OneMath.
  • Exact equality is exact. == compares bits; use a tolerance when comparing results that went through different operations.

Why the results match across machines, and how to check it: Deterministic floating-point math in C#.