Browse Source

WIP: morph targets builder

Vicente Penades 6 years ago
parent
commit
de89d3bc62

+ 3 - 3
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -7,9 +7,9 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="LibGit2Sharp" Version="0.26.0" />
-    <PackageReference Include="NJsonSchema.CodeGeneration" Version="10.0.21" />
-    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.0.21" />
+    <PackageReference Include="LibGit2Sharp" Version="0.26.1" />
+    <PackageReference Include="NJsonSchema.CodeGeneration" Version="10.0.23" />
+    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.0.23" />
   </ItemGroup>
 
 </Project>

+ 12 - 1
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -262,8 +262,19 @@ namespace SharpGLTF.IO
                 writer.WriteStartObject();
                 foreach (var key in dict.Keys)
                 {
+                    var val = dict[key];
+                    if (val == null) continue;
+
+                    // if the value is a collection, we need to check if the collection is empty
+                    // to prevent writing the key, without writing the value.
+                    if (!(val is String || val is JsonSerializable))
+                    {
+                        if (val is System.Collections.IList xlist && xlist.Count == 0) continue;
+                        if (val is System.Collections.IDictionary xdict && xdict.Count == 0) continue;
+                    }
+
                     writer.WritePropertyName(key.ToString());
-                    _Serialize(writer, dict[key]);
+                    _Serialize(writer, val);
                 }
 
                 writer.WriteEndObject();

+ 15 - 6
src/SharpGLTF.Core/Memory/ColorArray.cs

@@ -31,8 +31,10 @@ namespace SharpGLTF.Memory
         /// <param name="dimensions">The number of elements per item. Currently only values 3 and 4 are supported.</param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="normalized">True if values are normalized.</param>
-        public ColorArray(Byte[] source, int byteOffset, int itemsCount, int byteStride, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(new BYTES(source), byteOffset, itemsCount, byteStride, dimensions, encoding, normalized) { }
+        /// <param name="defaultW">If <paramref name="dimensions"/> is 3, the W values are filled with this value</param>
+        public ColorArray(Byte[] source, int byteOffset, int itemsCount, int byteStride, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false, Single defaultW = 1)
+            : this(new BYTES(source), byteOffset, itemsCount, byteStride, dimensions, encoding, normalized)
+        { }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ColorArray"/> struct.
@@ -45,8 +47,10 @@ namespace SharpGLTF.Memory
         /// <param name="dimensions">The number of elements per item. Currently only values 3 and 4 are supported.</param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="normalized">True if values are normalized.</param>
-        public ColorArray(BYTES source, int byteStride = 0, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(source, 0, int.MaxValue, byteStride, dimensions, encoding, normalized) { }
+        /// <param name="defaultW">If <paramref name="dimensions"/> is 3, the W values are filled with this value</param>
+        public ColorArray(BYTES source, int byteStride = 0, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false, Single defaultW = 1)
+            : this(source, 0, int.MaxValue, byteStride, dimensions, encoding, normalized, defaultW)
+        { }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ColorArray"/> struct.
@@ -61,12 +65,15 @@ namespace SharpGLTF.Memory
         /// <param name="dimensions">The number of elements per item. Currently only values 3 and 4 are supported.</param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="normalized">True if values are normalized.</param>
-        public ColorArray(BYTES source, int byteOffset, int itemsCount, int byteStride, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+        /// <param name="defaultW">If <paramref name="dimensions"/> is 3, the W values are filled with this value</param>
+        public ColorArray(BYTES source, int byteOffset, int itemsCount, int byteStride, int dimensions = 4, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false, Single defaultW = 1)
         {
             Guard.MustBeBetweenOrEqualTo(dimensions, 3, 4, nameof(dimensions));
 
             _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, dimensions, encoding, normalized);
             _Dimensions = dimensions;
+
+            _DefaultW = defaultW;
         }
 
         #endregion
@@ -81,6 +88,8 @@ namespace SharpGLTF.Memory
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Vector4[] _DebugItems => this.ToArray();
 
+        private readonly float _DefaultW;
+
         #endregion
 
         #region API
@@ -94,7 +103,7 @@ namespace SharpGLTF.Memory
         {
             get
             {
-                return new Vector4(_Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2], _Dimensions < 4 ? 1 : _Accessor[index, 3]);
+                return new Vector4(_Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2], _Dimensions < 4 ? _DefaultW : _Accessor[index, 3]);
             }
 
             set

+ 4 - 4
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -244,7 +244,7 @@ namespace SharpGLTF.Memory
             return new SparseArray<Vector4>(bottom.AsVector4Array(), topValues.AsVector4Array(), topKeys);
         }
 
