Editor/Inference/SamRotation.cs
// C# adaptation of SAM 3D Body's rotation conventions. See LICENSE-SAM and THIRD_PARTY_NOTICES.md.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Global Euler conversion adapts RoMa/SciPy. See LICENSE-ROMA and LICENSE-SCIPY.
using System;
namespace HumanoidMocapX.Inference;
using Vector3 = System.Numerics.Vector3;
internal static class SamRotation
{
const float NormalizeEpsilon = 1e-12f, CompactSingularity = 1e-6f, GlobalSingularity = 1e-7f;
// SAM's compact angles use cross-product orthogonalization, unlike its global head.
internal static void CompactXYZ(ReadOnlySpan<float> columns, Span<float> angles)
{
var x = Normalize(new(columns[0], columns[1], columns[2]));
var z = Normalize(Vector3.Cross(x, new(columns[3], columns[4], columns[5])));
var y = Vector3.Cross(z, x);
var horizontal = MathF.Sqrt(x.X * x.X + x.Y * x.Y);
angles[0] = horizontal < CompactSingularity ? MathF.Atan2(-z.Y, y.Y) : MathF.Atan2(y.Z, z.Z);
angles[1] = MathF.Atan2(-x.Z, horizontal);
angles[2] = horizontal < CompactSingularity ? 0 : MathF.Atan2(x.Y, x.X);
}
internal static void GlobalZYX(ReadOnlySpan<float> columns, Span<float> angles)
{
var x = Normalize(new(columns[0], columns[1], columns[2]));
var raw = new Vector3(columns[3], columns[4], columns[5]);
var y = Normalize(raw - Vector3.Dot(x, raw) * x); var z = Vector3.Cross(x, y);
Span<float> matrix = stackalloc float[9] { x.X, y.X, z.X, x.Y, y.Y, z.Y, x.Z, y.Z, z.Z };
Euler(matrix, angles, true);
}
internal static void MatrixXYZ(ReadOnlySpan<float> angles, Span<float> matrix)
{
var sx = MathF.Sin(angles[0]); var cx = MathF.Cos(angles[0]);
var sy = MathF.Sin(angles[1]); var cy = MathF.Cos(angles[1]);
var sz = MathF.Sin(angles[2]); var cz = MathF.Cos(angles[2]);
matrix[0] = cy * cz; matrix[1] = sx * sy * cz - cx * sz; matrix[2] = cx * sy * cz + sx * sz;
matrix[3] = cy * sz; matrix[4] = sx * sy * sz + cx * cz; matrix[5] = cx * sy * sz - sx * cz;
matrix[6] = -sy; matrix[7] = sx * cy; matrix[8] = cx * cy;
}
internal static void Euler(ReadOnlySpan<float> matrix, Span<float> angles, bool intrinsicZYX = false)
{
var rotation = Quaternion(matrix);
var a = rotation.W - rotation.Y; var b = rotation.X + rotation.Z;
var c = rotation.Y + rotation.W; var d = rotation.Z - rotation.X;
var middle = 2 * MathF.Atan2(float.Hypot(c, d), float.Hypot(a, b));
var halfSum = MathF.Atan2(b, a); var halfDifference = MathF.Atan2(d, c);
angles[0] = intrinsicZYX ? halfSum + halfDifference : halfSum - halfDifference;
angles[2] = intrinsicZYX ? halfSum - halfDifference : halfSum + halfDifference;
if (MathF.Abs(middle) <= GlobalSingularity || MathF.Abs(middle - MathF.PI) <= GlobalSingularity)
{
angles[2] = 0;
angles[0] = MathF.Abs(middle) <= GlobalSingularity ? 2 * halfSum : 2 * (intrinsicZYX ? 1 : -1) * halfDifference;
}
angles[1] = middle - MathF.PI / 2;
for (var i = 0; i < 3; i++)
{
if (angles[i] < -MathF.PI) angles[i] += 2 * MathF.PI;
if (angles[i] > MathF.PI) angles[i] -= 2 * MathF.PI;
}
}
static System.Numerics.Quaternion Quaternion(ReadOnlySpan<float> m)
{
Span<float> diagonal = stackalloc float[4] { 1 + m[0] - m[4] - m[8], 1 + m[4] - m[8] - m[0],
1 + m[8] - m[0] - m[4], 1 + m[0] + m[4] + m[8] };
var largest = 0;
for (var i = 1; i < 4; i++) if (diagonal[i] > diagonal[largest]) largest = i;
var value = largest switch
{
0 => new System.Numerics.Quaternion(diagonal[0], m[1] + m[3], m[2] + m[6], m[7] - m[5]),
1 => new(m[1] + m[3], diagonal[1], m[5] + m[7], m[2] - m[6]),
2 => new(m[2] + m[6], m[5] + m[7], diagonal[2], m[3] - m[1]),
_ => new(m[7] - m[5], m[2] - m[6], m[3] - m[1], diagonal[3])
};
return System.Numerics.Quaternion.Normalize(value);
}
static Vector3 Normalize(Vector3 value) => value / MathF.Max(NormalizeEpsilon, value.Length());
}