Browse Source

Added an alternative double precission path for calculating inverse bind matrices.

Vicente Penades 5 years ago
parent
commit
b2a89d7a13

+ 2 - 4
src/Shared/_Extensions.cs

@@ -180,10 +180,8 @@ namespace SharpGLTF
         internal static bool IsValid(this in Matrix4x4 matrix)
         {
             if (!matrix._IsFinite()) return false;
-
-            if (!Matrix4x4.Decompose(matrix, out Vector3 s, out Quaternion r, out Vector3 t)) return false;
-
-            if (!Matrix4x4.Invert(matrix, out Matrix4x4 inverse)) return false;
+            if (!Matrix4x4.Decompose(matrix, out _, out _, out _)) return false;
+            if (!Matrix4x4.Invert(matrix, out _)) return false;
 
             return true;
         }

+ 12 - 0
src/SharpGLTF.Core/Debug/DebugViews.cs

@@ -95,4 +95,16 @@ namespace SharpGLTF.Debug
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         public Schema2.MeshPrimitive[] Primitives => _Value.Primitives.ToArray();
     }
+
+    internal sealed class _Matrix4x4DoubleProxy
+    {
+        public _Matrix4x4DoubleProxy(Transforms.Matrix4x4Double value) { _Value = value; }
+
+        private Transforms.Matrix4x4Double _Value;
+
+        public (Double X, Double Y, Double Z, Double W) Row1 => (_Value.M11, _Value.M12, _Value.M13, _Value.M14);
+        public (Double X, Double Y, Double Z, Double W) Row2 => (_Value.M21, _Value.M22, _Value.M23, _Value.M24);
+        public (Double X, Double Y, Double Z, Double W) Row3 => (_Value.M31, _Value.M32, _Value.M33, _Value.M34);
+        public (Double X, Double Y, Double Z, Double W) Row4 => (_Value.M41, _Value.M42, _Value.M43, _Value.M44);
+    }
 }

+ 28 - 0
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -232,6 +232,25 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        internal Transforms.Matrix4x4Double LocalMatrixPrecise
+        {
+            get
+            {
+                if (_matrix.HasValue) return new Transforms.Matrix4x4Double(_matrix.Value);
+
+                var s = _scale ?? Vector3.One;
+                var r = _rotation ?? Quaternion.Identity;
+                var t = _translation ?? Vector3.Zero;
+
+                return
+                    Transforms.Matrix4x4Double.CreateScale(s.X, s.Y, s.Z)
+                    *
+                    Transforms.Matrix4x4Double.CreateFromQuaternion(r.Sanitized())
+                    *
+                    Transforms.Matrix4x4Double.CreateTranslation(t.X, t.Y, t.Z);
+            }
+        }
+
         #pragma warning disable CA1721 // Property names should not match get methods
 
         /// <summary>
@@ -251,6 +270,15 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        internal Transforms.Matrix4x4Double WorldMatrixPrecise
+        {
+            get
+            {
+                var vs = this.VisualParent;
+                return vs == null ? LocalMatrixPrecise : LocalMatrixPrecise * vs.WorldMatrixPrecise;
+            }
+        }
+
         #pragma warning restore CA1721 // Property names should not match get methods
 
         #endregion

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

@@ -104,7 +104,7 @@ namespace SharpGLTF.Schema2
             {
                 Guard.NotNull(joints[i], nameof(joints));
 
-                var xform = Transforms.SkinnedTransform.CalculateInverseBinding(meshBindTransform, joints[i].WorldMatrix);
+                var xform = (Matrix4x4)Transforms.SkinnedTransform.CalculateInverseBinding((Transforms.Matrix4x4Double)meshBindTransform, joints[i].WorldMatrixPrecise);
 
                 pairs[i] = (joints[i], xform);
             }

+ 641 - 0
src/SharpGLTF.Core/Transforms/Matrix4x4Double.cs

