Browse Source

improving skinning API

Vicente Penades 6 years ago
parent
commit
69451d6d63
27 changed files with 599 additions and 292 deletions
  1. 7 0
      SharpGLTF.sln
  2. 12 0
      examples/InfiniteSkinnedTentacle/InfiniteSkinnedTentacle.csproj
  3. 105 0
      examples/InfiniteSkinnedTentacle/Program.cs
  4. 4 1
      examples/PointCloudGalaxy/Program.cs
  5. 5 0
      src/Shared/_Extensions.cs
  6. 35 9
      src/SharpGLTF.Core/Schema2/gltf.Node.cs
  7. 9 2
      src/SharpGLTF.Core/Schema2/gltf.Skin.cs
  8. 29 1
      src/SharpGLTF.Core/Transforms/AffineTransform.cs
  9. 3 3
      src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs
  10. 19 19
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  11. 47 25
      src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs
  12. 8 8
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexColumns.cs
  13. 3 3
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs
  14. 31 0
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs
  15. 25 7
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPosition.cs
  16. 92 69
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs
  17. 17 17
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs
  18. 15 0
      src/SharpGLTF.Toolkit/Schema2/AnimationExtensions.cs
  19. 3 3
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  20. 7 7
      tests/SharpGLTF.Tests/Geometry/VertexTypes/JointWeightPairTests.cs
  21. 14 14
      tests/SharpGLTF.Tests/Geometry/VertexTypes/VertexSkinningTests.cs
  22. 3 3
      tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs
  23. 4 4
      tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs
  24. 9 10
      tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs
  25. 2 2
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs
  26. 91 45
      tests/SharpGLTF.Tests/Utils.cs
  27. 0 40
      tests/SharpGLTF.Tests/VectorUtils.cs

+ 7 - 0
SharpGLTF.sln

@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{83
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PointCloudGalaxy", "examples\PointCloudGalaxy\PointCloudGalaxy.csproj", "{53B7933A-DD1B-4E75-90EC-94E46101C6CC}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PointCloudGalaxy", "examples\PointCloudGalaxy\PointCloudGalaxy.csproj", "{53B7933A-DD1B-4E75-90EC-94E46101C6CC}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfiniteSkinnedTentacle", "examples\InfiniteSkinnedTentacle\InfiniteSkinnedTentacle.csproj", "{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -59,6 +61,10 @@ Global
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Release|Any CPU.Build.0 = Release|Any CPU
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -70,6 +76,7 @@ Global
 		{41690879-1F91-4555-A40A-F67B01868D7E} = {072B725F-773F-4751-9616-E9778897C1D2}
 		{41690879-1F91-4555-A40A-F67B01868D7E} = {072B725F-773F-4751-9616-E9778897C1D2}
 		{68662AA0-8523-4B9E-9230-DE79F2B07EAB} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 		{68662AA0-8523-4B9E-9230-DE79F2B07EAB} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 		{53B7933A-DD1B-4E75-90EC-94E46101C6CC} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
+		{F64C6CC1-BD12-47B8-B6EA-D5609AC738DF} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {1D7BBAD9-834C-4981-AC96-0AA5226FC43F}
 		SolutionGuid = {1D7BBAD9-834C-4981-AC96-0AA5226FC43F}

+ 12 - 0
examples/InfiniteSkinnedTentacle/InfiniteSkinnedTentacle.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj" />
+  </ItemGroup>
+
+</Project>

+ 105 - 0
examples/InfiniteSkinnedTentacle/Program.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+using SharpGLTF.Schema2;
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+
+namespace InfiniteSkinnedTentacle
+{
+    using VERTEX = VertexBuilder<VertexPosition, VertexColor1, VertexJoints8x4>;
+    using MESH = MeshBuilder<VertexPosition, VertexColor1, VertexJoints8x4>;
+
+    class Program
+    {
+        // Skinning use cases and examples: https://github.com/KhronosGroup/glTF/issues/1403
+
+        private static readonly Random _Randomizer = new Random(17);
+
+        static void Main(string[] args)
+        {
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("default");
+
+            var mesh = model
+                .CreateMeshes(CreateMesh(10))
+                .First();
+
+            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation(-100, 0, 0), Quaternion.CreateFromYawPitchRoll(0f, 0.2f, 0f));
+            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation(   0, 0, 0), Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f));
+            AddTentacleSkeleton(scene, mesh, Matrix4x4.CreateTranslation( 100, 0, 0), Quaternion.CreateFromYawPitchRoll(0f, 0f, 0.2f));
+
+            model.SaveGLB("tentacle.glb");
+        }
+
+        static void AddTentacleSkeleton(Scene scene, Mesh mesh, Matrix4x4 origin, Quaternion anim)
+        {
+            var bindings = new List<Node>();
+
+            Node skeleton = scene.CreateNode();
+            skeleton.LocalTransform = origin;
+
+            Node bone = skeleton;
+
+            for (int i = 0; i < 10; ++i)
+            {
+                if (bone == null)
+                {
+                    bone = skeleton.CreateNode();
+                }
+                else
+                {
+                    bone = bone.CreateNode();
+                    bone.LocalTransform = Matrix4x4.CreateTranslation(0, 10, 0);
+                }
+
+                bone.WithRotationAnimation("Track0", (0, Quaternion.Identity), (1, anim), (2, Quaternion.Identity));
+
+                bindings.Add(bone);                
+            }
+
+            var skin = scene.LogicalParent.CreateSkin();            
+            skin.BindJoints(skeleton, bindings.ToArray());
+
+            scene.CreateNode()
+                .WithMesh(mesh)
+                .WithSkin(skin);
+        }
+
+        static MESH CreateMesh(int boneCount)
+        {
+            var mesh = VERTEX.CreateCompatibleMesh("skinned mesh");
+            var prim = mesh.UsePrimitive(new SharpGLTF.Materials.MaterialBuilder("Default"));
+
+            var a0 = default(VERTEX);
+            var a1 = default(VERTEX);
+            var a2 = default(VERTEX);
+            var a3 = default(VERTEX);            
+
+            for (int i = 0; i < boneCount; ++i)
+            {
+                VERTEX b0 = new VERTEX(new Vector3(-5, i * 10, -5), Vector4.One, (i, 1));
+                VERTEX b1 = new VERTEX(new Vector3(+5, i * 10, -5), Vector4.One, (i, 1));
+                VERTEX b2 = new VERTEX(new Vector3(+5, i * 10, +5), Vector4.One, (i, 1));
+                VERTEX b3 = new VERTEX(new Vector3(-5, i * 10, +5), Vector4.One, (i, 1));
+
+                if (i > 0)
+                {
+                    prim.AddPolygon(b0, b1, a1, a0);
+                    prim.AddPolygon(b1, b2, a2, a1);
+                    prim.AddPolygon(b2, b3, a3, a2);
+                    prim.AddPolygon(b3, b0, a0, a3);
+                }
+
+                a0 = b0;
+                a1 = b1;
+                a2 = b2;
+                a3 = b3;
+            }
+
+            return mesh;
+        }
+    }
+}

+ 4 - 1
examples/PointCloudGalaxy/Program.cs

