Move matrix code from System.Numerics to OneMath without silently transposing your transforms: the conventions are opposite, and the conversions handle it.
using RecursiveAI.OneMath; float angle = MathF.PI / 2; // System.Numerics (row vectors): T * R translates first, then rotates. var sn = System.Numerics.Matrix4x4.CreateTranslation(10, 0, 0) * System.Numerics.Matrix4x4.CreateRotationY(angle); // OneMath (column vectors): the same transform is R * T. var m = Matrix4x4F.CreateRotationY(angle) * Matrix4x4F.CreateTranslation(new Vector3F(10, 0, 0)); var p = new Vector3F(1, 0, 0); Console.WriteLine($"System.Numerics: {System.Numerics.Vector3.Transform(p.ToSystemNumerics(), sn)}"); Console.WriteLine($"OneMath: {m.TransformPoint(p)}"); // Where the translation lives. Console.WriteLine($"System.Numerics translation M41..M43: {sn.M41}, {sn.M42}, {sn.M43}"); Console.WriteLine($"OneMath translation M14, M24, M34: {m.M14}, {m.M24}, {m.M34}"); // Convert, don't copy fields: both directions transpose. Matrix4x4F same = Matrix4x4F.FromSystemNumerics(sn); Console.WriteLine($"FromSystemNumerics(sn) == m: {same == m}"); Console.WriteLine($"Round trip equal: {Matrix4x4F.FromSystemNumerics(m.ToSystemNumerics()) == m}"); // Copying fields one to one gives the transpose: a different transform. var copied = new Matrix4x4F(sn.M11, sn.M12, sn.M13, sn.M14, sn.M21, sn.M22, sn.M23, sn.M24, sn.M31, sn.M32, sn.M33, sn.M34, sn.M41, sn.M42, sn.M43, sn.M44); Console.WriteLine($"Field copy: {copied.TransformPoint(p)}");
System.Numerics: <-4.808253E-07, 0, -11> OneMath: (-4.808253E-07, 0, -11) System.Numerics translation M41..M43: -4.371139E-07, 0, -10 OneMath translation M14, M24, M34: -4.371139E-07, 0, -10 FromSystemNumerics(sn) == m: True Round trip equal: True Field copy: (-4.371139E-08, 0, 1)
The rule
- OneMath uses column vectors:
m * vtransforms the columnv, a 4x4’s translation sits in column 4 (M14,M24,M34), anda * bappliesbfirst. - System.Numerics uses row vectors: translation in
M41…M43, anda * bappliesafirst. The field namesMrcare the same, which is what makes a field-by-field copy look right and transform wrongly. - So reverse the operand order of every product, and convert with
FromSystemNumericsandToSystemNumerics, which transpose. Both directions round-trip exactly.
Vectors and quaternions convert with the same pair of methods; quaternions keep their components and their Hamilton product. Converting from double to float is only the explicit ToSystemNumerics() and rounds to nearest even.
Good to know
Both libraries give -11 here. The difference is the promise: OneMath’s results are the same bits on every OS, CPU and runtime, and System.Numerics makes no such promise. Keep System.Numerics for rendering if you like, and use OneMath for whatever the simulation, a replay or a lockstep peer has to agree on.