Browse Source

Added conversion between multiple scenes and SceneBuilders to expand mesh sharing to more case scenarios. Fixes #123

Vicente Penades 3 years ago
parent
commit
034a40b1a1

+ 323 - 246
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -11,221 +11,11 @@ using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.Material
 namespace SharpGLTF.Scenes
 {
     /// <summary>
-    /// Helper class to create a Schema2.Scene from one or multiple <see cref="SceneBuilder"/> instances.
+    /// Defines configurable options for converting <see cref="SceneBuilder"/> to <see cref="ModelRoot"/>
     /// </summary>
-    class Schema2SceneBuilder
-    {
-        #region data
-
-        private readonly Dictionary<Materials.MaterialBuilder, Material> _Materials = new Dictionary<Materials.MaterialBuilder, Material>();
-
-        private readonly Dictionary<MESHBUILDER, Mesh> _Meshes = new Dictionary<MESHBUILDER, Mesh>();
-
-        private readonly Dictionary<NodeBuilder, Node> _Nodes = new Dictionary<NodeBuilder, Node>();
-
-        #endregion
-
-        #region settings
-        public int GpuMeshInstancingMinCount { get; set; }
-
-        #endregion
-
-        #region API
-
-        public Mesh GetMesh(MESHBUILDER key) { return key == null ? null : _Meshes.TryGetValue(key, out Mesh val) ? val : null; }
-
-        public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
-
-        public static bool HasContent(Node node, bool checkTransform = true)
-        {
-            if (checkTransform && node.LocalMatrix != Matrix4x4.Identity) return true;
-
-            if (node.VisualChildren.Any()) return true;
-
-            if (node.Mesh != null) return true;
-            if (node.Skin != null) return true;
-            if (node.Camera != null) return true;
-            if (node.PunctualLight != null) return true;
-            if (node.GetGpuInstancing() != null) return true;
-
-            return false;
-        }
-
-        public void AddGeometryResources(ModelRoot root, IEnumerable<SceneBuilder> srcScenes, SceneBuilderSchema2Settings settings)
-        {
-            // gather all unique MeshBuilders
-
-            var srcMeshes = srcScenes
-                .SelectMany(item => item.Instances)
-                .Select(item => item.Content?.GetGeometryAsset())
-                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
-                .Distinct()
-                .ToArray();
-
-            // gather all unique MaterialBuilders
-
-            var materialGroups = srcMeshes
-                .SelectMany(item => item.Primitives)
-                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
-                .Select(item => item.Material)
-                .Distinct()
-                .ToList()
-                // group by equal content, to reduce material splitting whenever possible.
-                .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
-
-            // create a Schema2.Material for every MaterialBuilder.
-
-            foreach (var mg in materialGroups)
-            {
-                var val = root.CreateMaterial(mg.Key);
-                foreach (var key in mg) _Materials[key] = val;
-            }
-
-            // create a Schema2.Mesh for every MeshBuilder.
-
-            var dstMeshes = root.CreateMeshes(mat => _Materials[mat], settings, srcMeshes);
-
-            for (int i = 0; i < srcMeshes.Length; ++i)
-            {
-                _Meshes[srcMeshes[i]] = dstMeshes[i];
-            }
-
-            // TODO: here we could check that every dstMesh has been correctly created.
-        }
-
-        private void AddArmatureResources(IEnumerable<SceneBuilder> srcScenes, Func<Node> nodeFactory)
-        {
-            // 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 = srcScenes
-                .SelectMany(item => item.Instances)
-                .Select(item => item.Content?.GetArmatureRoot())
-                .Where(item => item != null)
-                .Select(item => item.Root)
-                .Distinct()
-                .ToList();
-
-            // create Schema2.Node trees for every armature
-
-            foreach (var armature in armatures)
-            {
-                CreateArmature(armature, nodeFactory);
-            }
-        }
-
-        private void CreateArmature(NodeBuilder srcNode, Func<Node> nodeFactory)
-        {
-            var dstNode = nodeFactory();
-
-            srcNode.TryCopyNameAndExtrasTo(dstNode);
-
-            _Nodes[srcNode] = dstNode;
-
-            if (srcNode.HasAnimations)
-            {
-                dstNode.LocalTransform = srcNode.LocalTransform.GetDecomposed();
-
-                // Copies all the animations to the target node.
-                if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
-                if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
-                if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
-            }
-            else
-            {
-                dstNode.LocalTransform = srcNode.LocalTransform;
-            }
-
-            foreach (var c in srcNode.VisualChildren) CreateArmature(c, () => dstNode.CreateNode());
-        }
-
-        public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<Transforms.SparseWeight8> animation)
-        {
-            Guard.NotNull(dstNode, nameof(dstNode));
-            Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
-
-            if (animation == null) return;
-
-            var dstMesh = dstNode.Mesh;
-
-            dstMesh.SetMorphWeights(animation.Value);
-
-            foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
-        }
-
-        public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<ArraySegment<float>> animation)
-        {
-            Guard.NotNull(dstNode, nameof(dstNode));
-            Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
-
-            if (animation == null) return;
-
-            var dstMesh = dstNode.Mesh;
-
-            dstMesh.SetMorphWeights(animation.Value);
-
-            foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
-        }
-
-        public void AddScene(Scene dstScene, SceneBuilder srcScene)
-        {
-            _Nodes.Clear();
-            AddArmatureResources(new[] { srcScene }, () => dstScene.CreateNode());
-
-            // gather single operators (RigidTransformer and SkinnedTransformer)
-
-            var srcSingleOperators = srcScene
-                .Instances
-                .Select(item => item.Content)
-                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
-                .OfType<IOperator<Scene>>();
-
-            // gather multi operators (Fixed Transformer)
-
-            var srcChildren = srcScene
-                .Instances
-                .Select(item => item.Content)
-                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
-                .OfType<FixedTransformer>();
-
-            var srcMultiOperators = _MeshInstancing.CreateFrom(srcChildren, this.GpuMeshInstancingMinCount);
-
-            // apply operators
-
-            var srcOperators = srcSingleOperators.Concat(srcMultiOperators);
-
-            foreach (var op in srcOperators)
-            {
-                op.ApplyTo(dstScene, this);
-            }
-
-            #if DEBUG
-            srcScene._VerifyConversion(dstScene);
-            #endif
-        }
-
-        #endregion
-
-        #region nested types
-
-        /// <summary>
-        /// Represents an object that can operate on a target object.
-        /// </summary>
-        /// <typeparam name="T">
-        /// The target type.
-        /// This is usually <see cref="Scene"/> or <see cref="Node"/>.
-        /// </typeparam>
-        public interface IOperator<T>
-        {
-            void ApplyTo(T target, Schema2SceneBuilder context);
-        }
-
-        #endregion
-    }
-
+    /// <remarks>
+    /// Used by <see cref="SceneBuilder.ToGltf2(SceneBuilderSchema2Settings)"/>
+    /// </remarks>
     public struct SceneBuilderSchema2Settings
     {
         public static SceneBuilderSchema2Settings Default => new SceneBuilderSchema2Settings
@@ -242,11 +32,34 @@ namespace SharpGLTF.Scenes
             GpuMeshInstancingMinCount = 3
         };
 
