Procházet zdrojové kódy

Added Color0 vertex attribute to morph targets

Vicente Penades před 6 roky
rodič
revize
0edff320b5

+ 20 - 0
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -28,6 +28,8 @@ namespace SharpGLTF.Transforms
         V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights);
         V3 TransformNormal(V3 normal, V3[] morphTargets, (int, float)[] skinWeights);
         V4 TransformTangent(V4 tangent, V3[] morphTargets, (int, float)[] skinWeights);
+
+        V4 MorphColors(V4 color, V4[] morphTargets);
     }
 
     public abstract class MorphTransform
@@ -99,6 +101,24 @@ namespace SharpGLTF.Transforms
             return p;
         }
 
+        protected V4 MorphVectors(V4 value, V4[] morphTargets)
+        {
+            if (_InvWeight == 1 || morphTargets == null || morphTargets.Length == 0) return value;
+
+            Guard.IsTrue(_MorphWeights.Length == morphTargets.Length, nameof(morphTargets));
+
+            var p = value * _InvWeight;
+
+            for (int i = 0; i < _MorphWeights.Length; ++i)
+            {
+                p += morphTargets[i] * _MorphWeights[i];
+            }
+
+            return p;
+        }
+
+        public V4 MorphColors(V4 color, V4[] morphTargets) { return MorphVectors(color, morphTargets); }
+
         #endregion
     }
 

+ 67 - 0
src/SharpGLTF.Toolkit/Geometry/MorphTargetColumns.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Geometry
+{
+    /// <summary>
+    /// Represents a collection of vertex attribute columns to be used as morph targets.
+    /// </summary>
+    /// <see href="https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets"/>
+    public class MorphTargetColumns
+    {
+        #region lifecycle
+
+        internal MorphTargetColumns() { }
+
+        #endregion
+
+        #region columns
+
+        public IList<Vector3> Positions { get; set; }
+        public IList<Vector3> Normals { get; set; }
+
+        /// <remarks>
+        /// Note that the w component for handedness is omitted when targeting TANGENT data since handedness cannot be displaced.
+        /// </remarks>
+        public IList<Vector3> Tangents { get; set; }
+
+        /// <remarks>
+        /// glTF v2 specification does not forbid morphing Color0 attribute, but it also states that it is not required by engines
+        /// to support it.
+        /// </remarks>
+        public IList<Vector4> Colors0 { get; set; }
+
+        #endregion
+
+        #region API
+
+        private static IList<T> _IsolateColumn<T>(IList<T> column)
+        {
+            if (column == null) return null;
+
+            var newColumn = new T[column.Count];
+
+            column.CopyTo(newColumn, 0);
+
+            return newColumn;
+        }
+
+        /// <summary>
+        /// Performs an in-place copy of the contents of every column,
+        /// which guarantees that the columns of this <see cref="MorphTargetColumns"/>
+        /// are not shared by any other object and can be modified safely.
+        /// </summary>
+        public void IsolateColumns()
+        {
+            this.Positions = _IsolateColumn(this.Positions);
+            this.Normals = _IsolateColumn(this.Normals);
+            this.Tangents = _IsolateColumn(this.Tangents);
+
+            this.Colors0 = _IsolateColumn(this.Colors0);
+        }
+
+        #endregion
+    }
+}

+ 96 - 84
src/SharpGLTF.Toolkit/Geometry/VertexColumns.cs → src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -12,16 +12,16 @@ namespace SharpGLTF.Geometry
     /// Represents a vertex buffer, where every vertex attribute is represented as a vector column.
     /// </summary>
     /// <remarks>
-    /// One of the use cases of <see cref="VertexColumns"/> is to bind the different attribute
+    /// One of the use cases of <see cref="VertexBufferColumns"/> is to bind the different attribute
     /// columns directly to the <see cref="Schema2.Accessor"/> source feed, which means that
     /// if you modify the contents of a column that is binded directly to a model, you're
     /// modifying the model's internal data.
     /// </remarks>