-        public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
+        public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues, Single defaultW = 1)
         {
             Guard.NotNull(bottom, nameof(bottom));
             Guard.NotNull(topValues, nameof(topValues));
@@ -253,7 +253,7 @@ namespace SharpGLTF.Memory
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
-            return new SparseArray<Vector4>(bottom.AsColorArray(), topValues.AsColorArray(), topKeys);
+            return new SparseArray<Vector4>(bottom.AsColorArray(defaultW), topValues.AsColorArray(defaultW), topKeys);
         }
 
         #endregion
@@ -310,11 +310,11 @@ namespace SharpGLTF.Memory
             return new Vector4Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
-        public ColorArray AsColorArray()
+        public ColorArray AsColorArray(Single defaultW = 1)
         {
             Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
             Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.VEC3 || _Attribute.Dimensions == DIMENSIONS.VEC4, nameof(_Attribute));
-            return new ColorArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Dimensions.DimCount(), _Attribute.Encoding, _Attribute.Normalized);
+            return new ColorArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Dimensions.DimCount(), _Attribute.Encoding, _Attribute.Normalized, defaultW);
         }
 
         public QuaternionArray AsQuaternionArray()

+ 3 - 3
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -247,14 +247,14 @@ namespace SharpGLTF.Schema2
             return MemoryAccessor.CreateVector4SparseArray(memory, sparseKV.Key, sparseKV.Value);
         }
 
-        public IList<Vector4> AsColorArray()
+        public IList<Vector4> AsColorArray(Single defaultW = 1)
         {
             var memory = _GetMemoryAccessor();
 
-            if (this._sparse == null) return memory.AsColorArray();
+            if (this._sparse == null) return memory.AsColorArray(defaultW);
 
             var sparseKV = this._sparse._CreateMemoryAccessors(this);
-            return MemoryAccessor.CreateColorSparseArray(memory, sparseKV.Key, sparseKV.Value);
+            return MemoryAccessor.CreateColorSparseArray(memory, sparseKV.Key, sparseKV.Value, defaultW);
         }
 
         public IList<Quaternion> AsQuaternionArray()

