Browse Source

SceneBuilder: skinning rountrip.

Vicente Penades 6 years ago
parent
commit
5223e5dc45
30 changed files with 960 additions and 169 deletions
  1. 1 1
      examples/InfiniteSkinnedTentacle/Program.cs
  2. 13 0
      src/Shared/_Extensions.cs
  3. 1 0
      src/SharpGLTF.Core/Schema2/gltf.Images.cs
  4. 18 2
      src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs
  5. 65 11
      src/SharpGLTF.Core/Schema2/gltf.MaterialsFactory.cs
  6. 4 2
      src/SharpGLTF.Core/Schema2/gltf.Node.cs
  7. 36 19
      src/SharpGLTF.Core/Schema2/gltf.Skin.cs
  8. 4 0
      src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
  9. 19 0
      src/SharpGLTF.Core/Transforms/MeshTransforms.cs
  10. 31 0
      src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs
  11. 0 13
      src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs
  12. 44 0
      src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs
  13. 39 0
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  14. 55 49
      src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs
  15. 1 0
      src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs
  16. 128 5
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs
  17. 123 17
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs
  18. 2 2
      src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs
  19. 1 1
      src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs
  20. 45 11
      src/SharpGLTF.Toolkit/Scenes/Content.cs
  21. 2 0
      src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs
  22. 14 2
      src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs
  23. 6 3
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs
  24. 6 4
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs
  25. 24 1
      src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs
  26. 103 13
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  27. 132 9
      src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs
  28. 39 0
      tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs
  29. 1 1
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs
  30. 3 3
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

+ 1 - 1
examples/InfiniteSkinnedTentacle/Program.cs

@@ -106,7 +106,7 @@ namespace InfiniteSkinnedTentacle
 
 
             scene
             scene
                 .CreateNode()
                 .CreateNode()
-                .WithSkinnedMesh(mesh, bindings.ToArray());
+                .WithSkinnedMesh(mesh, skeleton.WorldMatrix, bindings.ToArray());
 
 
             return bindings.Last();
             return bindings.Last();
         }
         }

+ 13 - 0
src/Shared/_Extensions.cs

@@ -360,6 +360,19 @@ namespace SharpGLTF
             return true;
             return true;
         }
         }
 
 
+        internal static bool _IsImage(this IReadOnlyList<Byte> data)
+        {
+            if (data == null) return false;
+            if (data.Count < 12) return false;
+
+            if (data._IsDdsImage()) return true;
+            if (data._IsJpgImage()) return true;
+            if (data._IsPngImage()) return true;
+            if (data._IsWebpImage()) return true;
+
+            return false;
+        }
+        
         #endregion
         #endregion
 
 
         #region vertex & index accessors
         #region vertex & index accessors

+ 1 - 0
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -348,6 +348,7 @@ namespace SharpGLTF.Schema2
         public Image UseImage(BYTES imageContent)
         public Image UseImage(BYTES imageContent)
         {
         {
             Guard.NotNullOrEmpty(imageContent, nameof(imageContent));
             Guard.NotNullOrEmpty(imageContent, nameof(imageContent));
+            Guard.IsTrue(imageContent._IsImage(), nameof(imageContent), $"{nameof(imageContent)} must be a valid image byte stream.");
 
 
             foreach (var img in this.LogicalImages)
             foreach (var img in this.LogicalImages)
             {
             {

+ 18 - 2
src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs

@@ -16,22 +16,24 @@ namespace SharpGLTF.Schema2
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal MaterialChannel(Material m, String key, Func<Boolean, TextureInfo> texInfo, Func<Single> cgetter, Action<Single> csetter)
+        internal MaterialChannel(Material m, String key, Func<Boolean, TextureInfo> texInfo, Single defval, Func<Single> cgetter, Action<Single> csetter)
             : this(m, key, texInfo)
             : this(m, key, texInfo)
         {
         {
             Guard.NotNull(cgetter, nameof(cgetter));
             Guard.NotNull(cgetter, nameof(cgetter));
             Guard.NotNull(csetter, nameof(csetter));
             Guard.NotNull(csetter, nameof(csetter));
 
 
+            _ParameterDefVal = new Vector4(defval, 0, 0, 0);
             _ParameterGetter = () => new Vector4(cgetter(), 0, 0, 0);
             _ParameterGetter = () => new Vector4(cgetter(), 0, 0, 0);
             _ParameterSetter = value => csetter(value.X);
             _ParameterSetter = value => csetter(value.X);
         }
         }
 
 
-        internal MaterialChannel(Material m, String key, Func<Boolean, TextureInfo> texInfo, Func<Vector4> cgetter, Action<Vector4> csetter)
+        internal MaterialChannel(Material m, String key, Func<Boolean, TextureInfo> texInfo, Vector4 defval, Func<Vector4> cgetter, Action<Vector4> csetter)
             : this(m, key, texInfo)
             : this(m, key, texInfo)
         {
         {
             Guard.NotNull(cgetter, nameof(cgetter));
             Guard.NotNull(cgetter, nameof(cgetter));
             Guard.NotNull(csetter, nameof(csetter));
             Guard.NotNull(csetter, nameof(csetter));
 
 
+            _ParameterDefVal = defval;
             _ParameterGetter = cgetter;
             _ParameterGetter = cgetter;
             _ParameterSetter = csetter;
             _ParameterSetter = csetter;
         }
         }
@@ -48,6 +50,7 @@ namespace SharpGLTF.Schema2
 
 
             _TextureInfo = texInfo;
             _TextureInfo = texInfo;
 
 
+            _ParameterDefVal = Vector4.Zero;
             _ParameterGetter = null;
             _ParameterGetter = null;
             _ParameterSetter = null;
             _ParameterSetter = null;
         }
         }
@@ -61,6 +64,7 @@ namespace SharpGLTF.Schema2
 
 
         private readonly Func<Boolean, TextureInfo> _TextureInfo;
         private readonly Func<Boolean, TextureInfo> _TextureInfo;
 
 
+        private readonly Vector4 _ParameterDefVal;
         private readonly Func<Vector4> _ParameterGetter;
         private readonly Func<Vector4> _ParameterGetter;
         private readonly Action<Vector4> _ParameterSetter;
         private readonly Action<Vector4> _ParameterSetter;
 
 
@@ -72,6 +76,8 @@ namespace SharpGLTF.Schema2
 
 
         public String Key => _Key;
         public String Key => _Key;
 
 
+        public Boolean HasDefaultContent => _CheckHasDefaultContent();
+
         /// <summary>
         /// <summary>
         /// Gets or sets the <see cref="Vector4"/> parameter of this channel.
         /// Gets or sets the <see cref="Vector4"/> parameter of this channel.
         /// The meaning of the <see cref="Vector4.X"/>, <see cref="Vector4.Y"/>. <see cref="Vector4.Z"/> and <see cref="Vector4.W"/>
         /// The meaning of the <see cref="Vector4.X"/>, <see cref="Vector4.Y"/>. <see cref="Vector4.Z"/> and <see cref="Vector4.W"/>
@@ -150,6 +156,16 @@ namespace SharpGLTF.Schema2
             texInfo.SetTransform(offset, scale, rotation, texCoordOverride);
             texInfo.SetTransform(offset, scale, rotation, texCoordOverride);
         }
         }
 
 
+        private bool _CheckHasDefaultContent()
+        {
+            if (this.Parameter != _ParameterDefVal) return false;
+            if (this.Texture != null) return false;
+
+            // there's no point in keep checking because if there's no texture, all other elements are irrelevant.
+
+            return true;
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 65 - 11
src/SharpGLTF.Core/Schema2/gltf.MaterialsFactory.cs

@@ -66,11 +66,32 @@ namespace SharpGLTF.Schema2
                 foreach (var c in channels) yield return c;
                 foreach (var c in channels) yield return c;
             }
             }
 
 
-            yield return new MaterialChannel(this, "Normal", _GetNormalTexture, () => _GetNormalTexture(false)?.Scale ?? 0, value => _GetNormalTexture(true).Scale = value);
-
-            yield return new MaterialChannel(this, "Occlusion", _GetOcclusionTexture, () => _GetOcclusionTexture(false)?.Strength ?? 0, value => _GetOcclusionTexture(true).Strength = value);
-
-            yield return new MaterialChannel(this, "Emissive", _GetEmissiveTexture, () => this._EmissiveColor, value => this._EmissiveColor = value );
+            yield return new MaterialChannel
+                (
+                this, "Normal",
+                _GetNormalTexture,
+                MaterialNormalTextureInfo.ScaleDefault,
+                () => _GetNormalTexture(false)?.Scale ?? MaterialNormalTextureInfo.ScaleDefault,
+                value => _GetNormalTexture(true).Scale = value
+                );
+
+            yield return new MaterialChannel
+                (
+                this, "Occlusion",
+                _GetOcclusionTexture,
+                MaterialOcclusionTextureInfo.StrengthDefault,
+                () => _GetOcclusionTexture(false)?.Strength ?? MaterialOcclusionTextureInfo.StrengthDefault,
+                value => _GetOcclusionTexture(true).Strength = value
+                );
+
+            yield return new MaterialChannel
+                (
+                this, "Emissive",
+                _GetEmissiveTexture,
+                Vector4.Zero,
+                () => this._EmissiveColor,
+                value => this._EmissiveColor = value
+                );
         }
         }
 
 
         private Vector4 _EmissiveColor
         private Vector4 _EmissiveColor
@@ -144,6 +165,8 @@ namespace SharpGLTF.Schema2
             set => _baseColorFactor = value.AsNullable(_baseColorFactorDefault);
             set => _baseColorFactor = value.AsNullable(_baseColorFactorDefault);
         }
         }
 
 
+        public static Vector4 ParameterDefault => new Vector4((float)_metallicFactorDefault, (float)_roughnessFactorDefault,0,0);
+
         public Vector4 Parameter
         public Vector4 Parameter
         {
         {
             get
             get
@@ -165,9 +188,24 @@ namespace SharpGLTF.Schema2
 
 
         public IEnumerable<MaterialChannel> GetChannels(Material material)
         public IEnumerable<MaterialChannel> GetChannels(Material material)
         {
         {
-            yield return new MaterialChannel(material, "BaseColor", _GetBaseTexture, () => this.Color, value => this.Color = value);
-
-            yield return new MaterialChannel(material, "MetallicRoughness", _GetMetallicTexture, () => this.Parameter, value => this.Parameter = value);
+            yield return new MaterialChannel
+                (
+                material, "BaseColor",
+                _GetBaseTexture,
+                _baseColorFactorDefault,
+                () => this.Color,
+                value => this.Color = value
+                );
+
+            yield return new MaterialChannel
+                (
+                material,
+                "MetallicRoughness",
+                _GetMetallicTexture,
+                ParameterDefault,
+                () => this.Parameter,
+                value => this.Parameter = value
+                );
         }
         }
     }
     }
 
 
@@ -199,6 +237,8 @@ namespace SharpGLTF.Schema2
             set => _diffuseFactor = value.AsNullable(_diffuseFactorDefault);
             set => _diffuseFactor = value.AsNullable(_diffuseFactorDefault);
         }
         }
 
 