-    public class VertexColumns
+    public class VertexBufferColumns
     {
         #region Data Columns
 
-#pragma warning disable CA2227 // Collection properties should be read only
+        #pragma warning disable CA2227 // Collection properties should be read only
 
         public IList<Vector3> Positions { get; set; }
         public IList<Vector3> Normals { get; set; }
@@ -30,8 +30,8 @@ namespace SharpGLTF.Geometry
         public IList<Vector4> Colors0 { get; set; }
         public IList<Vector4> Colors1 { get; set; }
 
-        public IList<Vector2> Textures0 { get; set; }
-        public IList<Vector2> Textures1 { get; set; }
+        public IList<Vector2> TexCoords0 { get; set; }
+        public IList<Vector2> TexCoords1 { get; set; }
 
         public IList<Vector4> Joints0 { get; set; }
         public IList<Vector4> Joints1 { get; set; }
@@ -39,105 +39,99 @@ namespace SharpGLTF.Geometry
         public IList<Vector4> Weights0 { get; set; }
         public IList<Vector4> Weights1 { get; set; }
 
-        public class MorphTarget
-        {
-            public IList<Vector3> Positions { get; set; }
-            public IList<Vector3> Normals { get; set; }
-            public IList<Vector3> Tangents { get; set; }
-        }
-
-#pragma warning restore CA2227 // Collection properties should be read only
+        #pragma warning restore CA2227 // Collection properties should be read only
 
-        private List<MorphTarget> _MorphTargets;
+        private List<MorphTargetColumns> _MorphTargets;
 
-        private static readonly IReadOnlyList<MorphTarget> _EmptyMorphTargets = new MorphTarget[0];
-
-        public IReadOnlyList<MorphTarget> MorphTargets => _MorphTargets == null ? _EmptyMorphTargets : _MorphTargets;
+        public IReadOnlyList<MorphTargetColumns> MorphTargets => _MorphTargets == null ? (IReadOnlyList<MorphTargetColumns>)Array.Empty<MorphTargetColumns>() : _MorphTargets;
 
         #endregion
 
         #region API
 
-        /// <summary>
-        /// Creates an internal copy of the <see cref="Positions"/> columns,
-        /// which ensures the column data is not shared with other objects
-        /// </summary>
-        public void IsolatePositions()
-        {
-            if (this.Positions == null) return;
-
-            var newPositions = new Vector3[this.Positions.Count];
-
-            this.Positions.CopyTo(newPositions, 0);
-
-            this.Positions = newPositions;
-        }
-
-        /// <summary>
-        /// Creates an internal copy of the <see cref="Normals"/> columns,
-        /// which ensures the column data is not shared with other objects
-        /// </summary>
-        public void IsolateNormals()
+        private static IList<T> _IsolateColumn<T>(IList<T> column)
         {
-            if (this.Normals == null) return;
+            if (column == null) return null;
 
-            var newNormals = new Vector3[this.Normals.Count];
+            var newColumn = new T[column.Count];
 
-            this.Normals.CopyTo(newNormals, 0);
+            column.CopyTo(newColumn, 0);
 
-            this.Normals = newNormals;
+            return newColumn;
         }
 
         /// <summary>
-        /// Creates an internal copy of the <see cref="Tangents"/> columns,
-        /// which ensures the column data is not shared with other objects
+        /// Performs an in-place copy of the contents of every column,
+        /// which guarantees that the columns of this <see cref="VertexBufferColumns"/>
+        /// are not shared by any other object and can be modified safely.
         /// </summary>
-        public void IsolateTangents()
+        public void IsolateColumns()
         {
-            if (this.Tangents == null) return;
+            this.Positions = _IsolateColumn(this.Positions);
+            this.Normals = _IsolateColumn(this.Normals);
+            this.Tangents = _IsolateColumn(this.Tangents);
 
-            var newTangents = new Vector4[this.Tangents.Count];
+            this.Colors0 = _IsolateColumn(this.Colors0);
+            this.Colors1 = _IsolateColumn(this.Colors1);
 
-            this.Tangents.CopyTo(newTangents, 0);
+            this.TexCoords0 = _IsolateColumn(this.TexCoords0);
+            this.TexCoords1 = _IsolateColumn(this.TexCoords1);
 
-            this.Tangents = newTangents;
-        }
+            this.Joints0 = _IsolateColumn(this.Joints0);
+            this.Joints1 = _IsolateColumn(this.Joints1);
 
-        public void ApplyNormals(IReadOnlyDictionary<Vector3, Vector3> normalsMap)
-        {
-            IsolateNormals();
+            this.Weights0 = _IsolateColumn(this.Weights0);
+            this.Weights1 = _IsolateColumn(this.Weights1);
 
-            if (this.Normals == null) this.Normals = new Vector3[Positions.Count];
+            if (_MorphTargets == null) return;
 
-            for (int i = 0; i < Normals.Count; ++i)
-            {
-                if (normalsMap.TryGetValue(this.Positions[i], out Vector3 nrm))
-                {
-                    this.Normals[i] = nrm;
-                }
-            }
+            foreach (var mt in _MorphTargets) mt.IsolateColumns();
         }
 
+        /// <summary>
+        /// Applies a transform to the columns of this <see cref="VertexBufferColumns"/>
+        /// </summary>
+        /// <param name="transform">A Mesh transformer object</param>
+        /// <remarks>
+        /// This is a one time operation.
+        /// Once it's applied, skinning and morphing columns are removed, since they're baked
+        /// into the position, normal and tangent columns.
+        /// </remarks>
         public void ApplyTransform(Transforms.ITransform transform)
         {
-            // since positions, normals and tangents might be binded directly to the model's buffer data,
-            // and we don't want to modify the source data, we create a copy of the columns.
-
-            IsolatePositions();
-            IsolateNormals();
-            IsolateTangents();
+            Guard.NotNull(this.Positions, nameof(this.Positions), "Missing Positions column");
+            if (this.Normals != null) Guard.IsTrue(this.Positions.Count == this.Normals.Count, nameof(this.Normals), "Column length mismatch.");
+            if (this.Tangents != null) Guard.IsTrue(this.Positions.Count == this.Tangents.Count, nameof(this.Tangents), "Column length mismatch.");
+            if (this.Colors0 != null) Guard.IsTrue(this.Positions.Count == this.Colors0.Count, nameof(this.Colors0), "Column length mismatch.");
+            if (this.Colors1 != null) Guard.IsTrue(this.Positions.Count == this.Colors1.Count, nameof(this.Colors1), "Column length mismatch.");
+            if (this.TexCoords0 != null) Guard.IsTrue(this.Positions.Count == this.TexCoords0.Count, nameof(this.TexCoords0), "Column length mismatch.");
+            if (this.TexCoords1 != null) Guard.IsTrue(this.Positions.Count == this.TexCoords1.Count, nameof(this.TexCoords1), "Column length mismatch.");
+            if (this.Joints0 != null) Guard.IsTrue(this.Positions.Count == this.Joints0.Count, nameof(this.Joints0), "Column length mismatch.");
+            if (this.Joints1 != null) Guard.IsTrue(this.Positions.Count == this.Joints1.Count, nameof(this.Joints1), "Column length mismatch.");
+            if (this.Weights0 != null) Guard.IsTrue(this.Positions.Count == this.Weights0.Count, nameof(this.Weights0), "Column length mismatch.");
+            if (this.Weights1 != null) Guard.IsTrue(this.Positions.Count == this.Weights1.Count, nameof(this.Weights1), "Column length mismatch.");
+
+            // since the attributes we want to overwrite might be binded directly to the model's buffer
+            // data, and we don't want to modify the source data, we isolate the columns to be overwritten.
+
+            this.Positions = _IsolateColumn(this.Positions);
+            this.Normals = _IsolateColumn(this.Normals);
+            this.Tangents = _IsolateColumn(this.Tangents);
+            this.Colors0 = _IsolateColumn(this.Colors0);
 
             // prepare morph data, if available
 
             Vector3[] morphPositions = null;
             Vector3[] morphNormals = null;
             Vector3[] morphTangents = null;
+            Vector4[] morphColors0 = null;
 
             if (_MorphTargets != null)
             {
                 if (_MorphTargets.All(item => item.Positions != null)) morphPositions = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Normals != null)) morphNormals = new Vector3[this.MorphTargets.Count];
                 if (_MorphTargets.All(item => item.Tangents != null)) morphTangents = new Vector3[this.MorphTargets.Count];
+                if (_MorphTargets.All(item => item.Colors0 != null)) morphColors0 = new Vector4[this.MorphTargets.Count];
             }
 
             // prepare skinning data, if available