@@ -0,0 +1,641 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace SharpGLTF.Transforms
+{
+    // stripped from https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.cs
+
+    [StructLayout(LayoutKind.Sequential)]
+    [DebuggerTypeProxy(typeof(Debug._Matrix4x4DoubleProxy))]
+    public struct Matrix4x4Double : IEquatable<Matrix4x4Double>
+    {
+        #region constants
+
+        private static readonly Matrix4x4Double _identity = new Matrix4x4Double
+        (
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            0, 0, 0, 1
+        );
+
+        #endregion
+
+        #region constructors
+
+        /// <summary>
+        /// Constructs a Matrix4x4 from the given components.
+        /// </summary>
+        public Matrix4x4Double(double m11, double m12, double m13, double m14,
+                         double m21, double m22, double m23, double m24,
+                         double m31, double m32, double m33, double m34,
+                         double m41, double m42, double m43, double m44)
+        {
+            this.M11 = m11;
+            this.M12 = m12;
+            this.M13 = m13;
+            this.M14 = m14;
+
+            this.M21 = m21;
+            this.M22 = m22;
+            this.M23 = m23;
+            this.M24 = m24;
+
+            this.M31 = m31;
+            this.M32 = m32;
+            this.M33 = m33;
+            this.M34 = m34;
+
+            this.M41 = m41;
+            this.M42 = m42;
+            this.M43 = m43;
+            this.M44 = m44;
+        }
+
+        public Matrix4x4Double(Matrix4x4 other)
+        {
+            this.M11 = other.M11;
+            this.M12 = other.M12;
+            this.M13 = other.M13;
+            this.M14 = other.M14;
+
+            this.M21 = other.M21;
+            this.M22 = other.M22;
+            this.M23 = other.M23;
+            this.M24 = other.M24;
+
+            this.M31 = other.M31;
+            this.M32 = other.M32;
+            this.M33 = other.M33;
+            this.M34 = other.M34;
+
+            this.M41 = other.M41;
+            this.M42 = other.M42;
+            this.M43 = other.M43;
+            this.M44 = other.M44;
+        }
+
+        /// <summary>
+        /// Creates a translation matrix.
+        /// </summary>
+        /// <param name="xPosition">The amount to translate on the X-axis.</param>
+        /// <param name="yPosition">The amount to translate on the Y-axis.</param>
+        /// <param name="zPosition">The amount to translate on the Z-axis.</param>
+        /// <returns>The translation matrix.</returns>
+        public static Matrix4x4Double CreateTranslation(double xPosition, double yPosition, double zPosition)
+        {
+            Matrix4x4Double result;
+
+            result.M11 = 1.0;
+            result.M12 = 0.0;
+            result.M13 = 0.0;
+            result.M14 = 0.0;
+            result.M21 = 0.0;
+            result.M22 = 1.0;
+            result.M23 = 0.0;
+            result.M24 = 0.0;
+            result.M31 = 0.0;
+            result.M32 = 0.0;
+            result.M33 = 1.0;
+            result.M34 = 0.0;
+
+            result.M41 = xPosition;
+            result.M42 = yPosition;
+            result.M43 = zPosition;
+            result.M44 = 1.0f;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Creates a scaling matrix.
+        /// </summary>
+        /// <param name="xScale">Value to scale by on the X-axis.</param>
+        /// <param name="yScale">Value to scale by on the Y-axis.</param>
+        /// <param name="zScale">Value to scale by on the Z-axis.</param>
+        /// <returns>The scaling matrix.</returns>
+        public static Matrix4x4Double CreateScale(double xScale, double yScale, double zScale)
+        {
+            Matrix4x4Double result;
+
+            result.M11 = xScale;
+            result.M12 = 0.0;
+            result.M13 = 0.0;
+            result.M14 = 0.0;
+            result.M21 = 0.0;
+            result.M22 = yScale;
+            result.M23 = 0.0;
+            result.M24 = 0.0;
+            result.M31 = 0.0;
+            result.M32 = 0.0;
+            result.M33 = zScale;
+            result.M34 = 0.0;
+            result.M41 = 0.0;
+            result.M42 = 0.0;
+            result.M43 = 0.0;
+            result.M44 = 1.0;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Creates a rotation matrix from the given Quaternion rotation value.
+        /// </summary>
+        /// <param name="quaternion">The source Quaternion.</param>
+        /// <returns>The rotation matrix.</returns>
+        public static Matrix4x4Double CreateFromQuaternion(Quaternion quaternion)
+        {
+            Matrix4x4Double result;
+
+            double qX = quaternion.X;
+            double qY = quaternion.Y;
+            double qZ = quaternion.Z;
+            double qW = quaternion.W;
+
+            double xx = qX * qX;
+            double yy = qY * qY;
+            double zz = qZ * qZ;
+
+            double xy = qX * qY;
+            double wz = qZ * qW;
+            double xz = qZ * qX;
+            double wy = qY * qW;
+            double yz = qY * qZ;
+            double wx = qX * qW;
+
+            result.M11 = 1.0 - (2.0 * (yy + zz));
+            result.M12 = 2.0 * (xy + wz);
+            result.M13 = 2.0 * (xz - wy);
+            result.M14 = 0.0;
+            result.M21 = 2.0 * (xy - wz);
+            result.M22 = 1.0 - (2.0 * (zz + xx));
+            result.M23 = 2.0 * (yz + wx);
+            result.M24 = 0.0;
+            result.M31 = 2.0 * (xz + wy);
+            result.M32 = 2.0 * (yz - wx);
+            result.M33 = 1.0 - (2.0 * (yy + xx));
+            result.M34 = 0.0;
+            result.M41 = 0.0;
+            result.M42 = 0.0;
+            result.M43 = 0.0;
+            result.M44 = 1.0;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Converts a <see cref="Matrix4x4Double"/> to a <see cref="Matrix4x4"/>
+        /// </summary>
+        /// <param name="mat">The matrix to convert.</param>
+        /// <remarks>
+        /// Since converting From double precission to single precission
+        /// implies a loss of precission, this conversion is explicit, so
+        /// developers will be aware of when the precission loss is happening.
+        /// </remarks>
+        public static explicit operator Matrix4x4(Matrix4x4Double mat)
+        {
+            return new Matrix4x4
+                (
+                (float)mat.M11,
+                (float)mat.M12,
+                (float)mat.M13,
+                (float)mat.M14,
+                (float)mat.M21,
+                (float)mat.M22,
+                (float)mat.M23,
+                (float)mat.M24,
+                (float)mat.M31,
+                (float)mat.M32,
+                (float)mat.M33,
+                (float)mat.M34,
+                (float)mat.M41,
+                (float)mat.M42,
+                (float)mat.M43,
+                (float)mat.M44
+                );
+        }
+
+        /// <summary>
+        /// converts from <see cref="Matrix4x4"/> to <see cref="Matrix4x4Double"/>
+        /// </summary>
+        /// <param name="mat">The matrix to convert.</param>
+        public static implicit operator Matrix4x4Double(Matrix4x4 mat)
+        {
+            return new Matrix4x4Double
+                (
+                mat.M11,
+                mat.M12,
+                mat.M13,
+                mat.M14,
+                mat.M21,
+                mat.M22,
+                mat.M23,
+                mat.M24,
+                mat.M31,
+                mat.M32,
+                mat.M33,
+                mat.M34,
+                mat.M41,
+                mat.M42,
+                mat.M43,
+                mat.M44
+                );
+        }
+
+        #endregion
+
+        #region Public Fields
+
+        /// <summary>
+        /// Value at row 1, column 1 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M11;
+        /// <summary>
+        /// Value at row 1, column 2 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M12;
+        /// <summary>
+        /// Value at row 1, column 3 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M13;
+        /// <summary>
+        /// Value at row 1, column 4 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M14;
+
+        /// <summary>
+        /// Value at row 2, column 1 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M21;
+        /// <summary>
+        /// Value at row 2, column 2 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M22;
+        /// <summary>
+        /// Value at row 2, column 3 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M23;
+        /// <summary>
+        /// Value at row 2, column 4 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M24;
+
+        /// <summary>
+        /// Value at row 3, column 1 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M31;
+        /// <summary>
+        /// Value at row 3, column 2 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M32;
+        /// <summary>
+        /// Value at row 3, column 3 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M33;
+        /// <summary>
+        /// Value at row 3, column 4 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M34;
+
+        /// <summary>
+        /// Value at row 4, column 1 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M41;
+        /// <summary>
+        /// Value at row 4, column 2 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M42;
+        /// <summary>
+        /// Value at row 4, column 3 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M43;
+        /// <summary>
+        /// Value at row 4, column 4 of the matrix.
+        /// </summary>
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        public Double M44;
+
+        /// <summary>
+        /// Returns the hash code for this instance.
+        /// </summary>
+        /// <returns>The hash code.</returns>
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return M11.GetHashCode() + M12.GetHashCode() + M13.GetHashCode() + M14.GetHashCode() +
+                       M21.GetHashCode() + M22.GetHashCode() + M23.GetHashCode() + M24.GetHashCode() +
+                       M31.GetHashCode() + M32.GetHashCode() + M33.GetHashCode() + M34.GetHashCode() +
+                       M41.GetHashCode() + M42.GetHashCode() + M43.GetHashCode() + M44.GetHashCode();
+            }
+        }
+
+        /// <summary>
+        /// Returns a boolean indicating whether the given two matrices are equal.
+        /// </summary>
+        /// <param name="value1">The first matrix to compare.</param>
+        /// <param name="value2">The second matrix to compare.</param>
+        /// <returns>True if the given matrices are equal; False otherwise.</returns>
+        public static bool operator ==(Matrix4x4Double value1, Matrix4x4Double value2)
+        {
+            return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && value1.M33 == value2.M33 && value1.M44 == value2.M44 && // Check diagonal element first for early out.
+                    value1.M12 == value2.M12 && value1.M13 == value2.M13 && value1.M14 == value2.M14 && value1.M21 == value2.M21 &&
+                    value1.M23 == value2.M23 && value1.M24 == value2.M24 && value1.M31 == value2.M31 && value1.M32 == value2.M32 &&
+                    value1.M34 == value2.M34 && value1.M41 == value2.M41 && value1.M42 == value2.M42 && value1.M43 == value2.M43);
+        }
+
+        /// <summary>
+        /// Returns a boolean indicating whether the given two matrices are not equal.
+        /// </summary>
+        /// <param name="value1">The first matrix to compare.</param>
+        /// <param name="value2">The second matrix to compare.</param>
+        /// <returns>True if the given matrices are not equal; False if they are equal.</returns>
+        public static bool operator !=(Matrix4x4Double value1, Matrix4x4Double value2)
+        {
+            return (value1.M11 != value2.M11 || value1.M12 != value2.M12 || value1.M13 != value2.M13 || value1.M14 != value2.M14 ||
+                    value1.M21 != value2.M21 || value1.M22 != value2.M22 || value1.M23 != value2.M23 || value1.M24 != value2.M24 ||
+                    value1.M31 != value2.M31 || value1.M32 != value2.M32 || value1.M33 != value2.M33 || value1.M34 != value2.M34 ||
+                    value1.M41 != value2.M41 || value1.M42 != value2.M42 || value1.M43 != value2.M43 || value1.M44 != value2.M44);
+        }
+
+        /// <summary>
+        /// Returns a boolean indicating whether this matrix instance is equal to the other given matrix.
+        /// </summary>
+        /// <param name="other">The matrix to compare this instance to.</param>
+        /// <returns>True if the matrices are equal; False otherwise.</returns>
+        public bool Equals(Matrix4x4Double other) => this == other;
+
+        /// <summary>
+        /// Returns a boolean indicating whether the given Object is equal to this matrix instance.
+        /// </summary>
+        /// <param name="obj">The Object to compare against.</param>
+        /// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
+        public override bool Equals(object obj) => (obj is Matrix4x4Double other) && (this == other);
+
+        #endregion Public Fields
+
+        #region properties
+
+        /// <summary>
+        /// Returns the multiplicative identity matrix.
+        /// </summary>
+        public static Matrix4x4Double Identity
+        {
+            get { return _identity; }
+        }
+
+        /// <summary>
+        /// Gets or sets the translation component of this matrix.
+        /// </summary>
+        public (Double x, Double y, Double z) Translation
+        {
+            get
+            {
+                return (M41, M42, M43);
+            }
+            set
+            {
+                M41 = value.x;
+                M42 = value.y;
+                M43 = value.z;
+            }
+        }
+
+        #endregion
+
+        #region methods
+
+        /// <summary>
+        /// Attempts to calculate the inverse of the given matrix. If successful, result will contain the inverted matrix.
+        /// </summary>
+        /// <param name="matrix">The source matrix to invert.</param>
+        /// <param name="result">If successful, contains the inverted matrix.</param>
+        /// <returns>True if the source matrix could be inverted; False otherwise.</returns>
+        public static bool Invert(Matrix4x4Double matrix, out Matrix4x4Double result)
+        {
+            //                                       -1
+            // If you have matrix M, inverse Matrix M   can compute
+            //
+            //     -1       1
+            //    M   = --------- A
+            //            det(M)
+            //
+            // A is adjugate (adjoint) of M, where,
+            //
+            //      T
+            // A = C
+            //
+            // C is Cofactor matrix of M, where,
+            //           i + j
+            // C   = (-1)      * det(M  )
+            //  ij                    ij
+            //
+            //     [ a b c d ]
+            // M = [ e f g h ]
+            //     [ i j k l ]
+            //     [ m n o p ]
+            //
+            // First Row
+            //           2 | f g h |
+            // C   = (-1)  | j k l | = + ( f ( kp - lo ) - g ( jp - ln ) + h ( jo - kn ) )
+            //  11         | n o p |
+            //
+            //           3 | e g h |
+            // C   = (-1)  | i k l | = - ( e ( kp - lo ) - g ( ip - lm ) + h ( io - km ) )
+            //  12         | m o p |
+            //
+            //           4 | e f h |
+            // C   = (-1)  | i j l | = + ( e ( jp - ln ) - f ( ip - lm ) + h ( in - jm ) )
+            //  13         | m n p |
+            //
+            //           5 | e f g |
+            // C   = (-1)  | i j k | = - ( e ( jo - kn ) - f ( io - km ) + g ( in - jm ) )
+            //  14         | m n o |
+            //
+            // Second Row
+            //           3 | b c d |
+            // C   = (-1)  | j k l | = - ( b ( kp - lo ) - c ( jp - ln ) + d ( jo - kn ) )
+            //  21         | n o p |
+            //
+            //           4 | a c d |
+            // C   = (-1)  | i k l | = + ( a ( kp - lo ) - c ( ip - lm ) + d ( io - km ) )
+            //  22         | m o p |
+            //
+            //           5 | a b d |
+            // C   = (-1)  | i j l | = - ( a ( jp - ln ) - b ( ip - lm ) + d ( in - jm ) )
+            //  23         | m n p |
+            //
+            //           6 | a b c |
+            // C   = (-1)  | i j k | = + ( a ( jo - kn ) - b ( io - km ) + c ( in - jm ) )
+            //  24         | m n o |
+            //
+            // Third Row
+            //           4 | b c d |
+            // C   = (-1)  | f g h | = + ( b ( gp - ho ) - c ( fp - hn ) + d ( fo - gn ) )
+            //  31         | n o p |
+            //
+            //           5 | a c d |
+            // C   = (-1)  | e g h | = - ( a ( gp - ho ) - c ( ep - hm ) + d ( eo - gm ) )
+            //  32         | m o p |
+            //
+            //           6 | a b d |
+            // C   = (-1)  | e f h | = + ( a ( fp - hn ) - b ( ep - hm ) + d ( en - fm ) )
+            //  33         | m n p |
+            //
+            //           7 | a b c |
+            // C   = (-1)  | e f g | = - ( a ( fo - gn ) - b ( eo - gm ) + c ( en - fm ) )
+            //  34         | m n o |
+            //
+            // Fourth Row
+            //           5 | b c d |
+            // C   = (-1)  | f g h | = - ( b ( gl - hk ) - c ( fl - hj ) + d ( fk - gj ) )
+            //  41         | j k l |
+            //
+            //           6 | a c d |
+            // C   = (-1)  | e g h | = + ( a ( gl - hk ) - c ( el - hi ) + d ( ek - gi ) )
+            //  42         | i k l |
+            //
+            //           7 | a b d |
+            // C   = (-1)  | e f h | = - ( a ( fl - hj ) - b ( el - hi ) + d ( ej - fi ) )
+            //  43         | i j l |
+            //
+            //           8 | a b c |
+            // C   = (-1)  | e f g | = + ( a ( fk - gj ) - b ( ek - gi ) + c ( ej - fi ) )
+            //  44         | i j k |
+            //
+            // Cost of operation
+            // 53 adds, 104 muls, and 1 div.
+            double a = matrix.M11, b = matrix.M12, c = matrix.M13, d = matrix.M14;
+            double e = matrix.M21, f = matrix.M22, g = matrix.M23, h = matrix.M24;
+            double i = matrix.M31, j = matrix.M32, k = matrix.M33, l = matrix.M34;
+            double m = matrix.M41, n = matrix.M42, o = matrix.M43, p = matrix.M44;
+
+            double kp_lo = (k * p) - (l * o);
+            double jp_ln = (j * p) - (l * n);
+            double jo_kn = (j * o) - (k * n);
+            double ip_lm = (i * p) - (l * m);
+            double io_km = (i * o) - (k * m);
+            double in_jm = (i * n) - (j * m);
+
+            double a11 = +(f * kp_lo - g * jp_ln + h * jo_kn);
+            double a12 = -(e * kp_lo - g * ip_lm + h * io_km);
+            double a13 = +(e * jp_ln - f * ip_lm + h * in_jm);
+            double a14 = -(e * jo_kn - f * io_km + g * in_jm);
+
+            double det = (a * a11) + (b * a12) + (c * a13) + (d * a14);
+
+            if (Math.Abs(det) < double.Epsilon)
+            {
+                result = new Matrix4x4Double(double.NaN, double.NaN, double.NaN, double.NaN,
+                                       double.NaN, double.NaN, double.NaN, double.NaN,
+                                       double.NaN, double.NaN, double.NaN, double.NaN,
+                                       double.NaN, double.NaN, double.NaN, double.NaN);
+                return false;
+            }
+
+            double invDet = 1.0 / det;
+
+            result.M11 = a11 * invDet;
+            result.M21 = a12 * invDet;
+            result.M31 = a13 * invDet;
+            result.M41 = a14 * invDet;
+
+            result.M12 = -(b * kp_lo - c * jp_ln + d * jo_kn) * invDet;
+            result.M22 = +(a * kp_lo - c * ip_lm + d * io_km) * invDet;
+            result.M32 = -(a * jp_ln - b * ip_lm + d * in_jm) * invDet;
+            result.M42 = +(a * jo_kn - b * io_km + c * in_jm) * invDet;
+
+            double gp_ho = (g * p) - (h * o);
+            double fp_hn = (f * p) - (h * n);
+            double fo_gn = (f * o) - (g * n);
+            double ep_hm = (e * p) - (h * m);
+            double eo_gm = (e * o) - (g * m);
+            double en_fm = (e * n) - (f * m);
+
+            result.M13 = +(b * gp_ho - c * fp_hn + d * fo_gn) * invDet;
+            result.M23 = -(a * gp_ho - c * ep_hm + d * eo_gm) * invDet;
+            result.M33 = +(a * fp_hn - b * ep_hm + d * en_fm) * invDet;
+            result.M43 = -(a * fo_gn - b * eo_gm + c * en_fm) * invDet;
+
+            double gl_hk = (g * l) - (h * k);
+            double fl_hj = (f * l) - (h * j);
+            double fk_gj = (f * k) - (g * j);
+            double el_hi = (e * l) - (h * i);
+            double ek_gi = (e * k) - (g * i);
+            double ej_fi = (e * j) - (f * i);
+
+            result.M14 = -(b * gl_hk - c * fl_hj + d * fk_gj) * invDet;
+            result.M24 = +(a * gl_hk - c * el_hi + d * ek_gi) * invDet;
+            result.M34 = -(a * fl_hj - b * el_hi + d * ej_fi) * invDet;
+            result.M44 = +(a * fk_gj - b * ek_gi + c * ej_fi) * invDet;
+
+            return true;
+        }
+
+        /// <summary>
+        /// Multiplies a matrix by another matrix.
+        /// </summary>
+        /// <param name="value1">The first source matrix.</param>
+        /// <param name="value2">The second source matrix.</param>
+        /// <returns>The result of the multiplication.</returns>
+        public static Matrix4x4Double Multiply(Matrix4x4Double value1, Matrix4x4Double value2) => value1 * value2;
+
+        /// <summary>
+        /// Multiplies a matrix by another matrix.
+        /// </summary>
+        /// <param name="value1">The first source matrix.</param>
+        /// <param name="value2">The second source matrix.</param>
+        /// <returns>The result of the multiplication.</returns>
+        public static Matrix4x4Double operator *(Matrix4x4Double value1, Matrix4x4Double value2)
+        {
+            Matrix4x4Double m;
+
+            // First row
+            m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21 + value1.M13 * value2.M31 + value1.M14 * value2.M41;
+            m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22 + value1.M13 * value2.M32 + value1.M14 * value2.M42;
+            m.M13 = value1.M11 * value2.M13 + value1.M12 * value2.M23 + value1.M13 * value2.M33 + value1.M14 * value2.M43;
+            m.M14 = value1.M11 * value2.M14 + value1.M12 * value2.M24 + value1.M13 * value2.M34 + value1.M14 * value2.M44;
+
+            // Second row
+            m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21 + value1.M23 * value2.M31 + value1.M24 * value2.M41;
+            m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22 + value1.M23 * value2.M32 + value1.M24 * value2.M42;
+            m.M23 = value1.M21 * value2.M13 + value1.M22 * value2.M23 + value1.M23 * value2.M33 + value1.M24 * value2.M43;
+            m.M24 = value1.M21 * value2.M14 + value1.M22 * value2.M24 + value1.M23 * value2.M34 + value1.M24 * value2.M44;
+
+            // Third row
+            m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value1.M33 * value2.M31 + value1.M34 * value2.M41;
+            m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value1.M33 * value2.M32 + value1.M34 * value2.M42;
+            m.M33 = value1.M31 * value2.M13 + value1.M32 * value2.M23 + value1.M33 * value2.M33 + value1.M34 * value2.M43;
+            m.M34 = value1.M31 * value2.M14 + value1.M32 * value2.M24 + value1.M33 * value2.M34 + value1.M34 * value2.M44;
+
+            // Fourth row
+            m.M41 = value1.M41 * value2.M11 + value1.M42 * value2.M21 + value1.M43 * value2.M31 + value1.M44 * value2.M41;
+            m.M42 = value1.M41 * value2.M12 + value1.M42 * value2.M22 + value1.M43 * value2.M32 + value1.M44 * value2.M42;
+            m.M43 = value1.M41 * value2.M13 + value1.M42 * value2.M23 + value1.M43 * value2.M33 + value1.M44 * value2.M43;
+            m.M44 = value1.M41 * value2.M14 + value1.M42 * value2.M24 + value1.M43 * value2.M34 + value1.M44 * value2.M44;
+
+            return m;
+        }
+
+        #endregion
+    }
+}

