Browse Source

Improving Matrix error check.

Vicente Penades 3 years ago
parent
commit
3a7aec1d31

+ 3 - 48
src/Shared/_Extensions.cs

@@ -180,56 +180,11 @@ namespace SharpGLTF
             if (!Matrix4x4.Invert(src, out Matrix4x4 dst)) Guard.IsTrue(false, nameof(src), "Matrix cannot be inverted.");
 
             return dst;
-        }
-
-        [Flags]
-        internal enum MatrixCheck
-        {
-            Finite = 0,
-            NonZero = 1,
-            Identity = 2,
-            IdentityColumn4 = 4,
-            Invertible = 8,
-            Decomposable = 16,
-            PositiveDeterminant = 32,
-
-            LocalTransform = Invertible | Decomposable | IdentityColumn4,
-
-            /// <remarks>
-            /// A world matrix can be built from a concatenation of local tranforms.<br/>
-            /// Which means it can be a squeezed matrix, and not decomposable.
-            /// </remarks>
-            WorldTransform = Invertible | IdentityColumn4,
-
-            /// <summary>
-            /// Since an inverse bind matrix is built from the inverse of a WorldMatrix,
-            /// the same rules apply.
-            /// </summary>
-            InverseBindMatrix = Invertible | IdentityColumn4
-        }
+        }        
 
-        internal static bool IsValid(this in Matrix4x4 matrix, MatrixCheck check, float tolerance = 0)
+        internal static bool IsValid(this in Matrix4x4 matrix, Transforms.Matrix4x4Factory.MatrixCheck check, float tolerance = 0)
         {
-            if (!matrix._IsFinite()) return false;
-
-            if (check.HasFlag(MatrixCheck.NonZero) && matrix == default) return false;
-            if (check.HasFlag(MatrixCheck.Identity) && matrix != Matrix4x4.Identity) return false;
-            if (check.HasFlag(MatrixCheck.IdentityColumn4))
-            {
-                // Acording to gltf schema
-                // "The fourth row of each matrix MUST be set to [0.0, 0.0, 0.0, 1.0]."
-                // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview
-                if (matrix.M14 != 0) return false;
-                if (matrix.M24 != 0) return false;
-                if (matrix.M34 != 0) return false;
-                if (Math.Abs(matrix.M44 - 1) > tolerance) return false;
-            }
-
-            if (check.HasFlag(MatrixCheck.Invertible) && !Matrix4x4.Invert(matrix, out _)) return false;
-            if (check.HasFlag(MatrixCheck.Decomposable) && !Matrix4x4.Decompose(matrix, out _, out _, out _)) return false;
-            if (check.HasFlag(MatrixCheck.PositiveDeterminant) && matrix.GetDeterminant() <= 0) return false;
-
-            return true;
+            return Transforms.Matrix4x4Factory.IsValid(matrix, check, tolerance);
         }
 
         #endregion

+ 50 - 46
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -188,67 +188,41 @@ namespace SharpGLTF.Schema2
         #pragma warning disable CA1721 // Property names should not match get methods
 
         /// <summary>
-        /// Gets or sets the local Scale, Rotation and Translation of this <see cref="Node"/>.
+        /// Gets or sets the world <see cref="Matrix4x4"/> of this <see cref="Node"/>.
         /// </summary>
-        public TRANSFORM LocalTransform
+        public Matrix4x4 WorldMatrix
         {
-            get => TRANSFORM.CreateFromAny(_matrix, _scale, _rotation, _translation);
+            get
+            {
+                var vs = VisualParent;
+                return vs == null ? LocalMatrix : Transforms.Matrix4x4Factory.LocalToWorld(vs.WorldMatrix, LocalMatrix);
+            }
             set
             {
-                Guard.IsFalse(this._skin.HasValue, _NOTRANSFORMMESSAGE);
-                Guard.IsTrue(value.IsValid, nameof(value));
+                Transforms.Matrix4x4Factory.GuardMatrix(nameof(value), value, Transforms.Matrix4x4Factory.MatrixCheck.WorldTransform);
 
-                if (value.IsMatrix && this.IsTransformAnimated)
-                {
-                    value = value.GetDecomposed(); // animated nodes require a decomposed transform.
-                }
-
-                if (value.IsMatrix)
-                {
-                    _matrix = value.Matrix.AsNullable(Matrix4x4.Identity);
-                    _scale = null;
-                    _rotation = null;
-                    _translation = null;
-                }
-                else if (value.IsSRT)
-                {
-                    _matrix = null;
-                    _scale = value.Scale.AsNullable(Vector3.One);
-                    _rotation = value.Rotation.Sanitized().AsNullable(Quaternion.Identity);
-                    _translation = value.Translation.AsNullable(Vector3.Zero);
-                }
-                else
-                {
-                    throw new ArgumentException("Undefined", nameof(value));
-                }
+                var vs = VisualParent;
+                LocalMatrix = vs == null ? value : Transforms.Matrix4x4Factory.WorldToLocal(vs.WorldMatrix, value);
             }
         }
 
         /// <summary>