@@ -145,7 +139,9 @@ namespace SharpGLTF.Geometry
             var jw0 = Joints0 != null && Weights0 != null;
             var jw1 = Joints1 != null && Weights1 != null;
 
-            var jwjwjwjw = new (int, float)[(jw0 ? 4 : 0) + (jw1 ? 4 : 0)];
+            var skinning = new (int, float)[(jw0 ? 4 : 0) + (jw1 ? 4 : 0)];
+
+            // loop over every vertex
 
             int vcount = Positions.Count;
 
@@ -155,38 +151,44 @@ namespace SharpGLTF.Geometry
                 {
                     var j = Joints0[i];
                     var w = Weights0[i];
-                    jwjwjwjw[0] = ((int)j.X, w.X);
-                    jwjwjwjw[1] = ((int)j.Y, w.Y);
-                    jwjwjwjw[2] = ((int)j.Z, w.Z);
-                    jwjwjwjw[3] = ((int)j.W, w.W);
+                    skinning[0] = ((int)j.X, w.X);
+                    skinning[1] = ((int)j.Y, w.Y);
+                    skinning[2] = ((int)j.Z, w.Z);
+                    skinning[3] = ((int)j.W, w.W);
                 }
 
                 if (jw1)
                 {
                     var j = Joints1[i];
                     var w = Weights1[i];
-                    jwjwjwjw[4] = ((int)j.X, w.X);
-                    jwjwjwjw[5] = ((int)j.Y, w.Y);
-                    jwjwjwjw[6] = ((int)j.Z, w.Z);
-                    jwjwjwjw[7] = ((int)j.W, w.W);
+                    skinning[4] = ((int)j.X, w.X);
+                    skinning[5] = ((int)j.Y, w.Y);
+                    skinning[6] = ((int)j.Z, w.Z);
+                    skinning[7] = ((int)j.W, w.W);
                 }
 
                 if (Positions != null)
                 {
                     _FillMorphData(morphPositions, vc => vc.Positions[i]);
-                    Positions[i] = transform.TransformPosition(Positions[i], morphPositions, jwjwjwjw);
+                    Positions[i] = transform.TransformPosition(Positions[i], morphPositions, skinning);
                 }
 
                 if (Normals != null)
                 {
                     _FillMorphData(morphNormals, vc => vc.Normals[i]);
-                    Normals[i] = transform.TransformNormal(Normals[i], morphNormals, jwjwjwjw);
+                    Normals[i] = transform.TransformNormal(Normals[i], morphNormals, skinning);
                 }
 
                 if (Tangents != null)
                 {
                     _FillMorphData(morphTangents, vc => vc.Tangents[i]);
-                    Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, jwjwjwjw);
+                    Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, skinning);
+                }
+
+                if (Colors0 != null)
+                {
+                    _FillMorphData(morphColors0, vc => vc.Colors0[i]);
+                    Colors0[i] = transform.MorphColors(Colors0[i], morphColors0);
                 }
             }
 