-        public bool UseStridedBuffers;
+        /// <summary>
+        /// When true, meshes will be created using strided vertices when possible.
+        /// </summary>
+        /// <remarks>
+        /// this option is not taken into account by meshes with morph targets.
+        /// </remarks>
+        public bool UseStridedBuffers { get; set; }
 
-        public bool CompactVertexWeights;
+        /// <summary>
+        /// if meshes have Skin Weights, defines the output vertex element format:<br/>
+        /// - True: Short<br/>
+        /// - False: Float<br/>
+        /// </summary>
+        public bool CompactVertexWeights { get; set; }
 
-        public int GpuMeshInstancingMinCount;
+        /// <summary>
+        /// determines the mínimum number mesh instances required to enable Gpu mesh instancing.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// Set to <see cref="int.MaxValue"/> to disable gpu instancing.
+        /// </para>
+        /// <para>
+        /// If set to a small value like 10, any mesh instance collection smaller than this will be instantiated
+        /// using individual nodes, otherwise it will use Gpu Instancing extension.
+        /// </para>
+        /// </remarks>
+        public int GpuMeshInstancingMinCount { get; set; }
     }
 
     public partial class SceneBuilder : IConvertibleToGltf2
@@ -305,10 +118,42 @@ namespace SharpGLTF.Scenes
 
         #region from Schema2 to SceneBuilder
 