+ 113 - 22
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -6,7 +6,16 @@ using SharpGLTF.Geometry.VertexTypes;
 
 namespace SharpGLTF.Geometry
 {
-    public class MorphTargetBuilder<TvG>
+    public interface IMorphTargetReader
+    {
+        int TargetsCount { get; }
+
+        IReadOnlyCollection<int> GetTargetIndices(int morphTargetIndex);
+
+        IVertexGeometry GetVertexDisplacement(int morphTargetIndex, int vertexIndex);
+    }
+
+    public class MorphTargetBuilder<TvG> : IMorphTargetReader
         where TvG : struct, IVertexGeometry
     {
         #region lifecycle
@@ -27,7 +36,7 @@ namespace SharpGLTF.Geometry
 
         #region properties
 
-        public int Count => _Targets.Count;
+        public int TargetsCount => _Targets.Count;
 
         public Boolean AbsoluteMode => true;
 
@@ -35,27 +44,76 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        public IReadOnlyDictionary<int, TvG> GetTarget(int idx) { return _Targets[idx]; }
+        public IReadOnlyCollection<int> GetTargetIndices(int morphTargetIndex)
+        {
+            return morphTargetIndex < _Targets.Count ? _Targets[morphTargetIndex].Keys : (IReadOnlyCollection<int>)Array.Empty<int>();
+        }
+
+        IVertexGeometry IMorphTargetReader.GetVertexDisplacement(int morphTargetIndex, int vertexIndex)
+        {
+            return _GetVertexDisplacement(morphTargetIndex, vertexIndex);
+        }
+
+        TvG GetVertexDisplacement(int morphTargetIndex, int vertexIndex)
+        {
+            return _GetVertexDisplacement(morphTargetIndex, vertexIndex);
+        }
+
+        private TvG _GetVertexDisplacement(int morphTargetIndex, int vertexIndex)
+        {
+            var target = _Targets[morphTargetIndex];
+
+            if (target.TryGetValue(vertexIndex, out TvG value))
+            {
+                if (this.AbsoluteMode) value = (TvG)value.ToDisplaceMorph(_BaseVertexFunc(vertexIndex));
+
+                return value;
+            }
+
+            return default;
+        }
+
+        public TvG GetVertex(int morphTargetIndex, int vertexIndex)
+        {
+            var target = _Targets[morphTargetIndex];
+
+            if (target.TryGetValue(vertexIndex, out TvG value))
+            {
+                if (!this.AbsoluteMode) value = (TvG)value.ToAbsoluteMorph(_BaseVertexFunc(vertexIndex));
+
+                return value;
+            }
+
+            return _BaseVertexFunc(vertexIndex);
+        }
 
-        public void SetRelativeVertex(int morphTargetIndex, int vertexIndex, TvG value)
+        public void SetVertexDisplacement(int morphTargetIndex, int vertexIndex, TvG value)
         {
-            if (AbsoluteMode)
+            if (object.Equals(value, default(TvG)))
             {
-                // value = value.ToAbsoluteMorph(_BaseVertexFunc(vertexIndex));
+                _RemoveVertex(morphTargetIndex, vertexIndex);
+                return;
+            }
 
-                throw new NotImplementedException();
+            if (this.AbsoluteMode)
+            {
+                value = (TvG)value.ToAbsoluteMorph(_BaseVertexFunc(vertexIndex));
             }
 
             _SetVertex(morphTargetIndex, vertexIndex, value);
         }
 
-        public void SetAbsoluteVertex(int morphTargetIndex, int vertexIndex, TvG value)
+        public void SetVertex(int morphTargetIndex, int vertexIndex, TvG value)
         {
-            if (!AbsoluteMode)
+            if (object.Equals(value, _BaseVertexFunc(vertexIndex)))
             {
-                // value = value.ToRelativeMorph(_BaseVertexFunc(vertexIndex));
+                _RemoveVertex(morphTargetIndex, vertexIndex);
+                return;
+            }
 
-                throw new NotImplementedException();
+            if (!this.AbsoluteMode)
+            {
+                value = (TvG)value.ToDisplaceMorph(_BaseVertexFunc(vertexIndex));
             }
 
             _SetVertex(morphTargetIndex, vertexIndex, value);
@@ -71,25 +129,58 @@ namespace SharpGLTF.Geometry
             _Targets[morphTargetIndex][vertexIndex] = value;
         }
 
-        public void AddAbsoluteVertices<TvM, TvS>(int morphTargetIndex, IReadOnlyDictionary<int, TvG> vertices, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
-            where TvM : struct, IVertexMaterial
-            where TvS : struct, IVertexSkinning
+        private void _RemoveVertex(int morphTargetIndex, int vertexIndex)
+        {
+            if (morphTargetIndex >= _Targets.Count) return;
+
+            _Targets[morphTargetIndex].Remove(vertexIndex);
+        }
+
+        #endregion
+
+        #region internals
+
+        internal void TransformVertices(Func<TvG, TvG> vertexFunc)
         {
-            foreach (var kvp in vertices)
+            for (int tidx = 0; tidx < _Targets.Count; ++tidx)
             {
-                if (!AbsoluteMode)
+                var target = _Targets[tidx];
+
+                foreach (var vidx in target.Keys)
                 {
-                    throw new NotImplementedException();
-                    // TODO: if vertices are in RELATIVE MODE, we must convert them to absolute mode, transform, and back to RELATIVE
-                }
+                    var g = GetVertex(tidx, vidx);
 
-                var v = new VertexBuilder<TvG, TvM, TvS>(kvp.Value, default(TvM), default(TvS));
-                v = vertexTransform(v);
+                    g = vertexFunc(g);
 
-                this.SetAbsoluteVertex(morphTargetIndex, kvp.Key, v.Geometry);
+                    SetVertex(tidx, vidx, g);
+                }
             }
         }
 
+        internal void SetMorphTargets(MorphTargetBuilder<TvG> other, IReadOnlyDictionary<int, int> vertexMap, Func<TvG, TvG> vertexFunc)
+        {
+            for (int tidx = 0; tidx < other.TargetsCount; ++tidx)
+            {
+                var indices = other.GetTargetIndices(tidx);
+
+                foreach (var srcVidx in indices)
+                {
+                    var g = other.GetVertex(tidx, srcVidx);
+
+                    if (vertexFunc != null) g = vertexFunc(g);
+
+                    var dstVidx = srcVidx;
+
+                    if (vertexMap != null)
+                    {
+                        if (!vertexMap.TryGetValue(srcVidx, out dstVidx)) dstVidx = -1;
+                    }
+
+                    if (dstVidx >= 0) this.SetVertex(tidx, dstVidx, g);
+                }
+            }
+        }
+        
         #endregion
     }
 }

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