@@ -201,7 +203,17 @@ namespace SharpGLTF.Geometry
             Weights1 = null;
         }
 
-        private void _FillMorphData(Vector3[] array, Func<MorphTarget, Vector3> selector)
+        private void _FillMorphData(Vector3[] array, Func<MorphTargetColumns, Vector3> selector)
+        {
+            if (array == null) return;
+
+            for (int i = 0; i < this._MorphTargets.Count; ++i)
+            {
+                array[i] = selector(this._MorphTargets[i]);
+            }
+        }
+
+        private void _FillMorphData(Vector4[] array, Func<MorphTargetColumns, Vector4> selector)
         {
             if (array == null) return;
 
@@ -231,8 +243,8 @@ namespace SharpGLTF.Geometry
             if (Colors0 != null && cctt.MaxColors > 0) cctt.SetColor(0, Colors0[index]);
             if (Colors1 != null && cctt.MaxColors > 1) cctt.SetColor(1, Colors1[index]);
 
-            if (Textures0 != null && cctt.MaxTextCoords > 0) cctt.SetTexCoord(0, Textures0[index]);
-            if (Textures1 != null && cctt.MaxTextCoords > 1) cctt.SetTexCoord(1, Textures1[index]);
+            if (TexCoords0 != null && cctt.MaxTextCoords > 0) cctt.SetTexCoord(0, TexCoords0[index]);
+            if (TexCoords1 != null && cctt.MaxTextCoords > 1) cctt.SetTexCoord(1, TexCoords1[index]);
 
             return cctt;
         }