@@ -2,18 +2,21 @@ using System;
 using System.Numerics;
 using System.Numerics;
 
 
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Materials;
 using SharpGLTF.Schema2;
 using SharpGLTF.Schema2;
 
 
 namespace PointCloudGalaxy
 namespace PointCloudGalaxy
 {
 {
+    using VERTEX = VertexBuilder<VertexPosition, VertexColor1, VertexEmpty>;
+
     class Program
     class Program
     {
     {
         static void Main(string[] args)
         static void Main(string[] args)
         {
         {
             var material = new MaterialBuilder("material1").WithUnlitShader();
             var material = new MaterialBuilder("material1").WithUnlitShader();
 
 
-            var mesh = new MeshBuilder<SharpGLTF.Geometry.VertexTypes.VertexPosition, SharpGLTF.Geometry.VertexTypes.VertexColor1>("points");
+            var mesh = VERTEX.CreateCompatibleMesh("points");
 
 
             // create a point cloud primitive
             // create a point cloud primitive
             var pointCloud = mesh.UsePrimitive(material, 1);
             var pointCloud = mesh.UsePrimitive(material, 1);

+ 5 - 0
src/Shared/_Extensions.cs

@@ -54,6 +54,11 @@ namespace SharpGLTF
             return v.X._IsReal() & v.Y._IsReal() & v.Z._IsReal() & v.W._IsReal();
             return v.X._IsReal() & v.Y._IsReal() & v.Z._IsReal() & v.W._IsReal();
         }
         }
 
 
+        internal static bool _IsReal(this Quaternion v)
+        {
+            return v.X._IsReal() & v.Y._IsReal() & v.Z._IsReal() & v.W._IsReal();
+        }
+
         internal static Vector3 WithLength(this Vector3 v, float len)
         internal static Vector3 WithLength(this Vector3 v, float len)
         {
         {
             return Vector3.Normalize(v) * len;
             return Vector3.Normalize(v) * len;

+ 35 - 9
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -15,6 +15,12 @@ namespace SharpGLTF.Schema2
     [System.Diagnostics.DebuggerDisplay("Node[{LogicalIndex}] {Name} SkinJoint:{IsSkinJoint} T:{LocalTransform.Translation.X} {LocalTransform.Translation.Y} {LocalTransform.Translation.Z}")]
     [System.Diagnostics.DebuggerDisplay("Node[{LogicalIndex}] {Name} SkinJoint:{IsSkinJoint} T:{LocalTransform.Translation.X} {LocalTransform.Translation.Y} {LocalTransform.Translation.Z}")]
     public sealed partial class Node : IVisualNodeContainer
     public sealed partial class Node : IVisualNodeContainer
     {
     {
+        #region constants
+
+        private const string _NOTRANSFORMMESSAGE = "Node instances with a Skin must not contain spatial transformations.";
+
+        #endregion
+
         #region lifecycle
         #region lifecycle
 
 
         internal Node()
         internal Node()
@@ -72,8 +78,15 @@ namespace SharpGLTF.Schema2
             get => Transforms.AffineTransform.Evaluate(_matrix, _scale, _rotation, _translation);
             get => Transforms.AffineTransform.Evaluate(_matrix, _scale, _rotation, _translation);
             set
             set
             {
             {
-                if (value == Matrix4x4.Identity) _matrix = null;
-                else _matrix = value;
+                if (value == Matrix4x4.Identity)
+                {
+                    _matrix = null;
+                }
+                else
+                {
+                    Guard.IsFalse(this._skin.HasValue, _NOTRANSFORMMESSAGE);
+                    _matrix = value;
+                }
 
 
                 _scale = null;
                 _scale = null;
                 _rotation = null;
                 _rotation = null;
@@ -89,10 +102,14 @@ namespace SharpGLTF.Schema2
             get => new Transforms.AffineTransform(_matrix, _scale, _rotation, _translation);
             get => new Transforms.AffineTransform(_matrix, _scale, _rotation, _translation);
             set
             set
             {
             {
+                Guard.IsFalse(this._skin.HasValue, _NOTRANSFORMMESSAGE);
+
+                Guard.IsTrue(value.IsValid, nameof(value));
+
                 _matrix = null;
                 _matrix = null;
-                _scale = value.Scale;
-                _rotation = value.Rotation.Sanitized();
-                _translation = value.Translation;
+                _scale = value.Scale.AsNullable(Vector3.One);
+                _rotation = value.Rotation.Sanitized().AsNullable(Quaternion.Identity);
+                _translation = value.Translation.AsNullable(Vector3.Zero);
             }
             }
         }
         }
 
 
@@ -125,9 +142,11 @@ namespace SharpGLTF.Schema2
             get => this._mesh.HasValue ? this.LogicalParent.LogicalMeshes[this._mesh.Value] : null;
             get => this._mesh.HasValue ? this.LogicalParent.LogicalMeshes[this._mesh.Value] : null;
             set
             set
             {
             {
+                if (value == null) { this._mesh = null; return; }
+
                 Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
                 Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
-                var idx = this.LogicalParent.LogicalMeshes.IndexOfReference(value);
-                this._mesh = idx < 0 ? (int?)null : idx;
+
+                this._mesh = value.LogicalIndex;
             }
             }
         }
         }
 
 
@@ -139,9 +158,16 @@ namespace SharpGLTF.Schema2
             get => this._skin.HasValue ? this.LogicalParent.LogicalSkins[this._skin.Value] : null;
             get => this._skin.HasValue ? this.LogicalParent.LogicalSkins[this._skin.Value] : null;
             set
             set
             {
             {
+                if (value == null) { this._skin = null; return; }
+
                 Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
                 Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
-                var idx = this.LogicalParent.LogicalSkins.IndexOfReference(value);
-                this._skin = idx < 0 ? (int?)null : idx;
+                Guard.IsFalse(_matrix.HasValue, _NOTRANSFORMMESSAGE);
+                Guard.IsFalse(_scale.HasValue, _NOTRANSFORMMESSAGE);
+                Guard.IsFalse(_rotation.HasValue, _NOTRANSFORMMESSAGE);
+                Guard.IsFalse(_translation.HasValue, _NOTRANSFORMMESSAGE);
+                // Todo: guard against animations.
+
+                this._skin = value.LogicalIndex;
             }
             }
         }
         }
 
 

+ 9 - 2
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -133,13 +133,20 @@ namespace SharpGLTF.Schema2
             return true;
             return true;
         }
         }
 
 
-        public void BindJoints(params Node[] joints)
+        public void BindJoints(Node skeleton, params Node[] joints)
         {
         {
+            Guard.MustShareLogicalParent(this, skeleton, nameof(skeleton));
+            foreach (var j in joints) Guard.MustShareLogicalParent(this, j, nameof(joints));
+
+            this.Skeleton = skeleton;
+
+            Matrix4x4.Invert(skeleton.WorldMatrix, out Matrix4x4 skeletonBindXform);
+
             var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
             var pairs = new KeyValuePair<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;
+                var xform = joints[i].WorldMatrix * skeletonBindXform;
 
 
                 Matrix4x4.Invert(xform, out Matrix4x4 ixform);
                 Matrix4x4.Invert(xform, out Matrix4x4 ixform);
 
 

+ 29 - 1
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -30,6 +30,11 @@ namespace SharpGLTF.Transforms
             }
             }
         }
         }
 
 
+        public static implicit operator AffineTransform(Matrix4x4 matrix)
+        {
+            return new AffineTransform(matrix, null, null, null);
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -69,6 +74,29 @@ namespace SharpGLTF.Transforms
             }
             }
         }
         }
 
 
+        public bool IsValid
+        {
+            get
+            {
+                if (!Scale._IsReal()) return false;
+                if (!Rotation._IsReal()) return false;
+                if (!Translation._IsReal()) return false;
+
+                return true;
+            }
+        }
+
+        public bool IsIdentity
+        {
+            get
+            {
+                if (Scale != Vector3.One) return false;
+                if (Rotation != Quaternion.Identity) return false;
+                if (Translation != Vector3.Zero) return false;
+                return true;
+            }
+        }
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -99,7 +127,7 @@ namespace SharpGLTF.Transforms
 
 
             return childWorld * invWorld;
             return childWorld * invWorld;
         }
         }
-
+        
         #endregion
         #endregion
     }
     }
 }
 }

+ 3 - 3
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -121,7 +121,7 @@ namespace SharpGLTF.Geometry
             return _UsePrimitive((material, primitiveVertexCount));
             return _UsePrimitive((material, primitiveVertexCount));
         }
         }
 
 