+        public static Vector4 ParameterDefault => new Vector4(_specularFactorDefault, (float)_glossinessFactorDefault);
+
         public Vector4 Parameter
         public Vector4 Parameter
         {
         {
             get
             get
@@ -218,9 +258,23 @@ namespace SharpGLTF.Schema2
 
 
         public IEnumerable<MaterialChannel> GetChannels(Material material)
         public IEnumerable<MaterialChannel> GetChannels(Material material)
         {
         {
-            yield return new MaterialChannel(material, "Diffuse", _GetDiffuseTexture, () => this.Color, value => this.Color = value);
-
-            yield return new MaterialChannel(material, "SpecularGlossiness", _GetGlossinessTexture, () => this.Parameter, value => this.Parameter = value);
+            yield return new MaterialChannel
+                (
+                material, "Diffuse",
+                _GetDiffuseTexture,
+                _diffuseFactorDefault,
+                () => this.Color,
+                value => this.Color = value
+                );
+
+            yield return new MaterialChannel
+                (
+                material, "SpecularGlossiness",
+                _GetGlossinessTexture,
+                ParameterDefault,
+                () => this.Parameter,
+                value => this.Parameter = value
+                );
         }
         }
     }
     }
 
 

+ 4 - 2
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -188,8 +188,8 @@ namespace SharpGLTF.Schema2
             for (int i = 0; i < this.Skin.JointsCount; ++i)
             for (int i = 0; i < this.Skin.JointsCount; ++i)
             {
             {
                 var j = this.Skin.GetJoint(i);
                 var j = this.Skin.GetJoint(i);
-                jointXforms[i] = j.Key.GetWorldMatrix(animation, time);
-                invBindings[i] = j.Value;
+                jointXforms[i] = j.Item1.GetWorldMatrix(animation, time);
+                invBindings[i] = j.Item2;
             }
             }
 
 
             return new Transforms.SkinTransform(invBindings, jointXforms, weights, false);
             return new Transforms.SkinTransform(invBindings, jointXforms, weights, false);
@@ -290,6 +290,8 @@ namespace SharpGLTF.Schema2
         /// <returns>A collection of <see cref="Node"/> instances.</returns>
         /// <returns>A collection of <see cref="Node"/> instances.</returns>
         public static IEnumerable<Node> Flatten(IVisualNodeContainer container)
         public static IEnumerable<Node> Flatten(IVisualNodeContainer container)
         {
         {
+            if (container == null) yield break;
+
             if (container is Node n) yield return n;
             if (container is Node n) yield return n;
 
 
             foreach (var c in container.VisualChildren)
             foreach (var c in container.VisualChildren)

+ 36 - 19
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -66,7 +66,7 @@ namespace SharpGLTF.Schema2
             return this.LogicalParent.LogicalAccessors[this._inverseBindMatrices.Value];
             return this.LogicalParent.LogicalAccessors[this._inverseBindMatrices.Value];
         }
         }
 
 
-        public KeyValuePair<Node, Matrix4x4> GetJoint(int idx)
+        public (Node, Matrix4x4) GetJoint(int idx)
         {
         {
             var nodeIdx = _joints[idx];
             var nodeIdx = _joints[idx];
 
 
@@ -76,7 +76,7 @@ namespace SharpGLTF.Schema2
 
 
             var matrix = accessor == null ? Matrix4x4.Identity : accessor.AsMatrix4x4Array()[idx];
             var matrix = accessor == null ? Matrix4x4.Identity : accessor.AsMatrix4x4Array()[idx];
 
 
-            return new KeyValuePair<Node, Matrix4x4>(node, matrix);
+            return (node, matrix);
         }
         }
 
 
         public void BindJoints(params Node[] joints)
         public void BindJoints(params Node[] joints)
@@ -87,42 +87,53 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Binds the tree of bones to the associated skinned mesh.
+        /// Binds a bone armature of <see cref="Node"/> to the associated skinned mesh.
         /// </summary>
         /// </summary>
         /// <param name="meshBindTransform">The world transform matrix of the mesh at the time of binding.</param>
         /// <param name="meshBindTransform">The world transform matrix of the mesh at the time of binding.</param>
         /// <param name="joints">A collection of <see cref="Node"/> joints.</param>
         /// <param name="joints">A collection of <see cref="Node"/> joints.</param>
+        /// <remarks>
+        /// This method uses the <see cref="Node.WorldMatrix"/> value of each joint to computer the inverse bind matrix.
+        /// </remarks>
         public void BindJoints(Matrix4x4 meshBindTransform, params Node[] joints)
         public void BindJoints(Matrix4x4 meshBindTransform, params Node[] joints)
         {
         {
-            var meshBingInverse = meshBindTransform.Inverse();
+            Guard.NotNull(joints, nameof(joints));
 
 
-            var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
+            var pairs = new (Node, Matrix4x4)[joints.Length];
 
 
             for (int i = 0; i < pairs.Length; ++i)
             for (int i = 0; i < pairs.Length; ++i)
             {
             {
-                var xform = (joints[i].WorldMatrix * meshBingInverse).Inverse();
+                Guard.NotNull(joints[i], nameof(joints));
 
 
-                pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], xform);
+                var xform = Transforms.SkinTransform.CalculateInverseBinding(meshBindTransform, joints[i].WorldMatrix);
+
+                pairs[i] = (joints[i], xform);
             }
             }
 
 
             BindJoints(pairs);
             BindJoints(pairs);
         }
         }
 
 
-        public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
+        /// <summary>
+        /// Binds a bone armature of <see cref="Node"/> to the associated skinned mesh.
+        /// </summary>
+        /// <param name="joints">
+        /// A collection of <see cref="Node"/> joints,
+        /// where each joint has an Inverse Bind Matrix.
+        /// </param>
+        public void BindJoints((Node, Matrix4x4)[] joints)
         {
         {
-            _FindCommonAncestor(joints.Select(item => item.Key));
+            Guard.NotNull(joints, nameof(joints));
 
 
-            foreach (var j in joints) { Guard.IsTrue(j.Value._IsReal(), nameof(joints)); }
+            _FindCommonAncestor(joints.Select(item => item.Item1));
+
+            foreach (var j in joints) { Guard.IsTrue(j.Item2._IsReal(), nameof(joints)); }
 
 
             // inverse bind matrices accessor
             // inverse bind matrices accessor
 
 
             var data = new Byte[joints.Length * 16 * 4];
             var data = new Byte[joints.Length * 16 * 4];
-
             var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
             var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
-
-            matrices.Fill(joints.Select(item => item.Value));
+            matrices.Fill(joints.Select(item => item.Item2));
 
 
             var accessor = LogicalParent.CreateAccessor("Bind Matrices");
             var accessor = LogicalParent.CreateAccessor("Bind Matrices");
-
             accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
             accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
 
 
             this._inverseBindMatrices = accessor.LogicalIndex;
             this._inverseBindMatrices = accessor.LogicalIndex;
@@ -130,7 +141,7 @@ namespace SharpGLTF.Schema2
             // joints
             // joints
 
 
             _joints.Clear();
             _joints.Clear();
-            _joints.AddRange(joints.Select(item => item.Key.LogicalIndex));
+            _joints.AddRange(joints.Select(item => item.Item1.LogicalIndex));
         }
         }
 
 
         #endregion
         #endregion
@@ -154,8 +165,8 @@ namespace SharpGLTF.Schema2
                 var src = joints[i];
                 var src = joints[i];
                 var dst = GetJoint(i);
                 var dst = GetJoint(i);
 
 
-                if (!ReferenceEquals(src.Key, dst.Key)) return false;
-                if (src.Value != dst.Value) return false;
+                if (!ReferenceEquals(src.Key, dst.Item1)) return false;
+                if (src.Value != dst.Item2) return false;
             }
             }
 
 
             return true;
             return true;
