Browse Source

API improvements.

Vicente Penades 6 years ago
parent
commit
0f63d277ef

+ 1 - 122
examples/SharpGLTF.Runtime.MonoGame/_Extensions.cs

@@ -34,7 +34,7 @@ namespace SharpGLTF.Runtime
                 );
         }
 
-        public static void FixTextureSampler(this SharpGLTF.Schema2.ModelRoot root)
+        public static void FixTextureSampler(this Schema2.ModelRoot root)
         {
             // SharpGLTF 1.0.0-Alpha10 has an issue with TextureSamplers, it's fixed in newer versions
 
@@ -66,126 +66,5 @@ namespace SharpGLTF.Runtime
 
             return BoundingSphere.CreateFromPoints(points);
         }
-
-        public static int GetPrimitiveVertexSize(this Schema2.PrimitiveType ptype)
-        {
-            switch (ptype)
-            {
-                case Schema2.PrimitiveType.POINTS: return 1;
-                case Schema2.PrimitiveType.LINES: return 2;
-                case Schema2.PrimitiveType.LINE_LOOP: return 2;
-                case Schema2.PrimitiveType.LINE_STRIP: return 2;
-                case Schema2.PrimitiveType.TRIANGLES: return 3;
-                case Schema2.PrimitiveType.TRIANGLE_FAN: return 3;
-                case Schema2.PrimitiveType.TRIANGLE_STRIP: return 3;
-                default: throw new NotImplementedException();
-            }
-        }
-
-        public static IEnumerable<(int, int, int)> GetTriangleIndices(this Schema2.MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 3) return Enumerable.Empty<(int, int, int)>();
-
-            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
-        }
-
-        public static IEnumerable<(int, int, int)> GetTrianglesIndices(this Schema2.PrimitiveType ptype, int vertexCount)
-        {
-            return ptype.GetTrianglesIndices(Enumerable.Range(0, vertexCount).Select(item => (UInt32)item));
-        }
-
-        public static IEnumerable<(int, int, int)> GetTrianglesIndices(this Schema2.PrimitiveType ptype, IEnumerable<UInt32> sourceIndices)
-        {
-            switch (ptype)
-            {
-                case Schema2.PrimitiveType.TRIANGLES:
-                    {
-                        using (var ptr = sourceIndices.GetEnumerator())
-                        {
-                            while (true)
-                            {
-                                if (!ptr.MoveNext()) break;
-                                var a = ptr.Current;
-                                if (!ptr.MoveNext()) break;
-                                var b = ptr.Current;
-                                if (!ptr.MoveNext()) break;
-                                var c = ptr.Current;
-
-                                if (!_IsDegenerated(a, b, c)) yield return ((int)a, (int)b, (int)c);
-                            }
-                        }
-
-                        break;
-                    }
-
-                case Schema2.PrimitiveType.TRIANGLE_FAN:
-                    {
-                        using (var ptr = sourceIndices.GetEnumerator())
-                        {
-                            if (!ptr.MoveNext()) break;
-                            var a = ptr.Current;
-                            if (!ptr.MoveNext()) break;
-                            var b = ptr.Current;
-
-                            while (true)
-                            {
-                                if (!ptr.MoveNext()) break;
-                                var c = ptr.Current;
-
-                                if (!_IsDegenerated(a, b, c)) yield return ((int)a, (int)b, (int)c);
-
-                                b = c;
-                            }
-                        }
-
-                        break;
-                    }
-
-                case Schema2.PrimitiveType.TRIANGLE_STRIP:
-                    {
-                        using (var ptr = sourceIndices.GetEnumerator())
-                        {
-                            if (!ptr.MoveNext()) break;
-                            var a = ptr.Current;
-                            if (!ptr.MoveNext()) break;
-                            var b = ptr.Current;
-
-                            bool reversed = false;
-
-                            while (true)
-                            {
-                                if (!ptr.MoveNext()) break;
-                                var c = ptr.Current;
-
-                                if (!_IsDegenerated(a, b, c))
-                                {
-                                    if (reversed) yield return ((int)b, (int)a, (int)c);
-                                    else yield return ((int)a, (int)b, (int)c);
-                                }
-
-                                a = b;
-                                b = c;
-                                reversed = !reversed;
-                            }
-                        }
-
-                        break;
-                    }
-
-                default: throw new NotImplementedException();
-            }            
-        }
-
-        static bool _IsDegenerated(uint a, uint b, uint c)
-        {
-            if (a == b) return true;
-            if (a == c) return true;
-            if (b == c) return true;
-            return false;
-        }
-
-
     }
 }