+ 11 - 4
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -386,12 +386,19 @@ namespace SharpGLTF.Transforms
         /// <returns>A <see cref="TRANSFORM"/> representing the inverse bind transform.</returns>
         public static TRANSFORM CalculateInverseBinding(TRANSFORM meshWorldTransform, TRANSFORM jointWorldTransform)
         {
-            // var xform = meshWorldTransform.Inverse();
-            // xform = jointWorldTransform * xform;
-            // return xform.Inverse();
-
             var invJoint = jointWorldTransform.Inverse();
 
+            if (meshWorldTransform == TRANSFORM.Identity) return invJoint;
+
+            return meshWorldTransform * invJoint;
+        }
+
+        public static Matrix4x4Double CalculateInverseBinding(Matrix4x4Double meshWorldTransform, Matrix4x4Double jointWorldTransform)
+        {
+            if (!Matrix4x4Double.Invert(jointWorldTransform, out Matrix4x4Double invJoint)) Guard.IsTrue(false, nameof(jointWorldTransform), "Matrix cannot be inverted.");
+
+            if (meshWorldTransform == Matrix4x4Double.Identity) return invJoint;
+
             return meshWorldTransform * invJoint;
         }
 

+ 35 - 0
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -115,6 +115,25 @@ namespace SharpGLTF.Scenes
             }
         }
 