@@ -198,7 +209,13 @@ namespace SharpGLTF.Schema2
         /// <returns>The <see cref="Node"/> root of the tree.</returns>
         /// <returns>The <see cref="Node"/> root of the tree.</returns>
         private Node _FindCommonAncestor(IEnumerable<Node> nodes)
         private Node _FindCommonAncestor(IEnumerable<Node> nodes)
         {
         {
-            foreach (var j in nodes) Guard.MustShareLogicalParent(this, j, nameof(nodes));
+            if (nodes == null) return null;
+
+            foreach (var j in nodes)
+            {
+                Guard.NotNull(j, nameof(nodes));
+                Guard.MustShareLogicalParent(this, j, nameof(nodes));
+            }
 
 
             var workingNodes = nodes.ToList();
             var workingNodes = nodes.ToList();
 
 
@@ -256,7 +273,7 @@ namespace SharpGLTF.Schema2
 
 
                 for (int i = 0; i < this.JointsCount; ++i)
                 for (int i = 0; i < this.JointsCount; ++i)
                 {
                 {
-                    var jointNode = GetJoint(i).Key;
+                    var jointNode = GetJoint(i).Item1;
 
 
                     if (skeletonNode == jointNode) continue;
                     if (skeletonNode == jointNode) continue;
                     if (skeletonNode._ContainsVisualNode(jointNode, true)) continue;
                     if (skeletonNode._ContainsVisualNode(jointNode, true)) continue;

+ 4 - 0
src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs

@@ -107,6 +107,8 @@ namespace SharpGLTF.Schema2
     {
     {
         #region properties
         #region properties
 
 
+        public static Single ScaleDefault => (float)_scaleDefault;
+
         public Single Scale
         public Single Scale
         {
         {
             get => (Single)this._scale.AsValue(_scaleDefault);
             get => (Single)this._scale.AsValue(_scaleDefault);
@@ -121,6 +123,8 @@ namespace SharpGLTF.Schema2
     {
     {
         #region properties
         #region properties
 
 
+        public static Single StrengthDefault => (float)_strengthDefault;
+
         public Single Strength
         public Single Strength
         {
         {
             get => (Single)this._strength.AsValue(_strengthDefault);
             get => (Single)this._strength.AsValue(_strengthDefault);

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

@@ -352,5 +352,24 @@ namespace SharpGLTF.Transforms
         }
         }
 
 
         #endregion
         #endregion
+
+        #region helper utilities
+
+        /// <summary>
+        /// Calculates the inverse bind matrix to use for runtime skinning.
+        /// </summary>
+        /// <param name="meshWorldTransform">The world space <see cref="TRANSFORM"/> of the mesh at the time of binding (POSE).</param>
+        /// <param name="jointWorldTransform">The world space <see cref="TRANSFORM"/> of the given bone joint at the time of binding (POSE).</param>
+        /// <returns>A <see cref="TRANSFORM"/> representing the inverse bind transform.</returns>
+        public static Matrix4x4 CalculateInverseBinding(Matrix4x4 meshWorldTransform, Matrix4x4 jointWorldTransform)
+        {
+            var xform = meshWorldTransform.Inverse();
+
+            xform = jointWorldTransform * xform;
+
+            return xform.Inverse();
+        }
+
+        #endregion
     }
     }
 }
 }

+ 31 - 0
src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs

@@ -73,6 +73,8 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
+        public void Clear() { _Keys.Clear(); }
+
         public void RemoveKey(float offset) { _Keys.Remove(offset); }
         public void RemoveKey(float offset) { _Keys.Remove(offset); }
 
 
         public void SetPoint(float offset, T value, bool isLinear = true)
         public void SetPoint(float offset, T value, bool isLinear = true)
@@ -149,6 +151,35 @@ namespace SharpGLTF.Animations
             return (_Keys[offsets.Item1], _Keys[offsets.Item2], offsets.Item3);
             return (_Keys[offsets.Item1], _Keys[offsets.Item2], offsets.Item3);
         }
         }
 
 
+        public void SetCurve(ICurveSampler<T> curve)
+        {
+            if (curve is IConvertibleCurve<T> convertible)
+            {
+                if (convertible.MaxDegree == 0)
+                {
+                    var linear = convertible.ToStepCurve();
+                    foreach (var p in linear) this.SetPoint(p.Key, p.Value, false);
+                    return;
+                }
+
+                if (convertible.MaxDegree == 1)
+                {
+                    var linear = convertible.ToLinearCurve();
+                    foreach (var p in linear) this.SetPoint(p.Key, p.Value);
+                    return;
+                }
+
+                if (convertible.MaxDegree > 1)
+                {
+                    var linear = convertible.ToLinearCurve();
+                    foreach (var p in linear) this.SetPoint(p.Key, p.Value);
+                    return;
+                }
+            }
+
+            throw new NotImplementedException();
+        }
+
         #endregion
         #endregion
 
 
         #region With* API
         #region With* API

+ 0 - 13
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -9,19 +9,6 @@ using SharpGLTF.Geometry.VertexTypes;
 
 
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
-    public interface IMeshBuilder<TMaterial>
-    {
-        string Name { get; set; }
-
-        IEnumerable<TMaterial> Materials { get; }
-
-        IReadOnlyCollection<IPrimitive<TMaterial>> Primitives { get; }
-
-        IPrimitiveBuilder UsePrimitive(TMaterial material, int primitiveVertexCount = 3);
-
-        void Validate();
-    }
-
     /// <summary>
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
     /// </summary>

+ 44 - 0
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SharpGLTF.Geometry
+{
+    public interface IMeshBuilder<TMaterial>
+    {
+        string Name { get; set; }
+
+        IEnumerable<TMaterial> Materials { get; }
+
+        IReadOnlyCollection<IPrimitive<TMaterial>> Primitives { get; }
+
+        IPrimitiveBuilder UsePrimitive(TMaterial material, int primitiveVertexCount = 3);
+
+        void Validate();
+    }
+
+    static class MeshBuilderToolkit
+    {
+        public static IMeshBuilder<TMaterial> CreateMeshBuilderFromVertexAttributes<TMaterial>(params string[] vertexAttributes)
+        {
+            Type meshType = GetMeshBuilderType(typeof(TMaterial), vertexAttributes);
+
+            var mesh = Activator.CreateInstance(meshType, string.Empty);
+
+            return mesh as IMeshBuilder<TMaterial>;
+        }
+
+        public static Type GetMeshBuilderType(Type materialType, string[] vertexAttributes)
+        {
+            var tvg = VertexTypes.VertexUtils.GetVertexGeometryType(vertexAttributes);
+            var tvm = VertexTypes.VertexUtils.GetVertexMaterialType(vertexAttributes);
+            var tvs = VertexTypes.VertexUtils.GetVertexSkinningType(vertexAttributes);
+
+            var meshType = typeof(MeshBuilder<,,,>);
+
+            meshType = meshType.MakeGenericType(materialType, tvg, tvm, tvs);
+            return meshType;
+        }
+    }
+}

+ 39 - 0
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -59,6 +59,10 @@ namespace SharpGLTF.Geometry
         /// </summary>
         /// </summary>
         Type VertexType { get; }
         Type VertexType { get; }
 
 
+        int AddPoint(IVertexBuilder a);
+
+        (int, int) AddLine(IVertexBuilder a, IVertexBuilder b);
+
         (int, int, int) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c);
         (int, int, int) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c);
     }
     }
 
 
@@ -188,6 +192,22 @@ namespace SharpGLTF.Geometry
             return _Vertices.Use(vertex);
             return _Vertices.Use(vertex);
         }
         }
 
 
+        /// <summary>
+        /// Adds a point.
+        /// </summary>
+        /// <param name="a">vertex for this point.</param>
+        /// <returns>The indices of the vertices.</returns>
+        public int AddPoint(IVertexBuilder a)
+        {
+            Guard.NotNull(a, nameof(a));
+
+            var expectedType = typeof(VertexBuilder<TvG, TvM, TvS>);
+
+            var aa = a.GetType() != expectedType ? a.ConvertTo<TvG, TvM, TvS>() : (VertexBuilder<TvG, TvM, TvS>)a;
+
+            return AddPoint(aa);
+        }
+
         /// <summary>
         /// <summary>
         /// Adds a point.
         /// Adds a point.
         /// </summary>
         /// </summary>
@@ -200,6 +220,25 @@ namespace SharpGLTF.Geometry
             return UseVertex(a);
             return UseVertex(a);
         }
         }
 
 
+        /// <summary>
+        /// Adds a line.
+        /// </summary>
+        /// <param name="a">First corner of the line.</param>
+        /// <param name="b">Second corner of the line.</param>
+        /// <returns>The indices of the vertices.</returns>
+        public (int, int) AddLine(IVertexBuilder a, IVertexBuilder b)
+        {
+            Guard.NotNull(a, nameof(a));
+            Guard.NotNull(b, nameof(b));
+
+            var expectedType = typeof(VertexBuilder<TvG, TvM, TvS>);
+
+            var aa = a.GetType() != expectedType ? a.ConvertTo<TvG, TvM, TvS>() : (VertexBuilder<TvG, TvM, TvS>)a;
+            var bb = b.GetType() != expectedType ? b.ConvertTo<TvG, TvM, TvS>() : (VertexBuilder<TvG, TvM, TvS>)b;
+
+            return AddLine(aa, bb);
+        }
+
         /// <summary>
         /// <summary>
         /// Adds a line.
         /// Adds a line.
         /// </summary>
         /// </summary>

+ 55 - 49
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -223,46 +223,46 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        public TvG GetVertexGeometry<TvG>(int index)
-            where TvG : struct, IVertexGeometry
+        public MorphTargetColumns AddMorphTarget()
         {
         {
-            var pnt = default(TvG);
-
-            if (Positions != null) pnt.SetPosition(Positions[index]);
-            if (Normals != null) pnt.SetNormal(Normals[index]);
-            if (Tangents != null) pnt.SetTangent(Tangents[index]);
+            if (_MorphTargets == null) _MorphTargets = new List<MorphTargetColumns>();
+            var mt = new MorphTargetColumns();
+            _MorphTargets.Add(mt);
 
 
-            return pnt;
+            return mt;
         }
         }
+        
+        #endregion
 
 
-        public TvM GetVertexMaterial<TvM>(int index)
-            where TvM : struct, IVertexMaterial
-        {
-            var cctt = default(TvM);
+        #region API - Vertex indexing
 
 
-            if (Colors0 != null && cctt.MaxColors > 0) cctt.SetColor(0, Colors0[index]);
-            if (Colors1 != null && cctt.MaxColors > 1) cctt.SetColor(1, Colors1[index]);
+        private void CopyTo(int index, IVertexGeometry v)
+        {
+            if (Positions != null) v.SetPosition(Positions[index]);
+            if (Normals != null) v.SetNormal(Normals[index]);
+            if (Tangents != null) v.SetTangent(Tangents[index]);
+        }
 
 
-            if (TexCoords0 != null && cctt.MaxTextCoords > 0) cctt.SetTexCoord(0, TexCoords0[index]);
-            if (TexCoords1 != null && cctt.MaxTextCoords > 1) cctt.SetTexCoord(1, TexCoords1[index]);
+        private void CopyTo(int index, IVertexMaterial v)
+        {
+            if (Colors0 != null && v.MaxColors > 0) v.SetColor(0, Colors0[index]);
+            if (Colors1 != null && v.MaxColors > 1) v.SetColor(1, Colors1[index]);
 
 
-            return cctt;
+            if (TexCoords0 != null && v.MaxTextCoords > 0) v.SetTexCoord(0, TexCoords0[index]);
+            if (TexCoords1 != null && v.MaxTextCoords > 1) v.SetTexCoord(1, TexCoords1[index]);
         }
         }
 
 
-        public TvS GetVertexSkinning<TvS>(int index)
-            where TvS : struct, IVertexSkinning
+        private void CopyTo(int index, IVertexSkinning v)
         {
         {
-            var jjjj = default(TvS);
-
             if (Joints0 != null && Weights0 != null)
             if (Joints0 != null && Weights0 != null)
             {
             {
                 var j = Joints0[index];
                 var j = Joints0[index];
                 var w = Weights0[index];
                 var w = Weights0[index];
 
 
-                jjjj.SetJointBinding(0, (int)j.X, w.X);
-                jjjj.SetJointBinding(1, (int)j.Y, w.Y);
-                jjjj.SetJointBinding(2, (int)j.Z, w.Z);
-                jjjj.SetJointBinding(3, (int)j.W, w.W);
+                v.SetJointBinding(0, (int)j.X, w.X);
+                v.SetJointBinding(1, (int)j.Y, w.Y);
+                v.SetJointBinding(2, (int)j.Z, w.Z);
+                v.SetJointBinding(3, (int)j.W, w.W);
             }
             }
 
 
             if (Joints1 != null && Weights1 != null)
             if (Joints1 != null && Weights1 != null)
@@ -270,24 +270,40 @@ namespace SharpGLTF.Geometry
                 var j = Joints1[index];
                 var j = Joints1[index];
                 var w = Weights1[index];
                 var w = Weights1[index];
 
 
-                jjjj.SetJointBinding(4, (int)j.X, w.X);
-                jjjj.SetJointBinding(5, (int)j.Y, w.Y);
-                jjjj.SetJointBinding(6, (int)j.Z, w.Z);
-                jjjj.SetJointBinding(7, (int)j.W, w.W);
+                v.SetJointBinding(4, (int)j.X, w.X);
+                v.SetJointBinding(5, (int)j.Y, w.Y);
+                v.SetJointBinding(6, (int)j.Z, w.Z);
+                v.SetJointBinding(7, (int)j.W, w.W);
             }
             }
+        }
+
+        public IVertexBuilder GetVertex(Type vertexType, int index)
+        {
+            var v = (IVertexBuilder)Activator.CreateInstance(vertexType);
+
+            var g = v.GetGeometry();
+            CopyTo(index, g);
+            v.SetGeometry(g);
 
 
-            return jjjj;
+            var m = v.GetMaterial();
+            CopyTo(index, m);
+            v.SetMaterial(m);
+
+            var s = v.GetSkinning();
+            CopyTo(index, s);
+            v.SetSkinning(s);
+
+            return v;
         }
         }
 
 
         public VertexBuilder<TvG, TvM, VertexEmpty> GetVertex<TvG, TvM>(int index)
         public VertexBuilder<TvG, TvM, VertexEmpty> GetVertex<TvG, TvM>(int index)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            return new VertexBuilder<TvG, TvM, VertexEmpty>