@@ -291,10 +303,10 @@ namespace SharpGLTF.Geometry
                 );
         }
 
-        public MorphTarget AddMorphTarget()
+        public MorphTargetColumns AddMorphTarget()
         {
-            if (_MorphTargets == null) _MorphTargets = new List<MorphTarget>();
-            var mt = new MorphTarget();
+            if (_MorphTargets == null) _MorphTargets = new List<MorphTargetColumns>();
+            var mt = new MorphTargetColumns();
             _MorphTargets.Add(mt);
 
             return mt;

+ 8 - 6
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -335,9 +335,9 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        public static VertexColumns GetVertexColumns(this MeshPrimitive primitive)
+        public static VertexBufferColumns GetVertexColumns(this MeshPrimitive primitive)
         {
-            var columns = new VertexColumns();
+            var columns = new VertexBufferColumns();
 
             _CopyTo(primitive.VertexAccessors, columns);
 
@@ -349,7 +349,7 @@ namespace SharpGLTF.Schema2
             return columns;
         }
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexColumns dstColumns)
+        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexBufferColumns dstColumns)
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
@@ -358,8 +358,8 @@ namespace SharpGLTF.Schema2
             if (vertexAccessors.ContainsKey("COLOR_0")) dstColumns.Colors0 = vertexAccessors["COLOR_0"].AsColorArray();
             if (vertexAccessors.ContainsKey("COLOR_1")) dstColumns.Colors1 = vertexAccessors["COLOR_1"].AsColorArray();
 
-            if (vertexAccessors.ContainsKey("TEXCOORD_0")) dstColumns.Textures0 = vertexAccessors["TEXCOORD_0"].AsVector2Array();
-            if (vertexAccessors.ContainsKey("TEXCOORD_1")) dstColumns.Textures1 = vertexAccessors["TEXCOORD_1"].AsVector2Array();
+            if (vertexAccessors.ContainsKey("TEXCOORD_0")) dstColumns.TexCoords0 = vertexAccessors["TEXCOORD_0"].AsVector2Array();
+            if (vertexAccessors.ContainsKey("TEXCOORD_1")) dstColumns.TexCoords1 = vertexAccessors["TEXCOORD_1"].AsVector2Array();
 
             if (vertexAccessors.ContainsKey("JOINTS_0")) dstColumns.Joints0 = vertexAccessors["JOINTS_0"].AsVector4Array();
             if (vertexAccessors.ContainsKey("JOINTS_1")) dstColumns.Joints1 = vertexAccessors["JOINTS_1"].AsVector4Array();
@@ -368,11 +368,13 @@ namespace SharpGLTF.Schema2
             if (vertexAccessors.ContainsKey("WEIGHTS_1")) dstColumns.Weights1 = vertexAccessors["WEIGHTS_1"].AsVector4Array();
         }
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexColumns.MorphTarget dstColumns)
+        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, MorphTargetColumns dstColumns)
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
             if (vertexAccessors.ContainsKey("TANGENT")) dstColumns.Tangents = vertexAccessors["TANGENT"].AsVector3Array();
+
+            if (vertexAccessors.ContainsKey("COLOR_0")) dstColumns.Colors0 = vertexAccessors["COLOR_0"].AsVector4Array();
         }
 
         public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)