-        public void AddMesh(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
+        public void AddMesh(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
         {
         {
             foreach (var p in mesh.Primitives)
             foreach (var p in mesh.Primitives)
             {
             {
@@ -135,8 +135,8 @@ namespace SharpGLTF.Geometry
         /// Transforms all the points of all the <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/>
         /// Transforms all the points of all the <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/>
         /// of the this <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/> using the given lambfa function.
         /// of the this <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/> using the given lambfa function.
         /// </summary>
         /// </summary>
-        /// <param name="vertexTransform">A lambda function to transform <see cref="Vertex{TvP, TvM, TvS}"/> vertices.</param>
-        public void TransformVertices(Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
+        /// <param name="vertexTransform">A lambda function to transform <see cref="VertexBuilder{TvP, TvM, TvS}"/> vertices.</param>
+        public void TransformVertices(Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
         {
         {
             foreach (var p in Primitives) p.TransformVertices(vertexTransform);
             foreach (var p in Primitives) p.TransformVertices(vertexTransform);
         }
         }

+ 19 - 19
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -15,7 +15,7 @@ namespace SharpGLTF.Geometry
 
 
         int VertexCount { get; }
         int VertexCount { get; }
 
 
-        Vertex<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
+        VertexBuilder<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning;
             where TvSS : struct, IVertexSkinning;
@@ -33,9 +33,9 @@ namespace SharpGLTF.Geometry
     {
     {
         void AddTriangle<TvPP, TvMM, TvSS>
         void AddTriangle<TvPP, TvMM, TvSS>
             (
             (
-            Vertex<TvPP, TvMM, TvSS> a,
-            Vertex<TvPP, TvMM, TvSS> b,
-            Vertex<TvPP, TvMM, TvSS> c
+            VertexBuilder<TvPP, TvMM, TvSS> a,
+            VertexBuilder<TvPP, TvMM, TvSS> b,
+            VertexBuilder<TvPP, TvMM, TvSS> c
             )
             )
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvMM : struct, IVertexMaterial
@@ -98,7 +98,7 @@ namespace SharpGLTF.Geometry
 
 
         private readonly int _PrimitiveVertexCount;
         private readonly int _PrimitiveVertexCount;
 
 
-        private readonly VertexList<Vertex<TvP, TvM, TvS>> _Vertices = new VertexList<Vertex<TvP, TvM, TvS>>();
+        private readonly VertexList<VertexBuilder<TvP, TvM, TvS>> _Vertices = new VertexList<VertexBuilder<TvP, TvM, TvS>>();
         private readonly List<int> _Indices = new List<int>();
         private readonly List<int> _Indices = new List<int>();
 
 
         #endregion
         #endregion
@@ -119,7 +119,7 @@ namespace SharpGLTF.Geometry
 
 
         public int VertexCount => Vertices.Count;
         public int VertexCount => Vertices.Count;
 
 
-        public IReadOnlyList<Vertex<TvP, TvM, TvS>> Vertices => _Vertices;
+        public IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> Vertices => _Vertices;
 
 
         public IReadOnlyList<int> Indices => _Indices;
         public IReadOnlyList<int> Indices => _Indices;
 
 
@@ -143,7 +143,7 @@ namespace SharpGLTF.Geometry
         /// <typeparamref name="TvS"/> fragments.
         /// <typeparamref name="TvS"/> fragments.
         /// </param>
         /// </param>
         /// <returns>The index of the vertex.</returns>
         /// <returns>The index of the vertex.</returns>
-        public int UseVertex(Vertex<TvP, TvM, TvS> vertex)
+        public int UseVertex(VertexBuilder<TvP, TvM, TvS> vertex)
         {
         {
             if (_Scrict) vertex.Validate();
             if (_Scrict) vertex.Validate();
 
 
@@ -154,7 +154,7 @@ namespace SharpGLTF.Geometry
         /// Adds a point.
         /// Adds a point.
         /// </summary>
         /// </summary>
         /// <param name="a">vertex for this point.</param>
         /// <param name="a">vertex for this point.</param>
-        public void AddPoint(Vertex<TvP, TvM, TvS> a)
+        public void AddPoint(VertexBuilder<TvP, TvM, TvS> a)
         {
         {
             Guard.IsTrue(_PrimitiveVertexCount == 1, nameof(VerticesPerPrimitive), "Points are not supported for this primitive");
             Guard.IsTrue(_PrimitiveVertexCount == 1, nameof(VerticesPerPrimitive), "Points are not supported for this primitive");
 
 
@@ -166,7 +166,7 @@ namespace SharpGLTF.Geometry
         /// </summary>
         /// </summary>
         /// <param name="a">First corner of the line.</param>
         /// <param name="a">First corner of the line.</param>
         /// <param name="b">Second corner of the line.</param>
         /// <param name="b">Second corner of the line.</param>
-        public void AddLine(Vertex<TvP, TvM, TvS> a, Vertex<TvP, TvM, TvS> b)
+        public void AddLine(VertexBuilder<TvP, TvM, TvS> a, VertexBuilder<TvP, TvM, TvS> b)
         {
         {
             Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
             Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
 
 
@@ -192,7 +192,7 @@ namespace SharpGLTF.Geometry
         /// <param name="a">First corner of the triangle.</param>
         /// <param name="a">First corner of the triangle.</param>
         /// <param name="b">Second corner of the triangle.</param>
         /// <param name="b">Second corner of the triangle.</param>
         /// <param name="c">Third corner of the triangle.</param>
         /// <param name="c">Third corner of the triangle.</param>
-        public void AddTriangle(Vertex<TvP, TvM, TvS> a, Vertex<TvP, TvM, TvS> b, Vertex<TvP, TvM, TvS> c)
+        public void AddTriangle(VertexBuilder<TvP, TvM, TvS> a, VertexBuilder<TvP, TvM, TvS> b, VertexBuilder<TvP, TvM, TvS> c)
         {
         {
             Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
             Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
 
 
@@ -222,7 +222,7 @@ namespace SharpGLTF.Geometry
         /// Polygon triangulation is performed by a <see cref="IPolygonTriangulator"/>
         /// Polygon triangulation is performed by a <see cref="IPolygonTriangulator"/>
         /// instance defined in <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}.Triangulator"/>
         /// instance defined in <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}.Triangulator"/>
         /// </remarks>
         /// </remarks>
-        public void AddPolygon(params Vertex<TvP, TvM, TvS>[] points)
+        public void AddPolygon(params VertexBuilder<TvP, TvM, TvS>[] points)
         {
         {
             Span<Vector3> vertices = stackalloc Vector3[points.Length];
             Span<Vector3> vertices = stackalloc Vector3[points.Length];
 
 
@@ -249,7 +249,7 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive, Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> vertexTransform)
+        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive, Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
         {
         {
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));
 
 
@@ -301,26 +301,26 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        public void AddTriangle<TvPP, TvMM, TvSS>(Vertex<TvPP, TvMM, TvSS> a, Vertex<TvPP, TvMM, TvSS> b, Vertex<TvPP, TvMM, TvSS> c)
+        public void AddTriangle<TvPP, TvMM, TvSS>(VertexBuilder<TvPP, TvMM, TvSS> a, VertexBuilder<TvPP, TvMM, TvSS> b, VertexBuilder<TvPP, TvMM, TvSS> c)
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning
             where TvSS : struct, IVertexSkinning
         {
         {
-            var aa = a.CloneAs<TvP, TvM, TvS>();
-            var bb = b.CloneAs<TvP, TvM, TvS>();
-            var cc = c.CloneAs<TvP, TvM, TvS>();
+            var aa = a.ConvertTo<TvP, TvM, TvS>();
+            var bb = b.ConvertTo<TvP, TvM, TvS>();
+            var cc = c.ConvertTo<TvP, TvM, TvS>();
 
 
             AddTriangle(aa, bb, cc);
             AddTriangle(aa, bb, cc);
         }
         }
 
 
-        public Vertex<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
+        public VertexBuilder<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning
             where TvSS : struct, IVertexSkinning
         {
         {
             var v = _Vertices[index];
             var v = _Vertices[index];
 
 
-            return new Vertex<TvPP, TvMM, TvSS>(v.Geometry.CloneAs<TvPP>(), v.Material.CloneAs<TvMM>(), v.Skinning.CloneAs<TvSS>());
+            return new VertexBuilder<TvPP, TvMM, TvSS>(v.Geometry.ConvertTo<TvPP>(), v.Material.ConvertTo<TvMM>(), v.Skinning.ConvertTo<TvSS>());
         }
         }
 
 
         private IEnumerable<int> _GetPointIndices()
         private IEnumerable<int> _GetPointIndices()
@@ -344,7 +344,7 @@ namespace SharpGLTF.Geometry
             return Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
             return Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
         }
         }
 
 
-        public void TransformVertices(Func<Vertex<TvP, TvM, TvS>, Vertex<TvP, TvM, TvS>> transformFunc)
+        public void TransformVertices(Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> transformFunc)
         {
         {
             _Vertices.TransformVertices(transformFunc);
             _Vertices.TransformVertices(transformFunc);
         }
         }

+ 47 - 25
src/SharpGLTF.Toolkit/Geometry/Vertex.cs → src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -35,71 +35,71 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x8"/>.
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
     /// </typeparam>
     [System.Diagnostics.DebuggerDisplay("Vertex {Geometry} {Material} {Skinning}")]
     [System.Diagnostics.DebuggerDisplay("Vertex {Geometry} {Material} {Skinning}")]
-    public struct Vertex<TvP, TvM, TvS>
+    public struct VertexBuilder<TvP, TvM, TvS>
         where TvP : struct, IVertexGeometry
         where TvP : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
         where TvS : struct, IVertexSkinning
     {
     {
         #region constructors
         #region constructors
 
 
-        public Vertex(TvP g, TvM m, TvS s)
+        public VertexBuilder(TvP g, TvM m, TvS s)
         {
         {
             Geometry = g;
             Geometry = g;
             Material = m;
             Material = m;
             Skinning = s;
             Skinning = s;
         }
         }
 
 
-        public Vertex(TvP g, TvM m)
+        public VertexBuilder(TvP g, TvM m, params (int, float)[] bindings)
         {
         {
             Geometry = g;
             Geometry = g;
             Material = m;
             Material = m;
             Skinning = default;
             Skinning = default;
+
+            for (int i = 0; i < bindings.Length; ++i)
+            {
+                Skinning.SetBoneBinding(i, bindings[i].Item1, bindings[i].Item2);
+            }
         }
         }
 
 
-        public Vertex(TvP g, TvS s)
+        public VertexBuilder(TvP g, TvM m)
         {
         {
             Geometry = g;
             Geometry = g;
-            Material = default;
-            Skinning = s;
+            Material = m;
+            Skinning = default;
         }
         }
 
 
-        public Vertex(TvP g)
+        public VertexBuilder(TvP g, TvS s)
         {
         {
             Geometry = g;
             Geometry = g;
             Material = default;
             Material = default;
-            Skinning = default;
+            Skinning = s;
         }
         }
 
 
-        public static implicit operator Vertex<TvP, TvM, TvS>((TvP, TvM, TvS) tuple)
+        public VertexBuilder(TvP g)
         {
         {
-            return new Vertex<TvP, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
+            Geometry = g;
+            Material = default;
+            Skinning = default;
         }
         }
 
 
-        public static implicit operator Vertex<TvP, TvM, TvS>((TvP, TvM) tuple)
+        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvM, TvS) tuple)
         {
         {
-            return new Vertex<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
+            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
         }
         }
 
 
-        public static implicit operator Vertex<TvP, TvM, TvS>((TvP, TvS) tuple)
+        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvM) tuple)
         {
         {
-            return new Vertex<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
+            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
         }
         }
 
 
-        public static implicit operator Vertex<TvP, TvM, TvS>(TvP g)
+        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvS) tuple)
         {
         {
-            return new Vertex<TvP, TvM, TvS>(g);
+            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
         }
         }
 
 
-        public Vertex<TvPP, TvMM, TvSS> CloneAs<TvPP, TvMM, TvSS>()
-            where TvPP : struct, IVertexGeometry
-            where TvMM : struct, IVertexMaterial
-            where TvSS : struct, IVertexSkinning
+        public static implicit operator VertexBuilder<TvP, TvM, TvS>(TvP g)
         {
         {
-            var p = Geometry.CloneAs<TvPP>();
-            var m = Material.CloneAs<TvMM>();
-            var s = Skinning.CloneAs<TvSS>();
-
-            return new Vertex<TvPP, TvMM, TvSS>(p, m, s);
+            return new VertexBuilder<TvP, TvM, TvS>(g);
         }
         }
 
 
         #endregion
         #endregion
@@ -131,6 +131,28 @@ namespace SharpGLTF.Geometry
             Skinning.Validate();
             Skinning.Validate();
         }
         }
 
 
+        public VertexBuilder<TvPP, TvMM, TvSS> ConvertTo<TvPP, TvMM, TvSS>()
+            where TvPP : struct, IVertexGeometry
+            where TvMM : struct, IVertexMaterial
+            where TvSS : struct, IVertexSkinning
+        {
+            var p = Geometry.ConvertTo<TvPP>();
+            var m = Material.ConvertTo<TvMM>();
+            var s = Skinning.ConvertTo<TvSS>();
+
+            return new VertexBuilder<TvPP, TvMM, TvSS>(p, m, s);
+        }
+
+        public static MeshBuilder<TMaterial, TvP, TvM, TvS> CreateCompatibleMesh<TMaterial>(string name = null)
+        {
+            return new MeshBuilder<TMaterial, TvP, TvM, TvS>(name);
+        }
+
+        public static MeshBuilder<TvP, TvM, TvS> CreateCompatibleMesh(string name = null)
+        {
+            return new MeshBuilder<TvP, TvM, TvS>(name);
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 8 - 8
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexColumns.cs

@@ -83,10 +83,10 @@ namespace SharpGLTF.Geometry.VertexTypes
                 var j = Joints0[index];
                 var j = Joints0[index];
                 var w = Weights0[index];
                 var w = Weights0[index];
 
 
-                jjjj.SetBinding(0, (int)j.X, w.X);
-                jjjj.SetBinding(1, (int)j.Y, w.Y);
-                jjjj.SetBinding(2, (int)j.Z, w.Z);
-                jjjj.SetBinding(3, (int)j.W, w.W);
+                jjjj.SetBoneBinding(0, (int)j.X, w.X);
+                jjjj.SetBoneBinding(1, (int)j.Y, w.Y);
+                jjjj.SetBoneBinding(2, (int)j.Z, w.Z);
+                jjjj.SetBoneBinding(3, (int)j.W, w.W);
             }
             }
 
 
             if (Joints1 != null && Weights1 != null)
             if (Joints1 != null && Weights1 != null)
@@ -94,10 +94,10 @@ namespace SharpGLTF.Geometry.VertexTypes
                 var j = Joints1[index];
                 var j = Joints1[index];
                 var w = Weights1[index];
                 var w = Weights1[index];
 
 
-                jjjj.SetBinding(4, (int)j.X, w.X);
-                jjjj.SetBinding(5, (int)j.Y, w.Y);
-                jjjj.SetBinding(6, (int)j.Z, w.Z);
-                jjjj.SetBinding(7, (int)j.W, w.W);
+                jjjj.SetBoneBinding(4, (int)j.X, w.X);
+                jjjj.SetBoneBinding(5, (int)j.Y, w.Y);
+                jjjj.SetBoneBinding(6, (int)j.Z, w.Z);
+                jjjj.SetBoneBinding(7, (int)j.W, w.W);
             }
             }
 
 
             return jjjj;
             return jjjj;

+ 3 - 3
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -25,10 +25,10 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         Vector2 IVertexMaterial.GetTexCoord(int index) { throw new NotSupportedException(); }
         Vector2 IVertexMaterial.GetTexCoord(int index) { throw new NotSupportedException(); }
 
 
-        void IVertexSkinning.SetBinding(int index, int joint, float weight) { }
+        void IVertexSkinning.SetBoneBinding(int index, int joint, float weight) { }
 
 
-        JointWeightPair IVertexSkinning.GetBinding(int index) { throw new NotSupportedException(); }
+        BoneBinding IVertexSkinning.GetBoneBinding(int index) { throw new NotSupportedException(); }
 
 
-        public IEnumerable<JointWeightPair> Bindings => Enumerable.Empty<JointWeightPair>();
+        public IEnumerable<BoneBinding> BoneBindings => Enumerable.Empty<BoneBinding>();
     }
     }
 }
 }

+ 31 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -32,6 +32,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             Color = color;
             Color = color;
         }
         }
 
 
+        public VertexColor1(IVertexMaterial src)
+        {
+            this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
+        }
+
         public static implicit operator VertexColor1(Vector4 color)
         public static implicit operator VertexColor1(Vector4 color)
         {
         {
             return new VertexColor1(color);
             return new VertexColor1(color);
@@ -89,6 +94,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord = uv;
             TexCoord = uv;
         }
         }
 
 
+        public VertexTexture1(IVertexMaterial src)
+        {
+            this.TexCoord = src.MaxTextures > 0 ? src.GetTexCoord(0) : Vector2.Zero;
+        }
+
         public static implicit operator VertexTexture1(Vector2 uv)
         public static implicit operator VertexTexture1(Vector2 uv)
         {
         {
             return new VertexTexture1(uv);
             return new VertexTexture1(uv);
@@ -146,6 +156,12 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord = tex;
             TexCoord = tex;
         }
         }
 
 
+        public VertexColor1Texture1(IVertexMaterial src)
+        {
+            this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
+            this.TexCoord = src.MaxTextures > 0 ? src.GetTexCoord(0) : Vector2.Zero;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -206,6 +222,13 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord1 = tex1;
             TexCoord1 = tex1;
         }
         }
 
 
+        public VertexColor1Texture2(IVertexMaterial src)
+        {
+            this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
+            this.TexCoord0 = src.MaxTextures > 0 ? src.GetTexCoord(0) : Vector2.Zero;
+            this.TexCoord1 = src.MaxTextures > 1 ? src.GetTexCoord(1) : Vector2.Zero;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -279,6 +302,14 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord1 = tex1;
             TexCoord1 = tex1;
         }
         }
 
 