-                (
-                GetVertexGeometry<TvG>(index),
-                GetVertexMaterial<TvM>(index)
-                );
+            var g = default(TvG); CopyTo(index, g);
+            var m = default(TvM); CopyTo(index, m);
+
+            return new VertexBuilder<TvG, TvM, VertexEmpty>(g, m);
         }
         }
 
 
         public VertexBuilder<TvG, TvM, TvS> GetVertex<TvG, TvM, TvS>(int index)
         public VertexBuilder<TvG, TvM, TvS> GetVertex<TvG, TvM, TvS>(int index)
@@ -295,21 +311,11 @@ namespace SharpGLTF.Geometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
-            return new VertexBuilder<TvG, TvM, TvS>
-                (
-                GetVertexGeometry<TvG>(index),
-                GetVertexMaterial<TvM>(index),
-                GetVertexSkinning<TvS>(index)
-                );
-        }
+            var g = default(TvG); CopyTo(index, g);
+            var m = default(TvM); CopyTo(index, m);
+            var s = default(TvS); CopyTo(index, s);
 
 
-        public MorphTargetColumns AddMorphTarget()
-        {
-            if (_MorphTargets == null) _MorphTargets = new List<MorphTargetColumns>();
-            var mt = new MorphTargetColumns();
-            _MorphTargets.Add(mt);
-
-            return mt;
+            return new VertexBuilder<TvG, TvM, TvS>(g, m, s);
         }
         }
 
 
         #endregion
         #endregion

+ 1 - 0
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -335,6 +335,7 @@ namespace SharpGLTF.Geometry
             Guard.NotNull(skinning, nameof(skinning));
             Guard.NotNull(skinning, nameof(skinning));
             this.Skinning = skinning.GetType() == typeof(TvS) ? (TvS)skinning : skinning.ConvertTo<TvS>();
             this.Skinning = skinning.GetType() == typeof(TvS) ? (TvS)skinning : skinning.ConvertTo<TvS>();
         }
         }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 128 - 5
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -21,7 +21,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Defines a Vertex attribute with a Color material.
+    /// Defines a Vertex attribute with a material Color.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("𝐂:{Color}")]
     [System.Diagnostics.DebuggerDisplay("𝐂:{Color}")]
     public struct VertexColor1 : IVertexMaterial
     public struct VertexColor1 : IVertexMaterial
@@ -78,6 +78,66 @@ namespace SharpGLTF.Geometry.VertexTypes
         #endregion
         #endregion
     }
     }
 
 
+    /// <summary>
+    /// Defines a Vertex attribute with a two material Colors.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("𝐂₀:{Color0} 𝐂₁:{Color1}")]
+    public struct VertexColor2 : IVertexMaterial
+    {
+        #region constructors
+
+        public VertexColor2(Vector4 color0, Vector4 color1)
+        {
+            Color0 = color0;
+            Color1 = color1;
+        }
+
+        public VertexColor2(IVertexMaterial src)
+        {
+            this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
+            this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
+        }
+
+        #endregion
+
+        #region data
+
+        [VertexAttribute("COLOR_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
+        public Vector4 Color0;
+
+        [VertexAttribute("COLOR_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
+        public Vector4 Color1;
+
+        public int MaxColors => 2;
+
+        public int MaxTextCoords => 0;
+
+        #endregion
+
+        #region API
+
+        void IVertexMaterial.SetColor(int setIndex, Vector4 color)
+        {
+            if (setIndex == 0) this.Color0 = color;
+            if (setIndex == 1) this.Color1 = color;
+        }
+
+        void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }
+
+        public Vector4 GetColor(int index)
+        {
+            if (index == 0) return Color0;
+            if (index == 1) return Color1;
+            throw new ArgumentOutOfRangeException(nameof(index));
+        }
+
+        public Vector2 GetTexCoord(int index) { throw new NotSupportedException(); }
+
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
+
+        #endregion
+    }
+
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a Texture Coordinate.
     /// Defines a Vertex attribute with a Texture Coordinate.
     /// </summary>
     /// </summary>
@@ -136,6 +196,69 @@ namespace SharpGLTF.Geometry.VertexTypes
         #endregion
         #endregion
     }
     }
 
 
+    /// <summary>
+    /// Defines a Vertex attribute with two Texture Coordinates.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
+    public struct VertexTexture2 : IVertexMaterial
+    {
+        #region constructors
+
+        public VertexTexture2(Vector2 uv0, Vector2 uv1)
+        {
+            TexCoord0 = uv0;
+            TexCoord1 = uv1;
+        }
+
+        public VertexTexture2(IVertexMaterial src)
+        {
+            this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
+            this.TexCoord1 = src.MaxTextCoords > 1 ? src.GetTexCoord(1) : Vector2.Zero;
+        }
+
+        #endregion
+
+        #region data
+
+        [VertexAttribute("TEXCOORD_0")]
+        public Vector2 TexCoord0;
+
+        [VertexAttribute("TEXCOORD_1")]
+        public Vector2 TexCoord1;
+
+        public int MaxColors => 0;
+
+        public int MaxTextCoords => 2;
+
+        #endregion
+
+        #region API
+
+        void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
+
+        void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord)
+        {
+            if (setIndex == 0) this.TexCoord0 = coord;
+            if (setIndex == 1) this.TexCoord1 = coord;
+        }
+
+        public Vector4 GetColor(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            if (index == 0) return TexCoord0;
+            if (index == 1) return TexCoord1;
+            throw new ArgumentOutOfRangeException(nameof(index));
+        }
+
+        public void Validate() { FragmentPreprocessors.ValidateVertexMaterial(this); }
+
+        #endregion
+    }
+
     /// <summary>
     /// <summary>
     /// Defines a Vertex attribute with a Color material and a Texture Coordinate.
     /// Defines a Vertex attribute with a Color material and a Texture Coordinate.
     /// </summary>
     /// </summary>
