Browse Source

Added Runtime namespace.

Vicente Penades 6 years ago
parent
commit
4d5f84eef7

+ 16 - 0
src/Shared/_Extensions.cs

@@ -170,6 +170,22 @@ namespace SharpGLTF
 
             int h = 0;
 
+            // this will handle default(ArraySegment<T>)
+            if (collection is IReadOnlyList<T> list)
+            {
+                count = Math.Min(count, list.Count);
+
+                for (int i = 0; i < count; ++i)
+                {
+                    var element = list[i];
+
+                    h ^= element == null ? 0 : element.GetHashCode();
+                    h *= 17;
+                }
+
+                return h;
+            }
+
             foreach (var element in collection.Take(count))
             {
                 h ^= element == null ? 0 : element.GetHashCode();

+ 6 - 0
src/SharpGLTF.Core/Animations/SamplerFactory.cs

@@ -285,6 +285,12 @@ namespace SharpGLTF.Animations
 
         #region sampler creation
 
+        internal static IEnumerable<T> Isolate<T>(this IEnumerable<T> collection, bool isolateMemory)
+        {
+            if (isolateMemory) collection = collection.ToArray();
+            return collection;
+        }
+
         public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(Single, Vector3)> collection, bool isLinear = true)
         {
             if (collection == null) return null;

+ 70 - 0
src/SharpGLTF.Core/Runtime/AnimatableProperty.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using SharpGLTF.Animations;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// Defines an animatable property with a default value and a collection of animation curve tracks.
+    /// </summary>
+    /// <typeparam name="T">A type that can be interpolated with <see cref="ICurveSampler{T}"/></typeparam>
+    sealed class AnimatableProperty<T>
+       where T : struct
+    {
+        #region lifecycle
+
+        internal AnimatableProperty(T defval)
+        {
+            Value = defval;
+        }
+
+        #endregion
+
+        #region data
+
+        private Dictionary<string, ICurveSampler<T>> _Tracks;
+
+        /// <summary>
+        /// Gets the default value of this instance.
+        /// When animations are disabled, or there's no animation track available, this will be the returned value.
+        /// </summary>
+        public T Value { get; private set; }
+
+        #endregion
+
+        #region properties
+
+        public bool IsAnimated => Tracks.Count > 0;
+
+        public IReadOnlyDictionary<string, ICurveSampler<T>> Tracks => _Tracks;
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Evaluates the value of this <see cref="AnimatableProperty{T}"/> at a given <paramref name="offset"/> for a given <paramref name="track"/>.
+        /// </summary>
+        /// <param name="track">An animation track name, or null.</param>
+        /// <param name="offset">A time offset within the given animation track.</param>
+        /// <returns>The evaluated value taken from the animation <paramref name="track"/>, or <see cref="Value"/> if a track was not found.</returns>
+        public T GetValueAt(string track, float offset)
+        {
+            if (_Tracks == null) return this.Value;
+
+            return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetPoint(offset) : this.Value;
+        }
+
+        public void AddCurve(string name, ICurveSampler<T> sampler)
+        {
+            if (_Tracks == null) _Tracks = new Dictionary<string, ICurveSampler<T>>();
+            _Tracks[name] = sampler;
+        }
+
+        #endregion
+    }
+}

+ 58 - 0
src/SharpGLTF.Core/Runtime/README.md