+        public VertexColor2Texture2(IVertexMaterial src)
+        {
+            this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
+            this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
+            this.TexCoord0 = src.MaxTextures > 0 ? src.GetTexCoord(0) : Vector2.Zero;
+            this.TexCoord1 = src.MaxTextures > 1 ? src.GetTexCoord(1) : Vector2.Zero;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data

+ 25 - 7
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPosition.cs

@@ -38,6 +38,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             this.Position = new Vector3(px, py, pz);
             this.Position = new Vector3(px, py, pz);
         }
         }
 
 
+        public VertexPosition(IVertexGeometry src)
+        {
+            this.Position = src.GetPosition();
+        }
+
         public static implicit operator VertexPosition(Vector3 position)
         public static implicit operator VertexPosition(Vector3 position)
         {
         {
             return new VertexPosition(position);
             return new VertexPosition(position);
@@ -89,14 +94,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexPositionNormal(Vector3 p, Vector3 n)
         public VertexPositionNormal(Vector3 p, Vector3 n)
         {
         {
-            Position = p;
-            Normal = Vector3.Normalize(n);
+            this.Position = p;
+            this.Normal = Vector3.Normalize(n);
         }
         }
 
 
         public VertexPositionNormal(float px, float py, float pz, float nx, float ny, float nz)
         public VertexPositionNormal(float px, float py, float pz, float nx, float ny, float nz)
         {
         {
-            Position = new Vector3(px, py, pz);
-            Normal = Vector3.Normalize(new Vector3(nx, ny, nz));
+            this.Position = new Vector3(px, py, pz);
+            this.Normal = Vector3.Normalize(new Vector3(nx, ny, nz));
+        }
+
+        public VertexPositionNormal(IVertexGeometry src)
+        {
+            this.Position = src.GetPosition();
+            src.TryGetNormal(out this.Normal);
         }
         }
 
 
         #endregion
         #endregion
@@ -150,9 +161,16 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexPositionNormalTangent(Vector3 p, Vector3 n, Vector4 t)
         public VertexPositionNormalTangent(Vector3 p, Vector3 n, Vector4 t)
         {
         {
-            Position = p;
-            Normal = Vector3.Normalize(n);
-            Tangent = t;
+            this.Position = p;
+            this.Normal = Vector3.Normalize(n);
+            this.Tangent = t;
+        }
+
+        public VertexPositionNormalTangent(IVertexGeometry src)
+        {
+            this.Position = src.GetPosition();
+            src.TryGetNormal(out this.Normal);
+            src.TryGetTangent(out this.Tangent);
         }
         }
 
 
         #endregion
         #endregion

+ 92 - 69
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -9,24 +9,34 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// Represents a a Node Joint index and its weight in a skinning system.
     /// Represents a a Node Joint index and its weight in a skinning system.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{Joint} = {Weight}")]
     [System.Diagnostics.DebuggerDisplay("{Joint} = {Weight}")]
-    public struct JointWeightPair : IComparable<JointWeightPair>
+    public struct BoneBinding : IComparable<BoneBinding>
     {
     {
-        public static implicit operator JointWeightPair((int, float) jw)
-        {
-            return new JointWeightPair(jw.Item1, jw.Item2);
-        }
+        #region constructors
 
 
-        public JointWeightPair(int joint, float weight)
+        public BoneBinding(int joint, float weight)
         {
         {
             this.Joint = joint;
             this.Joint = joint;
             this.Weight = weight;
             this.Weight = weight;
             if (Weight == 0) Joint = 0;
             if (Weight == 0) Joint = 0;
         }
         }
 
 
+        public static implicit operator BoneBinding((int, float) jw)
+        {
+            return new BoneBinding(jw.Item1, jw.Item2);
+        }
+
+        #endregion
+
+        #region data
+
         public int Joint;
         public int Joint;
         public float Weight;
         public float Weight;
 
 
-        public int CompareTo(JointWeightPair other)
+        #endregion
+
+        #region API
+
+        public int CompareTo(BoneBinding other)
         {
         {
             var a = this.Weight.CompareTo(other.Weight);
             var a = this.Weight.CompareTo(other.Weight);
             if (a != 0) return a;
             if (a != 0) return a;
@@ -34,7 +44,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return this.Joint.CompareTo(other.Joint);
             return this.Joint.CompareTo(other.Joint);
         }
         }
 
 
-        internal static void InPlaceReverseBubbleSort(Span<JointWeightPair> span)
+        internal static void InPlaceReverseBubbleSort(Span<BoneBinding> span)
         {
         {
             for (int i = 1; i < span.Length; ++i)
             for (int i = 1; i < span.Length; ++i)
             {
             {
@@ -58,10 +68,10 @@ namespace SharpGLTF.Geometry.VertexTypes
         /// <summary>
         /// <summary>
         /// Calculates the scale to use on the first <paramref name="count"/> weights.
         /// Calculates the scale to use on the first <paramref name="count"/> weights.
         /// </summary>
         /// </summary>
-        /// <param name="span">A collection of <see cref="JointWeightPair"/>.</param>
+        /// <param name="span">A collection of <see cref="BoneBinding"/>.</param>
         /// <param name="count">The number of items to take from the beginning of <paramref name="span"/>.</param>
         /// <param name="count">The number of items to take from the beginning of <paramref name="span"/>.</param>
         /// <returns>A Scale factor.</returns>
         /// <returns>A Scale factor.</returns>
-        internal static float CalculateScaleFor(Span<JointWeightPair> span, int count)
+        internal static float CalculateScaleFor(Span<BoneBinding> span, int count)
         {
         {
             System.Diagnostics.Debug.Assert(count < span.Length, nameof(count));
             System.Diagnostics.Debug.Assert(count < span.Length, nameof(count));
 
 
@@ -78,14 +88,16 @@ namespace SharpGLTF.Geometry.VertexTypes
             return ww / w;
             return ww / w;
         }
         }
 
 
-        public static IEnumerable<JointWeightPair> GetJoints(IVertexSkinning vs)
+        public static IEnumerable<BoneBinding> GetBindings(IVertexSkinning vs)
         {
         {
             for (int i = 0; i < vs.MaxBindings; ++i)
             for (int i = 0; i < vs.MaxBindings; ++i)
             {
             {
-                var jw = vs.GetBinding(i);
+                var jw = vs.GetBoneBinding(i);
                 if (jw.Weight != 0) yield return jw;
                 if (jw.Weight != 0) yield return jw;
             }
             }
         }
         }
+
+        #endregion
     }
     }
 
 
     public interface IVertexSkinning
     public interface IVertexSkinning
@@ -99,11 +111,11 @@ namespace SharpGLTF.Geometry.VertexTypes
         // - 0 weight joints point to joint 0
         // - 0 weight joints point to joint 0
         void Validate();
         void Validate();
 
 
-        JointWeightPair GetBinding(int index);
+        BoneBinding GetBoneBinding(int index);
 
 
-        void SetBinding(int index, int joint, float weight);
+        void SetBoneBinding(int index, int joint, float weight);
 
 
-        IEnumerable<JointWeightPair> Bindings { get; }
+        IEnumerable<BoneBinding> BoneBindings { get; }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -119,7 +131,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = Vector4.UnitX;
             Weights = Vector4.UnitX;
         }
         }
 
 
-        public VertexJoints8x4(JointWeightPair a, JointWeightPair b)
+        public VertexJoints8x4(BoneBinding a, BoneBinding b)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, 0, 0);
             Joints = new Vector4(a.Joint, b.Joint, 0, 0);
             Weights = new Vector4(a.Weight, b.Weight, 0, 0);
             Weights = new Vector4(a.Weight, b.Weight, 0, 0);
@@ -127,7 +139,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             InPlaceSort();
         }
         }
 
 
-        public VertexJoints8x4(JointWeightPair a, JointWeightPair b, JointWeightPair c)
+        public VertexJoints8x4(BoneBinding a, BoneBinding b, BoneBinding c)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
@@ -135,7 +147,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             InPlaceSort();
         }
         }
 
 