+        internal Transforms.Matrix4x4Double LocalMatrixPrecise
+        {
+            get
+            {
+                if (_Matrix.HasValue) return new Transforms.Matrix4x4Double(_Matrix.Value);
+
+                var s = _Scale?.Value ?? Vector3.One;
+                var r = _Rotation?.Value ?? Quaternion.Identity;
+                var t = _Translation?.Value ?? Vector3.Zero;
+
+                return
+                    Transforms.Matrix4x4Double.CreateScale(s.X, s.Y, s.Z)
+                    *
+                    Transforms.Matrix4x4Double.CreateFromQuaternion(r.Sanitized())
+                    *
+                    Transforms.Matrix4x4Double.CreateTranslation(t.X, t.Y, t.Z);
+            }
+        }
+
         #pragma warning disable CA1721 // Property names should not match get methods
 
         /// <summary>
@@ -156,6 +175,15 @@ namespace SharpGLTF.Scenes
             }
         }
 
+        internal Transforms.Matrix4x4Double WorldMatrixPrecise
+        {
+            get
+            {
+                var vs = this.Parent;
+                return vs == null ? LocalMatrixPrecise : LocalMatrixPrecise * vs.WorldMatrixPrecise;
+            }
+        }
+
         #pragma warning restore CA1721 // Property names should not match get methods
 
         #endregion
@@ -358,6 +386,13 @@ namespace SharpGLTF.Scenes
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
         }
 
+        public Matrix4x4 GetInverseBindMatrix(Matrix4x4? meshWorldMatrix = null)
+        {
+            Transforms.Matrix4x4Double mwx = meshWorldMatrix ?? Matrix4x4.Identity;
+
+            return (Matrix4x4)Transforms.SkinnedTransform.CalculateInverseBinding(mwx, this.WorldMatrixPrecise);
+        }
+
         #endregion
 
         #region With* API

+ 0 - 1
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -48,7 +48,6 @@ namespace SharpGLTF.Scenes
             if (!(Content is SCHEMA2NODE schema2Target)) return;
 
             var skinnedMeshNode = dstScene.CreateNode();
-
             skinnedMeshNode.Name = _NodeName;
 
             if (_TargetBindMatrix.HasValue)