-        /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
+        /// Gets or sets the local Scale, Rotation and Translation of this <see cref="Node"/>.
         /// </summary>
-        public Matrix4x4 LocalMatrix
+        public TRANSFORM LocalTransform
         {
-            get => LocalTransform.Matrix;
-            set => LocalTransform = value;
+            get => TRANSFORM.CreateFromAny(_matrix, _scale, _rotation, _translation);
+            set => _SetLocalTransform(value);
         }
 
         /// <summary>
-        /// Gets or sets the world <see cref="Matrix4x4"/> of this <see cref="Node"/>.
+        /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
         /// </summary>
-        public Matrix4x4 WorldMatrix
+        public Matrix4x4 LocalMatrix
         {
-            get
-            {
-                var vs = VisualParent;
-                return vs == null ? LocalMatrix : Transforms.Matrix4x4Factory.LocalToWorld(vs.WorldMatrix, LocalMatrix);
-            }
-            set
-            {
-                var vs = VisualParent;
-                LocalMatrix = vs == null ? value : Transforms.Matrix4x4Factory.WorldToLocal(vs.WorldMatrix, value);
-            }
-        }
+            get => LocalTransform.Matrix;
+            set => LocalTransform = value;
+        }        
 
         /// <summary>
         /// Gets the local <see cref="Transforms.Matrix4x4Double"/> of this <see cref="Node"/>.
@@ -536,6 +510,36 @@ namespace SharpGLTF.Schema2
             return new NodeCurveSamplers(this, animation);
         }
 
+        private void _SetLocalTransform(TRANSFORM value)
+        {
+            Guard.IsFalse(this._skin.HasValue, _NOTRANSFORMMESSAGE);
+            Guard.IsTrue(value.IsValid, nameof(value));
+
+            if (value.IsMatrix && this.IsTransformAnimated)
+            {
+                value = value.GetDecomposed(); // animated nodes require a decomposed transform.
+            }
+
+            if (value.IsMatrix)
+            {
+                _matrix = value.Matrix.AsNullable(Matrix4x4.Identity);
+                _scale = null;
+                _rotation = null;
+                _translation = null;
+            }
+            else if (value.IsSRT)
+            {
+                _matrix = null;
+                _scale = value.Scale.AsNullable(Vector3.One);
+                _rotation = value.Rotation.Sanitized().AsNullable(Quaternion.Identity);
+                _translation = value.Translation.AsNullable(Vector3.Zero);
+            }
+            else
+            {
+                throw new ArgumentException("Undefined", nameof(value));
+            }
+        }
+
         #endregion
 
         #region validation
@@ -693,7 +697,7 @@ namespace SharpGLTF.Schema2
             // TODO: nameless nodes with decomposed transform
             // could be considered intrinsic.
 