+ 15 - 0
src/Shared/_Extensions.cs

@@ -518,6 +518,21 @@ namespace SharpGLTF
             }
         }
 
+        public static int GetPrimitiveVertexSize(this PrimitiveType ptype)
+        {
+            switch (ptype)
+            {
+                case PrimitiveType.POINTS: return 1;
+                case PrimitiveType.LINES: return 2;
+                case PrimitiveType.LINE_LOOP: return 2;
+                case PrimitiveType.LINE_STRIP: return 2;
+                case PrimitiveType.TRIANGLES: return 3;
+                case PrimitiveType.TRIANGLE_FAN: return 3;
+                case PrimitiveType.TRIANGLE_STRIP: return 3;
+                default: throw new NotImplementedException();
+            }
+        }
+
         public static IEnumerable<(int, int)> GetLinesIndices(this PrimitiveType ptype, int vertexCount)
         {
             return ptype.GetLinesIndices(Enumerable.Range(0, vertexCount).Select(item => (UInt32)item));

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.LogicalChildOfRoot.cs

@@ -16,7 +16,7 @@ namespace SharpGLTF.Schema2
         public String Name
         {
             get => _name;
-            internal set => _name = value;
+            set => _name = value;
         }
 
         #endregion

+ 74 - 16
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -72,7 +72,7 @@ namespace SharpGLTF.Schema2
 
         #endregion
 
-        #region API
+        #region API - Buffers
 
         public IEnumerable<BufferView> GetBufferViews(bool includeIndices, bool includeVertices, bool includeMorphs)
         {
@@ -110,6 +110,21 @@ namespace SharpGLTF.Schema2
             return indices.Select(idx => this.LogicalParent.LogicalParent.LogicalBufferViews[idx]);
         }
 
+        public IReadOnlyList<KeyValuePair<String, Accessor>> GetVertexAccessorsByBuffer(BufferView vb)
+        {
+            Guard.NotNull(vb, nameof(vb));
+            Guard.MustShareLogicalParent(this.LogicalParent, vb, nameof(vb));
+
+            return VertexAccessors
+                .Where(key => key.Value.SourceBufferView == vb)
+                .OrderBy(item => item.Value.ByteOffset)
+                .ToArray();
+        }
+
+        #endregion
+
+        #region API - Vertices
+
         public Accessor GetVertexAccessor(string attributeKey)
         {
             Guard.NotNullOrEmpty(attributeKey, nameof(attributeKey));
@@ -134,6 +149,15 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        public Memory.MemoryAccessor GetVertices(string attributeKey)
+        {
+            return GetVertexAccessor(attributeKey)._GetMemoryAccessor();
+        }
+
+        #endregion
+
+        #region API - Indices
+
         public Accessor GetIndexAccessor()
         {
             if (!this._indices.HasValue) return null;
@@ -150,6 +174,55 @@ namespace SharpGLTF.Schema2
             this._indices = accessor.LogicalIndex;
         }
 
+        /// <summary>
+        /// Gets the raw list of indices of this primitive.
+        /// </summary>
+        /// <returns>A list of indices, or null.</returns>
+        public IList<UInt32> GetIndices() => IndexAccessor?.AsIndicesArray();
+
+        /// <summary>
+        /// Decodes the raw indices and returns a list of indexed points.
+        /// </summary>
+        /// <returns>A sequence of indexed points.</returns>
+        public IEnumerable<int> GetPointIndices()
+        {
+            if (this.DrawPrimitiveType.GetPrimitiveVertexSize() != 1) return Enumerable.Empty<int>();
+
+            if (this.IndexAccessor == null) return Enumerable.Range(0, VertexAccessors.Values.First().Count);
+
+            return this.IndexAccessor.AsIndicesArray().Select(item => (int)item);
+        }
+
+        /// <summary>
+        /// Decodes the raw indices and returns a list of indexed lines.
+        /// </summary>
+        /// <returns>A sequence of indexed lines.</returns>
+        public IEnumerable<(int, int)> GetLineIndices()
+        {
+            if (this.DrawPrimitiveType.GetPrimitiveVertexSize() != 2) return Enumerable.Empty<(int, int)>();
+
+            if (this.IndexAccessor == null) return this.DrawPrimitiveType.GetLinesIndices(VertexAccessors.Values.First().Count);
+
+            return this.DrawPrimitiveType.GetLinesIndices(this.IndexAccessor.AsIndicesArray());
+        }
+
+        /// <summary>
+        /// Decodes the raw indices and returns a list of indexed triangles.
+        /// </summary>
+        /// <returns>A sequence of indexed triangles.</returns>
+        public IEnumerable<(int, int, int)> GetTriangleIndices()
+        {
+            if (this.DrawPrimitiveType.GetPrimitiveVertexSize() != 3) return Enumerable.Empty<(int, int, int)>();
+
+            if (this.IndexAccessor == null) return this.DrawPrimitiveType.GetTrianglesIndices(VertexAccessors.Values.First().Count);
+
+            return this.DrawPrimitiveType.GetTrianglesIndices(this.IndexAccessor.AsIndicesArray());
+        }
+
+        #endregion
+
+        #region API - Morph Targets
+
         public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int idx)
         {
             return new ReadOnlyLinqDictionary<String, int, Accessor>(_targets[idx], alidx => this.LogicalParent.LogicalParent.LogicalAccessors[alidx]);
@@ -175,21 +248,6 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        public IReadOnlyList<KeyValuePair<String, Accessor>> GetVertexAccessorsByBuffer(BufferView vb)
-        {
-            Guard.NotNull(vb, nameof(vb));
-            Guard.MustShareLogicalParent(this.LogicalParent, vb, nameof(vb));
-
-            return VertexAccessors
-                .Where(key => key.Value.SourceBufferView == vb)
-                .OrderBy(item => item.Value.ByteOffset)
-                .ToArray();
-        }
-
-        public Memory.IntegerArray GetIndices() => IndexAccessor.AsIndicesArray();
-
-        public Memory.MemoryAccessor GetVertices(string attributeKey) => GetVertexAccessor(attributeKey)._GetMemoryAccessor();
-
         #endregion
 
         #region validation

+ 5 - 5
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -286,7 +286,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Node"/> instance.</returns>
         public Node CreateNode(string name = null)
         {
-            var node = this.LogicalParent._CreateLogicalNode(this._children);
+            var node = this.LogicalParent._CreateVisualNode(this._children);
             node.Name = name;
             return node;
         }
@@ -473,17 +473,17 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        internal Node _CreateLogicalNode()
+        public Node CreateLogicalNode()
         {
             var n = new Node();
             _nodes.Add(n);
             return n;
         }
 
-        internal Node _CreateLogicalNode(IList<int> children)
+        internal Node _CreateVisualNode(IList<int> parentChildren)
         {
-            var n = _CreateLogicalNode();
-            children.Add(n.LogicalIndex);
+            var n = CreateLogicalNode();
+            parentChildren.Add(n.LogicalIndex);
             return n;
         }
     }

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.Scene.cs

@@ -40,7 +40,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Node"/> instance.</returns>
         public Node CreateNode(String name = null)
         {
-            var n = this.LogicalParent._CreateLogicalNode(this._nodes);
+            var n = this.LogicalParent._CreateVisualNode(this._nodes);
             n.Name = name;
             return n;
         }

+ 5 - 0
src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs

@@ -35,6 +35,11 @@ namespace SharpGLTF.Animations
             this.Value = other.Value;
         }
 