+        public static SceneBuilder[] CreateFrom(ModelRoot model)
+        {
+            return model == null
+                ? Array.Empty<SceneBuilder>()
+                : CreateFrom(model.LogicalScenes).ToArray();
+        }
+
         public static SceneBuilder CreateFrom(Scene srcScene)
         {
             if (srcScene == null) return null;
 
+            // gather shared mesh instances
+
+            var dstMeshIntances = _GatherMeshInstances(Node.Flatten(srcScene));
+
+            return _CreateFrom(srcScene, dstMeshIntances);
+        }
+
+        public static IEnumerable<SceneBuilder> CreateFrom(IEnumerable<Scene> srcScenes)
+        {
+            if (srcScenes == null) yield break;
+
+            // gather shared mesh instances
+
+            var dstMeshIntances = _GatherMeshInstances(srcScenes.Distinct().SelectMany(s => Node.Flatten(s)));
+
+            // process each scene
+
+            foreach (var srcScene in srcScenes)
+            {
+                yield return _CreateFrom(srcScene, dstMeshIntances);
+            }
+        }
+
+        private static SceneBuilder _CreateFrom(Scene srcScene, IReadOnlyDictionary<Node, MESHBUILDER> dstInstances)
+        {
             // Process armatures
 
             var dstNodes = new Dictionary<Node, NodeBuilder>();
@@ -325,12 +170,7 @@ namespace SharpGLTF.Scenes
 
             dstScene.SetNameAndExtrasFrom(srcScene);
 
-            // process mesh instances
-            var srcMeshInstances = Node.Flatten(srcScene)
-                .Where(item => item.Mesh != null)
-                .ToList();
-
-            _AddMeshInstances(dstScene, dstNodes, srcMeshInstances);
+            _AddMeshInstances(dstScene, Node.Flatten(srcScene), dstNodes, dstInstances);
 
             // process cameras
             var srcCameraInstances = Node.Flatten(srcScene)
@@ -352,53 +192,74 @@ namespace SharpGLTF.Scenes
             return dstScene;
         }
 
-        private static void _AddMeshInstances(SceneBuilder dstScene, IReadOnlyDictionary<Node, NodeBuilder> dstNodes, IReadOnlyList<Node> srcInstances)
+        private static IReadOnlyDictionary<Node, MESHBUILDER> _GatherMeshInstances(IEnumerable<Node> srcNodes)
         {
-            var dstMeshes = srcInstances
-                            .Select(item => item.Mesh)
-                            .Distinct()
-                            .ToDictionary(item => item, item => item.ToMeshBuilder());
+            // filter all the nodes with meshes
 
-            foreach (var srcInstance in srcInstances)
+            var srcInstances = srcNodes
+                .Where(item => item.Mesh != null);
+
+            // create a dictionary of shared Mesh => MeshBuilder pairs.
+
+            var srcMeshes = srcInstances
+                .Select(item => item.Mesh)
+                .Distinct()
+                .ToDictionary(item => item, item => item.ToMeshBuilder());
+
+            // return a Node => MeshBuilder dictionary.
+
+            return srcInstances
+                .ToDictionary(item => item, item => srcMeshes[item.Mesh]);
+        }
+
+        private static void _AddMeshInstances(SceneBuilder dstScene, IEnumerable<Node> srcNodes, IReadOnlyDictionary<Node, NodeBuilder> nodesDict, IReadOnlyDictionary<Node, MESHBUILDER> meshesDict)
+        {
+            foreach (var srcNode in srcNodes)
             {
-                var dstMesh = dstMeshes[srcInstance.Mesh];
+                if (!meshesDict.TryGetValue(srcNode, out var dstMesh)) continue; // nothing to do.
 
-                if (srcInstance.Skin == null)
+                if (srcNode.Skin == null)
                 {
-                    var dstNode = dstNodes[srcInstance];
+                    // rigid mesh instance
+
+                    var dstNode = nodesDict[srcNode];
 
-                    var gpuInstancing = srcInstance.GetGpuInstancing();
+                    var gpuInstancing = srcNode.GetGpuInstancing();
 
-                    if (gpuInstancing != null)
+                    if (gpuInstancing == null)
                     {
-                        foreach (var xinst in gpuInstancing.LocalTransforms)
-                        {
-                            var dstInst = dstScene.AddRigidMesh(dstMesh, dstNode, xinst);
+                        var dstInstance = dstScene.AddRigidMesh(dstMesh, dstNode);
 
-                            // if we add morphing the the mesh, all meshes would morph simultaneously??
-                            _CopyMorphingAnimation(dstInst, srcInstance);
-                        }
+                        _CopyMorphingAnimation(dstInstance, srcNode);
                     }
                     else
                     {
-                        var dstInst = dstScene.AddRigidMesh(dstMesh, dstNode);
+                        // use gpu instancing extension
+
+                        foreach (var xinst in gpuInstancing.LocalTransforms)
+                        {
+                            var dstInstance = dstScene.AddRigidMesh(dstMesh, dstNode, xinst);
 
-                        _CopyMorphingAnimation(dstInst, srcInstance);
+                            // if we add morphing to the mesh, all meshes would morph simultaneously??
+                            _CopyMorphingAnimation(dstInstance, srcNode);
+                        }
                     }
                 }
                 else
                 {
-                    var joints = new (NodeBuilder, Matrix4x4)[srcInstance.Skin.JointsCount];
+                    // skinned mesh instance
+
+                    var joints = new (NodeBuilder, Matrix4x4)[srcNode.Skin.JointsCount];
 
                     for (int i = 0; i < joints.Length; ++i)
                     {
-                        var (j, ibm) = srcInstance.Skin.GetJoint(i);
-                        joints[i] = (dstNodes[j], ibm);
+                        var (j, ibm) = srcNode.Skin.GetJoint(i);
+                        joints[i] = (nodesDict[j], ibm);
                     }
 
                     var dstInst = dstScene.AddSkinnedMesh(dstMesh, joints);
 
-                    _CopyMorphingAnimation(dstInst, srcInstance);
+                    _CopyMorphingAnimation(dstInst, srcNode);
                 }
             }
         }