@@ -1,67 +0,0 @@
-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
-    }
-}

+ 71 - 42
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -27,6 +27,8 @@ namespace SharpGLTF.Geometry
         /// </summary>
         IReadOnlyList<IVertexBuilder> Vertices { get; }
 
+        IMorphTargetReader MorphTargets { get; }
+
         /// <summary>
         /// Gets the indices of all points, given that <see cref="VerticesPerPrimitive"/> is 1.
         /// </summary>
@@ -61,6 +63,8 @@ namespace SharpGLTF.Geometry
         /// </summary>
         Type VertexType { get; }
 
+        void SetVertexDisplacement(int morphTargetIndex, int vertexIndex, IVertexGeometry vertex);
+
         int AddPoint(IVertexBuilder a);
 
         (int, int) AddLine(IVertexBuilder a, IVertexBuilder b);
@@ -159,22 +163,23 @@ namespace SharpGLTF.Geometry
 
         public MorphTargetBuilder<TvG> MorphTargets => _MorphTargets;
 
+        IMorphTargetReader IPrimitiveReader<TMaterial>.MorphTargets => _MorphTargets;
+
         #endregion
 
         #region API
 
         /// <summary>
-        /// Checks if <paramref name="v"/> is a compatible vertex and casts it, or converts it if it is not.
+        /// Checks if <paramref name="vertex"/> is a compatible vertex and casts it, or converts it if it is not.
         /// </summary>
-        /// <param name="v">Any vertex</param>
+        /// <param name="vertex">Any vertex</param>
         /// <returns>A vertex compatible with this primitive.</returns>
-        private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder v)
+        private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder vertex)
         {
-            Guard.NotNull(v, nameof(v));
+            Guard.NotNull(vertex, nameof(vertex));
 
-            var vv = v.ConvertTo<TvG, TvM, TvS>();
-
-            System.Diagnostics.Debug.Assert(vv.Position == v.GetGeometry().GetPosition());
+            var vv = vertex.ConvertTo<TvG, TvM, TvS>();
+            System.Diagnostics.Debug.Assert(vv.Position == vertex.GetGeometry().GetPosition());
 
             return vv;
         }
@@ -194,6 +199,16 @@ namespace SharpGLTF.Geometry
             return _Vertices.Use(vertex);
         }
 
+        void IPrimitiveBuilder.SetVertexDisplacement(int morphTargetIndex, int vertexIndex, IVertexGeometry vertex)
+        {
+            Guard.NotNull(vertex, nameof(vertex));
+
+            var v = vertex.ConvertToGeometry<TvG>();
+            System.Diagnostics.Debug.Assert(v.GetPosition() == vertex.GetPosition());
+
+            this._MorphTargets.SetVertexDisplacement(morphTargetIndex, vertexIndex, v);
+        }
+
         /// <summary>
         /// Adds a point.
         /// </summary>
@@ -244,74 +259,88 @@ namespace SharpGLTF.Geometry
             return AddQuadrangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c), ConvertVertex(d));
         }
 
