Browse Source

Added SharpGLTF.Runtime library

vpenades 2 years ago
parent
commit
afffdb48f8

+ 14 - 0
SharpGLTF.sln

@@ -52,6 +52,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.ThirdParty.Tests"
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Trimmed.App", "tests\SharpGLTF.Trimmed.App\SharpGLTF.Trimmed.App.csproj", "{A09437F7-403C-44A2-B1FE-15CC535B64CA}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Trimmed.App", "tests\SharpGLTF.Trimmed.App\SharpGLTF.Trimmed.App.csproj", "{A09437F7-403C-44A2-B1FE-15CC535B64CA}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGLTF.Runtime", "src\SharpGLTF.Runtime\SharpGLTF.Runtime.csproj", "{E06015B0-8DBC-4DC9-81A2-605DAFE36821}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGLTF.Runtime.Tests", "tests\SharpGLTF.Runtime.Tests\SharpGLTF.Runtime.Tests.csproj", "{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -114,6 +118,14 @@ Global
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Release|Any CPU.Build.0 = Release|Any CPU
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E06015B0-8DBC-4DC9-81A2-605DAFE36821}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E06015B0-8DBC-4DC9-81A2-605DAFE36821}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E06015B0-8DBC-4DC9-81A2-605DAFE36821}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E06015B0-8DBC-4DC9-81A2-605DAFE36821}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -133,6 +145,8 @@ Global
 		{A63C2A2D-950F-4C76-9299-2B2D325A8653} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 		{A63C2A2D-950F-4C76-9299-2B2D325A8653} = {83E7E49D-8A28-45E8-9DBD-1F3AEDEF3E42}
 		{38B27C0B-DB22-4CB7-A6DA-4A6C2A8385A8} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
 		{38B27C0B-DB22-4CB7-A6DA-4A6C2A8385A8} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
 		{A09437F7-403C-44A2-B1FE-15CC535B64CA} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
+		{E06015B0-8DBC-4DC9-81A2-605DAFE36821} = {072B725F-773F-4751-9616-E9778897C1D2}
+		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {1D7BBAD9-834C-4981-AC96-0AA5226FC43F}
 		SolutionGuid = {1D7BBAD9-834C-4981-AC96-0AA5226FC43F}

+ 1 - 1
examples/SharpGLTF.Runtime.MonoGame/SharpGLTF.Runtime.MonoGame.csproj

@@ -12,7 +12,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\src\SharpGLTF.Core\SharpGLTF.Core.csproj" />
+    <ProjectReference Include="..\..\src\SharpGLTF.Runtime\SharpGLTF.Runtime.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 2 - 1
src/SharpGLTF.Core/SharpGLTF.Core.csproj

@@ -3,7 +3,8 @@
   <PropertyGroup>    
   <PropertyGroup>    
     <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
     <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
     <AssemblyName>SharpGLTF.Core</AssemblyName>
     <AssemblyName>SharpGLTF.Core</AssemblyName>
-    <RootNamespace>SharpGLTF</RootNamespace>    
+    <RootNamespace>SharpGLTF</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>

+ 79 - 79
src/SharpGLTF.Runtime/Runtime/README.md → src/SharpGLTF.Runtime/README.md

@@ -1,79 +1,79 @@
-# SharpGLTF.Runtime
-
-This namespace contains some utility classes to help rendering glTF models with
-client graphics engines which typically use GPU accelerated resources.
-
-The process is this; first of all we load a glTF model:
-
-```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 _logical_ 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[i] = new AwesomeMesh(model.LogicalMeshes[i]);
-}
-```
-
-Next, we create a scene template from the default glTF scene:
-```c#
-
-// a SceneTemplate is an immutable object that
-// represent the resource asset in memory
-
-var modelTemplate = SharpGLTF.Runtime.SceneTemplate(model.DefaultScene,true);
-
-// SceneInstances are lightweight objects that reference
-// the original template and can be animated separately.
-// each SceneInstance can be set to a specific animation/time,
-// and individual nodes can be edited at will, without affecting
-// the state of sibling instances.
-
-var inst1 = modelTemplate.CreateInstance();
-    inst1.SetAnimationFrame("Walking", 2.17f);
-    
-var inst2 = modelTemplate.CreateInstance();
-    inst2.SetAnimationFrame("Running", 3.523f);
-    
-var inst3 = modelTemplate.CreateInstance();
-    inst3.SetAnimationFrame("Running", 1.32f);
-    inst3.SetWorldMatrix("Head", Matrix.LookAt(...) ); // example of manually setting a single node matrix
-    
-    RenderInstance(inst1, Matrix4x4.CreateTranslation(-10,0,0));
-    RenderInstance(inst2, Matrix4x4.CreateTranslation(  0,0,0));
-    RenderInstance(inst3, Matrix4x4.CreateTranslation( 10,0,0));
-```
-
-Finally, we render the instances like this:
-```c#
-void RenderInstance(SharpGLTF.Runtime.SceneInstance modelInstance, Matrix4x4 modelMatrix)
-{
-    foreach(var drawable in modelInstance.DrawableInstances)
-    {
-        var gpuMesh = gpuMeshes[drawable.Template.LogicalMeshIndex];
-
-        if (drawable.Transform is SharpGLTF.Transforms.RigidTransform statXform)
-        {
-            AwesomeEngine.DrawRigidMesh(gpuMesh, modelMatrix, statXform.WorldMatrix);
-        }
-
-        if (drawable.Transform is SharpGLTF.Transforms.SkinnedLogicalMeshIndexTransform skinXform)
-        {
-            AwesomeEngine.DrawSkinnedMesh(gpuMesh, modelMatrix, skinXform.SkinMatrices);
-        }
-    }
-}
-
-```
-
-
-
-
-
-
-
+# SharpGLTF.Runtime
+
+This namespace contains some utility classes to help rendering glTF models with
+client graphics engines which typically use GPU accelerated resources.
+
+The process is this; first of all we load a glTF model:
+
+```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 _logical_ 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[i] = new AwesomeMesh(model.LogicalMeshes[i]);
+}
+```
+
+Next, we create a scene template from the default glTF scene:
+```c#
+
+// a SceneTemplate is an immutable object that
+// represent the resource asset in memory
+
+var modelTemplate = SharpGLTF.Runtime.SceneTemplate(model.DefaultScene,true);
+
+// SceneInstances are lightweight objects that reference
+// the original template and can be animated separately.
+// each SceneInstance can be set to a specific animation/time,
+// and individual nodes can be edited at will, without affecting
+// the state of sibling instances.
+
+var inst1 = modelTemplate.CreateInstance();
+    inst1.SetAnimationFrame("Walking", 2.17f);
+    
+var inst2 = modelTemplate.CreateInstance();
+    inst2.SetAnimationFrame("Running", 3.523f);
+    
+var inst3 = modelTemplate.CreateInstance();
+    inst3.SetAnimationFrame("Running", 1.32f);
+    inst3.SetWorldMatrix("Head", Matrix.LookAt(...) ); // example of manually setting a single node matrix
+    
+    RenderInstance(inst1, Matrix4x4.CreateTranslation(-10,0,0));
+    RenderInstance(inst2, Matrix4x4.CreateTranslation(  0,0,0));
+    RenderInstance(inst3, Matrix4x4.CreateTranslation( 10,0,0));
+```
+
+Finally, we render the instances like this:
+```c#
+void RenderInstance(SharpGLTF.Runtime.SceneInstance modelInstance, Matrix4x4 modelMatrix)
+{
+    foreach(var drawable in modelInstance.DrawableInstances)
+    {
+        var gpuMesh = gpuMeshes[drawable.Template.LogicalMeshIndex];
+
+        if (drawable.Transform is SharpGLTF.Transforms.RigidTransform statXform)
+        {
+            AwesomeEngine.DrawRigidMesh(gpuMesh, modelMatrix, statXform.WorldMatrix);
+        }
+
+        if (drawable.Transform is SharpGLTF.Transforms.SkinnedLogicalMeshIndexTransform skinXform)
+        {
+            AwesomeEngine.DrawSkinnedMesh(gpuMesh, modelMatrix, skinXform.SkinMatrices);
+        }
+    }
+}
+
+```
+
+
+
+
+
+
+

+ 19 - 0
src/SharpGLTF.Runtime/SharpGLTF.Runtime.csproj

@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <AssemblyName>SharpGLTF.Runtime</AssemblyName>
+    <RootNamespace>SharpGLTF.Runtime</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\Shared\Guard.cs" Link="Diagnostics\Guard.cs" />
+    <Compile Include="..\Shared\_Extensions.cs" Link="_Extensions.cs" />    
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SharpGLTF.Core\SharpGLTF.Core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 4 - 4
src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj

@@ -10,12 +10,12 @@
   <ItemGroup>
   <ItemGroup>
     <Compile Include="..\Shared\Guard.cs" Link="Diagnostics\Guard.cs" />
     <Compile Include="..\Shared\Guard.cs" Link="Diagnostics\Guard.cs" />
     <Compile Include="..\Shared\_Extensions.cs" Link="_Extensions.cs" />
     <Compile Include="..\Shared\_Extensions.cs" Link="_Extensions.cs" />
-    <Compile Include="..\SharpGLTF.Core\Runtime\VertexNormalsFactory.cs" Link="Geometry\VertexNormalsFactory.cs" />
-    <Compile Include="..\SharpGLTF.Core\Runtime\VertexTangentsFactory.cs" Link="Geometry\VertexTangentsFactory.cs" />
-  </ItemGroup>
+    <Compile Include="..\SharpGLTF.Runtime\Runtime\VertexNormalsFactory.cs" Link="Geometry\VertexNormalsFactory.cs" />
+    <Compile Include="..\SharpGLTF.Runtime\Runtime\VertexTangentsFactory.cs" Link="Geometry\VertexTangentsFactory.cs" />
+  </ItemGroup>  
 
 
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\SharpGLTF.Core\SharpGLTF.Core.csproj" />
+    <ProjectReference Include="..\SharpGLTF.Runtime\SharpGLTF.Runtime.csproj" />
   </ItemGroup>  
   </ItemGroup>  
   
   
 </Project>
 </Project>

+ 1 - 0
src/build-alpha.cmd

@@ -4,6 +4,7 @@ set VERSIONSUFFIX=alpha0030
 echo Building %VERSIONSUFFIX%
 echo Building %VERSIONSUFFIX%
 
 
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Core\SharpGLTF.Core.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Core\SharpGLTF.Core.csproj
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Runtime\SharpGLTF.Runtime.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 
 
 pause
 pause

+ 2 - 0
src/build-preview.cmd

@@ -8,11 +8,13 @@ set VERSIONSUFFIX=Preview-%TIMEKEY%
 echo Building 1.0.0-%VERSIONSUFFIX%
 echo Building 1.0.0-%VERSIONSUFFIX%
 
 
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Core\SharpGLTF.Core.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Core\SharpGLTF.Core.csproj
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Runtime\SharpGLTF.Runtime.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 
 
 set /p DUMMY=Hit ENTER to publish nuget packages on Github...
 set /p DUMMY=Hit ENTER to publish nuget packages on Github...
 
 
 dotnet nuget push "SharpGLTF.Core/bin/Release/SharpGLTF.Core.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 dotnet nuget push "SharpGLTF.Core/bin/Release/SharpGLTF.Core.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
+dotnet nuget push "SharpGLTF.Runtime/bin/Release/SharpGLTF.Runtime.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 dotnet nuget push "SharpGLTF.Toolkit/bin/Release/SharpGLTF.Toolkit.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 dotnet nuget push "SharpGLTF.Toolkit/bin/Release/SharpGLTF.Toolkit.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 
 
 pause
 pause

+ 10 - 0
tests/SharpGLTF.Runtime.Tests/Properties/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using NUnit.Framework;
+
+[assembly: ResourcePathFormat("*/Assets")]
+[assembly: AttachmentPathFormat("*/TestResults/?", true)]

+ 128 - 128
tests/SharpGLTF.Core.Tests/Runtime/SceneTemplateTests.cs → tests/SharpGLTF.Runtime.Tests/Runtime/SceneTemplateTests.cs

@@ -1,128 +1,128 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-
-using NUnit.Framework;
-
-using Plotly;
-
-namespace SharpGLTF.Runtime
-{
-    [Category("Core.Runtime")]
-    [AttachmentPathFormat("*/TestResults/Runtime/?", true)]
-    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.
-
-            (SceneTemplate, WeakReference<Schema2.ModelRoot>) scopedLoad()
-            {
-                var path = TestFiles.GetSampleModelsPaths()
-                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
-
-                var result = LoadModelTemplate(path);
-
-                GC.Collect();
-                GC.WaitForFullGCComplete();
-
-                return result;
-            }
-
-            var result = scopedLoad();
-
-            GC.Collect();
-            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 options = new Runtime.RuntimeOptions { IsolateMemory = true };
-
-            var template = SceneTemplate.Create(model.DefaultScene, options);
-
-            return (template, new WeakReference<Schema2.ModelRoot>(model));
-        }
-
-        [Test]
-        public static void TestMeshDecoding()
-        {
-            var modelPath = TestFiles.GetSampleModelsPaths()
-                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
-
-            var model = Schema2.ModelRoot.Load(modelPath);
-
-            model.AttachToCurrentTest("reference.plotly");
-
-
-            var scene = model.DefaultScene;
-            
-            var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
-            var sceneTemplate = SceneTemplate.Create(scene);
-            var sceneInstance = sceneTemplate.CreateInstance();
-
-            var duration = sceneInstance.Armature.AnimationTracks[0].Duration;
-            sceneInstance.Armature.SetAnimationFrame(0, duration/2);
-
-            IEnumerable<(Vector3,Vector3,Vector3, int)> evaluateTriangles(DrawableInstance inst)
-            {
-                var mesh = decodedMeshes[inst.Template.LogicalMeshIndex];
-
-                foreach(var prim in mesh.Primitives)
-                {
-                    foreach(var (idxA, idxB, idxC) in prim.TriangleIndices)
-                    {
-                        var posA = prim.GetPosition(idxA, inst.Transform);
-                        var posB = prim.GetPosition(idxB, inst.Transform);
-                        var posC = prim.GetPosition(idxC, inst.Transform);
-
-                        yield return (posA, posB, posC, 0xb0b0b0);
-                    }
-                }
-            }
-
-            var worldTriangles = sceneInstance.SelectMany(item => evaluateTriangles(item));            
-
-            var scenePlot = new PlotlyScene();
-            scenePlot.AppendTriangles(worldTriangles, c=>c);
-
-            AttachmentInfo
-                .From("result.html")
-                .WriteAllText(scenePlot.ToHtml());
-        }
-
-        [Test]
-        public static void TestMeshDecodingBounds()
-        {
-            var modelPath = TestFiles.GetSampleModelsPaths()
-                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
-
-            var model = Schema2.ModelRoot.Load(modelPath);
-
-            var (center, radius) = model.DefaultScene.EvaluateBoundingSphere(0.25f);           
-            
-            var sceneTemplate = SceneTemplate.Create(model.DefaultScene);
-            var sceneInstance = sceneTemplate.CreateInstance();
-            sceneInstance.Armature.SetAnimationFrame(0, 0.1f);
-
-            var vertices = sceneInstance.GetWorldVertices(model.LogicalMeshes.Decode()).ToList();
-
-            foreach(var p in vertices)
-            {
-                var d = (p - center).Length();
-                Assert.LessOrEqual(d, radius + 0.0001f);
-            }
-        }
-
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+using NUnit.Framework;
+
+using Plotly;
+
+namespace SharpGLTF.Runtime
+{
+    [Category("Core.Runtime")]
+    [AttachmentPathFormat("*/TestResults/Runtime/?", true)]
+    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.
+
+            (SceneTemplate, WeakReference<Schema2.ModelRoot>) scopedLoad()
+            {
+                var path = TestFiles.GetSampleModelsPaths()
+                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
+
+                var result = LoadModelTemplate(path);
+
+                GC.Collect();
+                GC.WaitForFullGCComplete();
+
+                return result;
+            }
+
+            var result = scopedLoad();
+
+            GC.Collect();
+            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 options = new Runtime.RuntimeOptions { IsolateMemory = true };
+
+            var template = SceneTemplate.Create(model.DefaultScene, options);
+
+            return (template, new WeakReference<Schema2.ModelRoot>(model));
+        }
+
+        [Test]
+        public static void TestMeshDecoding()
+        {
+            var modelPath = TestFiles.GetSampleModelsPaths()
+                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
+
+            var model = Schema2.ModelRoot.Load(modelPath);
+
+            model.AttachToCurrentTest("reference.plotly");
+
+
+            var scene = model.DefaultScene;
+            
+            var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
+            var sceneTemplate = SceneTemplate.Create(scene);
+            var sceneInstance = sceneTemplate.CreateInstance();
+
+            var duration = sceneInstance.Armature.AnimationTracks[0].Duration;
+            sceneInstance.Armature.SetAnimationFrame(0, duration/2);
+
+            IEnumerable<(Vector3,Vector3,Vector3, int)> evaluateTriangles(DrawableInstance inst)
+            {
+                var mesh = decodedMeshes[inst.Template.LogicalMeshIndex];
+
+                foreach(var prim in mesh.Primitives)
+                {
+                    foreach(var (idxA, idxB, idxC) in prim.TriangleIndices)
+                    {
+                        var posA = prim.GetPosition(idxA, inst.Transform);
+                        var posB = prim.GetPosition(idxB, inst.Transform);
+                        var posC = prim.GetPosition(idxC, inst.Transform);
+
+                        yield return (posA, posB, posC, 0xb0b0b0);
+                    }
+                }
+            }
+
+            var worldTriangles = sceneInstance.SelectMany(item => evaluateTriangles(item));            
+
+            var scenePlot = new PlotlyScene();
+            scenePlot.AppendTriangles(worldTriangles, c=>c);
+
+            AttachmentInfo
+                .From("result.html")
+                .WriteAllText(scenePlot.ToHtml());
+        }
+
+        [Test]
+        public static void TestMeshDecodingBounds()
+        {
+            var modelPath = TestFiles.GetSampleModelsPaths()
+                                .FirstOrDefault(item => item.Contains("BrainStem.glb"));
+
+            var model = Schema2.ModelRoot.Load(modelPath);
+
+            var (center, radius) = model.DefaultScene.EvaluateBoundingSphere(0.25f);           
+            
+            var sceneTemplate = SceneTemplate.Create(model.DefaultScene);
+            var sceneInstance = sceneTemplate.CreateInstance();
+            sceneInstance.Armature.SetAnimationFrame(0, 0.1f);
+
+            var vertices = sceneInstance.GetWorldVertices(model.LogicalMeshes.Decode()).ToList();
+
+            foreach(var p in vertices)
+            {
+                var d = (p - center).Length();
+                Assert.LessOrEqual(d, radius + 0.0001f);
+            }
+        }
+
+    }
+}

+ 32 - 0
tests/SharpGLTF.Runtime.Tests/SharpGLTF.Runtime.Tests.csproj

@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <IsPackable>false</IsPackable>
+    <RootNamespace>SharpGLTF</RootNamespace>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="..\Assets\**" LinkBase="Assets" CopyToOutputDirectory="PreserveNewest" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SharpGLTF.NUnit\SharpGLTF.NUnit.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+
+    <!--
+    <PackageReference Include="Mono.ApiTools.NuGetDiff" Version="1.3.1" />    
+    <PackageReference Include="Mono.ApiTools" Version="5.14.0.2" />
+    -->
+
+    <PackageReference Include="NUnit3TestAdapter" Version="4.4.2">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
+  </ItemGroup>
+
+</Project>