-        public VertexJoints8x4(JointWeightPair a, JointWeightPair b, JointWeightPair c, JointWeightPair d)
+        public VertexJoints8x4(BoneBinding a, BoneBinding b, BoneBinding c, BoneBinding d)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
@@ -143,6 +155,17 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             InPlaceSort();
         }
         }
 
 
+        public VertexJoints8x4(params (int, float)[] bindings)
+        {
+            Joints = Vector4.Zero;
+            Weights = Vector4.Zero;
+
+            for (int i = 0; i < bindings.Length; ++i)
+            {
+                this.SetBoneBinding(i, bindings[i].Item1, bindings[i].Item2);
+            }
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -167,19 +190,19 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
         }
 
 
-        public JointWeightPair GetBinding(int index)
+        public BoneBinding GetBoneBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointWeightPair((int)this.Joints.X, this.Weights.X);
-                case 1: return new JointWeightPair((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JointWeightPair((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JointWeightPair((int)this.Joints.W, this.Weights.W);
+                case 0: return new BoneBinding((int)this.Joints.X, this.Weights.X);
+                case 1: return new BoneBinding((int)this.Joints.Y, this.Weights.Y);
+                case 2: return new BoneBinding((int)this.Joints.Z, this.Weights.Z);
+                case 3: return new BoneBinding((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
 
 
-        public void SetBinding(int index, int joint, float weight)
+        public void SetBoneBinding(int index, int joint, float weight)
         {
         {
             switch (index)
             switch (index)
             {
             {
@@ -193,20 +216,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            Span<JointWeightPair> pairs = stackalloc JointWeightPair[4];
+            Span<BoneBinding> pairs = stackalloc BoneBinding[4];
 
 
-            pairs[0] = new JointWeightPair((int)Joints.X, Weights.X);
-            pairs[1] = new JointWeightPair((int)Joints.Y, Weights.Y);
-            pairs[2] = new JointWeightPair((int)Joints.Z, Weights.Z);
-            pairs[3] = new JointWeightPair((int)Joints.W, Weights.W);
+            pairs[0] = new BoneBinding((int)Joints.X, Weights.X);
+            pairs[1] = new BoneBinding((int)Joints.Y, Weights.Y);
+            pairs[2] = new BoneBinding((int)Joints.Z, Weights.Z);
+            pairs[3] = new BoneBinding((int)Joints.W, Weights.W);
 
 
-            JointWeightPair.InPlaceReverseBubbleSort(pairs);
+            BoneBinding.InPlaceReverseBubbleSort(pairs);
 
 
             Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
             Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
         }
         }
 
 
-        public IEnumerable<JointWeightPair> Bindings => JointWeightPair.GetJoints(this);
+        public IEnumerable<BoneBinding> BoneBindings => BoneBinding.GetBindings(this);
 
 
         #endregion
         #endregion
     }
     }
@@ -224,7 +247,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights = Vector4.UnitX;
             Weights = Vector4.UnitX;
         }
         }
 
 
-        public VertexJoints16x4(JointWeightPair a, JointWeightPair b)
+        public VertexJoints16x4(BoneBinding a, BoneBinding b)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, 0, 0);
             Joints = new Vector4(a.Joint, b.Joint, 0, 0);
             Weights = new Vector4(a.Weight, b.Weight, 0, 0);
             Weights = new Vector4(a.Weight, b.Weight, 0, 0);
@@ -232,7 +255,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             InPlaceSort();
         }
         }
 
 
-        public VertexJoints16x4(JointWeightPair a, JointWeightPair b, JointWeightPair c)
+        public VertexJoints16x4(BoneBinding a, BoneBinding b, BoneBinding c)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
@@ -240,7 +263,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             InPlaceSort();
         }
         }
 
 