-        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
+        public void Validate()
+        {
+            foreach (var v in _Vertices)
+            {
+                v.Validate();
+            }
+        }
+
+        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
+        {
+            _Vertices.TransformVertices(vertexTransformFunc);
+
+            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
+
+            _MorphTargets.TransformVertices(geoFunc);
+        }
+
+        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
         {
             if (primitive == null) return;
 
+            // vertex-vertex map so we can know where to set the morph targets.
+            var vmap = new Dictionary<int, int>();
+
             if (this.VerticesPerPrimitive == 1)
             {
                 foreach (var p in primitive.Points)
                 {
-                    var a = vertexTransform(primitive.Vertices[p]);
+                    var a = vertexTransformFunc(primitive.Vertices[p]);
 
-                    AddPoint(a);
-                }
+                    var idx = AddPoint(a);
 
-                return;
+                    vmap[p] = idx;
+                }
             }
 
             if (this.VerticesPerPrimitive == 2)
             {
                 foreach (var l in primitive.Lines)
                 {
-                    var a = vertexTransform(primitive.Vertices[l.Item1]);
-                    var b = vertexTransform(primitive.Vertices[l.Item2]);
+                    var a = vertexTransformFunc(primitive.Vertices[l.Item1]);
+                    var b = vertexTransformFunc(primitive.Vertices[l.Item2]);
 
-                    AddLine(a, b);
-                }
+                    var indices = AddLine(a, b);
 
-                return;
+                    vmap[l.Item1] = indices.Item1;
+                    vmap[l.Item2] = indices.Item2;
+                }
             }
 
             if (this.VerticesPerPrimitive == 3)
             {
                 foreach (var s in primitive.Surfaces)
                 {
-                    var a = vertexTransform(primitive.Vertices[s.Item1]);
-                    var b = vertexTransform(primitive.Vertices[s.Item2]);
-                    var c = vertexTransform(primitive.Vertices[s.Item3]);
+                    var a = vertexTransformFunc(primitive.Vertices[s.Item1]);
+                    var b = vertexTransformFunc(primitive.Vertices[s.Item2]);
+                    var c = vertexTransformFunc(primitive.Vertices[s.Item3]);
 
                     if (s.Item4.HasValue)
                     {
-                        var d = vertexTransform(primitive.Vertices[s.Item4.Value]);
-                        AddQuadrangle(a, b, c, d);
+                        var d = vertexTransformFunc(primitive.Vertices[s.Item4.Value]);
+                        var indices = AddQuadrangle(a, b, c, d);
+
+                        vmap[s.Item1] = indices.Item1;
+                        vmap[s.Item2] = indices.Item2;
+                        vmap[s.Item3] = indices.Item3;
+                        vmap[s.Item4.Value] = indices.Item4;
                     }
                     else
                     {
-                        AddTriangle(a, b, c);
+                        var indices = AddTriangle(a, b, c);
+
+                        vmap[s.Item1] = indices.Item1;
+                        vmap[s.Item2] = indices.Item2;
+                        vmap[s.Item3] = indices.Item3;
                     }
                 }
-
-                return;
             }
 
-            for (int i = 0; i < primitive._MorphTargets.Count; ++i)
-            {
-                this._MorphTargets.AddAbsoluteVertices<TvM, TvS>(i, primitive._MorphTargets.GetTarget(i), vertexTransform);
-            }
-        }
+            TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
 
-        public void Validate()
-        {
-            foreach (var v in _Vertices)
-            {
-                v.Validate();
-            }
-        }
-
-        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> transformFunc)
-        {
-            _Vertices.TransformVertices(transformFunc);
+            _MorphTargets.SetMorphTargets(primitive._MorphTargets, vmap, geoFunc);
         }
 
         #endregion
@@ -365,7 +394,7 @@ namespace SharpGLTF.Geometry
         {
             throw new NotSupportedException("Quadrangles are not supported for this primitive");
         }
-
+        
         #endregion
 
         #region helper types

+ 18 - 7
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -47,9 +47,9 @@ namespace SharpGLTF.Geometry
 
         #pragma warning restore CA2227 // Collection properties should be read only
 
-        private List<MorphTargetColumns> _MorphTargets;
+        private List<VertexBufferColumns> _MorphTargets;
 
-        public IReadOnlyList<MorphTargetColumns> MorphTargets => _MorphTargets == null ? (IReadOnlyList<MorphTargetColumns>)Array.Empty<MorphTargetColumns>() : _MorphTargets;
+        public IReadOnlyList<VertexBufferColumns> MorphTargets => _MorphTargets == null ? (IReadOnlyList<VertexBufferColumns>)Array.Empty<VertexBufferColumns>() : _MorphTargets;
 
         #endregion
 
@@ -191,7 +191,7 @@ namespace SharpGLTF.Geometry
             Weights1 = null;
         }
 