@@ -201,9 +324,9 @@ namespace SharpGLTF.Geometry.VertexTypes
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Defines a Vertex attribute with a Color material and two Texture Coordinates.
+    /// Defines a Vertex attribute with a material Colors and two Texture Coordinates.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕𝟎:{TexCoord0} 𝐔𝐕𝟏:{TexCoord1}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
     public struct VertexColor1Texture2 : IVertexMaterial
     public struct VertexColor1Texture2 : IVertexMaterial
     {
     {
         #region constructors
         #region constructors
@@ -273,9 +396,9 @@ namespace SharpGLTF.Geometry.VertexTypes
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Defines a Vertex attribute with a Color material and two Texture Coordinates.
+    /// Defines a Vertex attribute with two material Colors and two Texture Coordinates.
     /// </summary>
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐂𝟎:{Color0} 𝐂𝟏:{Color1} 𝐔𝐕𝟎:{TexCoord0} 𝐔𝐕𝟏:{TexCoord1}")]
+    [System.Diagnostics.DebuggerDisplay("𝐂₀:{Color0} 𝐂₁:{Color1} 𝐔𝐕₀:{TexCoord0} 𝐔𝐕₁:{TexCoord1}")]
     public struct VertexColor2Texture2 : IVertexMaterial
     public struct VertexColor2Texture2 : IVertexMaterial
     {
     {
         #region constructors
         #region constructors

+ 123 - 17
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -11,6 +11,72 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
 {
     static class VertexUtils
     static class VertexUtils
     {
     {
+        public static Type GetVertexGeometryType(params string[] vertexAttributes)
+        {
+            var t = typeof(VertexPosition);
+            if (vertexAttributes.Contains("NORMAL")) t = typeof(VertexPositionNormal);
+            if (vertexAttributes.Contains("TANGENT")) t = typeof(VertexPositionNormalTangent);
+            return t;
+        }
+
+        public static Type GetVertexMaterialType(params string[] vertexAttributes)
+        {
+            var colors = vertexAttributes.Contains("COLOR_0") ? 1 : 0;
+            colors = vertexAttributes.Contains("COLOR_1") ? 2 : colors;
+            colors = vertexAttributes.Contains("COLOR_2") ? 3 : colors;
+            colors = vertexAttributes.Contains("COLOR_3") ? 4 : colors;
+
+            var uvcoords = vertexAttributes.Contains("TEXCOORD_0") ? 1 : 0;
+            uvcoords = vertexAttributes.Contains("TEXCOORD_1") ? 2 : uvcoords;
+            uvcoords = vertexAttributes.Contains("TEXCOORD_2") ? 3 : uvcoords;
+            uvcoords = vertexAttributes.Contains("TEXCOORD_3") ? 4 : uvcoords;
+
+            if (colors == 0)
+            {
+                if (uvcoords == 0) return typeof(VertexEmpty);
+                if (uvcoords == 1) return typeof(VertexTexture1);
+                if (uvcoords >= 2) return typeof(VertexTexture2);
+            }
+
+            if (colors == 1)
+            {
+                if (uvcoords == 0) return typeof(VertexColor1);
+                if (uvcoords == 1) return typeof(VertexColor1Texture1);
+                if (uvcoords >= 2) return typeof(VertexColor1Texture2);
+            }
+
+            if (colors >= 2)
+            {
+                if (uvcoords == 0) return typeof(VertexColor2);
+                if (uvcoords == 1) return typeof(VertexColor2Texture2);
+                if (uvcoords >= 2) return typeof(VertexColor2Texture2);
+            }
+
+            return typeof(VertexEmpty);
+        }
+
+        public static Type GetVertexSkinningType(params string[] vertexAttributes)
+        {
+            var joints = vertexAttributes.Contains("JOINTS_0") && vertexAttributes.Contains("WEIGHTS_0") ? 4 : 0;
+            joints = vertexAttributes.Contains("JOINTS_1") && vertexAttributes.Contains("WEIGHTS_1") ? 8 : joints;
+
+            if (joints == 4) return typeof(VertexJoints16x4);
+            if (joints == 8) return typeof(VertexJoints16x8);
+
+            return typeof(VertexEmpty);
+        }
+
+        public static Type GetVertexBuilderType(params string[] vertexAttributes)
+        {
+            var tvg = GetVertexGeometryType(vertexAttributes);
+            var tvm = GetVertexMaterialType(vertexAttributes);
+            var tvs = GetVertexSkinningType(vertexAttributes);
+
+            var vtype = typeof(VertexBuilder<,,>);
+
+            return vtype.MakeGenericType(tvg, tvm, tvs);
+        }
+
         public static bool SanitizeVertex<TvG>(this TvG inVertex, out TvG outVertex)
         public static bool SanitizeVertex<TvG>(this TvG inVertex, out TvG outVertex)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
         {
         {
@@ -57,8 +123,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             // determine the vertex attributes from the first vertex.
             // determine the vertex attributes from the first vertex.
             var firstVertex = vertexBlocks
             var firstVertex = vertexBlocks
-                .First(item => item.Count > 0)
-                .First();
+                .First(item => item.Count > 0)[0];
 
 
             var tvg = firstVertex.GetGeometry().GetType();
             var tvg = firstVertex.GetGeometry().GetType();
             var tvm = firstVertex.GetMaterial().GetType();
             var tvm = firstVertex.GetMaterial().GetType();
@@ -217,10 +282,9 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (src.GetType() == typeof(TvP)) return (TvP)src;
             if (src.GetType() == typeof(TvP)) return (TvP)src;
 
 
             var dst = default(TvP);
             var dst = default(TvP);
+            src.CopyTo(dst);
 
 
-            dst.SetPosition(src.GetPosition());
-            if (src.TryGetNormal(out Vector3 nrm)) dst.SetNormal(nrm);
-            if (src.TryGetTangent(out Vector4 tgt)) dst.SetTangent(tgt);
+            System.Diagnostics.Debug.Assert(src.GetPosition() == dst.GetPosition());
 
 
             return dst;
             return dst;
         }
         }
@@ -231,7 +295,51 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (src.GetType() == typeof(TvM)) return (TvM)src;
             if (src.GetType() == typeof(TvM)) return (TvM)src;
 
 
             var dst = default(TvM);
             var dst = default(TvM);
+            src.CopyTo(dst);
+
+            #if DEBUG
+            for (int i = 0; i < Math.Min(src.MaxColors, dst.MaxColors); ++i)
+            {
+                System.Diagnostics.Debug.Assert(src.GetColor(i) == dst.GetColor(i));
+            }
+
+            for (int i = 0; i < Math.Min(src.MaxTextCoords, dst.MaxTextCoords); ++i)
+            {
+                System.Diagnostics.Debug.Assert(src.GetTexCoord(i) == dst.GetTexCoord(i));
+            }
+            #endif
+            return dst;
+        }
+
+        public static TvS ConvertTo<TvS>(this IVertexSkinning src)
+            where TvS : struct, IVertexSkinning
+        {
+            if (src.GetType() == typeof(TvS)) return (TvS)src;
+
+            var dst = default(TvS);
+            src.CopyTo(dst);
+
+            #if DEBUG
+
+            if (src.JointBindings == dst.JointBindings)
+            {
+                // todo, check bindings are the same.
+            }
+
+            #endif
+
+            return dst;
+        }
+
+        public static void CopyTo(this IVertexGeometry src, IVertexGeometry dst)
+        {
+            dst.SetPosition(src.GetPosition());
+            if (src.TryGetNormal(out Vector3 nrm)) dst.SetNormal(nrm);
+            if (src.TryGetTangent(out Vector4 tgt)) dst.SetTangent(tgt);
+        }
 
 
+        public static void CopyTo(this IVertexMaterial src, IVertexMaterial dst)
+        {
             int i = 0;
             int i = 0;
 
 
             while (i < Math.Min(src.MaxColors, dst.MaxColors))
             while (i < Math.Min(src.MaxColors, dst.MaxColors))
@@ -259,29 +367,29 @@ namespace SharpGLTF.Geometry.VertexTypes
                 dst.SetTexCoord(i, Vector2.Zero);
                 dst.SetTexCoord(i, Vector2.Zero);
                 ++i;
                 ++i;
             }
             }
-
-            return dst;
         }
         }
 
 
-        public static unsafe TvS ConvertTo<TvS>(this IVertexSkinning src)
-            where TvS : struct, IVertexSkinning
+        public static unsafe void CopyTo(this IVertexSkinning src, IVertexSkinning dst)
         {
         {
-            if (src.GetType() == typeof(TvS)) return (TvS)src;
-
             // create copy
             // create copy
 
 
-            var dst = default(TvS);
-
             if (dst.MaxBindings >= src.MaxBindings)
             if (dst.MaxBindings >= src.MaxBindings)
             {
             {
-                for (int i = 0; i < src.MaxBindings; ++i)
+                int i = 0;
+
+                for (i = 0; i < src.MaxBindings; ++i)
                 {
                 {
                     var jw = src.GetJointBinding(i);
                     var jw = src.GetJointBinding(i);
 
 
                     dst.SetJointBinding(i, jw.Joint, jw.Weight);
                     dst.SetJointBinding(i, jw.Joint, jw.Weight);
                 }
                 }
 
 
-                return dst;
+                while (i < dst.MaxBindings)
+                {
+                    dst.SetJointBinding(i++, 0, 0);
+                }
+
+                return;
             }
             }
 
 
             // if there's more source joints than destination joints, transfer with scale
             // if there's more source joints than destination joints, transfer with scale
@@ -301,8 +409,6 @@ namespace SharpGLTF.Geometry.VertexTypes
             {
             {
                 dst.SetJointBinding(i, srcjw[i].Joint, srcjw[i].Weight * w);
                 dst.SetJointBinding(i, srcjw[i].Joint, srcjw[i].Weight * w);
             }
             }
-
-            return dst;
         }
         }
     }
     }
 }
 }