+        public AnimatableProperty<T> Clone()
+        {
+            return new AnimatableProperty<T>(this);
+        }
+
         #endregion
 
         #region data

+ 1 - 2
src/SharpGLTF.Toolkit/Geometry/PackedPrimitiveBuilder.cs

@@ -37,7 +37,7 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        public void SetStridedVertices(IPrimitiveReader<TMaterial> srcPrim , EncodingType encoding)
+        public void SetStridedVertices(IPrimitiveReader<TMaterial> srcPrim, EncodingType encoding)
         {
             Guard.NotNull(srcPrim, nameof(srcPrim));
 
@@ -153,7 +153,6 @@ namespace SharpGLTF.Geometry
             }
             else
             {
-
                 var pt = PrimitiveType.LINES;
                 if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
 

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

@@ -64,8 +64,6 @@ namespace SharpGLTF.Geometry
     /// The vertex fragment type with Skin Joint Weights.
     /// Valid types are:
     /// <see cref="VertexEmpty"/>,
-    /// <see cref="VertexJoints8x4"/>,
-    /// <see cref="VertexJoints8x8"/>,
     /// <see cref="VertexJoints4"/>,
     /// <see cref="VertexJoints8"/>.
     /// </typeparam>

+ 0 - 4
src/SharpGLTF.Toolkit/Geometry/VertexTypes/FragmentPreprocessors.cs

@@ -87,8 +87,6 @@ namespace SharpGLTF.Geometry.VertexTypes
         /// The vertex fragment type with Skin Joint Weights.
         /// Valid types are:
         /// <see cref="VertexEmpty"/>,
-        /// <see cref="VertexJoints8x4"/>,
-        /// <see cref="VertexJoints8x8"/>,
         /// <see cref="VertexJoints4"/>,
         /// <see cref="VertexJoints8"/>.
         /// </typeparam>
@@ -217,8 +215,6 @@ namespace SharpGLTF.Geometry.VertexTypes
         /// The vertex fragment type with Skin Joint Weights.
         /// Valid types are:
         /// <see cref="VertexEmpty"/>,
-        /// <see cref="VertexJoints8x4"/>,
-        /// <see cref="VertexJoints8x8"/>,
         /// <see cref="VertexJoints4"/>,
         /// <see cref="VertexJoints8"/>.
         /// </typeparam>

+ 15 - 0
src/SharpGLTF.Toolkit/Scenes/Content.Schema2.cs

@@ -25,6 +25,11 @@ namespace SharpGLTF.Scenes
     {
         void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
         {
+            // we try to assign our mesh to the target node.
+            // but if the target node already has a mesh, we need to create
+            // a child node that will contain our mesh.
+
+            if (dstNode.Mesh != null) dstNode = dstNode.CreateNode();
             dstNode.Mesh = context.GetMesh(_Mesh);
         }
     }
@@ -33,6 +38,11 @@ namespace SharpGLTF.Scenes
     {
         void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
         {
+            // we try to assign our camera to the target node.
+            // but if the target node already has a mesh, we need to create
+            // a child node that will contain our camera.
+
+            if (dstNode.Camera != null) dstNode = dstNode.CreateNode();
             dstNode.WithOrthographicCamera(_XMag, _YMag, _ZNear, _ZFar);
         }
     }
@@ -41,6 +51,11 @@ namespace SharpGLTF.Scenes
     {
         void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
         {
+            // we try to assign our camera to the target node.
+            // but if the target node already has a mesh, we need to create
+            // a child node that will contain our camera.
+
+            if (dstNode.Camera != null) dstNode = dstNode.CreateNode();
             dstNode.WithPerspectiveCamera(_AspectRatio, _FovY, _ZNear, _ZFar);
         }
     }

+ 10 - 1
src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs

@@ -6,7 +6,7 @@ using SCHEMA2SCENE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Sc
 
 namespace SharpGLTF.Scenes
 {
-    public class InstanceBuilder : SCHEMA2SCENE
+    public sealed class InstanceBuilder : SCHEMA2SCENE
     {
         #region lifecycle
 
@@ -15,6 +15,15 @@ namespace SharpGLTF.Scenes
             _Parent = parent;
         }
 
+        internal InstanceBuilder Clone(SceneBuilder newParent)
+        {
+            var clone = new InstanceBuilder(newParent);
+            clone._Name = this.Name;
+            clone._ContentTransformer = this._ContentTransformer?.Clone();
+
+            return clone;
+        }
+
         #endregion
 
         #region data

+ 59 - 28
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -11,7 +11,7 @@ using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.Material
 namespace SharpGLTF.Scenes
 {
     /// <summary>
-    /// Helper class to create a Schema2.Scene from a Scene Builder
+    /// Helper class to create a Schema2.Scene from one or multiple <see cref="SceneBuilder"/> instances.
     /// </summary>
     class Schema2SceneBuilder
     {
@@ -31,11 +31,12 @@ namespace SharpGLTF.Scenes
 
         public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
 
-        public void AddScene(Scene dstScene, SceneBuilder srcScene, bool useStridedBuffers = true)
+        public void AddGeometryResources(ModelRoot root, IEnumerable<SceneBuilder> srcScenes, bool useStridedBuffers)
         {
             // gather all unique MeshBuilders
 
-            var srcMeshes = srcScene.Instances
+            var srcMeshes = srcScenes
+                .SelectMany(item => item.Instances)
                 .Select(item => item.Content?.GetGeometryAsset())
                 .Where(item => item != null)
                 .Distinct()
@@ -56,13 +57,13 @@ namespace SharpGLTF.Scenes
 
             foreach (var mg in materialGroups)
             {
-                var val = dstScene.LogicalParent.CreateMaterial(mg.Key);
+                var val = root.CreateMaterial(mg.Key);
                 foreach (var key in mg) _Materials[key] = val;
             }
 
             // create a Schema2.Mesh for every MeshBuilder.
 
-            var dstMeshes = dstScene.LogicalParent.CreateMeshes(mat => _Materials[mat], useStridedBuffers,  srcMeshes);
+            var dstMeshes = root.CreateMeshes(mat => _Materials[mat], useStridedBuffers, srcMeshes);
 
             for (int i = 0; i < srcMeshes.Length; ++i)
             {
@@ -70,10 +71,18 @@ namespace SharpGLTF.Scenes
             }
 
             // TODO: here we could check that every dstMesh has been correctly created.
+        }
+
+        private void AddArmatureResources(Func<string, Node> nodeFactory, IEnumerable<SceneBuilder> srcScenes)
+        {
+            // ALIGNMENT ISSUE:
+            // the toolkit builder is designed in a way that every instance can reuse the same node many times, even from different scenes.
+            // so the only way to handle this is to forcefully recreate all the nodes on every scene.
 
             // gather all NodeBuilder unique armatures
 
-            var armatures = srcScene.Instances
+            var armatures = srcScenes
+                .SelectMany(item => item.Instances)
                 .Select(item => item.Content?.GetArmatureAsset())
                 .Where(item => item != null)
                 .Select(item => item.Root)
@@ -84,29 +93,13 @@ namespace SharpGLTF.Scenes
 
             foreach (var armature in armatures)
             {
-                CreateArmature(dstScene,  armature);
-            }
-
-            // process instances
-
-            var schema2Instances = srcScene
-                .Instances
-                .OfType<IOperator<Scene>>();
-
-            foreach (var inst in schema2Instances)
-            {
-                inst.Setup(dstScene, this);
+                CreateArmature(nodeFactory, armature);
             }
         }
 
-        /// <summary>
-        /// Recursively converts all the <see cref="NodeBuilder"/> instances into <see cref="Schema2.Node"/> instances.
-        /// </summary>
-        /// <param name="container">The target <see cref="Schema2.Scene"/> or <see cref="Schema2.Node"/>.</param>
-        /// <param name="srcNode">The source <see cref="NodeBuilder"/> instance.</param>
-        private void CreateArmature(IVisualNodeContainer container, NodeBuilder srcNode)
+        private void CreateArmature(Func<string, Node> nodeFactory, NodeBuilder srcNode)
         {
-            var dstNode = container.CreateNode(srcNode.Name);
+            var dstNode = nodeFactory(srcNode.Name);
             _Nodes[srcNode] = dstNode;
 
             if (srcNode.HasAnimations)
@@ -123,7 +116,7 @@ namespace SharpGLTF.Scenes
                 dstNode.LocalMatrix = srcNode.LocalMatrix;
             }
 
-            foreach (var c in srcNode.VisualChildren) CreateArmature(dstNode, c);
+            foreach (var c in srcNode.VisualChildren) CreateArmature(dstNode.CreateNode, c);
         }
 
         public void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<Transforms.SparseWeight8> animation)
@@ -137,6 +130,21 @@ namespace SharpGLTF.Scenes
             foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
         }
 
+        public void AddScene(Scene dstScene, SceneBuilder srcScene)
+        {
+            _Nodes.Clear();
+            AddArmatureResources(dstScene.CreateNode, new[] { srcScene });
+
+            var schema2Instances = srcScene
+                .Instances
+                .OfType<IOperator<Scene>>();
+
+            foreach (var inst in schema2Instances)
+            {
+                inst.Setup(dstScene, this);
+            }
+        }
+
         #endregion
 
         #region types
@@ -153,6 +161,25 @@ namespace SharpGLTF.Scenes
     {
         #region from SceneBuilder to Schema2
 
+        public static ModelRoot ToSchema2(IEnumerable<SceneBuilder> srcScenes, bool useStridedBuffers = true)
+        {
+            var context = new Schema2SceneBuilder();
+
+            var dstModel = ModelRoot.CreateModel();
+            context.AddGeometryResources(dstModel, srcScenes, useStridedBuffers);
+
+            foreach (var srcScene in srcScenes)
+            {
+                var dstScene = dstModel.UseScene(dstModel.LogicalScenes.Count);
+
+                dstScene.Name = srcScene.Name;
+
+                context.AddScene(dstScene, srcScene);
+            }
+
+            return dstModel;
+        }
+
         /// <summary>
         /// Converts this <see cref="SceneBuilder"/> instance into a <see cref="ModelRoot"/> instance.
         /// </summary>
@@ -160,12 +187,16 @@ namespace SharpGLTF.Scenes
         /// <returns>A new <see cref="ModelRoot"/> instance.</returns>
         public ModelRoot ToSchema2(bool useStridedBuffers = true)
         {
+            var context = new Schema2SceneBuilder();
+
             var dstModel = ModelRoot.CreateModel();
+            context.AddGeometryResources(dstModel, new[] { this }, useStridedBuffers);
 
             var dstScene = dstModel.UseScene(0);
 
-            var context = new Schema2SceneBuilder();
-            context.AddScene(dstScene, this, useStridedBuffers);
+            dstScene.Name = this.Name;
+
+            context.AddScene(dstScene, this);
 
             return dstModel;
         }

+ 29 - 0
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -10,8 +10,31 @@ namespace SharpGLTF.Scenes
 {
     public partial class SceneBuilder
     {
+        #region lifecycle
+
+        public SceneBuilder() { }
+
+        public SceneBuilder(string name)
+        {
+            _Name = name;
+        }
+
+        public SceneBuilder Clone()
+        {
+            var clone = new SceneBuilder();
+            clone._Name = this._Name;
+
+            clone._Instances.AddRange(this._Instances.Select(item => item.Clone(this)));
+
+            return clone;
+        }
+
+        #endregion
+
         #region data
 
+        private String _Name;
+
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private readonly List<InstanceBuilder> _Instances = new List<InstanceBuilder>();
 
@@ -19,6 +42,12 @@ namespace SharpGLTF.Scenes
 
         #region properties
 
+        public String Name
+        {
+            get => _Name;
+            set => _Name = value;
+        }
+
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public IReadOnlyList<InstanceBuilder> Instances => _Instances;
 

+ 39 - 0
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -28,6 +28,14 @@ namespace SharpGLTF.Scenes
             _Content = new MeshContent(mesh);
         }
 
+        public abstract ContentTransformer Clone();
+
+        protected ContentTransformer(ContentTransformer other)
+        {
+            this._Content = other._Content;
+            this._Morphings = other._Morphings?.Clone();
+        }
+
         #endregion
 
         #region data
@@ -87,6 +95,16 @@ namespace SharpGLTF.Scenes
             _WorldTransform = xform;
         }
 
+        protected StaticTransformer(StaticTransformer other) : base(other)
+        {
+            this._WorldTransform = other._WorldTransform;
+        }
+
+        public override ContentTransformer Clone()
+        {
+            return new StaticTransformer(this);
+        }
+
         #endregion
 
         #region data
@@ -128,6 +146,16 @@ namespace SharpGLTF.Scenes
             _Node = node;
         }
 
+        protected NodeTransformer(NodeTransformer other) : base(other)
+        {
+            this._Node = other._Node;
+        }
+
+        public override ContentTransformer Clone()
+        {
+            return new NodeTransformer(this);
+        }
+
         #endregion
 
         #region data
@@ -169,6 +197,17 @@ namespace SharpGLTF.Scenes
             SetJoints(joints);
         }
 