-        private void _FillMorphData(Vector3[] array, Func<MorphTargetColumns, Vector3> selector)
+        private void _FillMorphData(Vector3[] array, Func<VertexBufferColumns, Vector3> selector)
         {
             if (array == null) return;
 
@@ -201,7 +201,18 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        private void _FillMorphData(Vector4[] array, Func<MorphTargetColumns, Vector4> selector)
+        private void _FillMorphData(Vector3[] array, Func<VertexBufferColumns, Vector4> selector)
+        {
+            if (array == null) return;
+
+            for (int i = 0; i < this._MorphTargets.Count; ++i)
+            {
+                var v = selector(this._MorphTargets[i]);
+                array[i] = new Vector3(v.X, v.Y, v.Z);
+            }
+        }
+
+        private void _FillMorphData(Vector4[] array, Func<VertexBufferColumns, Vector4> selector)
         {
             if (array == null) return;
 
@@ -211,10 +222,10 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        public MorphTargetColumns AddMorphTarget()
+        public VertexBufferColumns AddMorphTarget()
         {
-            if (_MorphTargets == null) _MorphTargets = new List<MorphTargetColumns>();
-            var mt = new MorphTargetColumns();
+            if (_MorphTargets == null) _MorphTargets = new List<VertexBufferColumns>();
+            var mt = new VertexBufferColumns();
             _MorphTargets.Add(mt);
 
             return mt;

+ 39 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs

@@ -18,6 +18,9 @@ namespace SharpGLTF.Geometry.VertexTypes
         void SetTangent(Vector4 tangent);
 
         void ApplyTransform(Matrix4x4 xform);
+
+        IVertexGeometry ToAbsoluteMorph(IVertexGeometry baseValue);
+        IVertexGeometry ToDisplaceMorph(IVertexGeometry baseValue);
     }
 
     /// <summary>
@@ -66,6 +69,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         void IVertexGeometry.SetTangent(Vector4 tangent) { }
 
+        IVertexGeometry IVertexGeometry.ToAbsoluteMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPosition)baseValue;
+            return new VertexPosition(this.Position + bv.Position);
+        }
+
+        IVertexGeometry IVertexGeometry.ToDisplaceMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPosition)baseValue;
+            return new VertexPosition(this.Position - bv.Position);
+        }
+
         public Vector3 GetPosition() { return this.Position; }
 
         public bool TryGetNormal(out Vector3 normal) { normal = default; return false; }
@@ -135,6 +150,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         void IVertexGeometry.SetTangent(Vector4 tangent) { }
 
+        IVertexGeometry IVertexGeometry.ToAbsoluteMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPositionNormal)baseValue;
+            return new VertexPositionNormal(this.Position + bv.Position, this.Normal + bv.Normal);
+        }
+
+        IVertexGeometry IVertexGeometry.ToDisplaceMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPositionNormal)baseValue;
+            return new VertexPositionNormal(this.Position - bv.Position, this.Normal - bv.Normal);
+        }
+
         public Vector3 GetPosition() { return this.Position; }
 
         public bool TryGetNormal(out Vector3 normal) { normal = this.Normal; return true; }
@@ -199,6 +226,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         void IVertexGeometry.SetTangent(Vector4 tangent) { this.Tangent = tangent; }
 
+        IVertexGeometry IVertexGeometry.ToAbsoluteMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPositionNormalTangent)baseValue;
+            return new VertexPositionNormalTangent(this.Position + bv.Position, this.Normal + bv.Normal, this.Tangent + bv.Tangent);
+        }
+
+        IVertexGeometry IVertexGeometry.ToDisplaceMorph(IVertexGeometry baseValue)
+        {
+            var bv = (VertexPositionNormalTangent)baseValue;
+            return new VertexPositionNormalTangent(this.Position - bv.Position, this.Normal - bv.Normal, this.Tangent - bv.Tangent);
+        }
+
         public Vector3 GetPosition() { return this.Position; }
 
         public bool TryGetNormal(out Vector3 normal) { normal = this.Normal; return true; }

+ 65 - 24
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -501,10 +501,10 @@ namespace SharpGLTF.Schema2
         {
             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"].AsVector4Array();
+            if (vertexAccessors.ContainsKey("TANGENT")) dstColumns.Tangents = vertexAccessors["TANGENT"].AsColorArray(0);
 
-            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("COLOR_0")) dstColumns.Colors0 = vertexAccessors["COLOR_0"].AsColorArray(1);
+            if (vertexAccessors.ContainsKey("COLOR_1")) dstColumns.Colors1 = vertexAccessors["COLOR_1"].AsColorArray(1);
 
             if (vertexAccessors.ContainsKey("TEXCOORD_0")) dstColumns.TexCoords0 = vertexAccessors["TEXCOORD_0"].AsVector2Array();
             if (vertexAccessors.ContainsKey("TEXCOORD_1")) dstColumns.TexCoords1 = vertexAccessors["TEXCOORD_1"].AsVector2Array();