+ 2 - 2
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -233,7 +233,7 @@ namespace SharpGLTF.IO
 
 
         public void AddModel(ModelRoot model)
         public void AddModel(ModelRoot model)
         {
         {
-            foreach (var triangle in Schema2Toolkit.Triangulate<VGEOMETRY, VMATERIAL>(model.DefaultScene))
+            foreach (var triangle in Schema2Toolkit.EvaluateTriangles<VGEOMETRY, VMATERIAL>(model.DefaultScene))
             {
             {
                 var dstMaterial = default(Material);
                 var dstMaterial = default(Material);
 
 
@@ -256,7 +256,7 @@ namespace SharpGLTF.IO
 
 
         public void AddModel(ModelRoot model, Animation animation, float time)
         public void AddModel(ModelRoot model, Animation animation, float time)
         {
         {
-            foreach (var triangle in Schema2Toolkit.Triangulate<VGEOMETRY, VMATERIAL>(model.DefaultScene, animation, time))
+            foreach (var triangle in Schema2Toolkit.EvaluateTriangles<VGEOMETRY, VMATERIAL>(model.DefaultScene, animation, time))
             {
             {
                 var dstMaterial = default(Material);
                 var dstMaterial = default(Material);
 
 

+ 1 - 1
src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs

@@ -140,7 +140,7 @@ namespace SharpGLTF.Materials
         {
         {
             if (image.Count > 0)
             if (image.Count > 0)
             {
             {
-                Guard.IsTrue(image._IsJpgImage() || image._IsPngImage() || image._IsDdsImage() || image._IsWebpImage(), nameof(image), "Must be JPG, PNG, DDS or WEBP");
+                Guard.IsTrue(image._IsImage(), nameof(image), "Must be JPG, PNG, DDS or WEBP");
             }
             }
             else
             else
             {
             {

+ 45 - 11
src/SharpGLTF.Toolkit/Scenes/Content.cs

@@ -109,18 +109,26 @@ namespace SharpGLTF.Scenes
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public SkinTransformer(MESHBUILDER mesh, NodeBuilder[] joints)
+        public SkinTransformer(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, NodeBuilder[] joints)
         {
         {
+            Guard.NotNull(mesh, nameof(mesh));
+            Guard.NotNull(joints, nameof(joints));
+            Guard.IsTrue(NodeBuilder.IsValidArmature(joints), nameof(joints));
+
             _Target = new MeshContent(mesh);
             _Target = new MeshContent(mesh);
-            _TargetBindMatrix = null;
-            _Joints.AddRange(joints);
+            _TargetBindMatrix = meshBindMatrix;
+            _Joints.AddRange(joints.Select(item => (item, (Matrix4x4?)null)));
         }
         }
 
 
-        public SkinTransformer(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, NodeBuilder[] joints)
+        public SkinTransformer(MESHBUILDER mesh, (NodeBuilder, Matrix4x4)[] joints)
         {
         {
+            Guard.NotNull(mesh, nameof(mesh));
+            Guard.NotNull(joints, nameof(joints));
+            Guard.IsTrue(NodeBuilder.IsValidArmature(joints.Select(item => item.Item1)), nameof(joints));
+
             _Target = new MeshContent(mesh);
             _Target = new MeshContent(mesh);
-            _TargetBindMatrix = meshBindMatrix;
-            _Joints.AddRange(joints);
+            _TargetBindMatrix = null;
+            _Joints.AddRange(joints.Select(item => (item.Item1, (Matrix4x4?)item.Item2)));
         }
         }
 
 
         #endregion
         #endregion
@@ -131,7 +139,7 @@ namespace SharpGLTF.Scenes
         private Matrix4x4? _TargetBindMatrix;
         private Matrix4x4? _TargetBindMatrix;
 
 
         // condition: all NodeBuilder objects must have the same root.
         // condition: all NodeBuilder objects must have the same root.
-        private readonly List<NodeBuilder> _Joints = new List<NodeBuilder>();
+        private readonly List<(NodeBuilder, Matrix4x4?)> _Joints = new List<(NodeBuilder, Matrix4x4?)>();
 
 
         #endregion
         #endregion
 
 
@@ -139,17 +147,43 @@ namespace SharpGLTF.Scenes
 
 
         public MESHBUILDER GetGeometryAsset() { return (_Target as IRenderableContent)?.GetGeometryAsset(); }
         public MESHBUILDER GetGeometryAsset() { return (_Target as IRenderableContent)?.GetGeometryAsset(); }
 
 
-        public NodeBuilder GetArmatureAsset() { return _Joints.Select(item => item.Root).Distinct().FirstOrDefault(); }
+        public NodeBuilder GetArmatureAsset() { return _Joints.Select(item => item.Item1.Root).Distinct().FirstOrDefault(); }
 
 
         public void Setup(Scene dstScene, Schema2SceneBuilder context)
         public void Setup(Scene dstScene, Schema2SceneBuilder context)
         {
         {
             var skinnedMeshNode = dstScene.CreateNode();
             var skinnedMeshNode = dstScene.CreateNode();
 
 
-            var skinnedJoints = _Joints
-                .Select(j => context.GetNode(j))
+            if (_TargetBindMatrix.HasValue)
+            {
+                var dstNodes = new Node[_Joints.Count];
+
+                for (int i = 0; i < dstNodes.Length; ++i)
+                {
+                    var srcNode = _Joints[i];
+
+                    System.Diagnostics.Debug.Assert(!srcNode.Item2.HasValue);
+
+                    dstNodes[i] = context.GetNode(srcNode.Item1);
+                }
+
+                #if DEBUG
+                for (int i = 0; i < dstNodes.Length; ++i)
+                {
+                    var srcNode = _Joints[i];
+                    System.Diagnostics.Debug.Assert(dstNodes[i].WorldMatrix == srcNode.Item1.WorldMatrix);
+                }
+                #endif
+
+                skinnedMeshNode.WithSkinBinding(_TargetBindMatrix.Value, dstNodes);
+            }
+            else
+            {
+                var skinnedJoints = _Joints
+                .Select(j => (context.GetNode(j.Item1), j.Item2.Value) )
                 .ToArray();
                 .ToArray();
 
 
-            skinnedMeshNode.WithSkinBinding(_TargetBindMatrix ?? Matrix4x4.Identity, skinnedJoints);
+                skinnedMeshNode.WithSkinBinding(skinnedJoints);
+            }
 
 
             _Target.Setup(skinnedMeshNode, context);
             _Target.Setup(skinnedMeshNode, context);
         }
         }

+ 2 - 0
src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs

@@ -17,8 +17,10 @@ namespace SharpGLTF.Scenes
 
 
         #region data
         #region data
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private readonly SceneBuilder _Parent;
         private readonly SceneBuilder _Parent;
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private IContentRoot _Content;
         private IContentRoot _Content;
 
 
         #endregion
         #endregion

+ 14 - 2
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
@@ -109,7 +110,7 @@ namespace SharpGLTF.Scenes
                 if (value.Translation != Vector3.Zero)
                 if (value.Translation != Vector3.Zero)
                 {
                 {
                     if (Translation == null) Translation = new Animations.AnimatableProperty<Vector3>();
                     if (Translation == null) Translation = new Animations.AnimatableProperty<Vector3>();
-                    Translation.Value = value.Scale;
+                    Translation.Value = value.Translation;
                 }
                 }
             }
             }
         }
         }
@@ -180,7 +181,7 @@ namespace SharpGLTF.Scenes
             if (Translation == null)
             if (Translation == null)
             {
             {
                 Translation = new Animations.AnimatableProperty<Vector3>();
                 Translation = new Animations.AnimatableProperty<Vector3>();
-                Translation.Value = Vector3.One;
+                Translation.Value = Vector3.Zero;
             }
             }
 
 
             return Translation;
             return Translation;
@@ -217,6 +218,17 @@ namespace SharpGLTF.Scenes
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
         }
         }
 
 
+        public static bool IsValidArmature(IEnumerable<NodeBuilder> joints)
+        {
+            if (joints == null) return false;
+            if (!joints.Any()) return false;
+            if (joints.Any(item => item == null)) return false;
+
+            var root = joints.First().Root;
+
+            return joints.All(item => Object.ReferenceEquals(item.Root, root));
+        }
+
         #endregion
         #endregion
 
 
         #region With* API
         #region With* API

+ 6 - 3
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -6,10 +6,13 @@ using System.Numerics;
 
 
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 
 
+using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
+
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
-    using MESHBUILDER = Geometry.IMeshBuilder<Materials.MaterialBuilder>;
-
+    /// <summary>
+    /// Helper class to create a Schema2.Scene from a Scene Builder
+    /// </summary>
     class Schema2SceneBuilder
     class Schema2SceneBuilder
     {
     {
         #region data
         #region data
@@ -31,7 +34,6 @@ namespace SharpGLTF.Scenes
         public void AddScene(Scene dstScene, SceneBuilder srcScene)
         public void AddScene(Scene dstScene, SceneBuilder srcScene)
         {
         {
             // gather all MaterialBuilder unique instances
             // gather all MaterialBuilder unique instances
-            // find and group materials that have the same content.
 
 
             var materialGroups = srcScene.Instances
             var materialGroups = srcScene.Instances
                 .Select(item => item.GetGeometryAsset())
                 .Select(item => item.GetGeometryAsset())
@@ -41,6 +43,7 @@ namespace SharpGLTF.Scenes
                 .Where(item => item != null)
                 .Where(item => item != null)
                 .Distinct()
                 .Distinct()
                 .ToList()
                 .ToList()
+                // group by equal content, to reduce material splitting whenever possible.
                 .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
                 .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
 
 
             foreach (var mg in materialGroups)
             foreach (var mg in materialGroups)

+ 6 - 4
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -11,12 +11,14 @@ namespace SharpGLTF.Scenes
     {
     {
         #region data
         #region data
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private readonly List<InstanceBuilder> _Instances = new List<InstanceBuilder>();
         private readonly List<InstanceBuilder> _Instances = new List<InstanceBuilder>();
 
 
         #endregion
         #endregion
 
 
         #region properties
         #region properties
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public IReadOnlyList<InstanceBuilder> Instances => _Instances;
         public IReadOnlyList<InstanceBuilder> Instances => _Instances;
 
 
         #endregion
         #endregion
@@ -39,18 +41,18 @@ namespace SharpGLTF.Scenes
             _Instances.Add(instance);
             _Instances.Add(instance);
         }
         }
 
 
-        public void AddSkinnedMesh(MESHBUILDER mesh, params NodeBuilder[] joints)
+        public void AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, params NodeBuilder[] joints)
         {
         {
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new SkinTransformer(mesh, joints);
+            instance.Content = new SkinTransformer(mesh, meshBindMatrix, joints);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
         }
         }
 
 
-        public void AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, params NodeBuilder[] joints)
+        public void AddSkinnedMesh(MESHBUILDER mesh, params (NodeBuilder, Matrix4x4)[] joints)
         {
         {
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new SkinTransformer(mesh, meshBindMatrix, joints);
+            instance.Content = new SkinTransformer(mesh, joints);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
         }
         }

+ 24 - 1
src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

@@ -17,6 +17,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="Material"/> instance.</returns>
         /// <returns>This <see cref="Material"/> instance.</returns>
         public static Material WithDefault(this Material material)
         public static Material WithDefault(this Material material)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             return material.WithPBRMetallicRoughness();
             return material.WithPBRMetallicRoughness();
         }
         }
 
 
@@ -28,6 +30,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="Material"/> instance.</returns>
         /// <returns>This <see cref="Material"/> instance.</returns>
         public static Material WithDefault(this Material material, Vector4 diffuseColor)
         public static Material WithDefault(this Material material, Vector4 diffuseColor)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             var ch = material.WithPBRMetallicRoughness().FindChannel("BaseColor").Value;
             var ch = material.WithPBRMetallicRoughness().FindChannel("BaseColor").Value;
 
 
             ch.Parameter = diffuseColor;
             ch.Parameter = diffuseColor;
@@ -37,12 +41,16 @@ namespace SharpGLTF.Schema2
 
 
         public static Material WithDoubleSide(this Material material, bool enabled)
         public static Material WithDoubleSide(this Material material, bool enabled)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             material.DoubleSided = enabled;
             material.DoubleSided = enabled;
             return material;
             return material;
         }
         }
 
 
         public static Material WithChannelParameter(this Material material, string channelName, Vector4 parameter)
         public static Material WithChannelParameter(this Material material, string channelName, Vector4 parameter)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             var channel = material.FindChannel(channelName).Value;
             var channel = material.FindChannel(channelName).Value;
 
 
             channel.Parameter = parameter;
             channel.Parameter = parameter;
@@ -52,6 +60,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Material WithChannelTexture(this Material material, string channelName, int textureSet, string imageFilePath)
         public static Material WithChannelTexture(this Material material, string channelName, int textureSet, string imageFilePath)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             var image = material.LogicalParent.UseImageWithFile(imageFilePath);
             var image = material.LogicalParent.UseImageWithFile(imageFilePath);
 
 
             return material.WithChannelTexture(channelName, textureSet, image);
             return material.WithChannelTexture(channelName, textureSet, image);
@@ -59,6 +69,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Material WithChannelTexture(this Material material, string channelName, int textureSet, Image image)
         public static Material WithChannelTexture(this Material material, string channelName, int textureSet, Image image)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             var channel = material.FindChannel(channelName).Value;
             var channel = material.FindChannel(channelName).Value;
 
 
             channel.SetTexture(textureSet, image);
             channel.SetTexture(textureSet, image);
@@ -73,6 +85,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="Material"/> instance.</returns>
         /// <returns>This <see cref="Material"/> instance.</returns>
         public static Material WithPBRMetallicRoughness(this Material material)
         public static Material WithPBRMetallicRoughness(this Material material)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             material.InitializePBRMetallicRoughness();
             material.InitializePBRMetallicRoughness();
             return material;
             return material;
         }
         }
@@ -104,6 +118,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="Material"/> instance.</returns>
         /// <returns>This <see cref="Material"/> instance.</returns>
         public static Material WithPBRSpecularGlossiness(this Material material)
         public static Material WithPBRSpecularGlossiness(this Material material)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             material.InitializePBRSpecularGlossiness();
             material.InitializePBRSpecularGlossiness();
             return material;
             return material;
         }
         }
@@ -115,6 +131,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="Material"/> instance.</returns>
         /// <returns>This <see cref="Material"/> instance.</returns>
         public static Material WithUnlit(this Material material)
         public static Material WithUnlit(this Material material)
         {
         {
+            Guard.NotNull(material, nameof(material));
+
             material.InitializeUnlit();
             material.InitializeUnlit();
             return material;
             return material;
         }
         }
@@ -140,6 +158,8 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Image"/> instance.</returns>
         /// <returns>A <see cref="Image"/> instance.</returns>
         public static Image UseImageWithContent(this ModelRoot root, Byte[] imageContent)
         public static Image UseImageWithContent(this ModelRoot root, Byte[] imageContent)
         {
         {
+            Guard.NotNull(root, nameof(root));
+
             return root.UseImage(new ArraySegment<byte>(imageContent));
             return root.UseImage(new ArraySegment<byte>(imageContent));
         }
         }
 
 
@@ -225,11 +245,14 @@ namespace SharpGLTF.Schema2
         {
         {
             Guard.NotNull(srcMaterial, nameof(srcMaterial));
             Guard.NotNull(srcMaterial, nameof(srcMaterial));
             Guard.NotNull(dstMaterial, nameof(dstMaterial));
             Guard.NotNull(dstMaterial, nameof(dstMaterial));
+            Guard.NotNull(channelKeys, nameof(channelKeys));
 
 
             foreach (var k in channelKeys)
             foreach (var k in channelKeys)
             {
             {
                 var src = srcMaterial.FindChannel(k);
                 var src = srcMaterial.FindChannel(k);
-                if (src == null) continue;
+                if (!src.HasValue) continue;
+
+                if (src.Value.HasDefaultContent) continue;
 
 
                 var dst = dstMaterial.UseChannel(k);
                 var dst = dstMaterial.UseChannel(k);
 
 

+ 103 - 13
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -257,19 +257,61 @@ namespace SharpGLTF.Schema2
 
 
         #region evaluation
         #region evaluation
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> Triangulate<TvG, TvM, TvS>(this Mesh mesh)
+        public static IEnumerable<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)> EvaluateTriangles(this Mesh mesh)
+        {
+            if (mesh == null) return Enumerable.Empty<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)>();
+
+            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles());
+        }
+
+        public static IEnumerable<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)> EvaluateTriangles(this MeshPrimitive prim)
+        {
+            if (prim == null) yield break;
+
+            var vertices = prim.GetVertexColumns();
+            var triangles = prim.GetTriangleIndices();
+
+            bool hasNormals = vertices.Normals != null;
+
+            var vtype = VertexUtils.GetVertexBuilderType(prim.VertexAccessors.Keys.ToArray());
+
+            foreach (var t in triangles)
+            {
+                var a = vertices.GetVertex(vtype, t.Item1);
+                var b = vertices.GetVertex(vtype, t.Item2);
+                var c = vertices.GetVertex(vtype, t.Item3);
+
+                /*
+                if (!hasNormals)
+                {
+                    var n = Vector3.Cross(b.Position - a.Position, c.Position - a.Position);
+                    n = Vector3.Normalize(n);
+                    a.Geometry.SetNormal(n);
+                    b.Geometry.SetNormal(n);
+                    c.Geometry.SetNormal(n);
+                }*/
+
+                yield return (a, b, c, prim.Material);
+            }
+        }
+
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this Mesh mesh)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
-            return mesh.Primitives.SelectMany(item => item.Triangulate<TvG, TvM, TvS>());
+            if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)>();
+
+            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM, TvS>());
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> Triangulate<TvG, TvM, TvS>(this MeshPrimitive prim)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this MeshPrimitive prim)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
+            if (prim == null) yield break;
+
             var vertices = prim.GetVertexColumns();
             var vertices = prim.GetVertexColumns();
             var triangles = prim.GetTriangleIndices();
             var triangles = prim.GetTriangleIndices();
 
 