+        protected SkinTransformer(SkinTransformer other) : base(other)
+        {
+            this._TargetBindMatrix = other._TargetBindMatrix;
+            this._Joints.AddRange(other._Joints);
+        }
+
+        public override ContentTransformer Clone()
+        {
+            return new SkinTransformer(this);
+        }
+
         #endregion
 
         #region data

+ 0 - 42
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -343,48 +343,6 @@ namespace SharpGLTF.Schema2
 
         #region evaluation
 
-        public static int GetPrimitiveVertexSize(this PrimitiveType ptype)
-        {
-            switch (ptype)
-            {
-                case PrimitiveType.POINTS: return 1;
-                case PrimitiveType.LINES: return 2;
-                case PrimitiveType.LINE_LOOP: return 2;
-                case PrimitiveType.LINE_STRIP: return 2;
-                case PrimitiveType.TRIANGLES: return 3;
-                case PrimitiveType.TRIANGLE_FAN: return 3;
-                case PrimitiveType.TRIANGLE_STRIP: return 3;
-                default: throw new NotImplementedException();
-            }
-        }
-
-        public static IEnumerable<int> GetPointIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 1) return Enumerable.Empty<int>();
-
-            if (primitive.IndexAccessor == null) return Enumerable.Range(0, primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.IndexAccessor.AsIndicesArray().Select(item => (int)item);
-        }
-
-        public static IEnumerable<(int, int)> GetLineIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 2) return Enumerable.Empty<(int, int)>();
-
-            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetLinesIndices(primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.DrawPrimitiveType.GetLinesIndices(primitive.IndexAccessor.AsIndicesArray());
-        }
-
-        public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 3) return Enumerable.Empty<(int, int, int)>();
-
-            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
-        }
-
         public static IEnumerable<(IVertexBuilder, Material)> EvaluatePoints(this Mesh mesh, MESHXFORM xform = null)
         {
             if (mesh == null) return Enumerable.Empty<(IVertexBuilder, Material)>();

+ 16 - 16
tests/SharpGLTF.Tests/Geometry/Parametric/SolidMeshUtils.cs

@@ -10,12 +10,21 @@ namespace SharpGLTF.Geometry.Parametric
 {
     using VERTEX = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexEmpty>;
 
-    interface IParametricShape<TMaterial>
+    abstract class ParametricShape<TMaterial>
     {
-        void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform);
+        public abstract void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform);
+
+        public MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty> ToMesh(Matrix4x4 xform)
+        {
+            var mesh = new MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty>();
+
+            AddTo(mesh, xform);
+
+            return mesh;
+        }
     }
 