@@ -605,4 +466,220 @@ namespace SharpGLTF.Scenes
 
         #endregion
     }
+
+    /// <summary>
+    /// Helper class to create a Schema2.Scene from one or multiple <see cref="SceneBuilder"/> instances.
+    /// </summary>
+    class Schema2SceneBuilder
+    {
+        #region data
+
+        private readonly Dictionary<Materials.MaterialBuilder, Material> _Materials = new Dictionary<Materials.MaterialBuilder, Material>();
+
+        private readonly Dictionary<MESHBUILDER, Mesh> _Meshes = new Dictionary<MESHBUILDER, Mesh>();
+
+        private readonly Dictionary<NodeBuilder, Node> _Nodes = new Dictionary<NodeBuilder, Node>();
+
+        #endregion
+
+        #region settings
+        public int GpuMeshInstancingMinCount { get; set; }
+
+        #endregion
+
+        #region API
+
+        public Mesh GetMesh(MESHBUILDER key) { return key == null ? null : _Meshes.TryGetValue(key, out Mesh val) ? val : null; }
+
+        public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
+
+        public static bool HasContent(Node node, bool checkTransform = true)
+        {
+            if (checkTransform && node.LocalMatrix != Matrix4x4.Identity) return true;
+
+            if (node.VisualChildren.Any()) return true;
+
+            if (node.Mesh != null) return true;
+            if (node.Skin != null) return true;
+            if (node.Camera != null) return true;
+            if (node.PunctualLight != null) return true;
+            if (node.GetGpuInstancing() != null) return true;
+
+            return false;
+        }
+
+        public void AddGeometryResources(ModelRoot root, IEnumerable<SceneBuilder> srcScenes, SceneBuilderSchema2Settings settings)
+        {
+            // gather all unique MeshBuilders
+
+            var srcMeshes = srcScenes
+                .SelectMany(item => item.Instances)
+                .Select(item => item.Content?.GetGeometryAsset())
+                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
+                .Distinct()
+                .ToArray();
+
+            // gather all unique MaterialBuilders
+
+            var materialGroups = srcMeshes
+                .SelectMany(item => item.Primitives)
+                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
+                .Select(item => item.Material)
+                .Distinct()
+                .ToList()
+                // group by equal content, to reduce material splitting whenever possible.
+                .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
+
+            // create a Schema2.Material for every MaterialBuilder.
+
+            foreach (var mg in materialGroups)
+            {
+                var val = root.CreateMaterial(mg.Key);
+                foreach (var key in mg) _Materials[key] = val;
+            }
+
+            // create a Schema2.Mesh for every MeshBuilder.
+
+            var dstMeshes = root.CreateMeshes(mat => _Materials[mat], settings, srcMeshes);
+
+            for (int i = 0; i < srcMeshes.Length; ++i)
+            {
+                _Meshes[srcMeshes[i]] = dstMeshes[i];
+            }
+
+            // TODO: here we could check that every dstMesh has been correctly created.
+        }
+
+        private void AddArmatureResources(IEnumerable<SceneBuilder> srcScenes, Func<Node> nodeFactory)
+        {
+            // 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 = srcScenes
+                .SelectMany(item => item.Instances)
+                .Select(item => item.Content?.GetArmatureRoot())
+                .Where(item => item != null)
+                .Select(item => item.Root)
+                .Distinct()
+                .ToList();
+
+            // create Schema2.Node trees for every armature
+
+            foreach (var armature in armatures)
+            {
+                CreateArmature(armature, nodeFactory);
+            }
+        }
+
+        private void CreateArmature(NodeBuilder srcNode, Func<Node> nodeFactory)
+        {
+            var dstNode = nodeFactory();
+
+            srcNode.TryCopyNameAndExtrasTo(dstNode);
+
+            _Nodes[srcNode] = dstNode;
+
+            if (srcNode.HasAnimations)
+            {
+                dstNode.LocalTransform = srcNode.LocalTransform.GetDecomposed();
+
+                // Copies all the animations to the target node.
+                if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
+                if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
+                if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
+            }
+            else
+            {
+                dstNode.LocalTransform = srcNode.LocalTransform;
+            }
+
+            foreach (var c in srcNode.VisualChildren) CreateArmature(c, () => dstNode.CreateNode());
+        }
+
+        public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<Transforms.SparseWeight8> animation)
+        {
+            Guard.NotNull(dstNode, nameof(dstNode));
+            Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
+
+            if (animation == null) return;
+
+            var dstMesh = dstNode.Mesh;
+
+            dstMesh.SetMorphWeights(animation.Value);
+
+            foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
+        }
+
+        public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<ArraySegment<float>> animation)
+        {
+            Guard.NotNull(dstNode, nameof(dstNode));
+            Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
+
+            if (animation == null) return;
+
+            var dstMesh = dstNode.Mesh;
+
+            dstMesh.SetMorphWeights(animation.Value);
+
+            foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
+        }
+
+        public void AddScene(Scene dstScene, SceneBuilder srcScene)
+        {
+            _Nodes.Clear();
+            AddArmatureResources(new[] { srcScene }, () => dstScene.CreateNode());
+
+            // gather single operators (RigidTransformer and SkinnedTransformer)
+
+            var srcSingleOperators = srcScene
+                .Instances
+                .Select(item => item.Content)
+                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
+                .OfType<IOperator<Scene>>();
+
+            // gather multi operators (Fixed Transformer)
+
+            var srcChildren = srcScene
+                .Instances
+                .Select(item => item.Content)
+                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
+                .OfType<FixedTransformer>();
+
+            var srcMultiOperators = _MeshInstancing.CreateFrom(srcChildren, this.GpuMeshInstancingMinCount);
+
+            // apply operators
+
+            var srcOperators = srcSingleOperators.Concat(srcMultiOperators);
+
+            foreach (var op in srcOperators)
+            {
+                op.ApplyTo(dstScene, this);
+            }
+
+            #if DEBUG
+            srcScene._VerifyConversion(dstScene);
+            #endif
+        }
+
+        #endregion
+
+        #region nested types
+
+        /// <summary>
+        /// Represents an object that can operate on a target object.
+        /// </summary>
+        /// <typeparam name="T">
+        /// The target type.
+        /// This is usually <see cref="Scene"/> or <see cref="Node"/>.
+        /// </typeparam>
+        public interface IOperator<T>
+        {
+            void ApplyTo(T target, Schema2SceneBuilder context);
+        }
+
+        #endregion
+    }
 }