@@ -0,0 +1,58 @@
+# SharpGLTF.Runtime
+
+This namespace contains some utility classes to help into rendering glTF models with
+client graphics engines typically using hardware resources.
+
+The process is this; first of all we load a glTF model:
+
+Loading a glTF document:
+```c#
+var model = SharpGLTF.Schema2.ModelRoot.Load("model.gltf");
+```
+
+Now, lets say you have an __AwesomeEngine__ which defines an __AwesomeMesh__ that
+is the equivalent of a __glTF Mesh__, so for each glTF Mesh we find in Model.LogicalMeshes,
+we create the equivalent AwesomeMesh:
+```c#
+var gpuMeshes = new AwesomeMesh[model.LogicalMeshes.Count];
+for(int i=0; i < model.LogicalMeshes.Count; ++i)
+{
+    gpuMeshes = new AwesomeMesh(model.LogicalMeshes[i]);
+}
+```
+
+Next, we create a scene template from the default glTF scene:
+```c#
+var modelTemplate = SharpGLTF.Runtime.SceneTemplate(model.DefaultScene,true);
+
+var modelInstance = modelTemplate.CreateInstance();
+```
+
+Finally, in our render call, we render the meshes like this:
+```c#
+void RenderFrame(Matrix4x4 modelMatrix)
+{
+    foreach(var drawable in modelInstance.DrawableReferences)
+    {
+        var gpuMesh = gpuMeshes[drawable.Item1];
+
+        if (drawable.Item2 is SharpGLTF.Transforms.StaticTransform statXform)
+        {
+            AwesomeEngine.DrawMesh(gpuMesh, modelMatrix, statXform.WorldMatrix);
+        }
+
+        if (drawable.Item2 is SharpGLTF.Transforms.SkinTransform skinXform)
+        {
+            AwesomeEngine.DrawMesh(gpuMesh, modelMatrix, skinXform.SkinMatrices);
+        }
+    }
+}
+
+```
+
+
+
+
+
+
+

+ 225 - 0
src/SharpGLTF.Core/Runtime/SceneInstance.cs

