using System.Collections; using NUnit.Framework; using Speckle.DoubleNumerics; using Speckle.Objects.Other; using Speckle.Sdk.Common; namespace Speckle.Objects.Tests.Unit.Geometry; [TestFixture, TestOf(typeof(Transform))] public class TransformTests { private const float FLOAT_TOLERANCE = 1e-6f; [Test, TestCaseSource(nameof(TransformTestCases))] public void ArrayBackAndForth(Matrix4x4 data) { var start = new Transform() { matrix = data, units = Units.None }; var asArr = Transform.CreateMatrix(start.ToArray()); var end = new Transform() { matrix = asArr, units = Units.None }; Assert.That(end.matrix, Is.EqualTo(data)); } [Test, TestCaseSource(nameof(TransformTestCases))] public void ConvertToUnits(Matrix4x4 data) { const float SF = 1000f; var transpose = Matrix4x4.Transpose(data); //NOTE: Transform expects matrices transposed (translation in column 4) var mm = Matrix4x4.Transpose( Transform.CreateMatrix( new Transform() { matrix = transpose, units = Units.Meters }.ConvertToUnits(Units.Millimeters) ) ); Matrix4x4.Decompose(data, out var ms, out var mr, out var mt); Matrix4x4.Decompose(mm, out var mms, out var mmr, out var mmt); Assert.Multiple(() => { Assert.That(mms.X, Is.EqualTo(ms.X).Within(FLOAT_TOLERANCE), "Expect scale x to be unchanged"); Assert.That(mms.Y, Is.EqualTo(ms.Y).Within(FLOAT_TOLERANCE), "Expect scale y to be unchanged"); Assert.That(mms.Z, Is.EqualTo(ms.Z).Within(FLOAT_TOLERANCE), "Expect scale z to be unchanged"); Assert.That(Quaternion.Dot(mr, mmr), Is.LessThan(1).Within(FLOAT_TOLERANCE), "Expect rot x to be equivalent"); Assert.That(mmt.X, Is.EqualTo(mt.X * SF).Within(FLOAT_TOLERANCE), $"Expect translation x to be scaled by {SF}"); Assert.That(mmt.Y, Is.EqualTo(mt.Y * SF).Within(FLOAT_TOLERANCE), $"Expect translation y to be scaled by {SF}"); Assert.That(mmt.Z, Is.EqualTo(mt.Z * SF).Within(FLOAT_TOLERANCE), $"Expect translation z to be scaled by {SF}"); }); } /// /// Set of TRS transforms (row dominant i.e. translation in row 4) /// All with non-negative scale and rotation (for ease of testing scale and rot independently) /// /// private static IEnumerable TransformTestCases() { var t = new Vector3(128.128f, 255.255f, 512.512f); var r = Quaternion.CreateFromYawPitchRoll(1.9f, 0.6666667f, 0.5f); var s = new Vector3(123f, 32f, 0.5f); yield return new TestCaseData(Matrix4x4.Identity).SetName("{m} Identity Matrix"); yield return new TestCaseData(Matrix4x4.CreateTranslation(t)).SetName("{m} Translation Only (positive)"); yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitX)).SetName("{m} Translation Only -X"); yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitY)).SetName("{m} Translation Only -Y"); yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitZ)).SetName("{m} Translation Only -Z"); yield return new TestCaseData(Matrix4x4.CreateTranslation(-t)).SetName("{m} Translation Only -XYZ "); yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.0f, 0.0f)).SetName("{m} Rotation Only X "); yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.5f, 0.0f)).SetName("{m} Rotation Only Y "); yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.0f, 0.5f)).SetName("{m} Rotation Only Z "); yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.5f, 0.5f)).SetName("{m} Rotation Only XYZ "); yield return new TestCaseData(Matrix4x4.CreateFromQuaternion(r)).SetName("{m} Rotation Only"); yield return new TestCaseData(Matrix4x4.Identity + Matrix4x4.CreateScale(s)).SetName("{m} Scale Only"); yield return new TestCaseData(Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r)).SetName( "{m} Translation + Rotation" ); yield return new TestCaseData( Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(s) ).SetName("{m} Translation + Rotation + Scale"); yield return new TestCaseData( Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(-s) ).SetName("{m} Translation + Rotation + -Scale"); } }