@@ -516,15 +516,6 @@ namespace SharpGLTF.Schema2
             if (vertexAccessors.ContainsKey("WEIGHTS_1")) dstColumns.Weights1 = vertexAccessors["WEIGHTS_1"].AsVector4Array();
         }
 
-        private static void _Initialize(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();
-        }
-
         /// <summary>
         /// Calculates a default set of normals for the given mesh.
         /// </summary>
@@ -657,10 +648,10 @@ namespace SharpGLTF.Schema2
                 .Distinct()
                 .ToArray();
 
-            Materials.MaterialBuilder defMat = null;
-
             var dstMesh = MeshBuilderToolkit.CreateMeshBuilderFromVertexAttributes<Materials.MaterialBuilder>(vertexAttributes);
 
+            Materials.MaterialBuilder defMat = null;
+
             var dstMaterials = new Dictionary<Material, Materials.MaterialBuilder>();
 
             IPrimitiveBuilder GetPrimitive(Material srcMaterial, int vcount)
@@ -687,25 +678,75 @@ namespace SharpGLTF.Schema2
                 return dstPrim;
             }
 
-            foreach (var srcTri in srcMesh.EvaluatePoints())
+            foreach (var srcPrim in srcMesh.Primitives)
             {
-                var dstPrim = GetPrimitive(srcTri.Item2, 1);
-                dstPrim.AddPoint(srcTri.Item1);
+                int vcount = 0;
+                if (srcPrim.GetPointIndices().Any()) vcount = 1;
+                if (srcPrim.GetLineIndices().Any()) vcount = 2;
+                if (srcPrim.GetTriangleIndices().Any()) vcount = 3;
+
+                var dstPrim = GetPrimitive(srcPrim.Material, vcount);
+
+                dstPrim.AddPrimitiveGeometry(srcPrim);
             }
 
-            foreach (var srcTri in srcMesh.EvaluateLines())
+            return dstMesh;
+        }
+
+        private static void AddPrimitiveGeometry(this IPrimitiveBuilder dstPrim, MeshPrimitive srcPrim)
+        {
+            Guard.NotNull(dstPrim, nameof(dstPrim));
+
+            var vertices = srcPrim.GetVertexColumns();
+            var vmap = new Dictionary<int, int>();
+
+            foreach (var srcPoint in srcPrim.GetPointIndices())
             {
-                var dstPrim = GetPrimitive(srcTri.Item3, 2);
-                dstPrim.AddLine(srcTri.Item1, srcTri.Item2);
+                var v = vertices.GetVertex(dstPrim.VertexType, srcPoint);
+
+                var idx = dstPrim.AddPoint(v);
+
+                vmap[srcPoint] = idx;
             }
 
-            foreach (var srcTri in srcMesh.EvaluateTriangles())
+            foreach (var srcLine in srcPrim.GetLineIndices())
             {
-                var dstPrim = GetPrimitive(srcTri.Item4, 3);
-                dstPrim.AddTriangle(srcTri.Item1, srcTri.Item2, srcTri.Item3);
+                var v1 = vertices.GetVertex(dstPrim.VertexType, srcLine.Item1);
+                var v2 = vertices.GetVertex(dstPrim.VertexType, srcLine.Item2);
+
+                var indices = dstPrim.AddLine(v1, v2);
+
+                vmap[srcLine.Item1] = indices.Item1;
+                vmap[srcLine.Item2] = indices.Item2;
+
             }
 
-            return dstMesh;
+            foreach (var srcTri in srcPrim.GetTriangleIndices())
+            {
+                var v1 = vertices.GetVertex(dstPrim.VertexType, srcTri.Item1);
+                var v2 = vertices.GetVertex(dstPrim.VertexType, srcTri.Item2);
+                var v3 = vertices.GetVertex(dstPrim.VertexType, srcTri.Item3);
+
+                var indices = dstPrim.AddTriangle(v1, v2, v3);
+
+                vmap[srcTri.Item1] = indices.Item1;
+                vmap[srcTri.Item2] = indices.Item2;
+                vmap[srcTri.Item3] = indices.Item3;
+            }
+
+            for (int tidx = 0; tidx < vertices.MorphTargets.Count; ++tidx)
+            {
+                var srcTarget = vertices.MorphTargets[tidx];
+
+                foreach (var kvp in vmap)
+                {
+                    if (kvp.Value < 0) continue;
+
+                    var v = srcTarget.GetVertex(dstPrim.VertexType, kvp.Key);
+
+                    dstPrim.SetVertexDisplacement(tidx, kvp.Value, v.GetGeometry());
+                }
+            }
         }
 
         public static void SaveAsWavefront(this ModelRoot model, string filePath)