-            Guard.IsTrue(basisTransform.IsValid(_Extensions.MatrixCheck.WorldTransform), nameof(basisTransform));
+            Transforms.Matrix4x4Factory.GuardMatrix(nameof(basisTransform), basisTransform, Transforms.Matrix4x4Factory.MatrixCheck.WorldTransform);
 
             // gather all root nodes:
             var rootNodes = this.LogicalNodes

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -127,7 +127,7 @@ namespace SharpGLTF.Schema2
             {
                 var ibm = joints[i].InverseBindMatrix;
 
-                Guard.IsTrue(ibm.IsValid(_Extensions.MatrixCheck.InverseBindMatrix, 0.01f), nameof(joints));
+                Transforms.Matrix4x4Factory.GuardMatrix($"{nameof(joints)}[{i}]", ibm, Transforms.Matrix4x4Factory.MatrixCheck.InverseBindMatrix, 0.01f);                
 
                 // fourth column (row in schema) is within tolerance
                 // so we can enforce exact values,

+ 55 - 50
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -178,11 +178,7 @@ namespace SharpGLTF.Transforms
 
         public AffineTransform(Matrix4x4 matrix)
         {
-            Guard.IsTrue(matrix._IsFinite(), nameof(matrix));
-            Guard.MustBeEqualTo(matrix.M14, 0, nameof(matrix.M14));
-            Guard.MustBeEqualTo(matrix.M24, 0, nameof(matrix.M24));
-            Guard.MustBeEqualTo(matrix.M34, 0, nameof(matrix.M34));
-            Guard.MustBeEqualTo(matrix.M44, 1, nameof(matrix.M44));
+            Matrix4x4Factory.GuardMatrix(nameof(matrix), matrix, Matrix4x4Factory.MatrixCheck.WorldTransform);
 
             _Representation = DATA_MAT;
 
@@ -470,51 +466,12 @@ namespace SharpGLTF.Transforms
         #endregion
 
         #region API
-
-        private void _VerifyDefined()
-        {
-            if (_Representation == DATA_UNDEFINED) throw new InvalidOperationException("Undefined");
-        }
-
-        private Matrix4x4 _GetMatrix()
-        {
-            if (IsMatrix)
-            {
-                return new Matrix4x4
-                (
-                    _M11, _M12, _M13, 0,
-                    _M21, _M22, _M23, 0,
-                    _M31, _M32, _M33, 0,
-                    _Translation.X,
-                    _Translation.Y,
-                    _Translation.Z, 1
-                );
-            }
-            else if (IsSRT)
-            {
-                var m = Matrix4x4.CreateScale(this.Scale) * Matrix4x4.CreateFromQuaternion(this.Rotation);
-                m.Translation = this.Translation;
-                return m;
-            }
-            else
-            {
-                _VerifyDefined();
-                return default;
-            }
-        }
-
-        private Vector3 _GetScale()
-        {
-            if (IsSRT) return new Vector3(_M11, _M12, _M13);
-            throw new InvalidOperationException(_RequiresSRTError);
-        }
-
-        private Quaternion _GetRotation()
-        {
-            if (IsSRT) return new Quaternion(_M21, _M22, _M23, _M31);
-            throw new InvalidOperationException(_RequiresSRTError);
-        }
-
+        
+        /// <summary>
+        /// If this object represents a <see cref="Matrix4x4"/>, it returns a decomposed representation.
+        /// </summary>
+        /// <returns></returns>
+        /// <exception cref="InvalidOperationException">The matrix cannot be decomposed.</exception>
         public AffineTransform GetDecomposed()
         {
             return TryDecompose(out AffineTransform xform)
@@ -652,6 +609,54 @@ namespace SharpGLTF.Transforms
 
             return new AffineTransform(s, r, t);
         }
+        
+        #endregion
+
+        #region internals
+
+        private void _VerifyDefined()
+        {
+            if (_Representation == DATA_UNDEFINED) throw new InvalidOperationException("Undefined");
+        }
+
+        private Matrix4x4 _GetMatrix()
+        {
+            if (IsMatrix)
+            {
+                return new Matrix4x4
+                (
+                    _M11, _M12, _M13, 0,
+                    _M21, _M22, _M23, 0,
+                    _M31, _M32, _M33, 0,
+                    _Translation.X,
+                    _Translation.Y,
+                    _Translation.Z, 1
+                );
+            }
+            else if (IsSRT)
+            {
+                var m = Matrix4x4.CreateScale(this.Scale) * Matrix4x4.CreateFromQuaternion(this.Rotation);
+                m.Translation = this.Translation;
+                return m;
+            }
+            else
+            {
+                _VerifyDefined();
+                return default;
+            }
+        }
+
+        private Vector3 _GetScale()
+        {
+            if (IsSRT) return new Vector3(_M11, _M12, _M13);
+            throw new InvalidOperationException(_RequiresSRTError);
+        }
+
+        private Quaternion _GetRotation()
+        {
+            if (IsSRT) return new Quaternion(_M21, _M22, _M23, _M31);
+            throw new InvalidOperationException(_RequiresSRTError);
+        }
 
         /// <summary>
         /// This method is equivalent to System.Numerics.Vector3.Transform(Vector3 v, Quaternion q)

+ 125 - 2
src/SharpGLTF.Core/Transforms/Matrix4x4Factory.cs

@@ -7,6 +7,118 @@ namespace SharpGLTF.Transforms
 {
     public static class Matrix4x4Factory
     {
+        #region diagnostics
+
+        [Flags]
+        public enum MatrixCheck
+        {
+            None = 0,
+
+            /// <summary>
+            /// All members of the matrix must be finite numbers.
+            /// </summary>
+            Finite = 1,
+
+            /// <summary>
+            /// the matrix must not be all zeros.
+            /// </summary>
+            NonZero = 2,
+
+            /// <summary>
+            /// The matrix must be the identity matrix.
+            /// </summary>
+            Identity = 4,
+
+            /// <summary>
+            /// The forth column of the matrix must be defined with EXACT values: [0,0,0,1].
+            /// See <see href="https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview">glTF spec</see>
+            /// </summary>
+            IdentityColumn4 = 8,
+
+            /// <summary>
+            /// The matrix must be invertible as in <see cref="Matrix4x4.Invert(Matrix4x4, out Matrix4x4)"/>.
+            /// </summary>
+            Invertible = 16,
+
+            /// <summary>
+            /// The matrix must be decomposable as in <see cref="Matrix4x4.Decompose(Matrix4x4, out Vector3, out Quaternion, out Vector3)"/>.
+            /// </summary>
+            Decomposable = 32,
+
+            /// <summary>
+            /// The matrix must have a positive determinant.
+            /// </summary>
+            PositiveDeterminant = 64,
+
+            /// <summary>
+            /// A local matrix must be invertible and decomposable to Scale-Rotation-Translation.
+            /// </summary>
+            LocalTransform = NonZero | Invertible | Decomposable | IdentityColumn4,
+
+            /// <remarks>
+            /// A world matrix can be built from a concatenation of local tranforms.<br/>
+            /// Which means it can be a squeezed matrix, and not decomposable.
+            /// </remarks>
+            WorldTransform = NonZero | Invertible | IdentityColumn4,
+
+            /// <summary>
+            /// Since an inverse bind matrix is built from the inverse of a WorldMatrix,
+            /// the same rules apply.
+            /// </summary>
+            InverseBindMatrix = WorldTransform,
+        }
+
+        private static MatrixCheck _Validate(in Matrix4x4 matrix, MatrixCheck check, float tolerance = 0)
+        {
+            if (!matrix._IsFinite()) return MatrixCheck.Finite;
+
+            if (check.HasFlag(MatrixCheck.NonZero) && matrix == default) return MatrixCheck.NonZero;
+            if (check.HasFlag(MatrixCheck.Identity) && matrix != Matrix4x4.Identity) return MatrixCheck.Identity;
+            if (check.HasFlag(MatrixCheck.IdentityColumn4))
+            {
+                // Acording to gltf schema
+                // "The fourth row of each matrix MUST be set to [0.0, 0.0, 0.0, 1.0]."
+                // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview
+                if (matrix.M14 != 0) return MatrixCheck.IdentityColumn4;
+                if (matrix.M24 != 0) return MatrixCheck.IdentityColumn4;
+                if (matrix.M34 != 0) return MatrixCheck.IdentityColumn4;
+                if (tolerance == 0)
+                {
+                    if (matrix.M44 != 1) return MatrixCheck.IdentityColumn4;
+                }
+                else
+                {
+                    if (Math.Abs(matrix.M44 - 1) > tolerance) return MatrixCheck.IdentityColumn4;
+                }
+            }
+
+            if (check.HasFlag(MatrixCheck.Invertible) && !Matrix4x4.Invert(matrix, out _)) return MatrixCheck.Invertible;
+            if (check.HasFlag(MatrixCheck.Decomposable) && !Matrix4x4.Decompose(matrix, out _, out _, out _)) return MatrixCheck.Decomposable;
+            if (check.HasFlag(MatrixCheck.PositiveDeterminant) && matrix.GetDeterminant() <= 0) return MatrixCheck.PositiveDeterminant;
+
+            return MatrixCheck.None;
+        }
+
+        public static bool IsValid(in Matrix4x4 matrix, MatrixCheck check, float tolerance = 0)
+        {
+            return _Validate(matrix, check, tolerance) == MatrixCheck.None;
+        }
+
+        [System.Diagnostics.DebuggerStepThrough]
+        public static void GuardMatrix(string argName, Matrix4x4 matrix, MatrixCheck check, float tolerance = 0)
+        {
+            var result = _Validate(matrix, check, tolerance);
+
+            if (result != MatrixCheck.None)
+            {
+                throw new ArgumentException($"Invalid Matrix. Fail: {result}", argName);
+            }
+        }
+
+        #endregion
+
+        #region API
+
         /// <summary>
         /// Evaluates a <see cref="Matrix4x4"/> transform based on the available parameters.
         /// </summary>
@@ -28,12 +140,21 @@ namespace SharpGLTF.Transforms
 
         public static Matrix4x4 LocalToWorld(in Matrix4x4 parentWorld, in Matrix4x4 childLocal)
         {
+            GuardMatrix(nameof(parentWorld), parentWorld, MatrixCheck.WorldTransform);
+            GuardMatrix(nameof(childLocal), childLocal, MatrixCheck.LocalTransform);
+
             return childLocal * parentWorld;
         }
-
+        
         public static Matrix4x4 WorldToLocal(in Matrix4x4 parentWorld, in Matrix4x4 childWorld)
         {
-            return childWorld * parentWorld.Inverse();
+            GuardMatrix(nameof(parentWorld), parentWorld, MatrixCheck.WorldTransform);
+            GuardMatrix(nameof(childWorld), childWorld, MatrixCheck.WorldTransform);
+
+            var parentInverse = parentWorld.Inverse();
+            parentInverse.M44 = 1f;
+
+            return childWorld * parentInverse;
         }
 
         /// <summary>
@@ -128,5 +249,7 @@ namespace SharpGLTF.Transforms
             xform.M32 = vz.Y;
             xform.M33 = vz.Z;
         }
+
+        #endregion
     }
 }

+ 6 - 6
src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs

@@ -278,18 +278,18 @@ namespace SharpGLTF.Validation
 
         public OUTTYPE IsMatrix(PARAMNAME pname, in System.Numerics.Matrix4x4 matrix, bool mustInvert = true, bool mustDecompose = true)
         {
-            var flags = _Extensions.MatrixCheck.NonZero;
-            if (mustInvert) flags |= _Extensions.MatrixCheck.Invertible;
-            if (mustDecompose) flags |= _Extensions.MatrixCheck.Decomposable;
+            var flags = Transforms.Matrix4x4Factory.MatrixCheck.NonZero;
+            if (mustInvert) flags |= Transforms.Matrix4x4Factory.MatrixCheck.Invertible;
+            if (mustDecompose) flags |= Transforms.Matrix4x4Factory.MatrixCheck.Decomposable;
             if (!matrix.IsValid(flags)) _DataThrow(pname, "Invalid Matrix");
             return this;
         }
 
         public OUTTYPE IsMatrix4x3(PARAMNAME pname, in System.Numerics.Matrix4x4 matrix, bool mustInvert = true, bool mustDecompose = true)
         {
-            var flags = _Extensions.MatrixCheck.IdentityColumn4;
-            if (mustInvert) flags |= _Extensions.MatrixCheck.Invertible;
-            if (mustDecompose) flags |= _Extensions.MatrixCheck.Decomposable;
+            var flags = Transforms.Matrix4x4Factory.MatrixCheck.IdentityColumn4;
+            if (mustInvert) flags |= Transforms.Matrix4x4Factory.MatrixCheck.Invertible;
+            if (mustDecompose) flags |= Transforms.Matrix4x4Factory.MatrixCheck.Decomposable;
 
             if (!matrix.IsValid(flags)) _DataThrow(pname, "Invalid Matrix");
             return this;

+ 29 - 15
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -9,6 +9,9 @@ using SharpGLTF.Schema2;
 
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 
+using M4X4FACTORY = SharpGLTF.Transforms.Matrix4x4Factory;
+using AFFINEXFORM = SharpGLTF.Transforms.AffineTransform;
+
 namespace SharpGLTF.Scenes
 {
     /// <summary>
@@ -129,7 +132,7 @@ namespace SharpGLTF.Scenes
         /// Mesh instances with a fixed transform cannot be animated,
         /// If you need morph animations, use <see cref="AddRigidMesh(MESHBUILDER, NodeBuilder)"/> instead.
         /// </remarks>
-        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, Transforms.AffineTransform meshWorldTransform)
+        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, AFFINEXFORM meshWorldTransform)
         {
             Guard.NotNull(mesh, nameof(mesh));
 
@@ -152,7 +155,7 @@ namespace SharpGLTF.Scenes
         /// Mesh instances with a fixed transform cannot be animated,
         /// If you need morph animations, use <see cref="AddRigidMesh(MESHBUILDER, NodeBuilder)"/> instead.
         /// </remarks>
-        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, NodeBuilder node, Transforms.AffineTransform instanceTransform)
+        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, NodeBuilder node, AFFINEXFORM instanceTransform)
         {
             Guard.NotNull(mesh, nameof(mesh));
             Guard.NotNull(node, nameof(node));
@@ -170,7 +173,7 @@ namespace SharpGLTF.Scenes
         public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
         {
             Guard.NotNull(mesh, nameof(mesh));
-            Guard.IsTrue(meshWorldMatrix.IsValid(_Extensions.MatrixCheck.WorldTransform), nameof(meshWorldMatrix));
+            M4X4FACTORY.GuardMatrix(nameof(meshWorldMatrix), meshWorldMatrix, M4X4FACTORY.MatrixCheck.WorldTransform);
             Guard.NotNull(joints, nameof(joints));
             GuardAll.NotNull(joints, nameof(joints));
 
@@ -186,7 +189,11 @@ namespace SharpGLTF.Scenes
         {
             Guard.NotNull(mesh, nameof(mesh));
             GuardAll.NotNull(joints.Select(item => item.Joint), nameof(joints));
-            GuardAll.AreTrue(joints.Select(item => item.InverseBindMatrix.IsValid(_Extensions.MatrixCheck.InverseBindMatrix, 0.01f)), nameof(joints), idx => $"matrix {idx} is invalid.");
+
+            for(int i=0; i < joints.Length; i++)
+            {
+                M4X4FACTORY.GuardMatrix($"{nameof(joints)}[{i}]", joints[i].InverseBindMatrix, M4X4FACTORY.MatrixCheck.InverseBindMatrix, 0.01f);
+            }            
 
             var instance = new InstanceBuilder(this);
             instance.Content = new SkinnedTransformer(mesh, joints);
@@ -218,26 +225,24 @@ namespace SharpGLTF.Scenes
             return AddCamera(camera, xform);
         }
 
-        public InstanceBuilder AddCamera(CameraBuilder camera, Matrix4x4 cameraMatrix)
+        public InstanceBuilder AddCamera(CameraBuilder camera, AFFINEXFORM cameraTransform)
         {
-            Guard.NotNull(camera, nameof(camera));
-            Guard.IsTrue(cameraMatrix.IsValid(_Extensions.MatrixCheck.LocalTransform), nameof(cameraMatrix));
+            Guard.NotNull(camera, nameof(camera));            
 
             var content = new CameraContent(camera);
             var instance = new InstanceBuilder(this);
-            instance.Content = new FixedTransformer(content, cameraMatrix);
+            instance.Content = new FixedTransformer(content, cameraTransform);
             _Instances.Add(instance);
             return instance;
         }
 
-        public InstanceBuilder AddLight(LightBuilder light, Matrix4x4 lightMatrix)
+        public InstanceBuilder AddLight(LightBuilder light, AFFINEXFORM lightTransform)
         {
-            Guard.NotNull(light, nameof(light));
-            Guard.IsTrue(lightMatrix.IsValid(_Extensions.MatrixCheck.LocalTransform), nameof(lightMatrix));
+            Guard.NotNull(light, nameof(light));           
 
             var content = new LightContent(light);
             var instance = new InstanceBuilder(this);
-            instance.Content = new FixedTransformer(content, lightMatrix);
+            instance.Content = new FixedTransformer(content, lightTransform);
             _Instances.Add(instance);
             return instance;
         }
@@ -284,21 +289,29 @@ namespace SharpGLTF.Scenes
         /// <param name="basisTransform">The transform to apply.</param>
         /// <param name="basisNodeName">The name of the dummy root node.</param>
         /// <remarks>
+        /// <para>
         /// In some circunstances, it's not possible to apply the
         /// <paramref name="basisTransform"/> to the nodes in the scene.<br/>
         /// In these cases a dummy node is created, and these
         /// nodes are made children of this dummy node.
+        /// </para>
+        /// <para>
+        /// This method is useful to switch axes (Z-UP or Y-UP) and left right handed mode.
+        /// </para>
+        /// <para>
+        /// This method should be called at the end, when the scene has been created completely.
+        /// </para>
         /// </remarks>
         public void ApplyBasisTransform(Matrix4x4 basisTransform, string basisNodeName = "BasisTransform")
         {
             if (basisTransform == Matrix4x4.Identity) return;
 
-            Guard.IsTrue(basisTransform.IsValid(_Extensions.MatrixCheck.WorldTransform), nameof(basisTransform));
+            M4X4FACTORY.GuardMatrix(nameof(basisTransform), basisTransform, M4X4FACTORY.MatrixCheck.WorldTransform);
 
             // find all explicit transforms
             foreach (var fixedXform in _Instances.Select(item => item.Content).OfType<FixedTransformer>())
             {
-                fixedXform.ChildTransform = Transforms.AffineTransform.Multiply(fixedXform.ChildTransform, basisTransform);
+                fixedXform.ChildTransform = AFFINEXFORM.Multiply(fixedXform.ChildTransform, basisTransform);
             }
 
             // gather all root nodes:
@@ -352,7 +365,8 @@ namespace SharpGLTF.Scenes
         public IReadOnlyList<InstanceBuilder> AddScene(SceneBuilder scene, Matrix4x4 sceneTransform)
         {
             Guard.NotNull(scene, nameof(scene));
-            Guard.IsTrue(sceneTransform.IsValid(_Extensions.MatrixCheck.WorldTransform), nameof(sceneTransform));
+
+            M4X4FACTORY.GuardMatrix(nameof(sceneTransform), sceneTransform, M4X4FACTORY.MatrixCheck.WorldTransform);
 
             scene = scene.DeepClone();
             scene.ApplyBasisTransform(sceneTransform);

+ 4 - 1
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -436,7 +436,10 @@ namespace SharpGLTF.Scenes
             Guard.NotNull(joints, nameof(joints));
             Guard.IsTrue(NodeBuilder.IsValidArmature(joints.Select(item => item.Joint)), nameof(joints));
 
-            GuardAll.AreTrue(joints.Select(item => item.InverseBindMatrix.IsValid(_Extensions.MatrixCheck.InverseBindMatrix, 0.01f)), nameof(joints), idx => $"matrix {idx} is invalid");
+            for(int i=0; i < joints.Length; ++i)
+            {
+                Transforms.Matrix4x4Factory.GuardMatrix($"{nameof(joints)}[{i}]", joints[i].InverseBindMatrix, Transforms.Matrix4x4Factory.MatrixCheck.InverseBindMatrix, 0.01f);
+            }            
 
             _MeshPoseWorldTransform = null;
             _Joints.Clear();

+ 1 - 1
tests/SharpGLTF.Core.Tests/Transforms/AffineTransformMatrixTests.cs

@@ -76,7 +76,7 @@ namespace SharpGLTF.Transforms
             // test normalization with uneven scaling
 
             testMatrix(Matrix4x4.CreateScale(0.0001f) * Matrix4x4.CreateFromYawPitchRoll(1, 2, 3), 0.0001f);
-            testMatrix(Matrix4x4.CreateScale(1000) * Matrix4x4.CreateFromYawPitchRoll(1, 2, 3), 0.0001f);
+            testMatrix(Matrix4x4.CreateScale(1000) * Matrix4x4.CreateFromYawPitchRoll(1, 2, 3), 0.0002f);
 
             var SxR = Matrix4x4.CreateScale(5, 1, 1) * Matrix4x4.CreateFromYawPitchRoll(1, 2, 3);   // Decomposable
             var RxS = Matrix4x4.CreateFromYawPitchRoll(1, 2, 3) * Matrix4x4.CreateScale(5, 1, 1);   // Not Decomposable