@@ -294,18 +336,20 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> Triangulate<TvG, TvM>(this Mesh mesh, Transforms.ITransform xform)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this Mesh mesh, Transforms.ITransform xform)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            return mesh.Primitives.SelectMany(item => item.Triangulate<TvG, TvM>(xform));
+            if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
+
+            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM>(xform));
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> Triangulate<TvG, TvM>(this MeshPrimitive prim, Transforms.ITransform xform)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this MeshPrimitive prim, Transforms.ITransform xform)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            if (!xform.Visible) yield break;
+            if (xform == null || !xform.Visible) yield break;
 
 
             var vertices = prim.GetVertexColumns();
             var vertices = prim.GetVertexColumns();
 
 
@@ -327,20 +371,22 @@ namespace SharpGLTF.Schema2
 
 
         public static VertexBufferColumns GetVertexColumns(this MeshPrimitive primitive)
         public static VertexBufferColumns GetVertexColumns(this MeshPrimitive primitive)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+
             var columns = new VertexBufferColumns();
             var columns = new VertexBufferColumns();
 
 
-            _CopyTo(primitive.VertexAccessors, columns);
+            _Initialize(primitive.VertexAccessors, columns);
 
 
             for (int i = 0; i < primitive.MorphTargetsCount; ++i)
             for (int i = 0; i < primitive.MorphTargetsCount; ++i)
             {
             {
                 var morphTarget = primitive.GetMorphTargetAccessors(i);
                 var morphTarget = primitive.GetMorphTargetAccessors(i);
-                _CopyTo(morphTarget, columns.AddMorphTarget());
+                _Initialize(morphTarget, columns.AddMorphTarget());
             }
             }
 
 
             return columns;
             return columns;
         }
         }
 
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexBufferColumns dstColumns)
+        private static void _Initialize(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexBufferColumns dstColumns)
         {
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
@@ -359,7 +405,7 @@ namespace SharpGLTF.Schema2
             if (vertexAccessors.ContainsKey("WEIGHTS_1")) dstColumns.Weights1 = vertexAccessors["WEIGHTS_1"].AsVector4Array();
             if (vertexAccessors.ContainsKey("WEIGHTS_1")) dstColumns.Weights1 = vertexAccessors["WEIGHTS_1"].AsVector4Array();
         }
         }
 
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, MorphTargetColumns dstColumns)
+        private static void _Initialize(IReadOnlyDictionary<string, Accessor> vertexAccessors, MorphTargetColumns dstColumns)
         {
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
@@ -370,6 +416,8 @@ namespace SharpGLTF.Schema2
 
 
         public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
         public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
         {
         {
+            if (primitive == null) return Enumerable.Empty<(int, int, int)>();
+
             if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
             if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
 
 
             return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
             return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
@@ -430,7 +478,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
                 var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
 
 
-                foreach (var tri in srcPrim.Triangulate<TvG, TvM, TvS>())
+                foreach (var tri in srcPrim.EvaluateTriangles<TvG, TvM, TvS>())
                 {
                 {
                     dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
                     dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
                 }
                 }
@@ -461,7 +509,7 @@ namespace SharpGLTF.Schema2
 
 
             Guard.NotNull(materialFunc, nameof(materialFunc));
             Guard.NotNull(materialFunc, nameof(materialFunc));
 
 
-            foreach (var tri in srcScene.Triangulate<VertexPositionNormal, VertexColor1Texture1>(animation, time))
+            foreach (var tri in srcScene.EvaluateTriangles<VertexPositionNormal, VertexColor1Texture1>(animation, time))
             {
             {
                 var material = materialFunc(tri.Item4);
                 var material = materialFunc(tri.Item4);
 
 
@@ -494,6 +542,48 @@ namespace SharpGLTF.Schema2
             return srcScene.ToStaticMeshBuilder<Materials.MaterialBuilder, TvG, TvM>(animation, time, convertMaterial);
             return srcScene.ToStaticMeshBuilder<Materials.MaterialBuilder, TvG, TvM>(animation, time, convertMaterial);
         }
         }
 
 
+        public static IMeshBuilder<Materials.MaterialBuilder> ToMeshBuilder(this Mesh srcMesh)
+        {
+            if (srcMesh == null) return null;
+
+            var vertexAttributes = srcMesh.Primitives
+                .SelectMany(item => item.VertexAccessors.Keys)
+                .Distinct()
+                .ToArray();
+
+            Materials.MaterialBuilder defMat = null;
+
+            var dstMaterials = new Dictionary<Material, Materials.MaterialBuilder>();
+
+            var dstMesh = MeshBuilderToolkit.CreateMeshBuilderFromVertexAttributes<Materials.MaterialBuilder>(vertexAttributes);
+
+            foreach (var srcTri in srcMesh.EvaluateTriangles())
+            {
+                IPrimitiveBuilder dstPrim = null;
+
+                if (srcTri.Item4 == null)
+                {
+                    if (defMat == null) defMat = Materials.MaterialBuilder.CreateDefault();
+                    dstPrim = dstMesh.UsePrimitive(defMat);
+                }
+                else
+                {
+                    if (!dstMaterials.TryGetValue(srcTri.Item4, out Materials.MaterialBuilder dstMat))
+                    {
+                        dstMat = new Materials.MaterialBuilder();
+                        srcTri.Item4.CopyTo(dstMat);
+                        dstMaterials[srcTri.Item4] = dstMat;
+                    }
+
+                    dstPrim = dstMesh.UsePrimitive(dstMat);
+                }
+
+                dstPrim.AddTriangle(srcTri.Item1, srcTri.Item2, srcTri.Item3);
+            }
+
+            return dstMesh;
+        }
+
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         {
         {
             Guard.NotNull(model, nameof(model));
             Guard.NotNull(model, nameof(model));

+ 132 - 9
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -15,12 +15,16 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithLocalTransform(this Node node, Transforms.AffineTransform xform)
         public static Node WithLocalTransform(this Node node, Transforms.AffineTransform xform)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             node.LocalTransform = xform;
             node.LocalTransform = xform;
             return node;
             return node;
         }
         }
 
 
         public static Node WithLocalTranslation(this Node node, Vector3 translation)
         public static Node WithLocalTranslation(this Node node, Vector3 translation)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var xform = node.LocalTransform;
             var xform = node.LocalTransform;
             xform.Translation = translation;
             xform.Translation = translation;
             node.LocalTransform = xform;
             node.LocalTransform = xform;
@@ -30,6 +34,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithLocalRotation(this Node node, Quaternion rotation)
         public static Node WithLocalRotation(this Node node, Quaternion rotation)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var xform = node.LocalTransform;
             var xform = node.LocalTransform;
             xform.Rotation = rotation;
             xform.Rotation = rotation;
             node.LocalTransform = xform;
             node.LocalTransform = xform;
@@ -39,6 +45,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithLocalScale(this Node node, Vector3 scale)
         public static Node WithLocalScale(this Node node, Vector3 scale)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var xform = node.LocalTransform;
             var xform = node.LocalTransform;
             xform.Scale = scale;
             xform.Scale = scale;
             node.LocalTransform = xform;
             node.LocalTransform = xform;
@@ -48,39 +56,69 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithMesh(this Node node, Mesh mesh)
         public static Node WithMesh(this Node node, Mesh mesh)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             node.Mesh = mesh;
             node.Mesh = mesh;
             return node;
             return node;
         }
         }
 
 
         public static Node WithSkin(this Node node, Skin skin)
         public static Node WithSkin(this Node node, Skin skin)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             node.Skin = skin;
             node.Skin = skin;
             return node;
             return node;
         }
         }
 
 
-        public static Node WithSkinBinding(this Node node, params Node[] joints)
+        public static Node WithSkinBinding(this Node node, Matrix4x4 meshPoseTransform, params Node[] joints)
         {
         {
-            return node.WithSkinBinding(node.WorldMatrix, joints);
+            Guard.NotNull(node, nameof(node));
+
+            var skin = node.LogicalParent.CreateSkin();
+            skin.BindJoints(meshPoseTransform, joints);
+
+            node.Skin = skin;
+            return node;
         }
         }
 
 
-        public static Node WithSkinBinding(this Node node, Matrix4x4 meshPoseTransform, params Node[] joints)
+        public static Node WithSkinBinding(this Node node, params (Node, Matrix4x4)[] joints)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var skin = node.LogicalParent.CreateSkin();
             var skin = node.LogicalParent.CreateSkin();
-            skin.BindJoints(meshPoseTransform, joints);
+            skin.BindJoints(joints);
 
 
             node.Skin = skin;
             node.Skin = skin;
             return node;
             return node;
         }
         }
 
 