-    class Cube<TMaterial> : IParametricShape<TMaterial>
+    class Cube<TMaterial> : ParametricShape<TMaterial>
     {
         #region lifecycle
 
@@ -49,7 +58,7 @@ namespace SharpGLTF.Geometry.Parametric
 
         #region API
 
-        public void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
+        public override void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
         {
             var x = Vector3.UnitX * _Size.X * 0.5f;
             var y = Vector3.UnitY * _Size.Y * 0.5f;
@@ -81,20 +90,11 @@ namespace SharpGLTF.Geometry.Parametric
                 new VERTEX( (p4, n), (Vector4.One, Vector2.UnitY) )
                 );
         }
-
-        public MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty> ToMesh(Matrix4x4 xform)
-        {
-            var mesh = new MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty>();
-
-            AddTo(mesh, xform);
-
-            return mesh;
-        }
-
+        
         #endregion
     }
 
-    class IcoSphere<TMaterial> : IParametricShape<TMaterial>
+    class IcoSphere<TMaterial> : ParametricShape<TMaterial>
     {
         #region lifecycle
 
@@ -116,7 +116,7 @@ namespace SharpGLTF.Geometry.Parametric
 
         #region API        
 
-        public void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
+        public override void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
         {
             // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
 

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

@@ -481,6 +481,33 @@ namespace SharpGLTF.Scenes
             scene.AttachToCurrentTest("mopth.gltf");
         }
 
+        [Test]
+        public void CreateSharedNodeInstanceScene()
+        {
+            // SceneBuilder API supports reusing a NodeBuilder in multiple instances with different content.
+            // but glTF nodes can only hold one mesh per node, so if we find this case we need to internally
+            // add an additional child node to give room to the the extra mesh.
+
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            var m = MaterialBuilder.CreateDefault();
+
+            var cube = new Cube<MaterialBuilder>(m, 1.7f, 1.7f, 1.7f).ToMesh(Matrix4x4.Identity);
+            var sphere = new IcoSphere<MaterialBuilder>(m).ToMesh(Matrix4x4.Identity);
+
+            var armature1 = new NodeBuilder("Skeleton1");
+            var joint0 = armature1
+                .CreateNode("Joint 0")
+                .WithLocalTranslation(new Vector3(0, 1, 0));
+
+            var scene = new SceneBuilder();
+            scene.AddMesh(cube, joint0);
+            scene.AddMesh(sphere, joint0);
+
+            scene.AttachToCurrentTest("instanced.glb");
+            scene.AttachToCurrentTest("instanced.gltf");
+        }
         
 
         [TestCase("AnimatedMorphCube.glb")]