diff --git a/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs b/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs index 698b6898f..d92808d9b 100644 --- a/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs +++ b/Assets/UniGLTF/Runtime/Extensions/UnityExtensions.cs @@ -180,6 +180,55 @@ namespace UniGLTF return scale; } + public static bool Nearly(in Matrix4x4 lhs, in Matrix4x4 rhs, float epsilon = 1e-3f) + { + if (Mathf.Abs(lhs.m00 - rhs.m00) > epsilon) return false; + if (Mathf.Abs(lhs.m01 - rhs.m01) > epsilon) return false; + if (Mathf.Abs(lhs.m02 - rhs.m02) > epsilon) return false; + if (Mathf.Abs(lhs.m03 - rhs.m03) > epsilon) return false; + if (Mathf.Abs(lhs.m10 - rhs.m10) > epsilon) return false; + if (Mathf.Abs(lhs.m11 - rhs.m11) > epsilon) return false; + if (Mathf.Abs(lhs.m12 - rhs.m12) > epsilon) return false; + if (Mathf.Abs(lhs.m13 - rhs.m13) > epsilon) return false; + if (Mathf.Abs(lhs.m20 - rhs.m20) > epsilon) return false; + if (Mathf.Abs(lhs.m21 - rhs.m21) > epsilon) return false; + if (Mathf.Abs(lhs.m22 - rhs.m22) > epsilon) return false; + if (Mathf.Abs(lhs.m23 - rhs.m23) > epsilon) return false; + if (Mathf.Abs(lhs.m30 - rhs.m30) > epsilon) return false; + if (Mathf.Abs(lhs.m31 - rhs.m31) > epsilon) return false; + if (Mathf.Abs(lhs.m32 - rhs.m32) > epsilon) return false; + if (Mathf.Abs(lhs.m33 - rhs.m33) > epsilon) return false; + return true; + } + + public static (Vector3 T, Quaternion R, Vector3 S) Extract(this Matrix4x4 m) + { + if (m.determinant < 0) + { + // ミラーリングを試行する + + // -X + { + var mm = m * Matrix4x4.Scale(new Vector3(-1, 1, 1)); + var ss = mm.ExtractScale(); + mm = mm * Matrix4x4.Scale(new Vector3(1 / ss.x, 1 / ss.y, 1 / ss.z)); + var tt = mm.ExtractPosition(); + var rr = mm.ExtractRotation(); + ss.x = -ss.x; + var mmm = Matrix4x4.TRS(tt, rr, ss); + if (Nearly(m, mmm)) + { + return (tt, rr, ss); + } + } + } + + var s = m.ExtractScale(); + var t = m.ExtractPosition(); + var r = m.ExtractRotation(); + return (t, r, s); + } + public static string RelativePathFrom(this Transform self, Transform root) { var path = new List(); diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/NodeImporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/NodeImporter.cs index 41a143d38..e511bca81 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/NodeImporter.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/NodeImporter.cs @@ -50,9 +50,7 @@ namespace UniGLTF if (node.matrix != null && node.matrix.Length > 0) { var m = UnityExtensions.MatrixFromArray(node.matrix); - go.transform.localRotation = m.ExtractRotation(); - go.transform.localPosition = m.ExtractPosition(); - go.transform.localScale = m.ExtractScale(); + (go.transform.localPosition, go.transform.localRotation, go.transform.localScale) = m.Extract(); } return go; } @@ -158,7 +156,7 @@ namespace UniGLTF } } - public static void SetupSkinning(glTF gltf, List nodes, int i, IAxisInverter inverter) + public static void SetupSkinning(glTF gltf, List nodes, int i, IAxisInverter inverter) { var x = nodes[i]; var skinnedMeshRenderer = x.Transform.GetComponent(); diff --git a/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs b/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs new file mode 100644 index 000000000..0b9f01b12 --- /dev/null +++ b/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; +using UnityEngine; + +namespace UniGLTF +{ + public class MatrixTests + { + static void AssertFloat(float expected, float value, string template, float epsilon = 1e-5f) + { + Assert.True(Mathf.Abs(value - expected) < epsilon, string.Format(template, expected, value)); + } + + static void AssertQuaternion(in Quaternion expected, in Quaternion value, string message) + { + AssertFloat(expected.x, value.x, $"{message}" + ".X: {0} != {1}"); + AssertFloat(expected.y, value.y, $"{message}" + ".Y: {0} != {1}"); + AssertFloat(expected.z, value.z, $"{message}" + ".Z: {0} != {1}"); + AssertFloat(expected.w, value.w, $"{message}" + ".W: {0} != {1}"); + } + + static void AssertVec3(in Vector3 expected, in Vector3 value, string message) + { + AssertFloat(expected.x, value.x, $"{message}" + ".X: {0} != {1}"); + AssertFloat(expected.y, value.y, $"{message}" + ".Y: {0} != {1}"); + AssertFloat(expected.z, value.z, $"{message}" + ".Z: {0} != {1}"); + } + + static void AssertExtract(in Vector3 t, in Quaternion r, in Vector3 s) + { + var m = Matrix4x4.TRS(t, r, s); + var (et, er, es) = m.Extract(); + AssertVec3(t, et, "T"); + AssertQuaternion(r, er, "R"); + AssertVec3(s, es, "S"); + } + + [Test] + public void ExtractTest() + { + AssertExtract(new Vector3(1, 2, 3), Quaternion.Euler(0, 90, -90), new Vector3(1, 2, 3)); + } + + [Test] + public void ExtractMirror_MinusX() + { + AssertExtract(new Vector3(1, 2, 3), Quaternion.Euler(0, 90, -90), new Vector3(-1, 2, 3)); + } + +#if false + // TODO: + [Test] + public void ExtractMirror_MinusY() + { + + AssertExtract(new Vector3(1, 2, 3), Quaternion.Euler(0, 90, -90), new Vector3(1, -2, 3)); + } + + [Test] + public void ExtractMirror_MinusZ() + { + AssertExtract(new Vector3(1, 2, 3), Quaternion.Euler(0, 90, -90), new Vector3(1, 2, -3)); + } + + [Test] + public void ExtractMirror_MinusXYZ() + { + AssertExtract(new Vector3(1, 2, 3), Quaternion.Euler(0, 90, -90), new Vector3(-1, -2, -3)); + } +#endif + } +} diff --git a/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs.meta b/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs.meta new file mode 100644 index 000000000..c72d38dd8 --- /dev/null +++ b/Assets/UniGLTF/Tests/UniGLTF/MatrixTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6f1c95a9487fb44fb4a366eda22bd02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: