Browse Source

refactoring Runtime namespace.
+tests

Vicente Penades 6 years ago
parent
commit
185ba2dc6b

+ 41 - 0
src/SharpGLTF.Core/Collections/NamedList.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Collections
+{
+    /// <summary>
+    /// Stores values by index, and optionally, by name.
+    /// </summary>
+    /// <typeparam name="T">Any type.</typeparam>
+    class NamedList<T> : List<T>
+    {
+        private Dictionary<string, int> _ByName;
+
+        public IReadOnlyCollection<String> Names => _ByName != null ? _ByName.Keys : (IReadOnlyCollection<String>)Array.Empty<string>();
+
+        public void SetValue(int index, string name, T value)
+        {
+            Guard.MustBeGreaterThanOrEqualTo(index, 0, nameof(index));
+
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (_ByName == null) _ByName = new Dictionary<string, int>();
+                _ByName[name] = index;
+            }
+
+            while (this.Count <= index) this.Add(default);
+
+            this[index] = value;
+        }
+
+        public int IndexOf(string name)
+        {
+            if (_ByName == null) return default;
+
+            if (string.IsNullOrEmpty(name)) return default;
+
+            return _ByName.TryGetValue(name, out int index) ? index : -1;
+        }
+    }
+}

+ 24 - 11
src/SharpGLTF.Core/Runtime/AnimatableProperty.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF.Runtime
 
         #region data
 
-        private Dictionary<string, ICurveSampler<T>> _Tracks;
+        private Collections.NamedList<ICurveSampler<T>> _Animations;
 
         /// <summary>
         /// Gets the default value of this instance.
@@ -40,29 +40,42 @@ namespace SharpGLTF.Runtime
 
         public bool IsAnimated => Tracks.Count > 0;
 
-        public IReadOnlyDictionary<string, ICurveSampler<T>> Tracks => _Tracks;
+        public IReadOnlyCollection<string> Tracks => _Animations?.Names;
 
         #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"/>.
+        /// Evaluates the value of this <see cref="AnimatableProperty{T}"/> at a given <paramref name="offset"/> for a given <paramref name="trackName"/>.
         /// </summary>
-        /// <param name="track">An animation track name, or null.</param>
+        /// <param name="trackName">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)
+        /// <returns>The evaluated value taken from the animation <paramref name="trackName"/>, or <see cref="Value"/> if a track was not found.</returns>
+        public T GetValueAt(string trackName, float offset)
         {
-            if (_Tracks == null) return this.Value;
+            var idx = _Animations?.IndexOf(trackName) ?? -1;
 
-            return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetPoint(offset) : this.Value;
+            return GetValueAt(idx, offset);
         }
 
-        public void AddCurve(string name, ICurveSampler<T> sampler)
+        public T GetValueAt(int trackLogicalIndex, float offset)
         {
-            if (_Tracks == null) _Tracks = new Dictionary<string, ICurveSampler<T>>();
-            _Tracks[name] = sampler;
+            if (_Animations == null) return this.Value;
+
+            if (trackLogicalIndex < 0 || trackLogicalIndex >= _Animations.Count) return this.Value;
+
+            return _Animations[trackLogicalIndex].GetPoint(offset);
+        }
+
+        public void AddCurve(int logicalIndex, string name, ICurveSampler<T> sampler)
+        {
+            Guard.NotNull(sampler, nameof(sampler));
+            Guard.MustBeGreaterThanOrEqualTo(logicalIndex, 0, nameof(logicalIndex));
+
+            if (_Animations == null) _Animations = new Collections.NamedList<ICurveSampler<T>>();
+
+            _Animations.SetValue(logicalIndex, name, sampler);
         }
 
         #endregion

+ 28 - 8
src/SharpGLTF.Core/Runtime/SceneInstance.cs

@@ -101,12 +101,12 @@ namespace SharpGLTF.Runtime
             LocalMatrix = XFORM.Multiply(xform, ipwm);
         }
 
-        public void SetPoseTransform() { SetAnimationFrame(null, 0); }
+        public void SetPoseTransform() { SetAnimationFrame(-1, 0); }
 
-        public void SetAnimationFrame(string trackName, float time)
+        public void SetAnimationFrame(int trackLogicalIndex, float time)
         {
-            this.MorphWeights = _Template.GetMorphWeights(trackName, time);
-            this.LocalMatrix = _Template.GetLocalMatrix(trackName, time);
+            this.MorphWeights = _Template.GetMorphWeights(trackLogicalIndex, time);
+            this.LocalMatrix = _Template.GetLocalMatrix(trackLogicalIndex, time);
         }
 
         #endregion
@@ -119,9 +119,9 @@ namespace SharpGLTF.Runtime
     {
         #region lifecycle
 
-        internal SceneInstance(NodeTemplate[] nodeTemplates, DrawableReference[] drawables, IReadOnlyDictionary<string, float> tracks)
+        internal SceneInstance(NodeTemplate[] nodeTemplates, DrawableReference[] drawables, Collections.NamedList<float> tracks)
         {
-            AnimationTracks = tracks;
+            _AnimationTracks = tracks;
 
             _NodeTemplates = nodeTemplates;
             _NodeInstances = new NodeInstance[_NodeTemplates.Length];
@@ -157,6 +157,8 @@ namespace SharpGLTF.Runtime
         private readonly DrawableReference[] _DrawableReferences;
         private readonly IGeometryTransform[] _DrawableTransforms;
 
+        private readonly Collections.NamedList<float> _AnimationTracks;
+
         #endregion
 
         #region properties
@@ -165,7 +167,7 @@ namespace SharpGLTF.Runtime
 
         public IEnumerable<NodeInstance> VisualNodes => _NodeInstances.Where(item => item.VisualParent == null);
 
-        public IReadOnlyDictionary<string, float> AnimationTracks { get; private set; }
+        public IEnumerable<String> AnimationTracks => _AnimationTracks.Names;
 
         /// <summary>
         /// Gets the number of drawable references.
@@ -198,9 +200,27 @@ namespace SharpGLTF.Runtime
             foreach (var n in _NodeInstances) n.SetPoseTransform();
         }
 
+        public float GetAnimationDuration(int trackLogicalIndex)
+        {
+            if (trackLogicalIndex < 0) return 0;
+            if (trackLogicalIndex >= _AnimationTracks.Count) return 0;
+
+            return _AnimationTracks[trackLogicalIndex];
+        }
+
+        public float GetAnimationDuration(string trackName)
+        {
+            return GetAnimationDuration(_AnimationTracks.IndexOf(trackName));
+        }
+
+        public void SetAnimationFrame(int trackLogicalIndex, float time)
+        {
+            foreach (var n in _NodeInstances) n.SetAnimationFrame(trackLogicalIndex, time);
+        }
+
         public void SetAnimationFrame(string trackName, float time)
         {
-            foreach (var n in _NodeInstances) n.SetAnimationFrame(trackName, time);
+            SetAnimationFrame(_AnimationTracks.IndexOf(trackName), time);
         }
 
         /// <summary>

+ 42 - 27
src/SharpGLTF.Core/Runtime/SceneTemplate.cs

@@ -15,7 +15,7 @@ namespace SharpGLTF.Runtime
     {
         #region lifecycle
 
-        internal NodeTemplate(Schema2.Node srcNode, int parentIdx, int[] childIndices, bool isolateMemory, IDictionary<string, float> animTracks)
+        internal NodeTemplate(Schema2.Node srcNode, int parentIdx, int[] childIndices, bool isolateMemory)
         {
             _LogicalSourceIndex = srcNode.LogicalIndex;
 
@@ -36,25 +36,20 @@ namespace SharpGLTF.Runtime
 
             foreach (var anim in srcNode.LogicalParent.LogicalAnimations)
             {
+                var index = anim.LogicalIndex;
                 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);
+                if (scaAnim != null) _Scale.AddCurve(index, name, scaAnim);
 
                 var rotAnim = anim.FindRotationSampler(srcNode)?.CreateCurveSampler(isolateMemory);
-                if (rotAnim != null) _Rotation.AddCurve(name, rotAnim);
+                if (rotAnim != null) _Rotation.AddCurve(index, name, rotAnim);
 
                 var traAnim = anim.FindTranslationSampler(srcNode)?.CreateCurveSampler(isolateMemory);
-                if (traAnim != null) _Translation.AddCurve(name, traAnim);
+                if (traAnim != null) _Translation.AddCurve(index, name, traAnim);
 
                 var mrpAnim = anim.FindSparseMorphSampler(srcNode)?.CreateCurveSampler(isolateMemory);
-                if (mrpAnim != null) _Morphing.AddCurve(name, mrpAnim);
+                if (mrpAnim != null) _Morphing.AddCurve(index, name, mrpAnim);
             }
         }
 
@@ -101,27 +96,29 @@ namespace SharpGLTF.Runtime
 
         #region API
 
-        public Transforms.SparseWeight8 GetMorphWeights(string trackName, float time)
+        public Transforms.SparseWeight8 GetMorphWeights(int trackLogicalIndex, float time)
         {
-            return _Morphing.GetValueAt(trackName, time);
+            if (trackLogicalIndex < 0) return _Morphing.Value;
+
+            return _Morphing.GetValueAt(trackLogicalIndex, time);
         }
 
-        public Transforms.AffineTransform GetLocalTransform(string trackName, float time)
+        public Transforms.AffineTransform GetLocalTransform(int trackLogicalIndex, float time)
         {
-            if (string.IsNullOrEmpty(trackName)) return Transforms.AffineTransform.Create(_LocalMatrix);
+            if (trackLogicalIndex < 0) return Transforms.AffineTransform.Create(_LocalMatrix);
 
-            var s = _Scale?.GetValueAt(trackName, time);
-            var r = _Rotation?.GetValueAt(trackName, time);
-            var t = _Translation?.GetValueAt(trackName, time);
+            var s = _Scale?.GetValueAt(trackLogicalIndex, time);
+            var r = _Rotation?.GetValueAt(trackLogicalIndex, time);
+            var t = _Translation?.GetValueAt(trackLogicalIndex, time);
 
             return Transforms.AffineTransform.Create(t, r, s);
         }
 
-        public Matrix4x4 GetLocalMatrix(string trackName, float time)
+        public Matrix4x4 GetLocalMatrix(int trackLogicalIndex, float time)
         {
-            if (string.IsNullOrEmpty(trackName)) return _LocalMatrix;
+            if (trackLogicalIndex < 0) return _LocalMatrix;
 
-            return GetLocalTransform(trackName, time).Matrix;
+            return GetLocalTransform(trackLogicalIndex, time).Matrix;
         }
 
         #endregion
@@ -268,6 +265,8 @@ namespace SharpGLTF.Runtime
         {
             Guard.NotNull(srcScene, nameof(srcScene));
 
+            // gather scene nodes.
+
             var srcNodes = Schema2.Node.Flatten(srcScene)
                 .Select((key, idx) => (key, idx))
                 .ToDictionary(pair => pair.key, pair => pair.idx);
@@ -278,7 +277,7 @@ namespace SharpGLTF.Runtime
                 return srcNodes[srcNode];
             }
 
-            var dstTracks = new Dictionary<string, float>();
+            // create bones.
 
             var dstNodes = new NodeTemplate[srcNodes.Count];
 
@@ -295,9 +294,11 @@ namespace SharpGLTF.Runtime
                     .Select(n => indexSolver(n))
                     .ToArray();
 
-                dstNodes[nidx] = new NodeTemplate(srcNode.Key, pidx, cidx, isolateMemory, dstTracks);
+                dstNodes[nidx] = new NodeTemplate(srcNode.Key, pidx, cidx, isolateMemory);
             }
 
+            // create drawables.
+
             var instances = srcNodes.Keys
                 .Where(item => item.Mesh != null)
                 .ToList();
@@ -315,10 +316,24 @@ namespace SharpGLTF.Runtime
                     (DrawableReference)new StaticDrawableReference(srcInstance, indexSolver);
             }
 
+            // gather animation durations.
+
+            var dstTracks = new Collections.NamedList<float>();
+
+            foreach (var anim in srcScene.LogicalParent.LogicalAnimations)
+            {
+                var index = anim.LogicalIndex;
+                var name = anim.Name;
+
+                float duration = dstTracks.Count <= index ? 0 : dstTracks[index];
+                duration = Math.Max(duration, anim.Duration);
+                dstTracks.SetValue(index, name, anim.Duration);
+            }
+
             return new SceneTemplate(dstNodes, drawables, dstTracks);
         }
 
-        private SceneTemplate(NodeTemplate[] nodes, DrawableReference[] drawables, IReadOnlyDictionary<string, float> animTracks)
+        private SceneTemplate(NodeTemplate[] nodes, DrawableReference[] drawables, Collections.NamedList<float> animTracks)
         {
             _NodeTemplates = nodes;
             _DrawableReferences = drawables;
@@ -332,7 +347,7 @@ namespace SharpGLTF.Runtime
         private readonly NodeTemplate[] _NodeTemplates;
         private readonly DrawableReference[] _DrawableReferences;
 
-        private readonly IReadOnlyDictionary<string, float> _AnimationTracks;
+        private readonly Collections.NamedList<float> _AnimationTracks;
 
         #endregion
 
@@ -344,9 +359,9 @@ namespace SharpGLTF.Runtime
         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.
+        /// Gets A collection of animation track names.
         /// </summary>
-        public IReadOnlyDictionary<string, float> AnimationTracks => _AnimationTracks;
+        public IEnumerable<string> AnimationTracks => _AnimationTracks.Names;
 
         #endregion
 

+ 0 - 71
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -232,77 +232,6 @@ namespace SharpGLTF.Schema2
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animation, time), lm);
         }
 
-        /// <summary>
-        /// Creates a <see cref="Transforms.IGeometryTransform"/> object, based on the current
-        /// transform state, that can be used to transform the <see cref="Mesh"/>
-        /// vertices to world space.
-        /// </summary>
-        /// <returns>A <see cref="Transforms.IGeometryTransform"/> object</returns>
-        public Transforms.IGeometryTransform GetGeometryTransform() { return GetGeometryTransform(null, 0); }
-
-        /// <summary>
-        /// Creates a <see cref="Transforms.IGeometryTransform"/> instance, based on the current
-        /// transform state, and the given <see cref="Animation"/>, that can be used
-        /// to transform the <see cref="Mesh"/> vertices to world space.
-        /// </summary>
-        /// <param name="animation">The <see cref="Animation"/> to use.</param>
-        /// <param name="time">The time within <paramref name="animation"/>.</param>
-        /// <returns>A <see cref="Transforms.IGeometryTransform"/> object</returns>
-        public Transforms.IGeometryTransform GetGeometryTransform(Animation animation, float time)
-        {
-            if (this.Skin == null)
-            {
-                var statXform = new Transforms.StaticTransform();
-                UpdateGeometryTransform(statXform, animation, time);
-                return statXform;
-            }
-            else
-            {
-                var skinXform = new Transforms.SkinTransform();
-                UpdateGeometryTransform(skinXform, animation, time);
-                return skinXform;
-            }
-        }
-
-        /// <summary>
-        /// Updates an existing instance of <see cref="Transforms.IGeometryTransform"/>.
-        /// </summary>
-        /// <param name="transform">The <see cref="Transforms.IGeometryTransform"/> to update.</param>
-        /// <param name="animation">The <see cref="Animation"/> to use.</param>
-        /// <param name="time">The time within <paramref name="animation"/>.</param>
-        public void UpdateGeometryTransform(Transforms.IGeometryTransform transform, Animation animation, float time)
-        {
-            Guard.NotNull(transform, nameof(transform));
-            if (animation != null) Guard.MustShareLogicalParent(this, animation, nameof(animation));
-
-            var weights = animation == null ? Transforms.SparseWeight8.Create(this.MorphWeights) : animation.GetSparseMorphWeights(this, time);
-
-            if (this.Skin == null)
-            {
-                if (transform is Transforms.StaticTransform statXform)
-                {
-                    statXform.Update(weights, false);
-                    statXform.Update(this.GetWorldMatrix(animation, time));
-                    return;
-                }
-            }
-            else
-            {
-                if (transform is Transforms.SkinTransform skinXform)
-                {
-                    skinXform.Update(weights, false);
-                    skinXform.Update(
-                        this.Skin.JointsCount,
-                        idx => this.Skin.GetJoint(idx).Item2,
-                        idx => this.Skin.GetJoint(idx).Item1.GetWorldMatrix(animation, time)
-                        );
-                    return;
-                }
-            }
-
-            throw new ArgumentException($"{nameof(transform)} type mismatch.", nameof(transform));
-        }
-
         #endregion
 
         #region API - hierarchy

+ 34 - 38
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -211,27 +211,27 @@ namespace SharpGLTF.Schema2
         /// <returns>A collection of triangles in world space.</returns>
         public static IEnumerable<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)> EvaluateTriangles(this Scene scene, Animation animation = null, float time = 0)
         {
-            return Node
-                .Flatten(scene)
-                .SelectMany(item => item.EvaluateTriangles(animation, time));
-        }
+            if (scene == null) return Enumerable.Empty<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)>();
 