@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using SharpGLTF.Transforms;
+
+using XFORM = System.Numerics.Matrix4x4;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// Defines a node of a scene graph in <see cref="SceneInstance"/>
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{Name}")]
+    public class NodeInstance
+    {
+        #region lifecycle
+
+        internal NodeInstance(NodeTemplate template, NodeInstance parent)
+        {
+            _Template = template;
+            _Parent = parent;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly NodeTemplate _Template;
+        private readonly NodeInstance _Parent;
+
+        private XFORM _LocalMatrix;
+        private XFORM? _WorldMatrix;
+
+        private SparseWeight8 _MorphWeights;
+
+        #endregion
+
+        #region properties
+
+        public String Name => _Template.Name;
+
+        public NodeInstance VisualParent => _Parent;
+
+        public SparseWeight8 MorphWeights
+        {
+            get => _MorphWeights;
+            set => _MorphWeights = value;
+        }
+
+        public XFORM LocalMatrix
+        {
+            get => _LocalMatrix;
+            set
+            {
+                _LocalMatrix = value;
+                _WorldMatrix = null;
+            }
+        }
+
+        public XFORM WorldMatrix
+        {
+            get => _GetWorldMatrix();
+            set => _SetWorldMatrix(value);
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether any of the transforms down the scene tree has been modified.
+        /// </summary>
+        private bool TransformChainIsDirty
+        {
+            get
+            {
+                if (!_WorldMatrix.HasValue) return true;
+
+                return _Parent == null ? false : _Parent.TransformChainIsDirty;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        private XFORM _GetWorldMatrix()
+        {
+            if (!TransformChainIsDirty) return _WorldMatrix.Value;
+
+            _WorldMatrix = _Parent == null ? _LocalMatrix : XFORM.Multiply(_LocalMatrix, _Parent.WorldMatrix);
+
+            return _WorldMatrix.Value;
+        }
+
+        private void _SetWorldMatrix(XFORM xform)
+        {
+            if (_Parent == null) { LocalMatrix = xform; return; }
+
+            XFORM.Invert(_Parent._GetWorldMatrix(), out XFORM ipwm);
+
+            LocalMatrix = XFORM.Multiply(xform, ipwm);
+        }
+
+        public void SetPoseTransform() { SetAnimationFrame(null, 0); }
+
+        public void SetAnimationFrame(string trackName, float time)
+        {
+            this.MorphWeights = _Template.GetMorphWeights(trackName, time);
+            this.LocalMatrix = _Template.GetLocalMatrix(trackName, time);
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Represents a specific and independent state of a <see cref="SceneTemplate"/>.
+    /// </summary>
+    public class SceneInstance
+    {
+        #region lifecycle
+
+        internal SceneInstance(NodeTemplate[] nodeTemplates, DrawableReference[] drawables, IReadOnlyDictionary<string, float> tracks)
+        {
+            AnimationTracks = tracks;
+
+            _NodeTemplates = nodeTemplates;
+            _NodeInstances = new NodeInstance[_NodeTemplates.Length];
+
+            for (var i = 0; i < _NodeInstances.Length; ++i)
+            {
+                var n = _NodeTemplates[i];
+                var pidx = _NodeTemplates[i].ParentIndex;
+
+                if (pidx >= i) throw new ArgumentException("invalid parent index", nameof(nodeTemplates));
+
+                var p = pidx < 0 ? null : _NodeInstances[pidx];
+
+                _NodeInstances[i] = new NodeInstance(n, p);
+            }
+
+            _DrawableReferences = drawables;
+            _DrawableTransforms = new IGeometryTransform[_DrawableReferences.Length];
+
+            for (int i = 0; i < _DrawableTransforms.Length; ++i)
+            {
+                _DrawableTransforms[i] = _DrawableReferences[i].CreateGeometryTransform();
+            }
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly NodeTemplate[] _NodeTemplates;
+        private readonly NodeInstance[] _NodeInstances;
+
+        private readonly DrawableReference[] _DrawableReferences;
+        private readonly IGeometryTransform[] _DrawableTransforms;
+
+        #endregion
+
+        #region properties
+
+        public IReadOnlyList<NodeInstance> LogicalNodes => _NodeInstances;
+
+        public IEnumerable<NodeInstance> VisualNodes => _NodeInstances.Where(item => item.VisualParent == null);
+
+        public IReadOnlyDictionary<string, float> AnimationTracks { get; private set; }
+
+        /// <summary>
+        /// Gets the number of drawable references.
+        /// </summary>
+        public int DrawableReferencesCount => _DrawableTransforms.Length;
+
+        /// <summary>
+        /// Gets a collection of drawable references, where Item1 is the logical Index
+        /// of a <see cref="Schema2.Mesh"/> in <see cref="Schema2.ModelRoot.LogicalMeshes"/>
+        /// and Item2 is an <see cref="IGeometryTransform"/> that can be used to transform
+        /// the <see cref="Schema2.Mesh"/> into world space.
+        /// </summary>
+        public IEnumerable<(int, IGeometryTransform)> DrawableReferences
+        {
+            get
+            {
+                for (int i = 0; i < _DrawableTransforms.Length; ++i)
+                {
+                    yield return GetDrawableReference(i);
+                }
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public void SetPoseTransforms()
+        {
+            foreach (var n in _NodeInstances) n.SetPoseTransform();
+        }
+
+        public void SetAnimationFrame(string trackName, float time)
+        {
+            foreach (var n in _NodeInstances) n.SetAnimationFrame(trackName, time);
+        }
+
+        /// <summary>
+        /// Gets a drawable reference pair, where Item1 is the logical Index
+        /// of a <see cref="Schema2.Mesh"/> in <see cref="Schema2.ModelRoot.LogicalMeshes"/>
+        /// and Item2 is an <see cref="IGeometryTransform"/> that can be used to transform
+        /// the <see cref="Schema2.Mesh"/> into world space.
+        /// </summary>
+        /// <param name="index">The index of the drawable reference, from 0 to <see cref="DrawableReferencesCount"/></param>
+        /// <returns>A drawable reference</returns>
+        public (int, IGeometryTransform) GetDrawableReference(int index)
+        {
+            var dref = _DrawableReferences[index];
+
+            dref.UpdateGeometryTransform(_DrawableTransforms[index], _NodeInstances);
+
+            return (dref.LogicalMeshIndex, _DrawableTransforms[index]);
+        }
+
+        #endregion
+    }
+}

+ 371 - 0
src/SharpGLTF.Core/Runtime/SceneTemplate.cs

@@ -0,0 +1,371 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// Defines a hierarchical transform node of a scene graph tree.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("[{LogicalNodeIndex}] {Name}")]
+    class NodeTemplate
+    {
+        #region lifecycle
+
+        internal NodeTemplate(Schema2.Node srcNode, int parentIdx, int[] childIndices, bool isolateMemory, IDictionary<string, float> animTracks)
+        {
+            _LogicalSourceIndex = srcNode.LogicalIndex;
+
+            _ParentIndex = parentIdx;
+            _ChildIndices = childIndices;
+
+            Name = srcNode.Name;
+
+            _LocalMatrix = srcNode.LocalMatrix;
+
+            var localXform = srcNode.LocalTransform;
+            _Rotation = new AnimatableProperty<Quaternion>(localXform.Rotation);
+            _Scale = new AnimatableProperty<Vector3>(localXform.Scale);
+            _Translation = new AnimatableProperty<Vector3>(localXform.Translation);
+
+            var mw = Transforms.SparseWeight8.Create(srcNode.MorphWeights);
+            _Morphing = new AnimatableProperty<Transforms.SparseWeight8>(mw);
+
+            foreach (var anim in srcNode.LogicalParent.LogicalAnimations)
+            {
+                var name = anim.Name;
+                if (string.IsNullOrWhiteSpace(name)) name = anim.LogicalIndex.ToString(System.Globalization.CultureInfo.InvariantCulture);
+
+                if (animTracks.TryGetValue(name, out float duration)) duration = Math.Max(duration, anim.Duration);
+                else duration = anim.Duration;
+
+                animTracks[name] = duration;
+
+                var scaAnim = anim.FindScaleSampler(srcNode)?.CreateCurveSampler(isolateMemory);
+                if (scaAnim != null) _Scale.AddCurve(name, scaAnim);
+
+                var rotAnim = anim.FindRotationSampler(srcNode)?.CreateCurveSampler(isolateMemory);
+                if (rotAnim != null) _Rotation.AddCurve(name, rotAnim);
+
+                var traAnim = anim.FindTranslationSampler(srcNode)?.CreateCurveSampler(isolateMemory);
+                if (traAnim != null) _Translation.AddCurve(name, traAnim);
+
+                var mrpAnim = anim.FindSparseMorphSampler(srcNode)?.CreateCurveSampler(isolateMemory);
+                if (mrpAnim != null) _Morphing.AddCurve(name, mrpAnim);
+            }
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _LogicalSourceIndex;
+
+        private readonly int _ParentIndex;
+        private readonly int[] _ChildIndices;
+
+        private readonly Matrix4x4 _LocalMatrix;
+
+        private readonly AnimatableProperty<Vector3> _Scale;
+        private readonly AnimatableProperty<Quaternion> _Rotation;
+        private readonly AnimatableProperty<Vector3> _Translation;
+        private readonly AnimatableProperty<Transforms.SparseWeight8> _Morphing;
+
+        #endregion
+
+        #region properties
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets the index of the source <see cref="Schema2.Node"/> in <see cref="Schema2.ModelRoot.LogicalNodes"/>
+        /// </summary>
+        public int LogicalNodeIndex => _LogicalSourceIndex;
+
+        /// <summary>
+        /// Gets the index of the parent <see cref="NodeTemplate"/> in <see cref="SceneTemplate._NodeTemplates"/>
+        /// </summary>
+        public int ParentIndex => _ParentIndex;
+
+        /// <summary>
+        /// Gets the list of indices of the children <see cref="NodeTemplate"/> in <see cref="SceneTemplate._NodeTemplates"/>
+        /// </summary>
+        public IReadOnlyList<int> ChildIndices => _ChildIndices;
+
+        public Matrix4x4 LocalMatrix => _LocalMatrix;
+
+        #endregion
+
+        #region API
+
+        public Transforms.SparseWeight8 GetMorphWeights(string trackName, float time)
+        {
+            return _Morphing.GetValueAt(trackName, time);
+        }
+
+        public Transforms.AffineTransform GetLocalTransform(string trackName, float time)
+        {
+            if (string.IsNullOrEmpty(trackName)) return Transforms.AffineTransform.Create(_LocalMatrix);
+
+            var s = _Scale?.GetValueAt(trackName, time);
+            var r = _Rotation?.GetValueAt(trackName, time);
+            var t = _Translation?.GetValueAt(trackName, time);
+
+            return Transforms.AffineTransform.Create(t, r, s);
+        }
+
+        public Matrix4x4 GetLocalMatrix(string trackName, float time)
+        {
+            if (string.IsNullOrEmpty(trackName)) return _LocalMatrix;
+
+            return GetLocalTransform(trackName, time).Matrix;
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a reference to a drawable mesh
+    /// </summary>
+    abstract class DrawableReference
+    {
+        #region lifecycle
+
+        protected DrawableReference(Schema2.Node node)
+        {
+            _LogicalMeshIndex = node.Mesh.LogicalIndex;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _LogicalMeshIndex;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the index of a <see cref="Schema2.Mesh"/> in <see cref="Schema2.ModelRoot.LogicalMeshes"/>
+        /// </summary>
+        public int LogicalMeshIndex => _LogicalMeshIndex;
+
+        #endregion
+
+        #region API
+
+        public abstract Transforms.IGeometryTransform CreateGeometryTransform();
+
+        public abstract void UpdateGeometryTransform(Transforms.IGeometryTransform geoxform, IReadOnlyList<NodeInstance> instances);
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a reference to a drawable static mesh
+    /// </summary>
+    sealed class StaticDrawableReference : DrawableReference
+    {
+        #region lifecycle
+
+        internal StaticDrawableReference(Schema2.Node node, Func<Schema2.Node,int> indexFunc) : base(node)
+        {
+            _NodeIndex = indexFunc(node);
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _NodeIndex;
+
+        #endregion
+
+        #region API
+
+        public override Transforms.IGeometryTransform CreateGeometryTransform() { return new Transforms.StaticTransform(); }
+
+        public override void UpdateGeometryTransform(Transforms.IGeometryTransform geoxform, IReadOnlyList<NodeInstance> instances)
+        {
+            var node = instances[_NodeIndex];
+
+            var statxform = (Transforms.StaticTransform)geoxform;
+            statxform.Update(node.WorldMatrix);
+            statxform.Update(node.MorphWeights, false);
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a reference to a drawable skinned mesh
+    /// </summary>
+    sealed class SkinnedDrawableReference : DrawableReference
+    {
+        #region lifecycle
+
+        internal SkinnedDrawableReference(Schema2.Node node, Func<Schema2.Node, int> indexFunc) : base(node)
+        {
+            var skin = node.Skin;
+
+            _MorphNodeIndex = indexFunc(node);
+
+            _JointsNodeIndices = new int[skin.JointsCount];
+            _BindMatrices = new System.Numerics.Matrix4x4[skin.JointsCount];            
+
+            for(int i=0; i < _JointsNodeIndices.Length; ++i)
+            {
+                var jm = skin.GetJoint(i);
+
+                _JointsNodeIndices[i] = indexFunc(jm.Item1);
+                _BindMatrices[i] = jm.Item2;
+            }
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _MorphNodeIndex;
+        private readonly int[] _JointsNodeIndices;
+        private readonly Matrix4x4[] _BindMatrices;
+
+        #endregion
+
+        #region API
+
+        public override Transforms.IGeometryTransform CreateGeometryTransform() { return new Transforms.SkinTransform(); }
+
+        public override void UpdateGeometryTransform(Transforms.IGeometryTransform geoxform, IReadOnlyList<NodeInstance> instances)
+        {
+            var skinxform = (Transforms.SkinTransform)geoxform;
+            skinxform.Update(_JointsNodeIndices.Length, idx => _BindMatrices[idx], idx => instances[_JointsNodeIndices[idx]].WorldMatrix);
+            skinxform.Update(instances[_MorphNodeIndex].MorphWeights, false);
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a templatized representation of a <see cref="Schema2.Scene"/> that can be used
+    /// to create <see cref="SceneInstance"/>, which can help render a scene on a client application.
+    /// </summary>
+    public class SceneTemplate
+    {
+        #region lifecycle
+
+        /// <summary>
+        /// Creates a new <see cref="SceneTemplate"/> from a given <see cref="Schema2.Scene"/>.
+        /// </summary>
+        /// <param name="srcScene">The source <see cref="Schema2.Scene"/> to templateize.</param>
+        /// <param name="isolateMemory">True if we want to copy data instead of sharing it.</param>
+        /// <returns>A new <see cref="SceneTemplate"/> instance.</returns>
+        public static SceneTemplate Create(Schema2.Scene srcScene, bool isolateMemory)
+        {
+            Guard.NotNull(srcScene, nameof(srcScene));
+
+            var srcNodes = Schema2.Node.Flatten(srcScene)
+                .Select((key, idx) => (key, idx))
+                .ToDictionary(pair => pair.key, pair => pair.idx);
+
+            int indexSolver(Schema2.Node srcNode)
+            {
+                if (srcNode == null) return -1;
+                return srcNodes[srcNode];
+            }
+
+            var dstTracks = new Dictionary<string, float>();
+
+            var dstNodes = new NodeTemplate[srcNodes.Count];
+
+            foreach (var srcNode in srcNodes)
+            {
+                var nidx = srcNode.Value;
+
+                // parent index
+                var pidx = indexSolver(srcNode.Key.VisualParent);
+                if (pidx >= nidx) throw new InvalidOperationException("parent indices should be below child indices");
+
+                // child indices
+                var cidx = srcNode.Key.VisualChildren
+                    .Select(n => indexSolver(n))
+                    .ToArray();
+
+                dstNodes[nidx] = new NodeTemplate(srcNode.Key, pidx, cidx, isolateMemory, dstTracks);
+            }
+
+            var instances = srcNodes.Keys
+                .Where(item => item.Mesh != null)
+                .ToList();
+
+            var drawables = new DrawableReference[instances.Count];
+
+            for (int i = 0; i < drawables.Length; ++i)
+            {
+                var srcInstance = instances[i];
+
+                drawables[i] = srcInstance.Skin != null
+                    ?
+                    (DrawableReference)new SkinnedDrawableReference(srcInstance, indexSolver)
+                    :
+                    (DrawableReference)new StaticDrawableReference(srcInstance, indexSolver);
+            }
+
+            return new SceneTemplate(dstNodes, drawables, dstTracks);
+        }
+
+        private SceneTemplate(NodeTemplate[] nodes, DrawableReference[] drawables, IReadOnlyDictionary<string, float> animTracks)
+        {
+            _NodeTemplates = nodes;
+            _DrawableReferences = drawables;
+            _AnimationTracks = animTracks;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly NodeTemplate[] _NodeTemplates;
+        private readonly DrawableReference[] _DrawableReferences;
+
+        private readonly IReadOnlyDictionary<string, float> _AnimationTracks;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the unique indices of <see cref="Schema2.Mesh"/> instances in <see cref="Schema2.ModelRoot.LogicalMeshes"/>
+        /// </summary>
+        public IEnumerable<int> LogicalMeshIds => _DrawableReferences.Select(item => item.LogicalMeshIndex).Distinct();
+
+        /// <summary>
+        /// Gets A collection of animation track names, and the time duration of each track, in seconds.
+        /// </summary>
+        public IReadOnlyDictionary<string, float> AnimationTracks => _AnimationTracks;
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Creates a new <see cref="SceneInstance"/> of this <see cref="SceneTemplate"/>
+        /// that can be used to render the scene.
+        /// </summary>
+        /// <returns>A new <see cref="SceneInstance"/> object.</returns>
+        public SceneInstance CreateInstance()
+        {
+            var inst = new SceneInstance(_NodeTemplates, _DrawableReferences, _AnimationTracks);
+
+            inst.SetPoseTransforms();
+
+            return inst;
+        }
+
+        #endregion
+    }
+}

+ 51 - 35
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -487,27 +487,31 @@ namespace SharpGLTF.Schema2
             _output = this._CreateOutputAccessor(kv.Item2).LogicalIndex;
         }
 
-        IEnumerable<(Single, Vector3)> IAnimationSampler<Vector3>.GetLinearKeys()
+        IEnumerable<(Single, Vector3)> IAnimationSampler<Vector3>.GetLinearKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsVector3Array();
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, Quaternion)> IAnimationSampler<Quaternion>.GetLinearKeys()
+        IEnumerable<(Single, Quaternion)> IAnimationSampler<Quaternion>.GetLinearKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsQuaternionArray();
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, SparseWeight8)> IAnimationSampler<SparseWeight8>.GetLinearKeys()
+        IEnumerable<(Single, SparseWeight8)> IAnimationSampler<SparseWeight8>.GetLinearKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
@@ -516,10 +520,12 @@ namespace SharpGLTF.Schema2
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsMultiArray(dimensions);
 
-            return keys.Zip(frames, (key, val) => (key, SparseWeight8.Create(val)));
+            return keys
+                .Zip(frames, (key, val) => (key, SparseWeight8.Create(val)))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, Single[])> IAnimationSampler<Single[]>.GetLinearKeys()
+        IEnumerable<(Single, Single[])> IAnimationSampler<Single[]>.GetLinearKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
@@ -528,30 +534,36 @@ namespace SharpGLTF.Schema2
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsMultiArray(dimensions);
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, (Vector3, Vector3, Vector3))> IAnimationSampler<Vector3>.GetCubicKeys()
+        IEnumerable<(Single, (Vector3, Vector3, Vector3))> IAnimationSampler<Vector3>.GetCubicKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
             var keys = this.Input.AsScalarArray();
             var frames = _GroupByThree(this.Output.AsVector3Array());
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> IAnimationSampler<Quaternion>.GetCubicKeys()
+        IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> IAnimationSampler<Quaternion>.GetCubicKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
             var keys = this.Input.AsScalarArray();
             var frames = _GroupByThree(this.Output.AsQuaternionArray());
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, (Single[], Single[], Single[]))> IAnimationSampler<Single[]>.GetCubicKeys()
+        IEnumerable<(Single, (Single[], Single[], Single[]))> IAnimationSampler<Single[]>.GetCubicKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
@@ -560,10 +572,12 @@ namespace SharpGLTF.Schema2
             var keys = this.Input.AsScalarArray();
             var frames = _GroupByThree(this.Output.AsMultiArray(dimensions));
 
-            return keys.Zip(frames, (key, val) => (key, val));
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .Isolate(isolateMemory);
         }
 
-        IEnumerable<(Single, (SparseWeight8, SparseWeight8, SparseWeight8))> IAnimationSampler<SparseWeight8>.GetCubicKeys()
+        IEnumerable<(Single, (SparseWeight8, SparseWeight8, SparseWeight8))> IAnimationSampler<SparseWeight8>.GetCubicKeys(bool isolateMemory)
         {
             Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
 
@@ -572,7 +586,9 @@ namespace SharpGLTF.Schema2
             var keys = this.Input.AsScalarArray();
             var frames = _GroupByThree(this.Output.AsMultiArray(dimensions));
 
-            return keys.Zip(frames, (key, val) => (key, (SparseWeight8.Create(val.Item1), SparseWeight8.Create(val.Item2), SparseWeight8.Create(val.Item3))));
+            return keys
+                .Zip(frames, (key, val) => (key, (SparseWeight8.Create(val.Item1), SparseWeight8.Create(val.Item2), SparseWeight8.Create(val.Item3))))
+                .Isolate(isolateMemory);
         }
 
         private static IEnumerable<(T, T, T)> _GroupByThree<T>(IEnumerable<T> collection)
@@ -593,57 +609,57 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        ICurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler()
+        ICurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler(bool isolateMemory)
         {
             var xsampler = this as IAnimationSampler<Vector3>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
 
             throw new NotImplementedException();
         }
 
-        ICurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler()
+        ICurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler(bool isolateMemory)
         {
             var xsampler = this as IAnimationSampler<Quaternion>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
 
             throw new NotImplementedException();
         }
 
-        ICurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler()
+        ICurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler(bool isolateMemory)
         {
             var xsampler = this as IAnimationSampler<Single[]>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
 
             throw new NotImplementedException();
         }
 
-        ICurveSampler<SparseWeight8> IAnimationSampler<SparseWeight8>.CreateCurveSampler()
+        ICurveSampler<SparseWeight8> IAnimationSampler<SparseWeight8>.CreateCurveSampler(bool isolateMemory)
         {
             var xsampler = this as IAnimationSampler<SparseWeight8>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
 
             throw new NotImplementedException();
@@ -656,11 +672,11 @@ namespace SharpGLTF.Schema2
     {
         AnimationInterpolationMode InterpolationMode { get; }
 
-        IEnumerable<(Single, T)> GetLinearKeys();
+        IEnumerable<(Single, T)> GetLinearKeys(bool isolateMemory = false);
 
-        IEnumerable<(Single, (T, T, T))> GetCubicKeys();
+        IEnumerable<(Single, (T, T, T))> GetCubicKeys(bool isolateMemory = false);
 
-        ICurveSampler<T> CreateCurveSampler();
+        ICurveSampler<T> CreateCurveSampler(bool isolateMemory = false);
     }
 
     public sealed partial class ModelRoot

+ 4 - 1
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -34,6 +34,7 @@ namespace SharpGLTF.Scenes
         private Animations.AnimatableProperty<Vector3> _Scale;
         private Animations.AnimatableProperty<Quaternion> _Rotation;
         private Animations.AnimatableProperty<Vector3> _Translation;
+        private Animations.AnimatableProperty<Transforms.SparseWeight8> _Morphings;
 
         #endregion
 
@@ -54,7 +55,7 @@ namespace SharpGLTF.Scenes
         /// <summary>
         /// Gets a value indicating whether this <see cref="NodeBuilder"/> has animations.
         /// </summary>
-        public bool HasAnimations => (_Scale?.IsAnimated ?? false) || (_Rotation?.IsAnimated ?? false) || (_Translation?.IsAnimated ?? false);
+        public bool HasAnimations => (_Scale?.IsAnimated ?? false) || (_Rotation?.IsAnimated ?? false) || (_Translation?.IsAnimated ?? false) || (_Morphings?.IsAnimated ?? false);
 
         public Animations.AnimatableProperty<Vector3> Scale => _Scale;
 
@@ -62,6 +63,8 @@ namespace SharpGLTF.Scenes
 
         public Animations.AnimatableProperty<Vector3> Translation => _Translation;
 
+        public Animations.AnimatableProperty<Transforms.SparseWeight8> Morphings => _Morphings;
+
         /// <summary>
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
         /// </summary>