+ 13 - 1
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -52,10 +52,22 @@ namespace SharpGLTF.Scenes
             return clone;
         }
 
+        [Obsolete("Use LoadDefaultScene(...); or LoadAllScenes(...) instead.", true)]
         public static SceneBuilder Load(string filePath, ReadSettings settings = null)
+        {
+            return LoadDefaultScene(filePath, settings);
+        }
+
+        public static SceneBuilder LoadDefaultScene(string filePath, ReadSettings settings = null)
+        {
+            var mdl = ModelRoot.Load(filePath, settings);
+            return CreateFrom(mdl.DefaultScene);
+        }
+
+        public static SceneBuilder[] LoadAllScenes(string filePath, ReadSettings settings = null)
         {
             var mdl = ModelRoot.Load(filePath, settings);
-            return mdl.DefaultScene.ToSceneBuilder();
+            return CreateFrom(mdl);
         }
 
         #endregion

+ 1 - 1
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -142,7 +142,7 @@ namespace SharpGLTF.Scenes
             }
             else
             {
-                if (_Children.First().Content is SCHEMA2NODE srcOperator)
+                if (_Children[0].Content is SCHEMA2NODE srcOperator)
                 {
                     var xforms = _Children
                         .Select(item => item.ChildTransform)

+ 9 - 3
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -15,6 +15,12 @@ namespace SharpGLTF.Scenes
     /// Represents the transform of a <see cref="InstanceBuilder.Content"/>.<br/>
     /// Applies a transform to the underlaying content object (usually a Mesh, a Camera or a light)
     /// </summary>
+    /// <remarks>
+    /// Base class of:<br/>
+    /// <see cref="FixedTransformer"/><br/>
+    /// <see cref="RigidTransformer"/><br/>
+    /// <see cref="SkinnedTransformer"/><br/>
+    /// </remarks>
     public abstract class ContentTransformer
     {
         #region debug
@@ -143,7 +149,7 @@ namespace SharpGLTF.Scenes
 
         public readonly struct DeepCloneContext
         {
-            public DeepCloneContext(IReadOnlyDictionary<NodeBuilder, NodeBuilder> nmap)
+            internal DeepCloneContext(IReadOnlyDictionary<NodeBuilder, NodeBuilder> nmap)
             {
                 _NodeMap = nmap;
             }
@@ -169,13 +175,13 @@ namespace SharpGLTF.Scenes
     {
         #region lifecycle
 
-        internal FixedTransformer(Object content, Transforms.AffineTransform transform)
+        internal FixedTransformer(Object content, TRANSFORM transform)
             : base(content)
         {
             _ChildTransform = transform;
         }
 
-        internal FixedTransformer(Object content, NodeBuilder parentNode, Transforms.AffineTransform childTransform)
+        internal FixedTransformer(Object content, NodeBuilder parentNode, TRANSFORM childTransform)
             : base(content)
         {
             _ParentNode = parentNode;

+ 38 - 1
tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs

@@ -728,7 +728,7 @@ namespace SharpGLTF.Scenes
         public void CreateSceneComposition()
         {
             // load Polly model
-            var polly = SceneBuilder.Load(TestFiles.GetPollyFileModelPath(), Validation.ValidationMode.TryFix);
+            var polly = SceneBuilder.LoadDefaultScene(TestFiles.GetPollyFileModelPath(), Validation.ValidationMode.TryFix);
             
             var xform0 = Matrix4x4.CreateFromYawPitchRoll(1, 0, 0) * Matrix4x4.CreateTranslation(1.5f, 0, 0);
             var xform1 = Matrix4x4.CreateFromYawPitchRoll(0, 1, 0) * Matrix4x4.CreateTranslation(-1.5f, 1, 0);
@@ -899,5 +899,42 @@ namespace SharpGLTF.Scenes
             return mesh1;
         }
 
+
+        [Test]
+        public void TestWholeModelConversionRoundtrip()
+        {
+            // create a cube mesh. This mesh will be shared along the way:
+
+            var cube = new MeshBuilder<VertexPosition,VertexEmpty,VertexEmpty>("Cube");
+            cube.AddCube(MaterialBuilder.CreateDefault(), Matrix4x4.Identity);
+
+            // create a gltf with 2 scenes:
+
+            var model1 = ModelRoot.CreateModel();
+            var m = model1.CreateMesh(cube);
+            model1.UseScene("Scene1").CreateNode("Node1").Mesh = m;
+            model1.UseScene("Scene2").CreateNode("Node2").Mesh = m;
+
+            // convert to SceneBuilder:
+
+            var scenes = SceneBuilder.CreateFrom(model1).ToArray();
+            Assert.AreEqual(2, scenes.Length);
+
+            var mesh1 = scenes[0].Instances[0].Content.GetGeometryAsset();
+            var mesh2 = scenes[1].Instances[0].Content.GetGeometryAsset();
+
+            Assert.AreSame(mesh1, mesh2, "both scenes must share the same MeshBuilder");
+
+            // convert back to gltf:
+
+            var model2 = SceneBuilder.ToGltf2(scenes, SceneBuilderSchema2Settings.Default);
+
+            // verify the mesh is still shared.
+
+            Assert.AreEqual(2, model2.LogicalScenes.Count);
+            Assert.AreEqual(2, model2.LogicalNodes.Count);
+            Assert.AreEqual(1, model2.LogicalMeshes.Count); // check the mesh is shared between the 2 scenes
+        }
+
     }
 }