-        public static Node WithSkinnedMesh(this Node node, Mesh mesh, params Node[] joints)
+        public static Node WithSkinnedMesh(this Node node, Mesh mesh, Matrix4x4 meshPoseTransform, params Node[] joints)
         {
         {
             Guard.NotNull(node, nameof(node));
             Guard.NotNull(node, nameof(node));
+            Guard.NotNull(mesh, nameof(mesh));
+            Guard.NotNull(joints, nameof(joints));
             Guard.MustShareLogicalParent(node, mesh, nameof(mesh));
             Guard.MustShareLogicalParent(node, mesh, nameof(mesh));
 
 
             foreach (var j in joints) Guard.MustShareLogicalParent(node, j, nameof(joints));
             foreach (var j in joints) Guard.MustShareLogicalParent(node, j, nameof(joints));
 
 
             // TODO: the joints must be visible in the visual tree that contains node.
             // TODO: the joints must be visible in the visual tree that contains node.
 
 
+            return node
+                .WithMesh(mesh)
+                .WithSkinBinding(meshPoseTransform, joints);
+        }
+
+        public static Node WithSkinnedMesh(this Node node, Mesh mesh, params (Node, Matrix4x4)[] joints)
+        {
+            Guard.NotNull(node, nameof(node));
+            Guard.NotNull(mesh, nameof(mesh));
+            Guard.NotNull(joints, nameof(joints));
+            Guard.MustShareLogicalParent(node, mesh, nameof(mesh));
+
+            foreach (var j in joints) Guard.MustShareLogicalParent(node, j.Item1, nameof(joints));
+
+            // TODO: the joints must be visible in the visual tree that contains node.
+
             return node
             return node
                 .WithMesh(mesh)
                 .WithMesh(mesh)
                 .WithSkinBinding(joints);
                 .WithSkinBinding(joints);
@@ -118,6 +156,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Node"/> instance, or Null.</returns>
         /// <returns>A <see cref="Node"/> instance, or Null.</returns>
         public static Node FindNode(this Scene scene, Predicate<Node> predicate)
         public static Node FindNode(this Scene scene, Predicate<Node> predicate)
         {
         {
+            Guard.NotNull(scene, nameof(scene));
             Guard.NotNull(predicate, nameof(predicate));
             Guard.NotNull(predicate, nameof(predicate));
 
 
             return scene.VisualChildren.FirstOrDefault(n => predicate(n));
             return scene.VisualChildren.FirstOrDefault(n => predicate(n));
@@ -131,6 +170,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Node"/> instance, or Null.</returns>
         /// <returns>A <see cref="Node"/> instance, or Null.</returns>
         public static Node FindNode(this Node node, Predicate<Node> predicate)
         public static Node FindNode(this Node node, Predicate<Node> predicate)
         {
         {
+            Guard.NotNull(node, nameof(node));
             Guard.NotNull(predicate, nameof(predicate));
             Guard.NotNull(predicate, nameof(predicate));
 
 
             if (predicate(node)) return node;
             if (predicate(node)) return node;
@@ -153,13 +193,13 @@ namespace SharpGLTF.Schema2
         /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
         /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
         /// <param name="time">The animation time.</param>
         /// <param name="time">The animation time.</param>
         /// <returns>A collection of triangles in world space.</returns>
         /// <returns>A collection of triangles in world space.</returns>
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> Triangulate<TvG, TvM>(this Scene scene, Animation animation = null, float time = 0)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this Scene scene, Animation animation = null, float time = 0)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
             return Node
             return Node
                 .Flatten(scene)
                 .Flatten(scene)
-                .SelectMany(item => item.Triangulate<TvG, TvM>(animation, time));
+                .SelectMany(item => item.EvaluateTriangles<TvG, TvM>(animation, time));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -171,7 +211,7 @@ namespace SharpGLTF.Schema2
         /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
         /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
         /// <param name="time">The animation time.</param>
         /// <param name="time">The animation time.</param>
         /// <returns>A collection of triangles in world space.</returns>
         /// <returns>A collection of triangles in world space.</returns>
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> Triangulate<TvG, TvM>(this Node node, Animation animation = null, float time = 0)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this Node node, Animation animation = null, float time = 0)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
@@ -181,7 +221,90 @@ namespace SharpGLTF.Schema2
 
 
             var xform = node.GetMeshWorldTransform(animation, time);
             var xform = node.GetMeshWorldTransform(animation, time);
 
 
-            return mesh.Triangulate<TvG, TvM>(xform);
+            return mesh.EvaluateTriangles<TvG, TvM>(xform);
+        }
+
+        public static Scenes.SceneBuilder ToSceneBuilder(this Scene srcScene)
+        {
+            if (srcScene == null) return null;
+
+            var dstNodes = new Dictionary<Node, Scenes.NodeBuilder>();
+
+            foreach (var srcArmature in srcScene.VisualChildren)
+            {
+                var dstArmature = new Scenes.NodeBuilder();
+                srcArmature.CopyToNodeBuilder(dstArmature, dstNodes);
+            }
+
+            var srcInstances = Node.Flatten(srcScene).Where(item => item.Mesh != null).ToList();
+
+            // TODO: we must process the armatures of the skin, in case the joints are outside the scene.
+
+            var dstMeshes = srcInstances
+                .Select(item => item.Mesh)
+                .Distinct()
+                .ToDictionary(item => item, item => item.ToMeshBuilder());
+
+            var dstScene = new Scenes.SceneBuilder();
+
+            foreach (var srcInstance in srcInstances)
+            {
+                var dstMesh = dstMeshes[srcInstance.Mesh];
+
+                if (srcInstance.Skin == null)
+                {
+                    var dstNode = dstNodes[srcInstance];
+                    dstScene.AddMesh(dstMesh, dstNode);
+                }
+                else
+                {
+                    var joints = new (Scenes.NodeBuilder, Matrix4x4)[srcInstance.Skin.JointsCount];
+
+                    for (int i = 0; i < joints.Length; ++i)
+                    {
+                        var j = srcInstance.Skin.GetJoint(i);
+                        joints[i] = (dstNodes[j.Item1], j.Item2);
+                    }
+
+                    dstScene.AddSkinnedMesh(dstMesh, joints);
+                }
+            }
+
+            return dstScene;
+        }
+
+        public static void CopyToNodeBuilder(this Node srcNode, Scenes.NodeBuilder dstNode,  IDictionary<Node, Scenes.NodeBuilder> nodeMapping)
+        {
+            Guard.NotNull(srcNode, nameof(srcNode));
+            Guard.NotNull(dstNode, nameof(dstNode));
+
+            dstNode.Name = srcNode.Name;
+            dstNode.LocalTransform = srcNode.LocalTransform;
+
+            foreach (var anim in srcNode.LogicalParent.LogicalAnimations)
+            {
+                var name = anim.Name;
+                if (string.IsNullOrWhiteSpace(name)) name = anim.LogicalIndex.ToString(System.Globalization.CultureInfo.InvariantCulture);
+
+                var scaAnim = anim.FindScaleSampler(srcNode)?.CreateCurveSampler();
+                if (scaAnim != null) dstNode.UseScale(name).SetCurve(scaAnim);
+
+                var rotAnim = anim.FindRotationSampler(srcNode)?.CreateCurveSampler();
+                if (rotAnim != null) dstNode.UseRotation(name).SetCurve(rotAnim);
+
+                var traAnim = anim.FindTranslationSampler(srcNode)?.CreateCurveSampler();
+                if (traAnim != null) dstNode.UseTranslation(name).SetCurve(traAnim);
+            }
+
+            if (nodeMapping == null) return;
+
+            nodeMapping[srcNode] = dstNode;
+
+            foreach (var srcChild in srcNode.VisualChildren)
+            {
+                var dstChild = dstNode.CreateNode();
+                srcChild.CopyToNodeBuilder(dstChild, nodeMapping);
+            }
         }
         }
 
 
         #endregion
         #endregion

+ 39 - 0
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -191,6 +191,7 @@ namespace SharpGLTF.Scenes
             scene.AddSkinnedMesh
             scene.AddSkinnedMesh
                 (
                 (
                 mesh,
                 mesh,
+                Matrix4x4.Identity,
                 joint0, // joint used for skinning joint index 0
                 joint0, // joint used for skinning joint index 0
                 joint1, // joint used for skinning joint index 1
                 joint1, // joint used for skinning joint index 1
                 joint2  // joint used for skinning joint index 2
                 joint2  // joint used for skinning joint index 2
@@ -290,6 +291,7 @@ namespace SharpGLTF.Scenes
             scene.AddSkinnedMesh
             scene.AddSkinnedMesh
                 (
                 (
                 mesh,
                 mesh,
+                armature1.WorldMatrix,
                 joint0, // joint used for skinning joint index 0
                 joint0, // joint used for skinning joint index 0
                 joint1, // joint used for skinning joint index 1
                 joint1, // joint used for skinning joint index 1
                 joint2  // joint used for skinning joint index 2
                 joint2  // joint used for skinning joint index 2
@@ -307,5 +309,42 @@ namespace SharpGLTF.Scenes
             scene.AttachToCurrentTest("skinned.glb");
             scene.AttachToCurrentTest("skinned.glb");
             scene.AttachToCurrentTest("skinned.gltf");
             scene.AttachToCurrentTest("skinned.gltf");
         }
         }
+
+
+        [TestCase("RiggedFigure.glb")]
+        [TestCase("RiggedSimple.glb")]
+        [TestCase("BoxAnimated.glb")]
+        [TestCase("AnimatedMorphCube.glb")]
+        [TestCase("AnimatedMorphSphere.glb")]
+        [TestCase("CesiumMan.glb")]
+        [TestCase("Monster.glb")]
+        [TestCase("BrainStem.glb")]
+        public void TestRoundTrip(string path)
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+
+            path = TestFiles
+                .GetSampleModelsPaths()
+                .FirstOrDefault(item => item.Contains(path));
+
+            var modelSrc = Schema2.ModelRoot.Load(path);
+            Assert.NotNull(modelSrc);
+
+            // perform roundtrip
+
+            var scene = Schema2.Schema2Toolkit.ToSceneBuilder(modelSrc.DefaultScene);
+
+            var modelBis = scene.ToSchema2();
+
+            // save file
+
+            path = System.IO.Path.GetFileNameWithoutExtension(path);
+            modelSrc.AttachToCurrentTest(path +"_src" + ".glb");
+            modelBis.AttachToCurrentTest(path +"_bis" + ".glb");
+
+            modelSrc.AttachToCurrentTest(path + "_src" + ".gltf");
+            modelBis.AttachToCurrentTest(path + "_bis" + ".gltf");
+        }
+
     }
     }
 }
 }

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs

@@ -35,7 +35,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             Assert.NotNull(model);
             Assert.NotNull(model);
 
 
             var triangles = model.DefaultScene
             var triangles = model.DefaultScene
-                .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexTexture1>(model.LogicalAnimations[0], 0.5f)
+                .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexTexture1>(model.LogicalAnimations[0], 0.5f)
                 .ToList();
                 .ToList();
 
 
             // Save as GLB, and also evaluate all triangles and save as Wavefront OBJ            
             // Save as GLB, and also evaluate all triangles and save as Wavefront OBJ            

+ 3 - 3
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -208,7 +208,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             Assert.NotNull(model);
             Assert.NotNull(model);
 
 
             var triangles = model.DefaultScene
             var triangles = model.DefaultScene
-                .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(null, 0)
+                .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(null, 0)
                 .ToArray();
                 .ToArray();
 
 
             model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".obj"));
             model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".obj"));
@@ -238,7 +238,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             model.AttachToCurrentTest(path + ".glb");
             model.AttachToCurrentTest(path + ".glb");
 
 
             var triangles = model.DefaultScene
             var triangles = model.DefaultScene
-                .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>()
+                .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>()
                 .ToArray();            
                 .ToArray();            
 
 
             var anim = model.LogicalAnimations[0];
             var anim = model.LogicalAnimations[0];
@@ -297,7 +297,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
                 TestContext.WriteLine($"    Morph Sparse : {msw.Weight0} {msw.Weight1}");
                 TestContext.WriteLine($"    Morph Sparse : {msw.Weight0} {msw.Weight1}");
 
 
                 var triangles = model.DefaultScene
                 var triangles = model.DefaultScene
-                    .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(anim, t)
+                    .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(anim, t)
                     .ToList();
                     .ToList();
 
 
                 var vertices = triangles
                 var vertices = triangles