-        public VertexJoints16x4(JointWeightPair a, JointWeightPair b, JointWeightPair c, JointWeightPair d)
+        public VertexJoints16x4(BoneBinding a, BoneBinding b, BoneBinding c, BoneBinding d)
         {
         {
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
             Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
             Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
@@ -272,19 +295,19 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
         }
 
 
-        public JointWeightPair GetBinding(int index)
+        public BoneBinding GetBoneBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointWeightPair((int)this.Joints.X, this.Weights.X);
-                case 1: return new JointWeightPair((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JointWeightPair((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JointWeightPair((int)this.Joints.W, this.Weights.W);
+                case 0: return new BoneBinding((int)this.Joints.X, this.Weights.X);
+                case 1: return new BoneBinding((int)this.Joints.Y, this.Weights.Y);
+                case 2: return new BoneBinding((int)this.Joints.Z, this.Weights.Z);
+                case 3: return new BoneBinding((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
 
 
-        public void SetBinding(int index, int joint, float weight)
+        public void SetBoneBinding(int index, int joint, float weight)
         {
         {
             switch (index)
             switch (index)
             {
             {
@@ -298,20 +321,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            Span<JointWeightPair> pairs = stackalloc JointWeightPair[4];
+            Span<BoneBinding> pairs = stackalloc BoneBinding[4];
 
 
-            pairs[0] = new JointWeightPair((int)Joints.X, Weights.X);
-            pairs[1] = new JointWeightPair((int)Joints.Y, Weights.Y);
-            pairs[2] = new JointWeightPair((int)Joints.Z, Weights.Z);
-            pairs[3] = new JointWeightPair((int)Joints.W, Weights.W);
+            pairs[0] = new BoneBinding((int)Joints.X, Weights.X);
+            pairs[1] = new BoneBinding((int)Joints.Y, Weights.Y);
+            pairs[2] = new BoneBinding((int)Joints.Z, Weights.Z);
+            pairs[3] = new BoneBinding((int)Joints.W, Weights.W);
 
 
-            JointWeightPair.InPlaceReverseBubbleSort(pairs);
+            BoneBinding.InPlaceReverseBubbleSort(pairs);
 
 
             Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
             Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
             Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
         }
         }
 
 
-        public IEnumerable<JointWeightPair> Bindings => JointWeightPair.GetJoints(this);
+        public IEnumerable<BoneBinding> BoneBindings => BoneBinding.GetBindings(this);
 
 
         #endregion
         #endregion
     }
     }
@@ -373,23 +396,23 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
         }
 
 
-        public JointWeightPair GetBinding(int index)
+        public BoneBinding GetBoneBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointWeightPair((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JointWeightPair((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JointWeightPair((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JointWeightPair((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JointWeightPair((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JointWeightPair((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JointWeightPair((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JointWeightPair((int)this.Joints1.W, this.Weights1.W);
+                case 0: return new BoneBinding((int)this.Joints0.X, this.Weights0.X);
+                case 1: return new BoneBinding((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return new BoneBinding((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return new BoneBinding((int)this.Joints0.W, this.Weights0.W);
+                case 4: return new BoneBinding((int)this.Joints1.X, this.Weights1.X);
+                case 5: return new BoneBinding((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return new BoneBinding((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return new BoneBinding((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
 
 
-        public void SetBinding(int index, int joint, float weight)
+        public void SetBoneBinding(int index, int joint, float weight)
         {
         {
             switch (index)
             switch (index)
             {
             {
@@ -405,7 +428,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public IEnumerable<JointWeightPair> Bindings => JointWeightPair.GetJoints(this);
+        public IEnumerable<BoneBinding> BoneBindings => BoneBinding.GetBindings(this);
 
 
         #endregion
         #endregion
     }
     }
@@ -467,23 +490,23 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
         }
 
 
-        public JointWeightPair GetBinding(int index)
+        public BoneBinding GetBoneBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointWeightPair((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JointWeightPair((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JointWeightPair((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JointWeightPair((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JointWeightPair((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JointWeightPair((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JointWeightPair((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JointWeightPair((int)this.Joints1.W, this.Weights1.W);
+                case 0: return new BoneBinding((int)this.Joints0.X, this.Weights0.X);
+                case 1: return new BoneBinding((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return new BoneBinding((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return new BoneBinding((int)this.Joints0.W, this.Weights0.W);
+                case 4: return new BoneBinding((int)this.Joints1.X, this.Weights1.X);
+                case 5: return new BoneBinding((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return new BoneBinding((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return new BoneBinding((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
 
 
-        public void SetBinding(int index, int joint, float weight)
+        public void SetBoneBinding(int index, int joint, float weight)
         {
         {
             switch (index)
             switch (index)
             {
             {
@@ -499,7 +522,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public IEnumerable<JointWeightPair> Bindings => JointWeightPair.GetJoints(this);
+        public IEnumerable<BoneBinding> BoneBindings => BoneBinding.GetBindings(this);
 
 
         #endregion
         #endregion
     }
     }

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

@@ -11,7 +11,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
 {
     static class VertexUtils
     static class VertexUtils
     {
     {
-        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TvP, TvM, TvS>(this IEnumerable<IReadOnlyList<Vertex<TvP, TvM, TvS>>> vertexBlocks)
+        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TvP, TvM, TvS>(this IEnumerable<IReadOnlyList<VertexBuilder<TvP, TvM, TvS>>> vertexBlocks)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -141,7 +141,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
         }
         }
 
 
-        private static Func<Vertex<TvP, TvM, TvS>, Object> GetItemValueFunc<TvP, TvM, TvS>(string attributeName)
+        private static Func<VertexBuilder<TvP, TvM, TvS>, Object> GetItemValueFunc<TvP, TvM, TvS>(string attributeName)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -158,7 +158,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        private static Single[] GetScalarColumn<TvP, TvM, TvS>(this IReadOnlyList<Vertex<TvP, TvM, TvS>> vertices, Func<Vertex<TvP, TvM, TvS>, Object> func)
+        private static Single[] GetScalarColumn<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -166,7 +166,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return GetColumn<TvP, TvM, TvS, Single>(vertices, func);
             return GetColumn<TvP, TvM, TvS, Single>(vertices, func);
         }
         }
 
 
-        private static Vector2[] GetVector2Column<TvP, TvM, TvS>(this IReadOnlyList<Vertex<TvP, TvM, TvS>> vertices, Func<Vertex<TvP, TvM, TvS>, Object> func)
+        private static Vector2[] GetVector2Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -174,7 +174,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return GetColumn<TvP, TvM, TvS, Vector2>(vertices, func);
             return GetColumn<TvP, TvM, TvS, Vector2>(vertices, func);
         }
         }
 
 
-        private static Vector3[] GetVector3Column<TvP, TvM, TvS>(this IReadOnlyList<Vertex<TvP, TvM, TvS>> vertices, Func<Vertex<TvP, TvM, TvS>, Object> func)
+        private static Vector3[] GetVector3Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -182,7 +182,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return GetColumn<TvP, TvM, TvS, Vector3>(vertices, func);
             return GetColumn<TvP, TvM, TvS, Vector3>(vertices, func);
         }
         }
 
 
-        private static Vector4[] GetVector4Column<TvP, TvM, TvS>(this IReadOnlyList<Vertex<TvP, TvM, TvS>> vertices, Func<Vertex<TvP, TvM, TvS>, Object> func)
+        private static Vector4[] GetVector4Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -190,7 +190,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return GetColumn<TvP, TvM, TvS, Vector4>(vertices, func);
             return GetColumn<TvP, TvM, TvS, Vector4>(vertices, func);
         }
         }
 
 
-        private static TColumn[] GetColumn<TvP, TvM, TvS, TColumn>(this IReadOnlyList<Vertex<TvP, TvM, TvS>> vertices, Func<Vertex<TvP, TvM, TvS>, Object> func)
+        private static TColumn[] GetColumn<TvP, TvM, TvS, TColumn>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
@@ -207,7 +207,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return dst;
             return dst;
         }
         }
 
 
-        public static TvP CloneAs<TvP>(this IVertexGeometry src)
+        public static TvP ConvertTo<TvP>(this IVertexGeometry src)
             where TvP : struct, IVertexGeometry
             where TvP : struct, IVertexGeometry
         {
         {
             if (src.GetType() == typeof(TvP)) return (TvP)src;
             if (src.GetType() == typeof(TvP)) return (TvP)src;
@@ -221,7 +221,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return dst;
             return dst;
         }
         }
 
 
-        public static TvM CloneAs<TvM>(this IVertexMaterial src)
+        public static TvM ConvertTo<TvM>(this IVertexMaterial src)
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
             if (src.GetType() == typeof(TvM)) return (TvM)src;
             if (src.GetType() == typeof(TvM)) return (TvM)src;
@@ -259,7 +259,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return dst;
             return dst;
         }
         }
 
 
-        public static unsafe TvS CloneAs<TvS>(this IVertexSkinning src)
+        public static unsafe TvS ConvertTo<TvS>(this IVertexSkinning src)
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
             if (src.GetType() == typeof(TvS)) return (TvS)src;
             if (src.GetType() == typeof(TvS)) return (TvS)src;
@@ -272,9 +272,9 @@ namespace SharpGLTF.Geometry.VertexTypes
             {
             {
                 for (int i = 0; i < src.MaxBindings; ++i)
                 for (int i = 0; i < src.MaxBindings; ++i)
                 {
                 {
-                    var jw = src.GetBinding(i);
+                    var jw = src.GetBoneBinding(i);
 
 
-                    dst.SetBinding(i, jw.Joint, jw.Weight);
+                    dst.SetBoneBinding(i, jw.Joint, jw.Weight);
                 }
                 }
 
 
                 return dst;
                 return dst;
@@ -282,20 +282,20 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             // if there's more source joints than destination joints, transfer with scale
             // if there's more source joints than destination joints, transfer with scale
 
 
-            Span<JointWeightPair> srcjw = stackalloc JointWeightPair[src.MaxBindings];
+            Span<BoneBinding> srcjw = stackalloc BoneBinding[src.MaxBindings];
 
 
             for (int i = 0; i < src.MaxBindings; ++i)
             for (int i = 0; i < src.MaxBindings; ++i)
             {
             {
-                srcjw[i] = src.GetBinding(i);
+                srcjw[i] = src.GetBoneBinding(i);
             }
             }
 
 
-            JointWeightPair.InPlaceReverseBubbleSort(srcjw);
+            BoneBinding.InPlaceReverseBubbleSort(srcjw);
 
 
-            var w = JointWeightPair.CalculateScaleFor(srcjw, dst.MaxBindings);
+            var w = BoneBinding.CalculateScaleFor(srcjw, dst.MaxBindings);
 
 
             for (int i = 0; i < dst.MaxBindings; ++i)
             for (int i = 0; i < dst.MaxBindings; ++i)
             {
             {
-                dst.SetBinding(i, srcjw[i].Joint, srcjw[i].Weight * w);
+                dst.SetBoneBinding(i, srcjw[i].Joint, srcjw[i].Weight * w);
             }
             }
 
 
             return dst;
             return dst;

+ 15 - 0
src/SharpGLTF.Toolkit/Schema2/AnimationExtensions.cs

@@ -15,6 +15,21 @@ namespace SharpGLTF.Schema2
             return animation ?? root.CreateAnimation(name);
             return animation ?? root.CreateAnimation(name);
         }
         }
 
 
+        public static Node WithScaleAnimation(this Node node, string animationName, params (Single, Vector3)[] keyframes)
+        {
+            return node.WithScaleAnimation(animationName, keyframes.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2));
+        }
+
+        public static Node WithRotationAnimation(this Node node, string animationName, params (Single, Quaternion)[] keyframes)
+        {
+            return node.WithRotationAnimation(animationName, keyframes.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2));
+        }
+
+        public static Node WithTranslationAnimation(this Node node, string animationName, params (Single, Vector3)[] keyframes)
+        {
+            return node.WithTranslationAnimation(animationName, keyframes.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2));
+        }
+
         public static Node WithScaleAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         public static Node WithScaleAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         {
         {
             var root = node.LogicalParent;
             var root = node.LogicalParent;

+ 3 - 3
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -194,7 +194,7 @@ namespace SharpGLTF.Schema2
             where TvP : struct, Geometry.VertexTypes.IVertexGeometry
             where TvP : struct, Geometry.VertexTypes.IVertexGeometry
         {
         {
             var xvertices = vertices
             var xvertices = vertices
-                .Select(item => new Geometry.Vertex<TvP, Geometry.VertexTypes.VertexEmpty, Geometry.VertexTypes.VertexEmpty>(item))
+                .Select(item => new Geometry.VertexBuilder<TvP, Geometry.VertexTypes.VertexEmpty, Geometry.VertexTypes.VertexEmpty>(item))
                 .ToList();
                 .ToList();
 
 
             return primitive.WithVertexAccessors(xvertices);
             return primitive.WithVertexAccessors(xvertices);
@@ -205,13 +205,13 @@ namespace SharpGLTF.Schema2
             where TvM : struct, Geometry.VertexTypes.IVertexMaterial
             where TvM : struct, Geometry.VertexTypes.IVertexMaterial
         {
         {
             var xvertices = vertices
             var xvertices = vertices
-                .Select(item => new Geometry.Vertex<TvP, TvM, Geometry.VertexTypes.VertexEmpty>(item.Item1, item.Item2))
+                .Select(item => new Geometry.VertexBuilder<TvP, TvM, Geometry.VertexTypes.VertexEmpty>(item.Item1, item.Item2))
                 .ToList();
                 .ToList();
 
 
             return primitive.WithVertexAccessors(xvertices);
             return primitive.WithVertexAccessors(xvertices);
         }
         }
 
 
-        public static MeshPrimitive WithVertexAccessors<TvP, TvM, TvS>(this MeshPrimitive primitive, IReadOnlyList<Geometry.Vertex<TvP, TvM, TvS>> vertices)
+        public static MeshPrimitive WithVertexAccessors<TvP, TvM, TvS>(this MeshPrimitive primitive, IReadOnlyList<Geometry.VertexBuilder<TvP, TvM, TvS>> vertices)
             where TvP : struct, Geometry.VertexTypes.IVertexGeometry
             where TvP : struct, Geometry.VertexTypes.IVertexGeometry
             where TvM : struct, Geometry.VertexTypes.IVertexMaterial
             where TvM : struct, Geometry.VertexTypes.IVertexMaterial
             where TvS : struct, Geometry.VertexTypes.IVertexSkinning
             where TvS : struct, Geometry.VertexTypes.IVertexSkinning

+ 7 - 7
tests/SharpGLTF.Tests/Geometry/VertexTypes/JointWeightPairTests.cs

@@ -15,15 +15,15 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
         {
             var pairs = new[]
             var pairs = new[]
             {
             {
-                new JointWeightPair(2,0),
-                new JointWeightPair(1,0.20f),
-                new JointWeightPair(3,0.15f),
-                new JointWeightPair(2,0.25f),
-                new JointWeightPair(4,0),
-                new JointWeightPair(7,0.40f)
+                new BoneBinding(2,0),
+                new BoneBinding(1,0.20f),
+                new BoneBinding(3,0.15f),
+                new BoneBinding(2,0.25f),
+                new BoneBinding(4,0),
+                new BoneBinding(7,0.40f)
             };
             };
 
 
-            JointWeightPair.InPlaceReverseBubbleSort(pairs);
+            BoneBinding.InPlaceReverseBubbleSort(pairs);
 
 
             Assert.AreEqual(7, pairs[0].Joint);
             Assert.AreEqual(7, pairs[0].Joint);
             Assert.AreEqual(2, pairs[1].Joint);
             Assert.AreEqual(2, pairs[1].Joint);

+ 14 - 14
tests/SharpGLTF.Tests/Geometry/VertexTypes/VertexSkinningTests.cs

@@ -14,23 +14,23 @@ namespace SharpGLTF.Geometry.VertexTypes
         public void TestCloneAs()
         public void TestCloneAs()
         {
         {
             var v8 = new VertexJoints8x8();
             var v8 = new VertexJoints8x8();
-            v8.SetBinding(0, 1, 0.2f);
-            v8.SetBinding(1, 2, 0.15f);
-            v8.SetBinding(2, 3, 0.25f);
-            v8.SetBinding(3, 4, 0.10f);
-            v8.SetBinding(4, 5, 0.30f);
+            v8.SetBoneBinding(0, 1, 0.2f);
+            v8.SetBoneBinding(1, 2, 0.15f);
+            v8.SetBoneBinding(2, 3, 0.25f);
+            v8.SetBoneBinding(3, 4, 0.10f);
+            v8.SetBoneBinding(4, 5, 0.30f);
 
 
-            var v4 = v8.CloneAs<VertexJoints8x4>();
+            var v4 = v8.ConvertTo<VertexJoints8x4>();
 
 
-            Assert.AreEqual(5, v4.GetBinding(0).Joint);
-            Assert.AreEqual(3, v4.GetBinding(1).Joint);
-            Assert.AreEqual(1, v4.GetBinding(2).Joint);
-            Assert.AreEqual(2, v4.GetBinding(3).Joint);
+            Assert.AreEqual(5, v4.GetBoneBinding(0).Joint);
+            Assert.AreEqual(3, v4.GetBoneBinding(1).Joint);
+            Assert.AreEqual(1, v4.GetBoneBinding(2).Joint);
+            Assert.AreEqual(2, v4.GetBoneBinding(3).Joint);
 
 
-            Assert.AreEqual(0.333333f, v4.GetBinding(0).Weight, 0.01f);
-            Assert.AreEqual(0.277777f, v4.GetBinding(1).Weight, 0.01f);
-            Assert.AreEqual(0.222222f, v4.GetBinding(2).Weight, 0.01f);
-            Assert.AreEqual(0.166666f, v4.GetBinding(3).Weight, 0.01f);
+            Assert.AreEqual(0.333333f, v4.GetBoneBinding(0).Weight, 0.01f);
+            Assert.AreEqual(0.277777f, v4.GetBoneBinding(1).Weight, 0.01f);
+            Assert.AreEqual(0.222222f, v4.GetBoneBinding(2).Weight, 0.01f);
+            Assert.AreEqual(0.166666f, v4.GetBoneBinding(3).Weight, 0.01f);
         }
         }
     }
     }
 }
 }

+ 3 - 3
tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs

@@ -71,7 +71,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithSolidTriangle()
         public void CreateSceneWithSolidTriangle()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // create model
             // create model
             var model = ModelRoot.CreateModel();
             var model = ModelRoot.CreateModel();
@@ -115,7 +115,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithTexturedTriangle()
         public void CreateSceneWithTexturedTriangle()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // we'll use our icon as the source texture
             // we'll use our icon as the source texture
             var imagePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "..\\..\\..\\..\\..\\build\\Icons\\glTF2Sharp.png");
             var imagePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "..\\..\\..\\..\\..\\build\\Icons\\glTF2Sharp.png");
@@ -162,7 +162,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithInterleavedQuadMesh()
         public void CreateSceneWithInterleavedQuadMesh()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var vertices = new[]
             var vertices = new[]
             {
             {

+ 4 - 4
tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs

@@ -15,7 +15,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithWithLightsExtension()
         public void CreateSceneWithWithLightsExtension()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var root = ModelRoot.CreateModel();
             var root = ModelRoot.CreateModel();
             var scene = root.UseScene("Empty Scene");
             var scene = root.UseScene("Empty Scene");
@@ -37,7 +37,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithSpecularGlossinessExtension()
         public void CreateSceneWithSpecularGlossinessExtension()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "glTF-Sample-Models", "2.0", "SpecGlossVsMetalRough", "glTF");
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "glTF-Sample-Models", "2.0", "SpecGlossVsMetalRough", "glTF");
 
 
@@ -81,7 +81,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithTextureImageExtension(string textureFileName)
         public void CreateSceneWithTextureImageExtension(string textureFileName)
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets");
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets");
 
 
@@ -123,7 +123,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CrateSceneWithTextureTransformExtension()
         public void CrateSceneWithTextureTransformExtension()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets");
             var basePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets");
 
 

+ 9 - 10
tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs

@@ -23,7 +23,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithInterleavedMeshBuilder()
         public void CreateSceneWithInterleavedMeshBuilder()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // define 4 vertices
             // define 4 vertices
             var v1 = new VPOSNRM(-10, 10, 0, -10, 10, 15);
             var v1 = new VPOSNRM(-10, 10, 0, -10, 10, 15);
@@ -55,7 +55,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithSharedBuffers()
         public void CreateSceneWithSharedBuffers()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // create materials
             // create materials
             var material1 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1));
             var material1 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1));
@@ -107,7 +107,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithAnimatedMeshBuilder()
         public void CreateSceneWithAnimatedMeshBuilder()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // create animation sequence with 4 frames
             // create animation sequence with 4 frames
             var keyframes = new Dictionary<Single, Vector3>()
             var keyframes = new Dictionary<Single, Vector3>()
@@ -142,7 +142,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithSkinnedAnimatedMeshBuilder()
         public void CreateSceneWithSkinnedAnimatedMeshBuilder()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
             
             
             // create animation sequence with 4 frames
             // create animation sequence with 4 frames
             var keyframes = new Dictionary<Single, Quaternion>
             var keyframes = new Dictionary<Single, Quaternion>
@@ -199,9 +199,8 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             // setup skin
             // setup skin
             var snode = scene.CreateNode("Skeleton Node");
             var snode = scene.CreateNode("Skeleton Node");
-            snode.Skin = model.CreateSkin();
-            snode.Skin.Skeleton = skelet;
-            snode.Skin.BindJoints(joint1, joint2, joint3);
+            snode.Skin = model.CreateSkin();            
+            snode.Skin.BindJoints(skelet, joint1, joint2, joint3);
 
 
             snode.WithMesh( model.CreateMesh(meshBuilder) );
             snode.WithMesh( model.CreateMesh(meshBuilder) );
 
 
@@ -213,7 +212,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithTerrain()
         public void CreateSceneWithTerrain()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // texture path
             // texture path
             var imagePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets", "Texture1.jpg");
             var imagePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "Assets", "Texture1.jpg");
@@ -259,7 +258,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithPointCloud()
         public void CreateSceneWithPointCloud()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var material = new MaterialBuilder("material1").WithUnlitShader();            
             var material = new MaterialBuilder("material1").WithUnlitShader();            
 
 
@@ -314,7 +313,7 @@ namespace SharpGLTF.Schema2.Authoring
         public void CreateSceneWithRandomCubes()
         public void CreateSceneWithRandomCubes()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             var rnd = new Random();
             var rnd = new Random();
 
 

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

@@ -33,7 +33,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
         public void LoadSampleModels(string section)
         public void LoadSampleModels(string section)
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             foreach (var f in TestFiles.GetSampleModelsPaths())
             foreach (var f in TestFiles.GetSampleModelsPaths())
             {
             {
@@ -89,7 +89,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
         public void LoadExtendedModels(string filePath)
         public void LoadExtendedModels(string filePath)
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             filePath = TestFiles
             filePath = TestFiles
                 .GetSampleModelsPaths()
                 .GetSampleModelsPaths()

+ 91 - 45
tests/SharpGLTF.Tests/TestUtils.cs → tests/SharpGLTF.Tests/Utils.cs

@@ -1,11 +1,14 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Numerics;
 using System.Text;
 using System.Text;
 
 
+using NUnit.Framework;
+
 namespace SharpGLTF
 namespace SharpGLTF
 {
 {
-    static class TestUtils
+    static class NUnitUtils
     {
     {
         public static string ToShortDisplayPath(this string path)
         public static string ToShortDisplayPath(this string path)
         {
         {
@@ -22,7 +25,7 @@ namespace SharpGLTF
             return System.IO.Path.Combine(dir, fxt);
             return System.IO.Path.Combine(dir, fxt);
         }        
         }        
 
 
-        public static string GetAttachmentPath(this NUnit.Framework.TestContext context, string fileName, bool ensureDirectoryExists = false)
+        public static string GetAttachmentPath(this TestContext context, string fileName, bool ensureDirectoryExists = false)
         {
         {
             var path = System.IO.Path.Combine(context.TestDirectory, "TestResults", $"{context.Test.ID}");
             var path = System.IO.Path.Combine(context.TestDirectory, "TestResults", $"{context.Test.ID}");
             var dir = path;
             var dir = path;
@@ -43,7 +46,7 @@ namespace SharpGLTF
         public static void AttachToCurrentTest(this Schema2.ModelRoot model, string fileName)
         public static void AttachToCurrentTest(this Schema2.ModelRoot model, string fileName)
         {
         {
             // find the output path for the current test
             // find the output path for the current test
-            fileName = NUnit.Framework.TestContext.CurrentContext.GetAttachmentPath(fileName, true);
+            fileName = TestContext.CurrentContext.GetAttachmentPath(fileName, true);
             
             
             if (fileName.ToLower().EndsWith(".glb"))
             if (fileName.ToLower().EndsWith(".glb"))
             {
             {
@@ -59,63 +62,40 @@ namespace SharpGLTF
             }
             }
 
 
             // Attach the saved file to the current test
             // Attach the saved file to the current test
-            NUnit.Framework.TestContext.AddTestAttachment(fileName);
+            TestContext.AddTestAttachment(fileName);
         }
         }
 
 
-        public static void AttachText(this NUnit.Framework.TestContext context, string fileName, string[] lines)
+        public static void AttachText(this TestContext context, string fileName, string[] lines)
         {
         {
             fileName = context.GetAttachmentPath(fileName, true);
             fileName = context.GetAttachmentPath(fileName, true);
 
 
             System.IO.File.WriteAllLines(fileName, lines.ToArray());
             System.IO.File.WriteAllLines(fileName, lines.ToArray());
 
 
-            NUnit.Framework.TestContext.AddTestAttachment(fileName);
-        }
-
-        public static void AttachShowDirLink(this NUnit.Framework.TestContext context)
-        {
-            context.AttachFileLink("📂 Show Directory", context.GetAttachmentPath(string.Empty));
+            TestContext.AddTestAttachment(fileName);
         }
         }
 
 
-        public static void AttachGltfValidatorLink(this NUnit.Framework.TestContext context)
+        public static void AttachShowDirLink(this TestContext context)
         {
         {
-            context.AttachUrlLink("🌍 khronos Validator", "http://github.khronos.org/glTF-Validator/");
-            context.AttachUrlLink("🌍 babylonjs sandbox", "https://sandbox.babylonjs.com/");
-            context.AttachUrlLink("🌍 donmccurdy sandbox", "https://gltf-viewer.donmccurdy.com/");            
+            context.AttachLink("📂 Show Directory", context.GetAttachmentPath(string.Empty));
         }
         }
 
 
-        public static void AttachFileLink(this NUnit.Framework.TestContext context, string linkPath, string targetPath)
+        public static void AttachGltfValidatorLinks(this TestContext context)
         {
         {
-            var sb = new StringBuilder();
-            sb.AppendLine("[InternetShortcut]");
-            sb.AppendLine("URL=file:///" + targetPath);
-            sb.AppendLine("IconIndex=0");
-            string icon = targetPath.Replace('\\', '/');
-            sb.AppendLine("IconFile=" + icon);
-
-            linkPath = System.IO.Path.ChangeExtension(linkPath, ".url");
-            linkPath = context.GetAttachmentPath(linkPath, true);
-
-            System.IO.File.WriteAllText(linkPath, sb.ToString());
-
-            NUnit.Framework.TestContext.AddTestAttachment(linkPath);
+            context.AttachLink("🌍 Khronos Validator", "http://github.khronos.org/glTF-Validator/");
+            context.AttachLink("🌍 BabylonJS Sandbox", "https://sandbox.babylonjs.com/");
+            context.AttachLink("🌍 Don McCurdy Sandbox", "https://gltf-viewer.donmccurdy.com/");            
         }
         }
 
 
-        public static void AttachUrlLink(this NUnit.Framework.TestContext context, string linkPath, string url)
+        public static void AttachLink(this TestContext context, string linkPath, string targetPath)
         {
         {
-            var sb = new StringBuilder();
-            sb.AppendLine("[InternetShortcut]");
-            sb.AppendLine("URL=" + url);            
-
-            linkPath = System.IO.Path.ChangeExtension(linkPath, ".url");
-            linkPath = context.GetAttachmentPath(linkPath, true);
+            linkPath = context.GetAttachmentPath(linkPath);
 
 
-            System.IO.File.WriteAllText(linkPath, sb.ToString());
+            linkPath = ShortcutUtils.CreateLink(linkPath, targetPath);
 
 
-            NUnit.Framework.TestContext.AddTestAttachment(linkPath);
-        }
+            TestContext.AddTestAttachment(linkPath);
+        }        
     }
     }
 
 
-
     static class DownloadUtils
     static class DownloadUtils
     {
     {
         private static readonly Object _DownloadMutex = new object();
         private static readonly Object _DownloadMutex = new object();
@@ -128,11 +108,11 @@ namespace SharpGLTF
             {
             {
                 if (LibGit2Sharp.Repository.Discover(localDirectoryPath) == null)
                 if (LibGit2Sharp.Repository.Discover(localDirectoryPath) == null)
                 {
                 {
-                    NUnit.Framework.TestContext.Progress.WriteLine($"Cloning {remoteUrl} can take several minutes; Please wait...");
+                    TestContext.Progress.WriteLine($"Cloning {remoteUrl} can take several minutes; Please wait...");
 
 
                     LibGit2Sharp.Repository.Clone(remoteUrl, localDirectoryPath);
                     LibGit2Sharp.Repository.Clone(remoteUrl, localDirectoryPath);
 
 
-                    NUnit.Framework.TestContext.Progress.WriteLine($"... Clone Completed");
+                    TestContext.Progress.WriteLine($"... Clone Completed");
 
 
                     return;
                     return;
                 }
                 }
@@ -146,7 +126,7 @@ namespace SharpGLTF
 
 
                     var r = LibGit2Sharp.Commands.Pull(repo, new LibGit2Sharp.Signature("Anonymous", "[email protected]", new DateTimeOffset(DateTime.Now)), options);
                     var r = LibGit2Sharp.Commands.Pull(repo, new LibGit2Sharp.Signature("Anonymous", "[email protected]", new DateTimeOffset(DateTime.Now)), options);
 
 
-                    NUnit.Framework.TestContext.Progress.WriteLine($"{remoteUrl} is {r.Status}");
+                    TestContext.Progress.WriteLine($"{remoteUrl} is {r.Status}");
                 }
                 }
             }
             }
         }
         }
@@ -159,7 +139,7 @@ namespace SharpGLTF
             {
             {
                 if (System.IO.File.Exists(localFilePath)) return localFilePath; // we check again because we could have downloaded the file while waiting.
                 if (System.IO.File.Exists(localFilePath)) return localFilePath; // we check again because we could have downloaded the file while waiting.
 
 
-                NUnit.Framework.TestContext.Progress.WriteLine($"Downloading {remoteUri}... Please Wait...");
+                TestContext.Progress.WriteLine($"Downloading {remoteUri}... Please Wait...");
 
 
                 var dir = System.IO.Path.GetDirectoryName(localFilePath);
                 var dir = System.IO.Path.GetDirectoryName(localFilePath);
                 System.IO.Directory.CreateDirectory(dir);
                 System.IO.Directory.CreateDirectory(dir);
@@ -171,7 +151,7 @@ namespace SharpGLTF
 
 
                 if (localFilePath.ToLower().EndsWith(".zip"))
                 if (localFilePath.ToLower().EndsWith(".zip"))
                 {
                 {
-                    NUnit.Framework.TestContext.Progress.WriteLine($"Extracting {localFilePath}...");
+                    TestContext.Progress.WriteLine($"Extracting {localFilePath}...");
 
 
                     var extractPath = System.IO.Path.Combine(dir, System.IO.Path.GetFileNameWithoutExtension(localFilePath));
                     var extractPath = System.IO.Path.Combine(dir, System.IO.Path.GetFileNameWithoutExtension(localFilePath));
 
 
@@ -181,6 +161,72 @@ namespace SharpGLTF
                 return localFilePath;
                 return localFilePath;
             }
             }
         }
         }
+    }
+
+    static class ShortcutUtils
+    {
+        public static string CreateLink(string localLinkPath, string targetPath)
+        {
+            if (string.IsNullOrWhiteSpace(localLinkPath)) throw new ArgumentNullException(nameof(localLinkPath));
+            if (string.IsNullOrWhiteSpace(targetPath)) throw new ArgumentNullException(nameof(targetPath));
+
+            if (!Uri.TryCreate(targetPath, UriKind.Absolute, out Uri uri)) throw new UriFormatException(nameof(targetPath));
+
+            var sb = new StringBuilder();
 
 
+            sb.AppendLine("[{000214A0-0000-0000-C000-000000000046}]");
+            sb.AppendLine("Prop3=19,11");
+            sb.AppendLine("[InternetShortcut]");
+            sb.AppendLine("IDList=");
+            sb.AppendLine($"URL={uri.AbsoluteUri}");
+
+            if (uri.IsFile)
+            {                
+                sb.AppendLine("IconIndex=1");
+                string icon = targetPath.Replace('\\', '/');
+                sb.AppendLine("IconFile=" + icon);
+            }
+            else
+            {
+                sb.AppendLine("IconIndex=0");
+            }
+
+            localLinkPath = System.IO.Path.ChangeExtension(localLinkPath, ".url");
+
+            System.IO.File.WriteAllText(localLinkPath, sb.ToString());
+
+            return localLinkPath;
+        }        
+    }
+
+    static class VectorUtils
+    {
+        public static Single NextSingle(this Random rnd)
+        {
+            return (Single)rnd.NextDouble();
+        }
+
+        public static Vector2 NextVector2(this Random rnd)
+        {
+            return new Vector2(rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static Vector3 NextVector3(this Random rnd)
+        {
+            return new Vector3(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static Vector4 NextVector4(this Random rnd)
+        {
+            return new Vector4(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
+        }
+
+        public static void AreEqual(Vector4 a, Vector4 b, double delta = 0)
+        {
+            Assert.AreEqual(a.X, b.X, delta);
+            Assert.AreEqual(a.Y, b.Y, delta);
+            Assert.AreEqual(a.Z, b.Z, delta);
+            Assert.AreEqual(a.W, b.W, delta);
+        }
     }
     }
 }
 }

+ 0 - 40
tests/SharpGLTF.Tests/VectorUtils.cs

@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF
-{
-    public static class VectorUtils
-    {
-        public static Single NextSingle(this Random rnd)
-        {
-            return (Single)rnd.NextDouble();
-        }
-
-        public static Vector2 NextVector2(this Random rnd)
-        {
-            return new Vector2(rnd.NextSingle(), rnd.NextSingle());
-        }
-
-        public static Vector3 NextVector3(this Random rnd)
-        {
-            return new Vector3(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
-        }
-
-        public static Vector4 NextVector4(this Random rnd)
-        {
-            return new Vector4(rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle(), rnd.NextSingle());
-        }
-
-        public static void AreEqual(Vector4 a, Vector4 b, double delta = 0)
-        {
-            Assert.AreEqual(a.X, b.X, delta);
-            Assert.AreEqual(a.Y, b.Y, delta);
-            Assert.AreEqual(a.Z, b.Z, delta);
-            Assert.AreEqual(a.W, b.W, delta);
-        }
-    }
-}