+ 13 - 0
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs

@@ -71,5 +71,18 @@ namespace SharpGLTF.Schema2.LoadAndSave
                 TestContext.WriteLine($"Triangle {ap} {an} {bp} {bn} {cp} {cn}");
             }
         }
+
+        [Test]
+        public void LoadUniVRM()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+
+            var path = TestFiles.GetUniVRMModelPath();
+            
+            var model = ModelRoot.Load(path);
+            Assert.NotNull(model);
+
+            model.AttachToCurrentTest("AliceModel.glb");
+        }
     }
 }

+ 1 - 1
tests/SharpGLTF.Tests/SharpGLTF.Tests.csproj

@@ -11,7 +11,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="LibGit2Sharp" Version="0.26.0" />
+    <PackageReference Include="LibGit2Sharp" Version="0.26.1" />
     <PackageReference Include="nunit" Version="3.12.0" />
     <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />

+ 13 - 0
tests/SharpGLTF.Tests/TestFiles.cs

@@ -21,6 +21,7 @@ namespace SharpGLTF
             _SchemaDir = System.IO.Path.Combine(workingDir, "glTF-Schema");
             _SampleModelsDir = System.IO.Path.Combine(workingDir, "glTF-Sample-Models");
             _PollyModelsDir = System.IO.Path.Combine(workingDir, "glTF-Blender-Exporter");
+            _UniVRMModelsDir = System.IO.Path.Combine(workingDir, "UniVRM");
             _BabylonJsMeshesDir = System.IO.Path.Combine(workingDir, "BabylonJS-MeshesLibrary");
             _BabylonJsPlaygroundDir = System.IO.Path.Combine(workingDir, "BabylonJS-PlaygroundScenes");
         }
@@ -34,7 +35,13 @@ namespace SharpGLTF
 
             var dstPath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "GeneratedReferenceModels", "v_0_6_1.zip");
             _GeneratedModelsDir = DownloadUtils.DownloadFile("https://github.com/KhronosGroup/glTF-Asset-Generator/releases/download/v0.6.1/GeneratedAssets-0.6.1.zip", dstPath);
+
+            dstPath = System.IO.Path.Combine(_UniVRMModelsDir, "AliciaSolid_vrm-0.40.vrm");
+            DownloadUtils.DownloadFile("https://github.com/vrm-c/UniVRMTest/raw/master/Models/Alicia_vrm-0.40/AliciaSolid_vrm-0.40.vrm", dstPath);
+
+
             
+
             TestContext.Progress.WriteLine("Checking out test files... It might take a while, please, wait...");            
 
             DownloadUtils.SyncronizeGitRepository("https://github.com/KhronosGroup/glTF-Sample-Models.git", _SampleModelsDir);
@@ -55,6 +62,7 @@ namespace SharpGLTF
         private static readonly string _SchemaDir;
         private static readonly string _SampleModelsDir;
         private static readonly string _PollyModelsDir;
+        private static readonly string _UniVRMModelsDir;
         private static readonly string _BabylonJsMeshesDir;
         private static readonly string _BabylonJsPlaygroundDir;
 
@@ -132,6 +140,11 @@ namespace SharpGLTF
             return System.IO.Path.Combine(_PollyModelsDir, "polly", "project_polly.glb");
         }
 
+        public static string GetUniVRMModelPath()
+        {
+            return System.IO.Path.Combine(_UniVRMModelsDir, "AliciaSolid_vrm-0.40.vrm");
+        }
+
         private static IReadOnlyList<string> GetModelPathsInDirectory(params string[] paths)
         {
             var dirPath = System.IO.Path.Combine(paths);