-        /// <summary>
-        /// Yields a collection of triangles representing the geometry in world space.
-        /// </summary>
-        /// <param name="node">A <see cref="Node"/> instance.</param>
-        /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
-        /// <param name="time">The animation time.</param>
-        /// <returns>A collection of triangles in world space.</returns>
-        public static IEnumerable<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)> EvaluateTriangles(this Node node, Animation animation = null, float time = 0)
-        {
-            var mesh = node?.Mesh;
+            var instance = Runtime.SceneTemplate
+                .Create(scene, false)
+                .CreateInstance();
 
-            if (node == null || mesh == null) return Enumerable.Empty<(IVertexBuilder, IVertexBuilder, IVertexBuilder, Material)>();
+            if (animation == null)
+            {
+                instance.SetPoseTransforms();
+            }
+            else
+            {
+                instance.SetAnimationFrame(animation.Name, time);
+            }
 
-            var xform = node.GetGeometryTransform(animation, time);
+            var meshes = scene.LogicalParent.LogicalMeshes;
 
-            return mesh.EvaluateTriangles(xform);
+            return instance
+                .DrawableReferences
+                .Where(item => item.Item2.Visible)
+                .SelectMany(item => meshes[item.Item1].EvaluateTriangles(item.Item2));
         }
 
         /// <summary>
@@ -247,31 +247,27 @@ namespace SharpGLTF.Schema2
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
         {
-            return Node
-                .Flatten(scene)
-                .SelectMany(item => item.EvaluateTriangles<TvG, TvM>(animation, time));
-        }
+            if (scene == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
 
-        /// <summary>
-        /// Yields a collection of triangles representing the geometry in world space.
-        /// </summary>
-        /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
-        /// <typeparam name="TvM">The vertex fragment type with Colors and Texture Coordinates.</typeparam>
-        /// <param name="node">A <see cref="Node"/> instance.</param>
-        /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
-        /// <param name="time">The animation time.</param>
-        /// <returns>A collection of triangles in world space.</returns>
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this Node node, Animation animation = null, float time = 0)
-            where TvG : struct, IVertexGeometry
-            where TvM : struct, IVertexMaterial
-        {
-            var mesh = node?.Mesh;
+            var instance = Runtime.SceneTemplate
+                .Create(scene, false)
+                .CreateInstance();
 
-            if (node == null || mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
+            if (animation == null)
+            {
+                instance.SetPoseTransforms();
+            }
+            else
+            {
+                instance.SetAnimationFrame(animation.Name, time);
+            }
 
-            var xform = node.GetGeometryTransform(animation, time);
+            var meshes = scene.LogicalParent.LogicalMeshes;
 
-            return mesh.EvaluateTriangles<TvG, TvM, VertexEmpty>(xform);
+            return instance
+                .DrawableReferences
+                .Where(item => item.Item2.Visible)
+                .SelectMany(item => meshes[item.Item1].EvaluateTriangles<TvG, TvM, VertexEmpty>(item.Item2));
         }
 
         public static Scenes.SceneBuilder ToSceneBuilder(this Scene srcScene)

+ 41 - 0
tests/SharpGLTF.Tests/Runtime/SceneTemplateTests.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF.Runtime
+{
+    [Category("Core.Runtime")]
+    public class SceneTemplateTests
+    {
+        [Test]
+        public void TestMemoryIsolation()
+        {
+            // this tests checks if, after creating a template from a scene,
+            // there's any reference from the template to the source model
+            // that prevents the source model to be garbage collected.
+
+            var path = TestFiles.GetSampleModelsPaths()
+                            .FirstOrDefault(item => item.Contains("CesiumMan.glb"));
+
+            var result = LoadModelTemplate(path);            
+
+            GC.Collect(0);
+            GC.WaitForFullGCComplete();
+
+            Assert.IsFalse(result.Item2.TryGetTarget(out Schema2.ModelRoot model));
+            Assert.Null(model);
+        }
+
+        private static (SceneTemplate, WeakReference<Schema2.ModelRoot>) LoadModelTemplate(string path)
+        {
+            var model = Schema2.ModelRoot.Load(path);
+
+            var template = SceneTemplate.Create(model.DefaultScene, true);
+
+            return (template, new WeakReference<Schema2.ModelRoot>(model));
+        }
+    }
+}

+ 7 - 1
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -283,11 +283,17 @@ namespace SharpGLTF.Schema2.LoadAndSave
 
             // pos_master
 
+            var instance = Runtime.SceneTemplate
+                .Create(model.DefaultScene, false)
+                .CreateInstance();
+
             var pvrt = node.Mesh.Primitives[0].GetVertexColumns();
 
             for (float t = 0; t < 5; t+=0.25f)
             {
-                var nodexform = node.GetGeometryTransform(anim, t);
+                instance.SetAnimationFrame(anim.LogicalIndex, t);
+
+                var nodexform = instance.GetDrawableReference(0).Item2;
 
                 TestContext.WriteLine($"Animation at {t}");