|
@@ -6,49 +6,92 @@ using System.Text;
|
|
|
namespace SharpGLTF.Transforms
|
|
namespace SharpGLTF.Transforms
|
|
|
{
|
|
{
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// Represents an affine transform in 3D space, with two exclusive representantions:<br/>
|
|
|
|
|
|
|
+ /// Represents an affine transform in 3D space, with two mutually exclusive representantions:<br/>
|
|
|
/// <list type="bullet">
|
|
/// <list type="bullet">
|
|
|
/// <item>
|
|
/// <item>
|
|
|
- /// When <see cref="IsMatrix"/> is true, A 4x3 Matrix. which is publicly<br/>
|
|
|
|
|
- /// exposed as a <see cref="Matrix4x4"/> matrix.
|
|
|
|
|
|
|
+ /// As a 4x3 Matrix. When <see cref="IsMatrix"/> is true.<br/>
|
|
|
|
|
+ /// Publicly exposed as <see cref="Matrix"/>.
|
|
|
/// </item>
|
|
/// </item>
|
|
|
/// <item>
|
|
/// <item>
|
|
|
- /// When <see cref="IsDecomposed"/> is true, A decomposed transform defined by:<br/>
|
|
|
|
|
- /// <see cref="Vector3"/> Scale.<br/>
|
|
|
|
|
- /// <see cref="Quaternion"/> Rotation.<br/>
|
|
|
|
|
- /// <see cref="Vector3"/> Translation.
|
|
|
|
|
|
|
+ /// As a Scale/Rotation/Translation chain. When <see cref="IsSRT"/> is true.<br/>
|
|
|
|
|
+ /// Publicly exposed as: <see cref="Scale"/>, <see cref="Rotation"/>, <see cref="Translation"/>.
|
|
|
/// </item>
|
|
/// </item>
|
|
|
/// </list>
|
|
/// </list>
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
/// <remarks>
|
|
|
/// <para>
|
|
/// <para>
|
|
|
/// Depending on how <see cref="AffineTransform"/> structures are created, the underlaying<br/>
|
|
/// Depending on how <see cref="AffineTransform"/> structures are created, the underlaying<br/>
|
|
|
- /// fields must be interprested as a Matrix4x3 or a decomposed sequence of Scale, Rotation and<br/>
|
|
|
|
|
- /// Translation.
|
|
|
|
|
|
|
+ /// fields must be interprested as a Matrix4x3 or a Scale/Rotation/Translation chain.
|
|
|
/// </para>
|
|
/// </para>
|
|
|
/// <para>
|
|
/// <para>
|
|
|
- /// This approach allows <see cref="AffineTransform"/> to preserve the source data, avoiding loosing<br/>
|
|
|
|
|
- /// precission when decomposing a matrix, or creating a matrix from a SRT transform.
|
|
|
|
|
|
|
+ /// This approach allows <see cref="AffineTransform"/> preserving the source transform, avoiding loosing<br/>
|
|
|
|
|
+ /// precission when decomposing a matrix, or creating a matrix from a SRT chain.
|
|
|
/// </para>
|
|
/// </para>
|
|
|
/// <para>
|
|
/// <para>
|
|
|
/// Decomposing matrices is tricky because not all valid matrices can be decomposed; in particular<br/>
|
|
/// Decomposing matrices is tricky because not all valid matrices can be decomposed; in particular<br/>
|
|
|
/// squewed matrices will fail to decompose. See <see href="https://github.com/vpenades/SharpGLTF/issues/41"/>.
|
|
/// squewed matrices will fail to decompose. See <see href="https://github.com/vpenades/SharpGLTF/issues/41"/>.
|
|
|
/// </para>
|
|
/// </para>
|
|
|
/// </remarks>
|
|
/// </remarks>
|
|
|
- [System.Diagnostics.DebuggerDisplay("AffineTransform 𝐒:{Scale} 𝐑:{Rotation} 𝚻:{Translation}")]
|
|
|
|
|
- public readonly struct AffineTransform
|
|
|
|
|
|
|
+ [System.Diagnostics.DebuggerDisplay("AffineTransform {ToDebuggerDisplayString(),nq}")]
|
|
|
|
|
+ public readonly struct AffineTransform : IEquatable<AffineTransform>
|
|
|
{
|
|
{
|
|
|
|
|
+ #region diagnostics
|
|
|
|
|
+
|
|
|
|
|
+ internal string ToDebuggerDisplayString()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!IsValid) return "INVALID";
|
|
|
|
|
+
|
|
|
|
|
+ if (IsIdentity) return "IDENTITY";
|
|
|
|
|
+
|
|
|
|
|
+ if (TryDecompose(out var decomposed))
|
|
|
|
|
+ {
|
|
|
|
|
+ var s = decomposed._GetScale();
|
|
|
|
|
+ var r = decomposed._GetRotation();
|
|
|
|
|
+
|
|
|
|
|
+ var txt = string.Empty;
|
|
|
|
|
+
|
|
|
|
|
+ var ss = Vector3.Max(Vector3.Zero, s - Vector3.One);
|
|
|
|
|
+ var hasScale = ss.X > 0.000001f || ss.Y > 0.000001f || ss.Z > 0.000001f;
|
|
|
|
|
+
|
|
|
|
|
+ if (hasScale) txt += $"𝐒:{s} ";
|
|
|
|
|
+ if (r != Quaternion.Identity) txt += $"𝐑:{r} ";
|
|
|
|
|
+ if (decomposed.Translation != Vector3.Zero) txt += $"𝚻:{decomposed.Translation} ";
|
|
|
|
|
+
|
|
|
|
|
+ return txt;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ return "Skewed Matrix ";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #endregion
|
|
|
|
|
+
|
|
|
#region constants
|
|
#region constants
|
|
|
|
|
|
|
|
- private const string _CannotDecomposeMessage = "Matrix is invalid or skewed.";
|
|
|
|
|
|
|
+ private const string _CannotDecomposeError = "Matrix is invalid or skewed.";
|
|
|
|
|
+
|
|
|
|
|
+ private const string _RequiresSRTError = "Needs to be in SRT representation. Call GetDecomposed() first.";
|
|
|
|
|
|
|
|
public static readonly AffineTransform Identity = new AffineTransform(null, null, null);
|
|
public static readonly AffineTransform Identity = new AffineTransform(null, null, null);
|
|
|
|
|
|
|
|
|
|
+ private const int DATA_UNDEFINED = 0;
|
|
|
|
|
+ private const int DATA_SRT = 1;
|
|
|
|
|
+ private const int DATA_MAT = 2;
|
|
|
|
|
+
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region factories
|
|
#region factories
|
|
|
|
|
|
|
|
- public static implicit operator AffineTransform(Matrix4x4 matrix) { return new AffineTransform(matrix); }
|
|
|
|
|
|
|
+ public static implicit operator AffineTransform((Quaternion r, Vector3 t) xform)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new AffineTransform(null, xform.r, xform.t);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static implicit operator AffineTransform(Matrix4x4 matrix)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new AffineTransform(matrix);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
public static AffineTransform CreateDecomposed(Matrix4x4 matrix)
|
|
public static AffineTransform CreateDecomposed(Matrix4x4 matrix)
|
|
|
{
|
|
{
|
|
@@ -61,6 +104,9 @@ namespace SharpGLTF.Transforms
|
|
|
{
|
|
{
|
|
|
if (matrix.HasValue)
|
|
if (matrix.HasValue)
|
|
|
{
|
|
{
|
|
|
|
|
+ Guard.MustBeNull(scale, nameof(scale));
|
|
|
|
|
+ Guard.MustBeNull(scale, nameof(rotation));
|
|
|
|
|
+ Guard.MustBeNull(scale, nameof(translation));
|
|
|
return new AffineTransform(matrix.Value);
|
|
return new AffineTransform(matrix.Value);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
@@ -71,31 +117,74 @@ namespace SharpGLTF.Transforms
|
|
|
|
|
|
|
|
public AffineTransform WithScale(Vector3 scale)
|
|
public AffineTransform WithScale(Vector3 scale)
|
|
|
{
|
|
{
|
|
|
- return new AffineTransform(scale, this.Rotation, this.Translation);
|
|
|
|
|
|
|
+ if (_Representation == DATA_UNDEFINED) return new AffineTransform(scale, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ var tmp = this;
|
|
|
|
|
+ if (tmp.IsMatrix) tmp = tmp.GetDecomposed();
|
|
|
|
|
+ return new AffineTransform(scale, tmp.Rotation, tmp.Translation);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public AffineTransform WithRotation(Quaternion rotation)
|
|
public AffineTransform WithRotation(Quaternion rotation)
|
|
|
{
|
|
{
|
|
|
- return new AffineTransform(this.Scale, rotation, this.Translation);
|
|
|
|
|
|
|
+ if (_Representation == DATA_UNDEFINED) return new AffineTransform(null, rotation, null);
|
|
|
|
|
+
|
|
|
|
|
+ var tmp = this;
|
|
|
|
|
+ if (tmp.IsMatrix) tmp = tmp.GetDecomposed();
|
|
|
|
|
+ return new AffineTransform(tmp.Scale, rotation, tmp.Translation);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public AffineTransform WithTranslation(Vector3 translation)
|
|
public AffineTransform WithTranslation(Vector3 translation)
|
|
|
{
|
|
{
|
|
|
- return new AffineTransform(this.Scale, this.Rotation, translation);
|
|
|
|
|
|
|
+ if (_Representation == DATA_UNDEFINED) return new AffineTransform(null, null, translation);
|
|
|
|
|
+
|
|
|
|
|
+ if (this.IsSRT) return new AffineTransform(this.Scale, this.Rotation, translation);
|
|
|
|
|
+ var tmp = this.Matrix;
|
|
|
|
|
+ tmp.Translation = translation;
|
|
|
|
|
+ return tmp;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region constructors
|
|
#region constructors
|
|
|
|
|
|
|
|
|
|
+ public AffineTransform(Vector3? scale, Quaternion? rotation, Vector3? translation)
|
|
|
|
|
+ : this(scale ?? Vector3.One, rotation ?? Quaternion.Identity, translation ?? Vector3.Zero)
|
|
|
|
|
+ { }
|
|
|
|
|
+
|
|
|
|
|
+ public AffineTransform(Vector3 scale, Quaternion rotation, Vector3 translation)
|
|
|
|
|
+ {
|
|
|
|
|
+ rotation = rotation.Sanitized();
|
|
|
|
|
+
|
|
|
|
|
+ Guard.IsTrue(scale._IsFinite(), nameof(scale));
|
|
|
|
|
+ Guard.IsTrue(rotation._IsFinite(), nameof(rotation));
|
|
|
|
|
+ Guard.IsTrue(translation._IsFinite(), nameof(translation));
|
|
|
|
|
+
|
|
|
|
|
+ _Representation = DATA_SRT;
|
|
|
|
|
+
|
|
|
|
|
+ _M11 = scale.X;
|
|
|
|
|
+ _M12 = scale.Y;
|
|
|
|
|
+ _M13 = scale.Z;
|
|
|
|
|
+
|
|
|
|
|
+ _M21 = rotation.X;
|
|
|
|
|
+ _M22 = rotation.Y;
|
|
|
|
|
+ _M23 = rotation.Z;
|
|
|
|
|
+ _M31 = rotation.W;
|
|
|
|
|
+
|
|
|
|
|
+ _M32 = 0; // unused
|
|
|
|
|
+ _M33 = 0; // unused
|
|
|
|
|
+
|
|
|
|
|
+ _Translation = translation;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public AffineTransform(Matrix4x4 matrix)
|
|
public AffineTransform(Matrix4x4 matrix)
|
|
|
{
|
|
{
|
|
|
- if (matrix.M14 != 0) throw new ArgumentException(nameof(matrix));
|
|
|
|
|
- if (matrix.M24 != 0) throw new ArgumentException(nameof(matrix));
|
|
|
|
|
- if (matrix.M34 != 0) throw new ArgumentException(nameof(matrix));
|
|
|
|
|
- if (matrix.M44 != 1) throw new ArgumentException(nameof(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));
|
|
|
|
|
|
|
|
- _Representation = 0;
|
|
|
|
|
|
|
+ _Representation = DATA_MAT;
|
|
|
|
|
|
|
|
_M11 = matrix.M11;
|
|
_M11 = matrix.M11;
|
|
|
_M12 = matrix.M12;
|
|
_M12 = matrix.M12;
|
|
@@ -112,111 +201,161 @@ namespace SharpGLTF.Transforms
|
|
|
_Translation = matrix.Translation;
|
|
_Translation = matrix.Translation;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public AffineTransform(Vector3? scale, Quaternion? rotation, Vector3? translation)
|
|
|
|
|
- : this(scale ?? Vector3.One, rotation ?? Quaternion.Identity, translation ?? Vector3.Zero)
|
|
|
|
|
- { }
|
|
|
|
|
-
|
|
|
|
|
- public AffineTransform(Vector3 scale, Quaternion rotation, Vector3 translation)
|
|
|
|
|
- {
|
|
|
|
|
- _Representation = 1;
|
|
|
|
|
-
|
|
|
|
|
- _M11 = scale.X;
|
|
|
|
|
- _M12 = scale.Y;
|
|
|
|
|
- _M13 = scale.Z;
|
|
|
|
|
-
|
|
|
|
|
- _M21 = rotation.X;
|
|
|
|
|
- _M22 = rotation.Y;
|
|
|
|
|
- _M23 = rotation.Z;
|
|
|
|
|
- _M31 = rotation.W;
|
|
|
|
|
- _M32 = 0;
|
|
|
|
|
- _M33 = 0;
|
|
|
|
|
-
|
|
|
|
|
- this._Translation = translation;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region data
|
|
#region data
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
- /// Determines the underlaying representation:<br/>
|
|
|
|
|
- /// 0 - Fields must be interpreted as a Matrix4x3.<br/>
|
|
|
|
|
- /// 1 - Fields must be interpreted as a Scale, Rotation and Translation sequence.
|
|
|
|
|
|
|
+ /// Determines what's represented by the data fields:<br/>
|
|
|
|
|
+ /// <list type="bullet">
|
|
|
|
|
+ /// <item><see cref="DATA_UNDEFINED"/> - Not defined.</item>
|
|
|
|
|
+ /// <item><see cref="DATA_MAT"/> - Fields must be interpreted as a 4x3 Matrix.</item>
|
|
|
|
|
+ /// <item><see cref="DATA_SRT"/> - Fields must be interpreted as a Scale, Rotation and Translation chain.</item>
|
|
|
|
|
+ /// </list>
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly Int32 _Representation;
|
|
private readonly Int32 _Representation;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M11<br/>
|
|
/// Matrix: M11<br/>
|
|
|
- /// Decomposed: Scale.X
|
|
|
|
|
|
|
+ /// SRT: Scale.X
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M11;
|
|
private readonly float _M11;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M12<br/>
|
|
/// Matrix: M12<br/>
|
|
|
- /// Decomposed: Scale.Y
|
|
|
|
|
|
|
+ /// SRT: Scale.Y
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M12;
|
|
private readonly float _M12;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M13<br/>
|
|
/// Matrix: M13<br/>
|
|
|
- /// Decomposed: Scale.Z
|
|
|
|
|
|
|
+ /// SRT: Scale.Z
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M13;
|
|
private readonly float _M13;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M21<br/>
|
|
/// Matrix: M21<br/>
|
|
|
- /// Decomposed: Rotation.X
|
|
|
|
|
|
|
+ /// SRT: Rotation.X
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M21;
|
|
private readonly float _M21;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M22<br/>
|
|
/// Matrix: M22<br/>
|
|
|
- /// Decomposed: Rotation.Y
|
|
|
|
|
|
|
+ /// SRT: Rotation.Y
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M22;
|
|
private readonly float _M22;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M23<br/>
|
|
/// Matrix: M23<br/>
|
|
|
- /// Decomposed: Rotation.Z
|
|
|
|
|
|
|
+ /// SRT: Rotation.Z
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M23;
|
|
private readonly float _M23;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M31<br/>
|
|
/// Matrix: M31<br/>
|
|
|
- /// Decomposed: Rotation.W
|
|
|
|
|
|
|
+ /// SRT: Rotation.W
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M31;
|
|
private readonly float _M31;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M32<br/>
|
|
/// Matrix: M32<br/>
|
|
|
- /// Decomposed: unused
|
|
|
|
|
|
|
+ /// SRT: unused
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M32;
|
|
private readonly float _M32;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Matrix: M32<br/>
|
|
/// Matrix: M32<br/>
|
|
|
- /// Decomposed: unused
|
|
|
|
|
|
|
+ /// SRT: unused
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly float _M33;
|
|
private readonly float _M33;
|
|
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Matrix and SRT: Translation
|
|
|
|
|
+ /// </summary>
|
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
|
|
|
private readonly Vector3 _Translation;
|
|
private readonly Vector3 _Translation;
|
|
|
|
|
|
|
|
|
|
+ public override int GetHashCode()
|
|
|
|
|
+ {
|
|
|
|
|
+ // we can only use the translation as hash code because it's the only value that
|
|
|
|
|
+ // is the same on SRT and Matrix representations.... otherwhise we would have
|
|
|
|
|
+ // to take the hash code of the matrix representation.
|
|
|
|
|
+ return _Translation.GetHashCode();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override bool Equals(object obj)
|
|
|
|
|
+ {
|
|
|
|
|
+ return obj is AffineTransform other && this.Equals(other);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool Equals(AffineTransform other)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (this.IsSRT && other.IsSRT)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (this._Translation != other._Translation) return false;
|
|
|
|
|
+ if (this._M11 != other._M11) return false;
|
|
|
|
|
+ if (this._M12 != other._M12) return false;
|
|
|
|
|
+ if (this._M13 != other._M13) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (this._M21 != other._M21) return false;
|
|
|
|
|
+ if (this._M22 != other._M22) return false;
|
|
|
|
|
+ if (this._M23 != other._M23) return false;
|
|
|
|
|
+ if (this._M31 != other._M31) return false;
|
|
|
|
|
+
|
|
|
|
|
+ System.Diagnostics.Debug.Assert(this._M32 == other._M32);
|
|
|
|
|
+ System.Diagnostics.Debug.Assert(this._M33 == other._M33);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return this.Matrix.Equals(other.Matrix);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
|
|
#region properties
|
|
#region properties
|
|
|
- public bool IsMatrix => _Representation == 0;
|
|
|
|
|
- public bool IsDecomposed => _Representation == 1;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ public bool IsValid
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_Representation == DATA_UNDEFINED) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!Translation._IsFinite()) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!_M11._IsFinite()) return false;
|
|
|
|
|
+ if (!_M12._IsFinite()) return false;
|
|
|
|
|
+ if (!_M13._IsFinite()) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!_M21._IsFinite()) return false;
|
|
|
|
|
+ if (!_M22._IsFinite()) return false;
|
|
|
|
|
+ if (!_M23._IsFinite()) return false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!_M31._IsFinite()) return false;
|
|
|
|
|
+ if (!_M32._IsFinite()) return false;
|
|
|
|
|
+ if (!_M33._IsFinite()) return false;
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Gets a value indicating whether this <see cref="AffineTransform"/> represents a <see cref="Matrix4x4"/>.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public bool IsMatrix => _Representation == DATA_MAT;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Gets a value indicating whether this <see cref="AffineTransform"/> represents a SRT chain.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public bool IsSRT => _Representation == DATA_SRT;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Gets the scale.
|
|
/// Gets the scale.
|
|
@@ -244,23 +383,31 @@ namespace SharpGLTF.Transforms
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public Matrix4x4 Matrix => _GetMatrix();
|
|
public Matrix4x4 Matrix => _GetMatrix();
|
|
|
|
|
|
|
|
- public bool IsValid
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Gets a value indicating whether this transform can be decomposed to SRT without precission loss.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public bool IsLosslessDecomposable
|
|
|
{
|
|
{
|
|
|
get
|
|
get
|
|
|
{
|
|
{
|
|
|
- if (!Translation._IsFinite()) return false;
|
|
|
|
|
|
|
+ _VerifyDefined();
|
|
|
|
|
|
|
|
- if (!_M11._IsFinite()) return false;
|
|
|
|
|
- if (!_M12._IsFinite()) return false;
|
|
|
|
|
- if (!_M13._IsFinite()) return false;
|
|
|
|
|
|
|
+ if (IsSRT) return true;
|
|
|
|
|
|
|
|
- if (!_M21._IsFinite()) return false;
|
|
|
|
|
- if (!_M22._IsFinite()) return false;
|
|
|
|
|
- if (!_M23._IsFinite()) return false;
|
|
|
|
|
|
|
+ // row 1
|
|
|
|
|
+ if (_M11 != 0) return false;
|
|
|
|
|
+ if (_M12 == 0) return false;
|
|
|
|
|
+ if (_M13 == 0) return false;
|
|
|
|
|
|
|
|
- if (!_M31._IsFinite()) return false;
|
|
|
|
|
- if (!_M32._IsFinite()) return false;
|
|
|
|
|
- if (!_M33._IsFinite()) return false;
|
|
|
|
|
|
|
+ // row 2
|
|
|
|
|
+ if (_M21 == 0) return false;
|
|
|
|
|
+ if (_M22 != 0) return false;
|
|
|
|
|
+ if (_M23 == 0) return false;
|
|
|
|
|
+
|
|
|
|
|
+ // row 3
|
|
|
|
|
+ if (_M31 == 0) return false;
|
|
|
|
|
+ if (_M32 == 0) return false;
|
|
|
|
|
+ if (_M33 != 0) return false;
|
|
|
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
@@ -270,10 +417,10 @@ namespace SharpGLTF.Transforms
|
|
|
{
|
|
{
|
|
|
get
|
|
get
|
|
|
{
|
|
{
|
|
|
- if (Translation != Vector3.Zero) return false;
|
|
|
|
|
-
|
|
|
|
|
- if (IsDecomposed)
|
|
|
|
|
|
|
+ if (IsSRT)
|
|
|
{
|
|
{
|
|
|
|
|
+ if (Translation != Vector3.Zero) return false;
|
|
|
|
|
+
|
|
|
// scale
|
|
// scale
|
|
|
if (_M11 != 1) return false;
|
|
if (_M11 != 1) return false;
|
|
|
if (_M12 != 1) return false;
|
|
if (_M12 != 1) return false;
|
|
@@ -285,8 +432,10 @@ namespace SharpGLTF.Transforms
|
|
|
if (_M23 != 0) return false;
|
|
if (_M23 != 0) return false;
|
|
|
if (_M31 != 1) return false;
|
|
if (_M31 != 1) return false;
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ else if (IsMatrix)
|
|
|
{
|
|
{
|
|
|
|
|
+ if (Translation != Vector3.Zero) return false;
|
|
|
|
|
+
|
|
|
// row 1
|
|
// row 1
|
|
|
if (_M11 != 1) return false;
|
|
if (_M11 != 1) return false;
|
|
|
if (_M12 != 0) return false;
|
|
if (_M12 != 0) return false;
|
|
@@ -302,6 +451,10 @@ namespace SharpGLTF.Transforms
|
|
|
if (_M32 != 0) return false;
|
|
if (_M32 != 0) return false;
|
|
|
if (_M33 != 1) return false;
|
|
if (_M33 != 1) return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ _VerifyDefined();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
@@ -311,6 +464,11 @@ namespace SharpGLTF.Transforms
|
|
|
|
|
|
|
|
#region API
|
|
#region API
|
|
|
|
|
|
|
|
|
|
+ private void _VerifyDefined()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_Representation == DATA_UNDEFINED) throw new InvalidOperationException("Undefined");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private Matrix4x4 _GetMatrix()
|
|
private Matrix4x4 _GetMatrix()
|
|
|
{
|
|
{
|
|
|
if (IsMatrix)
|
|
if (IsMatrix)
|
|
@@ -320,54 +478,109 @@ namespace SharpGLTF.Transforms
|
|
|
_M11, _M12, _M13, 0,
|
|
_M11, _M12, _M13, 0,
|
|
|
_M21, _M22, _M23, 0,
|
|
_M21, _M22, _M23, 0,
|
|
|
_M31, _M32, _M33, 0,
|
|
_M31, _M32, _M33, 0,
|
|
|
- _Translation.X, _Translation.Y, _Translation.Z, 1
|
|
|
|
|
|
|
+ _Translation.X,
|
|
|
|
|
+ _Translation.Y,
|
|
|
|
|
+ _Translation.Z, 1
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- var m = Matrix4x4.CreateScale(this.Scale) * Matrix4x4.CreateFromQuaternion(this.Rotation.Sanitized());
|
|
|
|
|
- m.Translation = this.Translation;
|
|
|
|
|
- return m;
|
|
|
|
|
|
|
+ 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()
|
|
private Vector3 _GetScale()
|
|
|
{
|
|
{
|
|
|
- if (IsDecomposed) return new Vector3(_M11, _M12, _M13);
|
|
|
|
|
- if (Matrix4x4.Decompose(_GetMatrix(), out var scale, out _, out _)) return scale;
|
|
|
|
|
- throw new InvalidOperationException(_CannotDecomposeMessage);
|
|
|
|
|
|
|
+ if (IsSRT) return new Vector3(_M11, _M12, _M13);
|
|
|
|
|
+ throw new InvalidOperationException(_RequiresSRTError);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private Quaternion _GetRotation()
|
|
private Quaternion _GetRotation()
|
|
|
{
|
|
{
|
|
|
- if (IsDecomposed) return new Quaternion(_M21, _M22, _M23, _M31);
|
|
|
|
|
- if (Matrix4x4.Decompose(_GetMatrix(), out _, out var rotation, out _)) return rotation;
|
|
|
|
|
- throw new InvalidOperationException(_CannotDecomposeMessage);
|
|
|
|
|
|
|
+ if (IsSRT) return new Quaternion(_M21, _M22, _M23, _M31);
|
|
|
|
|
+ throw new InvalidOperationException(_RequiresSRTError);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public AffineTransform GetDecomposed()
|
|
public AffineTransform GetDecomposed()
|
|
|
{
|
|
{
|
|
|
- if (IsDecomposed) return this;
|
|
|
|
|
- if (!Matrix4x4.Decompose(Matrix, out var s, out var r, out var t)) throw new InvalidOperationException(_CannotDecomposeMessage);
|
|
|
|
|
- return new AffineTransform(s, r, t);
|
|
|
|
|
|
|
+ return TryDecompose(out AffineTransform xform)
|
|
|
|
|
+ ? xform
|
|
|
|
|
+ : throw new InvalidOperationException(_CannotDecomposeError);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool TryDecompose(out AffineTransform transform)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (IsSRT) { transform = this; return true; }
|
|
|
|
|
+
|
|
|
|
|
+ if (IsLosslessDecomposable)
|
|
|
|
|
+ {
|
|
|
|
|
+ transform = new AffineTransform
|
|
|
|
|
+ (
|
|
|
|
|
+ new Vector3(_M11, _M22, _M33),
|
|
|
|
|
+ Quaternion.Identity,
|
|
|
|
|
+ this.Translation
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var x = Matrix4x4.Decompose(Matrix, out var s, out var r, out var t);
|
|
|
|
|
+
|
|
|
|
|
+ transform = x ? new AffineTransform(s, r, t) : this;
|
|
|
|
|
+
|
|
|
|
|
+ return x;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool TryDecompose(out Vector3 scale, out Quaternion rotation, out Vector3 translation)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (IsSRT)
|
|
|
|
|
+ {
|
|
|
|
|
+ scale = _GetScale();
|
|
|
|
|
+ rotation = _GetRotation();
|
|
|
|
|
+ translation = _Translation;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (IsLosslessDecomposable)
|
|
|
|
|
+ {
|
|
|
|
|
+ scale = new Vector3(_M11, _M22, _M33);
|
|
|
|
|
+ rotation = Quaternion.Identity;
|
|
|
|
|
+ translation = _Translation;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Matrix4x4.Decompose(Matrix, out scale, out rotation, out translation);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public static AffineTransform Blend(ReadOnlySpan<AffineTransform> transforms, ReadOnlySpan<float> weights)
|
|
public static AffineTransform Blend(ReadOnlySpan<AffineTransform> transforms, ReadOnlySpan<float> weights)
|
|
|
{
|
|
{
|
|
|
- var s = Vector3.Zero;
|
|
|
|
|
- var r = default(Quaternion);
|
|
|
|
|
- var t = Vector3.Zero;
|
|
|
|
|
|
|
+ var sss = Vector3.Zero;
|
|
|
|
|
+ var rrr = default(Quaternion);
|
|
|
|
|
+ var ttt = Vector3.Zero;
|
|
|
|
|
|
|
|
for (int i = 0; i < transforms.Length; ++i)
|
|
for (int i = 0; i < transforms.Length; ++i)
|
|
|
{
|
|
{
|
|
|
|
|
+ Guard.IsFalse(transforms[i]._Representation == DATA_UNDEFINED, nameof(transforms));
|
|
|
|
|
+
|
|
|
var w = weights[i];
|
|
var w = weights[i];
|
|
|
|
|
|
|
|
- s += transforms[i].Scale * w;
|
|
|
|
|
- r += transforms[i].Rotation * w;
|
|
|
|
|
- t += transforms[i].Translation * w;
|
|
|
|
|
|
|
+ transforms[i].TryDecompose(out var s, out var r, out var t);
|
|
|
|
|
+
|
|
|
|
|
+ sss += s * w;
|
|
|
|
|
+ rrr += r * w;
|
|
|
|
|
+ ttt += t * w;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- r = Quaternion.Normalize(r);
|
|
|
|
|
|
|
+ rrr = Quaternion.Normalize(rrr);
|
|
|
|
|
|
|
|
- return new AffineTransform(s, r, t);
|
|
|
|
|
|
|
+ return new AffineTransform(sss, rrr, ttt);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public static AffineTransform operator *(in AffineTransform a, in AffineTransform b)
|
|
public static AffineTransform operator *(in AffineTransform a, in AffineTransform b)
|
|
@@ -394,13 +607,16 @@ namespace SharpGLTF.Transforms
|
|
|
/// </returns>
|
|
/// </returns>
|
|
|
public static AffineTransform Multiply(in AffineTransform a, in AffineTransform b)
|
|
public static AffineTransform Multiply(in AffineTransform a, in AffineTransform b)
|
|
|
{
|
|
{
|
|
|
- // if any of the two operators is a matrix, perform a matrix multiplication.
|
|
|
|
|
|
|
+ // if any of the two operators is a matrix, do a matrix multiplication.
|
|
|
if (a.IsMatrix || b.IsMatrix)
|
|
if (a.IsMatrix || b.IsMatrix)
|
|
|
{
|
|
{
|
|
|
return new AffineTransform(a.Matrix * b.Matrix);
|
|
return new AffineTransform(a.Matrix * b.Matrix);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // if the B operator has an uneven scale AND a rotation, performa a matrix multiplication
|
|
|
|
|
|
|
+ Guard.IsFalse(a._Representation == DATA_UNDEFINED, nameof(a));
|
|
|
|
|
+ Guard.IsFalse(b._Representation == DATA_UNDEFINED, nameof(b));
|
|
|
|
|
+
|
|
|
|
|
+ // if the B operator has an uneven scale AND a rotation, do a matrix multiplication
|
|
|
// which produces a squeezed matrix and cannot be decomposed.
|
|
// which produces a squeezed matrix and cannot be decomposed.
|
|
|
|
|
|
|
|
var sb = b.Scale;
|
|
var sb = b.Scale;
|