Przeglądaj źródła

Moved & refactored some more code.

vpenades 4 lat temu
rodzic
commit
7c2c650544
31 zmienionych plików z 2748 dodań i 2727 usunięć
  1. 8 4
      src/AndroidDemo1/AndroidDemo1.csproj
  2. 1 1
      src/Demo1/Demo1.csproj
  3. 1 1
      src/Demo2/Demo2.csproj
  4. 1 1
      src/Demo3/Demo3.csproj
  5. 1 1
      src/MonoScene.Pipeline/MonoScene.Pipeline.csproj
  6. 5 5
      src/MonoScene.Runtime.Content/BaseContent.cs
  7. 7 198
      src/MonoScene.Runtime.Content/Pipeline/MeshDecoder.cs
  8. 208 0
      src/MonoScene.Runtime.Content/Pipeline/MeshPrimitiveDecoder.cs
  9. 1 1
      src/MonoScene.Runtime.EffectsPBR/MonoScene.Runtime.EffectsPBR.csproj
  10. 45 45
      src/MonoScene.Runtime.Model3D/Meshes/GraphicsResourceTracker.cs
  11. 151 151
      src/MonoScene.Runtime.Model3D/Meshes/Mesh.cs
  12. 81 81
      src/MonoScene.Runtime.Model3D/Meshes/MeshCollection.cs
  13. 176 176
      src/MonoScene.Runtime.Model3D/Meshes/MeshGeometry.cs
  14. 88 88
      src/MonoScene.Runtime.Model3D/Meshes/MeshPart.cs
  15. 165 165
      src/MonoScene.Runtime.Model3D/ModelArchitecture.md
  16. 16 16
      src/MonoScene.Runtime.Model3D/ModelGraph/AnimationTrackInfo.cs
  17. 144 144
      src/MonoScene.Runtime.Model3D/ModelGraph/ArmatureInstance.cs
  18. 77 77
      src/MonoScene.Runtime.Model3D/ModelGraph/ArmatureTemplate.cs
  19. 26 26
      src/MonoScene.Runtime.Model3D/ModelGraph/DrawableInstance.cs
  20. 167 167
      src/MonoScene.Runtime.Model3D/ModelGraph/DrawableTemplate.cs
  21. 408 408
      src/MonoScene.Runtime.Model3D/ModelGraph/MeshTransforms.cs
  22. 117 117
      src/MonoScene.Runtime.Model3D/ModelGraph/ModelCollection.cs
  23. 306 306
      src/MonoScene.Runtime.Model3D/ModelGraph/ModelInstance.cs
  24. 77 77
      src/MonoScene.Runtime.Model3D/ModelGraph/ModelTemplate.cs
  25. 127 127
      src/MonoScene.Runtime.Model3D/ModelGraph/NodeInstance.cs
  26. 175 175
      src/MonoScene.Runtime.Model3D/ModelGraph/NodeTemplate.cs
  27. 1 1
      src/MonoScene.Runtime.Model3D/MonoScene.Runtime.Model3D.csproj
  28. 161 161
      src/MonoScene.Runtime.Model3D/Skinning.MD
  29. 2 2
      src/MonoScene.Runtime.Scene3D/MonoScene.Runtime.Scene3D.csproj
  30. 4 4
      src/MonoScene.sln
  31. 1 1
      src/MonoSceneViewer/MonoGameViewer.csproj

+ 8 - 4
src/AndroidDemo1/AndroidDemo1.csproj

@@ -75,11 +75,15 @@
     <PackageReference Include="MonoGame.Framework.Android" Version="3.8.1.1825-develop" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\MonoScene.Content3D\MonoScene.Runtime.Content.csproj">
+    <ProjectReference Include="..\MonoScene.Pipeline\MonoScene.Pipeline.csproj">
+      <Project>{bf0082a1-44be-45b6-8679-bd22e5899050}</Project>
+      <Name>MonoScene.Pipeline</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MonoScene.Runtime.Content\MonoScene.Runtime.Content.csproj">
       <Project>{41112F36-3213-4C3F-8ECC-616F1B502B6A}</Project>
       <Name>MonoScene.Runtime.Content</Name>
     </ProjectReference>
-    <ProjectReference Include="..\MonoScene.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj">
+    <ProjectReference Include="..\MonoScene.Runtime.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj">
       <Project>{2563EE5B-0B4A-4EC1-BF78-8F270CACAB59}</Project>
       <Name>MonoScene.Runtime.EffectsPBR</Name>
     </ProjectReference>
@@ -87,11 +91,11 @@
       <Project>{c8ac1020-fe76-4bf5-9767-f63554900091}</Project>
       <Name>MonoScene.Pipeline.GLTF</Name>
     </ProjectReference>
-    <ProjectReference Include="..\MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj">
+    <ProjectReference Include="..\MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj">
       <Project>{486A02BA-D0C2-4EBB-B17A-DAC9C32A8C99}</Project>
       <Name>MonoScene.Runtime.Scene3D</Name>
     </ProjectReference>
-    <ProjectReference Include="..\MonoScene.Model3D\MonoScene.Runtime.Model3D.csproj">
+    <ProjectReference Include="..\MonoScene.Runtime.Model3D\MonoScene.Runtime.Model3D.csproj">
       <Project>{7B9B380E-D9AA-44F4-ABD3-36255EEE9F4A}</Project>
       <Name>MonoScene.Runtime.Model3D</Name>
     </ProjectReference>

+ 1 - 1
src/Demo1/Demo1.csproj

@@ -26,7 +26,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MonoScene.Pipeline.GLTF\MonoScene.Pipeline.GLTF.csproj" />
-    <ProjectReference Include="..\MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
src/Demo2/Demo2.csproj

@@ -26,7 +26,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MonoScene.Pipeline.GLTF\MonoScene.Pipeline.GLTF.csproj" />
-    <ProjectReference Include="..\MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
src/Demo3/Demo3.csproj

@@ -26,7 +26,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MonoScene.Pipeline.GLTF\MonoScene.Pipeline.GLTF.csproj" />
-    <ProjectReference Include="..\MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
   </ItemGroup>
   
   <ItemGroup>

+ 1 - 1
src/MonoScene.Pipeline/MonoScene.Pipeline.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\MonoScene.Model3D\MonoScene.Runtime.Model3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Model3D\MonoScene.Runtime.Model3D.csproj" />
   </ItemGroup>
 
   <ItemGroup>    

+ 5 - 5
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/BaseTemplate.cs → src/MonoScene.Runtime.Content/BaseContent.cs

@@ -2,15 +2,15 @@
 using System.Collections.Generic;
 using System.Text;
 
-namespace MonoScene.Graphics
+namespace MonoScene.Graphics.Content
 {
-    public abstract class BaseTemplate
+    public abstract class BaseContent
     {
-        public BaseTemplate() { }
+        public BaseContent() { }
 
-        public BaseTemplate(string name) { Name = name; }
+        public BaseContent(string name) { Name = name; }
 
-        public BaseTemplate(string name, Object tag) { Name = name; Tag = tag; }
+        public BaseContent(string name, Object tag) { Name = name; Tag = tag; }
 
         public string Name { get; set; }
 

+ 7 - 198
src/MonoScene.Runtime.Content/Pipeline/MeshDecoder.cs

@@ -14,13 +14,18 @@ using XNAV4 = Microsoft.Xna.Framework.Vector4;
 namespace MonoScene.Graphics.Pipeline
 {
     /// <summary>
-    /// Interface used by importers to create a proxy mesh that wraps the native format mesh.
+    /// Interface used by importers to wrap the imported mesh.
     /// </summary>
     /// <typeparam name="TMaterial"></typeparam>
     public interface IMeshDecoder<TMaterial>
         where TMaterial : class
     {
+        /// <summary>
+        /// The mesh name.
+        /// </summary>
         string Name { get; }
+
+        //
         Object Tag { get; }
         IReadOnlyList<IMeshPrimitiveDecoder<TMaterial>> Primitives { get; }        
     }    
@@ -96,201 +101,5 @@ namespace MonoScene.Graphics.Pipeline
         VertexInfluences GetSkinWeights(int vertexIndex);
 
         #endregion
-    }
-
-    
-
-    static class MeshPrimitiveDecoder
-    {
-        public static VertexDeclaration GetVertexDeclaration(this IMeshPrimitiveDecoder src)
-        {
-            // because the PBR shaders have a limited number of techniques,
-            // we require adding these attributes even if they're not used.
-            var nColors = Math.Max(src.ColorsCount, 1);
-            var nTextrs = Math.Max(src.TexCoordsCount, 2);
-
-            var e = new List<VertexElement>();
-            int offset = 0;
-
-            e.Add(new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Position, 0)); offset += 12;
-            e.Add(new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)); offset += 12;
-            e.Add(new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.Tangent, 0)); offset += 16;            
-
-            for(int i=0; i < nColors; ++i)
-            {
-                e.Add(new VertexElement(offset, VertexElementFormat.Color, VertexElementUsage.Color, i)); offset += 4;
-            }            
-
-            for (int i = 0; i < nTextrs; ++i)
-            {
-                e.Add(new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, i)); offset += 8;
-            }
-
-            if (src.JointsWeightsCount > 0)
-            {
-                e.Add(new VertexElement(offset, VertexElementFormat.Short4, VertexElementUsage.BlendIndices, 0)); offset += 8;
-                e.Add(new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0)); offset += 16;
-            }
-
-            return new VertexDeclaration(e.ToArray());
-        }
-
-        public static Byte[] ToXnaVertices(this IMeshPrimitiveDecoder src, VertexDeclaration decl)
-        {
-            var dst = new Byte[src.VertexCount * decl.VertexStride];
-
-            var elements = decl.GetVertexElements();
-
-            for (int i = 0; i < src.VertexCount; ++i)
-            {
-                var vrt = new VertexEncoder(dst, decl.VertexStride, i);
-
-                var jw = src.GetSkinWeights(i);
-
-                foreach (var e in elements)
-                {
-                    switch (e.VertexElementUsage)
-                    {
-                        case VertexElementUsage.Position: vrt.Encode(e, src.GetPosition(i)); break;
-                        case VertexElementUsage.Normal: vrt.Encode(e, src.GetNormal(i)); break;
-                        case VertexElementUsage.Tangent: vrt.Encode(e, src.GetTangent(i)); break;
-                        case VertexElementUsage.Color: vrt.Encode(e, src.GetColor(i, e.UsageIndex)); break;
-                        case VertexElementUsage.TextureCoordinate: vrt.Encode(e, src.GetTextureCoord(i, e.UsageIndex)); break;
-                        case VertexElementUsage.BlendIndices: vrt.Encode(e, jw.Indices); break;
-                        case VertexElementUsage.BlendWeight: vrt.Encode(e, jw.Weights); break;
-                    }
-                }
-            }
-
-            return dst;
-        }        
-    }
-    
-    /// <summary>
-    /// helps encoding a vertex byte array
-    /// TODO: if the host platform is Big Endian, we need to reverse the byte order here.
-    /// </summary>
-    readonly ref struct VertexEncoder
-    {
-        #region constructors
-        public VertexEncoder(Span<Byte> array, int vertexStride, int index)
-        {
-            _Vertex = array.Slice(index * vertexStride, vertexStride);
-        }
-
-        public VertexEncoder(Span<Byte> vertex) { _Vertex = vertex; }
-
-        #endregion
-
-        #region data
-
-        private readonly Span<Byte> _Vertex;
-
-        #endregion
-
-        #region API
-
-        public void Encode(VertexElement element, Vector2 value)
-        {
-            var dstVertex = _Vertex.Slice(element.Offset);
-
-            if (element.VertexElementFormat == VertexElementFormat.Vector2)
-            {
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
-                return;
-            }
-
-            throw new ArgumentException(nameof(element));
-        }
-
-        public void Encode(VertexElement element, Vector3 value)
-        {
-            var dstVertex = _Vertex.Slice(element.Offset);
-
-            if (element.VertexElementFormat == VertexElementFormat.Vector3)
-            {
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.Color)
-            {
-                var c = new Color(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
-                return;
-            }
-
-            throw new ArgumentException(nameof(element));
-        }
-
-        public void Encode(VertexElement element, Vector4 value)
-        {
-            var dstVertex = _Vertex.Slice(element.Offset);
-
-            if (element.VertexElementFormat == VertexElementFormat.Vector4)
-            {
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.Byte4)
-            {
-                var c = new Byte4(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.Color)
-            {
-                var c = new Color(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.Short4)
-            {
-                var ns = new Short4(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.NormalizedShort4)
-            {
-                var ns = new NormalizedShort4(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.HalfVector4)
-            {
-                var ns = new HalfVector4(value);
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
-                return;
-            }
-
-            throw new ArgumentException(nameof(element));
-        }
-
-        public void Encode(VertexElement element, Short4 value)
-        {
-            var dstVertex = _Vertex.Slice(element.Offset);
-
-            if (element.VertexElementFormat == VertexElementFormat.Vector4)
-            {
-                var v4 = value.ToVector4();
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref v4);
-                return;
-            }
-
-            if (element.VertexElementFormat == VertexElementFormat.Short4)
-            {
-                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
-                return;
-            }
-
-            throw new ArgumentException(nameof(element));
-        }
-
-        #endregion
-    }
+    }    
 }

+ 208 - 0
src/MonoScene.Runtime.Content/Pipeline/MeshPrimitiveDecoder.cs

@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Graphics.PackedVector;
+
+using XNAV2 = Microsoft.Xna.Framework.Vector2;
+using XNAV3 = Microsoft.Xna.Framework.Vector3;
+using XNAV4 = Microsoft.Xna.Framework.Vector4;
+
+namespace MonoScene.Graphics.Pipeline
+{
+    static class MeshPrimitiveDecoder
+    {
+        public static VertexDeclaration GetVertexDeclaration(this IMeshPrimitiveDecoder src)
+        {
+            // because the PBR shaders have a limited number of techniques,
+            // we require adding these attributes even if they're not used.
+            var nColors = Math.Max(src.ColorsCount, 1);
+            var nTextrs = Math.Max(src.TexCoordsCount, 2);
+
+            var e = new List<VertexElement>();
+            int offset = 0;
+
+            e.Add(new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Position, 0)); offset += 12;
+            e.Add(new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)); offset += 12;
+            e.Add(new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.Tangent, 0)); offset += 16;
+
+            for (int i = 0; i < nColors; ++i)
+            {
+                e.Add(new VertexElement(offset, VertexElementFormat.Color, VertexElementUsage.Color, i)); offset += 4;
+            }
+
+            for (int i = 0; i < nTextrs; ++i)
+            {
+                e.Add(new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, i)); offset += 8;
+            }
+
+            if (src.JointsWeightsCount > 0)
+            {
+                e.Add(new VertexElement(offset, VertexElementFormat.Short4, VertexElementUsage.BlendIndices, 0)); offset += 8;
+                e.Add(new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0)); offset += 16;
+            }
+
+            return new VertexDeclaration(e.ToArray());
+        }
+
+        public static Byte[] ToXnaVertices(this IMeshPrimitiveDecoder src, VertexDeclaration decl)
+        {
+            var dst = new Byte[src.VertexCount * decl.VertexStride];
+
+            var elements = decl.GetVertexElements();
+
+            for (int i = 0; i < src.VertexCount; ++i)
+            {
+                var vrt = new VertexEncoder(dst, decl.VertexStride, i);
+
+                var jw = src.GetSkinWeights(i);
+
+                foreach (var e in elements)
+                {
+                    switch (e.VertexElementUsage)
+                    {
+                        case VertexElementUsage.Position: vrt.Encode(e, src.GetPosition(i)); break;
+                        case VertexElementUsage.Normal: vrt.Encode(e, src.GetNormal(i)); break;
+                        case VertexElementUsage.Tangent: vrt.Encode(e, src.GetTangent(i)); break;
+                        case VertexElementUsage.Color: vrt.Encode(e, src.GetColor(i, e.UsageIndex)); break;
+                        case VertexElementUsage.TextureCoordinate: vrt.Encode(e, src.GetTextureCoord(i, e.UsageIndex)); break;
+                        case VertexElementUsage.BlendIndices: vrt.Encode(e, jw.Indices); break;
+                        case VertexElementUsage.BlendWeight: vrt.Encode(e, jw.Weights); break;
+                    }
+                }
+            }
+
+            return dst;
+        }
+    }
+
+    /// <summary>
+    /// helps encoding a vertex byte array
+    /// TODO: if the host platform is Big Endian, we need to reverse the byte order here.
+    /// </summary>
+    readonly ref struct VertexEncoder
+    {
+        #region constructors
+        public VertexEncoder(Span<Byte> array, int vertexStride, int index)
+        {
+            _Vertex = array.Slice(index * vertexStride, vertexStride);
+        }
+
+        public VertexEncoder(Span<Byte> vertex) { _Vertex = vertex; }
+
+        #endregion
+
+        #region data
+
+        private readonly Span<Byte> _Vertex;
+
+        #endregion
+
+        #region API
+
+        public void Encode(VertexElement element, Vector2 value)
+        {
+            var dstVertex = _Vertex.Slice(element.Offset);
+
+            if (element.VertexElementFormat == VertexElementFormat.Vector2)
+            {
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
+                return;
+            }
+
+            throw new ArgumentException(nameof(element));
+        }
+
+        public void Encode(VertexElement element, Vector3 value)
+        {
+            var dstVertex = _Vertex.Slice(element.Offset);
+
+            if (element.VertexElementFormat == VertexElementFormat.Vector3)
+            {
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.Color)
+            {
+                var c = new Color(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
+                return;
+            }
+
+            throw new ArgumentException(nameof(element));
+        }
+
+        public void Encode(VertexElement element, Vector4 value)
+        {
+            var dstVertex = _Vertex.Slice(element.Offset);
+
+            if (element.VertexElementFormat == VertexElementFormat.Vector4)
+            {
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.Byte4)
+            {
+                var c = new Byte4(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.Color)
+            {
+                var c = new Color(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref c);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.Short4)
+            {
+                var ns = new Short4(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.NormalizedShort4)
+            {
+                var ns = new NormalizedShort4(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.HalfVector4)
+            {
+                var ns = new HalfVector4(value);
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref ns);
+                return;
+            }
+
+            throw new ArgumentException(nameof(element));
+        }
+
+        public void Encode(VertexElement element, Short4 value)
+        {
+            var dstVertex = _Vertex.Slice(element.Offset);
+
+            if (element.VertexElementFormat == VertexElementFormat.Vector4)
+            {
+                var v4 = value.ToVector4();
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref v4);
+                return;
+            }
+
+            if (element.VertexElementFormat == VertexElementFormat.Short4)
+            {
+                System.Runtime.InteropServices.MemoryMarshal.Write(dstVertex, ref value);
+                return;
+            }
+
+            throw new ArgumentException(nameof(element));
+        }
+
+        #endregion
+    }
+}

+ 1 - 1
src/MonoScene.Runtime.EffectsPBR/MonoScene.Runtime.EffectsPBR.csproj

@@ -18,7 +18,7 @@
   </ItemGroup>  
 
   <ItemGroup>
-    <ProjectReference Include="..\MonoScene.Content3D\MonoScene.Runtime.Content.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Content\MonoScene.Runtime.Content.csproj" />
   </ItemGroup>
 
   <ItemGroup>

+ 45 - 45
src/MonoScene.Runtime.Model3D/Graphics/Meshes/GraphicsResourceTracker.cs → src/MonoScene.Runtime.Model3D/Meshes/GraphicsResourceTracker.cs

@@ -1,45 +1,45 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.Xna.Framework.Graphics
-{
-    // tracks all the disposable objects of a model;
-    // vertex buffers, index buffers, effects and textures.
-    class GraphicsResourceTracker
-    {
-        #region data
-
-        private readonly HashSet<GraphicsResource> _Disposables = new HashSet<GraphicsResource>();
-
-        #endregion
-
-        #region properties
-
-        public IEnumerable<GraphicsResource> Disposables => _Disposables;
-
-        #endregion
-
-        #region API        
-        public void AddDisposable(GraphicsResource resource)
-        {
-            if (resource == null) throw new ArgumentNullException();
-
-            if (Object.ReferenceEquals(resource, BlendState.Opaque)) throw new ArgumentException("Static");
-            if (Object.ReferenceEquals(resource, BlendState.AlphaBlend)) throw new ArgumentException("Static");
-
-            if (Object.ReferenceEquals(resource, SamplerState.LinearWrap)) throw new ArgumentException("Static");
-
-
-            if (_Disposables.Contains(resource)) throw new ArgumentException("Already Added");
-            _Disposables.Add(resource);
-        }
-
-        public void AddDisposables(IEnumerable<GraphicsResource> resources)
-        {
-            foreach (var r in resources) AddDisposable(r);
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    // tracks all the disposable objects of a model;
+    // vertex buffers, index buffers, effects and textures.
+    class GraphicsResourceTracker
+    {
+        #region data
+
+        private readonly HashSet<GraphicsResource> _Disposables = new HashSet<GraphicsResource>();
+
+        #endregion
+
+        #region properties
+
+        public IEnumerable<GraphicsResource> Disposables => _Disposables;
+
+        #endregion
+
+        #region API        
+        public void AddDisposable(GraphicsResource resource)
+        {
+            if (resource == null) throw new ArgumentNullException();
+
+            if (Object.ReferenceEquals(resource, BlendState.Opaque)) throw new ArgumentException("Static");
+            if (Object.ReferenceEquals(resource, BlendState.AlphaBlend)) throw new ArgumentException("Static");
+
+            if (Object.ReferenceEquals(resource, SamplerState.LinearWrap)) throw new ArgumentException("Static");
+
+
+            if (_Disposables.Contains(resource)) throw new ArgumentException("Already Added");
+            _Disposables.Add(resource);
+        }
+
+        public void AddDisposables(IEnumerable<GraphicsResource> resources)
+        {
+            foreach (var r in resources) AddDisposable(r);
+        }
+
+        #endregion
+    }
+}

+ 151 - 151
src/MonoScene.Runtime.Model3D/Graphics/Meshes/Mesh.cs → src/MonoScene.Runtime.Model3D/Meshes/Mesh.cs

@@ -1,151 +1,151 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-using Microsoft.Xna.Framework.Graphics;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Replaces <see cref="ModelMesh"/>
-    /// </summary>
-    public sealed class Mesh : IReadOnlyList<MeshPart>
-    {
-        #region lifecycle
-
-        public Mesh(GraphicsDevice graphicsDevice)
-        {
-            this._GraphicsDevice = graphicsDevice;
-        }        
-
-        #endregion
-
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        internal GraphicsDevice _GraphicsDevice;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly List<MeshPart> _Primitives = new List<MeshPart>();
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IReadOnlyList<Effect> _Effects;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IReadOnlyList<MeshPart> _OpaquePrimitives;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IReadOnlyList<Effect> _OpaqueEffects;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IReadOnlyList<MeshPart> _TranslucidPrimitives;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IReadOnlyList<Effect> _TranslucidEffects;        
-
-        #endregion
-
-        #region  properties
-        public string Name { get; set; }
-        public object Tag { get; set; }        
-
-        public IReadOnlyCollection<Effect> OpaqueEffects
-        {
-            get
-            {
-                if (_OpaqueEffects != null) return _OpaqueEffects;
-
-                // Create the shared effects collection on demand.
-
-                _OpaqueEffects = GetOpaqueParts()
-                    .Select(item => item.Effect)
-                    .Distinct()
-                    .ToArray();
-
-                return _OpaqueEffects;
-            }
-        }
-
-        public IReadOnlyCollection<Effect> TranslucidEffects
-        {
-            get
-            {
-                if (_TranslucidEffects != null) return _TranslucidEffects;
-
-                // Create the shared effects collection on demand.
-
-                _TranslucidEffects = GetTranslucidParts()
-                    .Select(item => item.Effect)
-                    .Distinct()
-                    .ToArray();
-
-                return _TranslucidEffects;
-            }
-        }
-
-        public int Count => _Primitives.Count;
-
-        public MeshPart this[int index] => _Primitives[index];
-
-        #endregion
-
-        #region API
-
-        public MeshPart CreateMeshPart()
-        {
-            var primitive = new MeshPart(this);
-
-            _Primitives.Add(primitive);
-
-            _OpaquePrimitives = null;
-            _TranslucidPrimitives = null;
-
-            InvalidateEffectCollection();            
-
-            return primitive;
-        }
-
-        internal void InvalidateEffectCollection()
-        {
-            _OpaqueEffects = null;
-            _TranslucidEffects = null;
-        }
-
-        private IReadOnlyList<MeshPart> GetOpaqueParts()
-        {
-            if (_OpaquePrimitives != null) return _OpaquePrimitives;
-            _OpaquePrimitives = _Primitives.Where(item => item.Blending == BlendState.Opaque).ToArray();
-            return _OpaquePrimitives;
-        }
-
-        private IReadOnlyList<MeshPart> GetTranslucidParts()
-        {
-            if (_TranslucidPrimitives != null) return _TranslucidPrimitives;
-            _TranslucidPrimitives = _Primitives.Where(item => item.Blending != BlendState.Opaque).ToArray();
-            return _TranslucidPrimitives;
-        }
-
-        public void DrawOpaque()
-        {
-            _GraphicsDevice.DepthStencilState = DepthStencilState.Default;
-
-            foreach (var part in GetOpaqueParts()) part.Draw(_GraphicsDevice);
-        }
-
-        public void DrawTranslucid()
-        {
-            _GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
-
-            foreach (var part in GetTranslucidParts()) part.Draw(_GraphicsDevice);
-        }
-
-        public IEnumerator<MeshPart> GetEnumerator() { return _Primitives.GetEnumerator(); }
-
-        IEnumerator IEnumerable.GetEnumerator() { return _Primitives.GetEnumerator(); }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Replaces <see cref="ModelMesh"/>
+    /// </summary>
+    public sealed class Mesh : IReadOnlyList<MeshPart>
+    {
+        #region lifecycle
+
+        public Mesh(GraphicsDevice graphicsDevice)
+        {
+            this._GraphicsDevice = graphicsDevice;
+        }        
+
+        #endregion
+
+        #region data
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        internal GraphicsDevice _GraphicsDevice;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private readonly List<MeshPart> _Primitives = new List<MeshPart>();
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IReadOnlyList<Effect> _Effects;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IReadOnlyList<MeshPart> _OpaquePrimitives;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IReadOnlyList<Effect> _OpaqueEffects;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IReadOnlyList<MeshPart> _TranslucidPrimitives;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IReadOnlyList<Effect> _TranslucidEffects;        
+
+        #endregion
+
+        #region  properties
+        public string Name { get; set; }
+        public object Tag { get; set; }        
+
+        public IReadOnlyCollection<Effect> OpaqueEffects
+        {
+            get
+            {
+                if (_OpaqueEffects != null) return _OpaqueEffects;
+
+                // Create the shared effects collection on demand.
+
+                _OpaqueEffects = GetOpaqueParts()
+                    .Select(item => item.Effect)
+                    .Distinct()
+                    .ToArray();
+
+                return _OpaqueEffects;
+            }
+        }
+
+        public IReadOnlyCollection<Effect> TranslucidEffects
+        {
+            get
+            {
+                if (_TranslucidEffects != null) return _TranslucidEffects;
+
+                // Create the shared effects collection on demand.
+
+                _TranslucidEffects = GetTranslucidParts()
+                    .Select(item => item.Effect)
+                    .Distinct()
+                    .ToArray();
+
+                return _TranslucidEffects;
+            }
+        }
+
+        public int Count => _Primitives.Count;
+
+        public MeshPart this[int index] => _Primitives[index];
+
+        #endregion
+
+        #region API
+
+        public MeshPart CreateMeshPart()
+        {
+            var primitive = new MeshPart(this);
+
+            _Primitives.Add(primitive);
+
+            _OpaquePrimitives = null;
+            _TranslucidPrimitives = null;
+
+            InvalidateEffectCollection();            
+
+            return primitive;
+        }
+
+        internal void InvalidateEffectCollection()
+        {
+            _OpaqueEffects = null;
+            _TranslucidEffects = null;
+        }
+
+        private IReadOnlyList<MeshPart> GetOpaqueParts()
+        {
+            if (_OpaquePrimitives != null) return _OpaquePrimitives;
+            _OpaquePrimitives = _Primitives.Where(item => item.Blending == BlendState.Opaque).ToArray();
+            return _OpaquePrimitives;
+        }
+
+        private IReadOnlyList<MeshPart> GetTranslucidParts()
+        {
+            if (_TranslucidPrimitives != null) return _TranslucidPrimitives;
+            _TranslucidPrimitives = _Primitives.Where(item => item.Blending != BlendState.Opaque).ToArray();
+            return _TranslucidPrimitives;
+        }
+
+        public void DrawOpaque()
+        {
+            _GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+
+            foreach (var part in GetOpaqueParts()) part.Draw(_GraphicsDevice);
+        }
+
+        public void DrawTranslucid()
+        {
+            _GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
+
+            foreach (var part in GetTranslucidParts()) part.Draw(_GraphicsDevice);
+        }
+
+        public IEnumerator<MeshPart> GetEnumerator() { return _Primitives.GetEnumerator(); }
+
+        IEnumerator IEnumerable.GetEnumerator() { return _Primitives.GetEnumerator(); }
+
+        #endregion
+    }
+}

+ 81 - 81
src/MonoScene.Runtime.Model3D/Graphics/Meshes/MeshCollection.cs → src/MonoScene.Runtime.Model3D/Meshes/MeshCollection.cs

@@ -1,81 +1,81 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-using Microsoft.Xna.Framework.Graphics;
-
-namespace MonoScene.Graphics
-{
-    public interface IMeshCollection : IReadOnlyList<Mesh>
-    {
-        Effect[] GetSharedEffects(IEnumerable<int> meshIndices);
-    }
-
-    [System.Diagnostics.DebuggerDisplay("{Count} Meshes {SharedEffects.Count} Shared effects.")]
-    public class MeshCollection : IDisposable, IMeshCollection
-    {
-        #region lifecycle
-
-        internal MeshCollection(Mesh[] meshes, GraphicsResource[] disposables)
-        {
-            _Disposables = disposables;
-            _Meshes = meshes;
-
-            _SharedEffects = _Meshes
-                .SelectMany(item => item.OpaqueEffects.Concat(item.TranslucidEffects))
-                .Distinct()
-                .ToArray();
-        }
-
-        public void Dispose()
-        {
-            if (_Disposables != null)
-            {
-                foreach (var d in _Disposables) d.Dispose();
-            }
-
-            _Disposables = null;            
-        }        
-
-        #endregion
-
-        #region data
-
-        private GraphicsResource[] _Disposables;
-
-        private readonly Mesh[] _Meshes;
-
-        private readonly Effect[] _SharedEffects;
-
-        #endregion
-
-        #region properties
-
-        public int Count => _Meshes.Length;
-
-        public Mesh this[int index] => _Meshes[index];
-        
-        #endregion
-
-        #region API
-
-        public IEnumerator<Mesh> GetEnumerator() { return (IEnumerator<Mesh>)_Meshes.GetEnumerator(); }
-
-        IEnumerator IEnumerable.GetEnumerator() { return _Meshes.GetEnumerator(); }
-
-        public Effect[] GetSharedEffects(IEnumerable<int> meshIndices)
-        {
-            // gather all effects used by the meshes indexed by meshIndices.
-
-            return meshIndices
-                .Select(item => _Meshes[item])
-                .SelectMany(item => item.OpaqueEffects.Concat(item.TranslucidEffects))
-                .Distinct()
-                .ToArray();
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoScene.Graphics
+{
+    public interface IMeshCollection : IReadOnlyList<Mesh>
+    {
+        Effect[] GetSharedEffects(IEnumerable<int> meshIndices);
+    }
+
+    [System.Diagnostics.DebuggerDisplay("{Count} Meshes {SharedEffects.Count} Shared effects.")]
+    public class MeshCollection : IDisposable, IMeshCollection
+    {
+        #region lifecycle
+
+        internal MeshCollection(Mesh[] meshes, GraphicsResource[] disposables)
+        {
+            _Disposables = disposables;
+            _Meshes = meshes;
+
+            _SharedEffects = _Meshes
+                .SelectMany(item => item.OpaqueEffects.Concat(item.TranslucidEffects))
+                .Distinct()
+                .ToArray();
+        }
+
+        public void Dispose()
+        {
+            if (_Disposables != null)
+            {
+                foreach (var d in _Disposables) d.Dispose();
+            }
+
+            _Disposables = null;            
+        }        
+
+        #endregion
+
+        #region data
+
+        private GraphicsResource[] _Disposables;
+
+        private readonly Mesh[] _Meshes;
+
+        private readonly Effect[] _SharedEffects;
+
+        #endregion
+
+        #region properties
+
+        public int Count => _Meshes.Length;
+
+        public Mesh this[int index] => _Meshes[index];
+        
+        #endregion
+
+        #region API
+
+        public IEnumerator<Mesh> GetEnumerator() { return (IEnumerator<Mesh>)_Meshes.GetEnumerator(); }
+
+        IEnumerator IEnumerable.GetEnumerator() { return _Meshes.GetEnumerator(); }
+
+        public Effect[] GetSharedEffects(IEnumerable<int> meshIndices)
+        {
+            // gather all effects used by the meshes indexed by meshIndices.
+
+            return meshIndices
+                .Select(item => _Meshes[item])
+                .SelectMany(item => item.OpaqueEffects.Concat(item.TranslucidEffects))
+                .Distinct()
+                .ToArray();
+        }
+
+        #endregion
+    }
+}

+ 176 - 176
src/MonoScene.Runtime.Model3D/Graphics/Meshes/MeshGeometry.cs → src/MonoScene.Runtime.Model3D/Meshes/MeshGeometry.cs

@@ -1,176 +1,176 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using Microsoft.Xna.Framework.Graphics;
-
-namespace MonoScene.Graphics
-{
-    public interface IMeshGeometry
-    {
-        void Bind(GraphicsDevice device, bool isMirrorTransform);
-        void Draw(GraphicsDevice device);
-    }
-
-    public class MeshTriangles : IMeshGeometry
-    {
-        #region lifecycle
-
-        public static MeshTriangles CreateFrom(Content.MeshGeometryContent content, IReadOnlyList<VertexBuffer> vb, IReadOnlyList<IndexBuffer> ib)
-        {
-            var tris = new MeshTriangles();
-
-            tris.SetVertexBuffer(vb[content.VertexBufferIndex], content.VertexOffset, content.VertexCount);
-            tris.SetIndexBuffer(ib[content.IndexBufferIndex], content.IndexOffset, content.PrimitiveCount);
-            tris.SetCullingStates(false);
-
-            return tris;
-        }
-
-        public void SetVertexBuffer(VertexBuffer vb, int offset, int count)
-        {
-            this._SharedVertexBuffer = vb;
-            this._VertexOffset = offset;
-            this._VertexCount = count;
-        }
-
-        public void SetIndexBuffer(IndexBuffer ib, int offset, int count)
-        {
-            this._SharedIndexBuffer = ib;
-            this._IndexOffset = offset;
-            this._PrimitiveCount = count;
-        }
-
-        public void SetCullingStates(bool doubleSided)
-        {
-            FrontRasterizer = doubleSided ? RasterizerState.CullNone : RasterizerState.CullCounterClockwise;
-            BackRasterizer = doubleSided ? RasterizerState.CullNone : RasterizerState.CullClockwise;
-        }
-
-        #endregion
-
-        #region data
-
-        // state used for normal rendering
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private RasterizerState _FrontRasterizer = RasterizerState.CullCounterClockwise;
-
-        // state used for mirrored tranform. This must be the same as _FrontRasterizer with reversed CullMode.
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private RasterizerState _BackRasterizer = RasterizerState.CullClockwise;
-        
-        private IndexBuffer _SharedIndexBuffer;        
-        private int _IndexOffset;        
-        private int _PrimitiveCount;
-        
-        private VertexBuffer _SharedVertexBuffer;        
-        private int _VertexOffset;        
-        private int _VertexCount;
-
-        #endregion
-
-        #region properties        
-
-        public RasterizerState FrontRasterizer
-        {
-            get => _FrontRasterizer;
-            set => _FrontRasterizer = value;
-        }
-
-        public RasterizerState BackRasterizer
-        {
-            get => _BackRasterizer;
-            set => _BackRasterizer = value;
-        }
-        
-        #endregion
-
-        #region API
-
-        public void Bind(GraphicsDevice device, bool isMirrorTransform)
-        {
-            device.SetVertexBuffer(_SharedVertexBuffer);
-            device.Indices = _SharedIndexBuffer;
-
-            device.RasterizerState = isMirrorTransform ? _BackRasterizer : _FrontRasterizer;
-        }
-
-        public void Draw(GraphicsDevice device)
-        {
-            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, _VertexOffset, _IndexOffset, _PrimitiveCount);            
-        }
-
-        public TVertex[] TryGetVertices<TVertex>()
-            where TVertex:struct,IVertexType
-        {
-            var data = new TVertex[_VertexCount];
-            _SharedVertexBuffer.GetData<TVertex>(data, _VertexOffset, _VertexCount);
-            return data;
-        }
-        
-        #endregion
-    }
-
-    public class MeshLines : IMeshGeometry
-    {
-        #region lifecycle       
-
-        public void SetVertexBuffer(VertexBuffer vb, int offset, int count)
-        {
-            this._SharedVertexBuffer = vb;
-            this._VertexOffset = offset;
-            this._VertexCount = count;
-        }
-
-        public void SetIndexBuffer(IndexBuffer ib, int offset, int count)
-        {
-            this._SharedIndexBuffer = ib;
-            this._IndexOffset = offset;
-            this._PrimitiveCount = count;
-        }
-
-        #endregion
-
-        #region data
-
-        // state used for line rendering
-        private RasterizerState _LineRasterizer = RasterizerState.CullNone;
-
-        private IndexBuffer _SharedIndexBuffer;
-        private int _IndexOffset;
-        private int _PrimitiveCount;
-
-        private VertexBuffer _SharedVertexBuffer;
-        private int _VertexOffset;
-        private int _VertexCount;
-
-        #endregion
-
-        #region properties        
-
-        public RasterizerState LineRasterizer
-        {
-            get => _LineRasterizer;
-            set => _LineRasterizer = value;
-        }
-        
-        #endregion
-
-        #region API
-
-        public void Bind(GraphicsDevice device, bool isMirrorTransform)
-        {
-            device.SetVertexBuffer(_SharedVertexBuffer);
-            device.Indices = _SharedIndexBuffer;
-
-            device.RasterizerState = _LineRasterizer;
-        }
-
-        public void Draw(GraphicsDevice device)
-        {
-            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, _VertexOffset, _IndexOffset, _PrimitiveCount);
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoScene.Graphics
+{
+    public interface IMeshGeometry
+    {
+        void Bind(GraphicsDevice device, bool isMirrorTransform);
+        void Draw(GraphicsDevice device);
+    }
+
+    public class MeshTriangles : IMeshGeometry
+    {
+        #region lifecycle
+
+        public static MeshTriangles CreateFrom(Content.MeshGeometryContent content, IReadOnlyList<VertexBuffer> vb, IReadOnlyList<IndexBuffer> ib)
+        {
+            var tris = new MeshTriangles();
+
+            tris.SetVertexBuffer(vb[content.VertexBufferIndex], content.VertexOffset, content.VertexCount);
+            tris.SetIndexBuffer(ib[content.IndexBufferIndex], content.IndexOffset, content.PrimitiveCount);
+            tris.SetCullingStates(false);
+
+            return tris;
+        }
+
+        public void SetVertexBuffer(VertexBuffer vb, int offset, int count)
+        {
+            this._SharedVertexBuffer = vb;
+            this._VertexOffset = offset;
+            this._VertexCount = count;
+        }
+
+        public void SetIndexBuffer(IndexBuffer ib, int offset, int count)
+        {
+            this._SharedIndexBuffer = ib;
+            this._IndexOffset = offset;
+            this._PrimitiveCount = count;
+        }
+
+        public void SetCullingStates(bool doubleSided)
+        {
+            FrontRasterizer = doubleSided ? RasterizerState.CullNone : RasterizerState.CullCounterClockwise;
+            BackRasterizer = doubleSided ? RasterizerState.CullNone : RasterizerState.CullClockwise;
+        }
+
+        #endregion
+
+        #region data
+
+        // state used for normal rendering
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private RasterizerState _FrontRasterizer = RasterizerState.CullCounterClockwise;
+
+        // state used for mirrored tranform. This must be the same as _FrontRasterizer with reversed CullMode.
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private RasterizerState _BackRasterizer = RasterizerState.CullClockwise;
+        
+        private IndexBuffer _SharedIndexBuffer;        
+        private int _IndexOffset;        
+        private int _PrimitiveCount;
+        
+        private VertexBuffer _SharedVertexBuffer;        
+        private int _VertexOffset;        
+        private int _VertexCount;
+
+        #endregion
+
+        #region properties        
+
+        public RasterizerState FrontRasterizer
+        {
+            get => _FrontRasterizer;
+            set => _FrontRasterizer = value;
+        }
+
+        public RasterizerState BackRasterizer
+        {
+            get => _BackRasterizer;
+            set => _BackRasterizer = value;
+        }
+        
+        #endregion
+
+        #region API
+
+        public void Bind(GraphicsDevice device, bool isMirrorTransform)
+        {
+            device.SetVertexBuffer(_SharedVertexBuffer);
+            device.Indices = _SharedIndexBuffer;
+
+            device.RasterizerState = isMirrorTransform ? _BackRasterizer : _FrontRasterizer;
+        }
+
+        public void Draw(GraphicsDevice device)
+        {
+            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, _VertexOffset, _IndexOffset, _PrimitiveCount);            
+        }
+
+        public TVertex[] TryGetVertices<TVertex>()
+            where TVertex:struct,IVertexType
+        {
+            var data = new TVertex[_VertexCount];
+            _SharedVertexBuffer.GetData<TVertex>(data, _VertexOffset, _VertexCount);
+            return data;
+        }
+        
+        #endregion
+    }
+
+    public class MeshLines : IMeshGeometry
+    {
+        #region lifecycle       
+
+        public void SetVertexBuffer(VertexBuffer vb, int offset, int count)
+        {
+            this._SharedVertexBuffer = vb;
+            this._VertexOffset = offset;
+            this._VertexCount = count;
+        }
+
+        public void SetIndexBuffer(IndexBuffer ib, int offset, int count)
+        {
+            this._SharedIndexBuffer = ib;
+            this._IndexOffset = offset;
+            this._PrimitiveCount = count;
+        }
+
+        #endregion
+
+        #region data
+
+        // state used for line rendering
+        private RasterizerState _LineRasterizer = RasterizerState.CullNone;
+
+        private IndexBuffer _SharedIndexBuffer;
+        private int _IndexOffset;
+        private int _PrimitiveCount;
+
+        private VertexBuffer _SharedVertexBuffer;
+        private int _VertexOffset;
+        private int _VertexCount;
+
+        #endregion
+
+        #region properties        
+
+        public RasterizerState LineRasterizer
+        {
+            get => _LineRasterizer;
+            set => _LineRasterizer = value;
+        }
+        
+        #endregion
+
+        #region API
+
+        public void Bind(GraphicsDevice device, bool isMirrorTransform)
+        {
+            device.SetVertexBuffer(_SharedVertexBuffer);
+            device.Indices = _SharedIndexBuffer;
+
+            device.RasterizerState = _LineRasterizer;
+        }
+
+        public void Draw(GraphicsDevice device)
+        {
+            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, _VertexOffset, _IndexOffset, _PrimitiveCount);
+        }
+
+        #endregion
+    }
+}

+ 88 - 88
src/MonoScene.Runtime.Model3D/Graphics/Meshes/MeshPart.cs → src/MonoScene.Runtime.Model3D/Meshes/MeshPart.cs

@@ -1,88 +1,88 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-using Microsoft.Xna.Framework.Graphics;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Replaces <see cref="ModelMeshPart"/>.
-    /// </summary>    
-    public sealed class MeshPart
-    {
-        #region lifecycle
-
-        internal MeshPart(Mesh parent)
-        {
-            _Parent = parent;
-        }        
-
-        #endregion
-
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly Mesh _Parent;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private Effect _Effect;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private BlendState _Blend = BlendState.Opaque;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IMeshGeometry _Geometry;
-
-        #endregion
-
-        #region properties        
-
-        public Effect Effect
-        {
-            get => _Effect;
-            set
-            {
-                if (_Effect == value) return;
-                _Effect = value;
-                _Parent.InvalidateEffectCollection(); // if we change this property, we need to invalidate the parent's effect collection.
-            }
-        }
-
-        public BlendState Blending
-        {
-            get => _Blend;
-            set => _Blend = value;
-        }
-        
-        public IMeshGeometry Geometry
-        {
-            get => _Geometry;
-            set => _Geometry = value;
-        }
-
-        #endregion
-
-        #region API
-
-        public void Draw(GraphicsDevice device)
-        {
-            // check if world matrix is a mirror matrix and requires the face culling to be reversed.
-            bool isMirrorTransform = false;
-            if (_Effect is IEffectMatrices ematrices) isMirrorTransform = ematrices.World.Determinant() < 0;
-
-            _Geometry.Bind(device, isMirrorTransform);            
-
-            device.BlendState = _Blend;            
-
-            foreach(var pass in _Effect.CurrentTechnique.Passes)
-            {
-                pass.Apply();
-
-                _Geometry.Draw(device);
-            }            
-        }
-
-        #endregion
-    }    
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Replaces <see cref="ModelMeshPart"/>.
+    /// </summary>    
+    public sealed class MeshPart
+    {
+        #region lifecycle
+
+        internal MeshPart(Mesh parent)
+        {
+            _Parent = parent;
+        }        
+
+        #endregion
+
+        #region data
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private readonly Mesh _Parent;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private Effect _Effect;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private BlendState _Blend = BlendState.Opaque;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IMeshGeometry _Geometry;
+
+        #endregion
+
+        #region properties        
+
+        public Effect Effect
+        {
+            get => _Effect;
+            set
+            {
+                if (_Effect == value) return;
+                _Effect = value;
+                _Parent.InvalidateEffectCollection(); // if we change this property, we need to invalidate the parent's effect collection.
+            }
+        }
+
+        public BlendState Blending
+        {
+            get => _Blend;
+            set => _Blend = value;
+        }
+        
+        public IMeshGeometry Geometry
+        {
+            get => _Geometry;
+            set => _Geometry = value;
+        }
+
+        #endregion
+
+        #region API
+
+        public void Draw(GraphicsDevice device)
+        {
+            // check if world matrix is a mirror matrix and requires the face culling to be reversed.
+            bool isMirrorTransform = false;
+            if (_Effect is IEffectMatrices ematrices) isMirrorTransform = ematrices.World.Determinant() < 0;
+
+            _Geometry.Bind(device, isMirrorTransform);            
+
+            device.BlendState = _Blend;            
+
+            foreach(var pass in _Effect.CurrentTechnique.Passes)
+            {
+                pass.Apply();
+
+                _Geometry.Draw(device);
+            }            
+        }
+
+        #endregion
+    }    
+}

+ 165 - 165
src/MonoScene.Runtime.Model3D/Graphics/ModelArchitecture.md → src/MonoScene.Runtime.Model3D/ModelArchitecture.md

@@ -1,165 +1,165 @@
-### ModelTemplate Overview
-
-When comparing MonoGame with the new architecture, the object that would mirror [MonoGame´s Model class](https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Graphics/Model.cs),
-is the [ModelTemplate class](ModelGraph/ModelTemplate.cs), it represents essentially the same thing;
-a _stateless resource_ representing a 3D object.
-
-But, unlike MonoGame's Model, [ModelTemplate class](ModelGraph/ModelTemplate.cs) is,
-by default, contained within a [ModelCollection class](ModelGraph/ModelCollection.cs), so when
-you load a glTF, you're not loading a single ModelTemplate, but a ModelCollection. In most
-cases, loading a glTF will produce a ModelCollection with a single ModelTemplate inside, so,
-why making it so complicated?
-
-The answer is _resource management_: glTF has a very smart architecture that allows reusing
-textures, materials, meshes and animations across multiple models, up to the point that
-we can have multiple models stored in a single file, all of them sharing the same index and
-vertex buffer.
-
-That's why, the ModelCollection is more than a simple list of ModelTemplate objects, it also
-holds the resources shared by the models.
-
-```c#
-    ModelCollection
-      ┣━ Shared Meshes (n)
-      ┣━ Shared Armatures (n)
-      ┗━ Models Templates (n)
-```
-
-With this design, we can have many models reusing expensive resources like meshes and armatures.
-
-BTW, Armatures are the skeletons for animated characters, they're expensive resources because they
-also hold the animation curves of each node.
-
-So an example of resource reusing, we can have something like this:
-
-```c#
-    ModelCollection
-      ┣━ Shared Meshes
-      ┃   ┣━ Mesh[0]
-      ┃   ┣━ Mesh[1]
-      ┃   ┣━ Mesh[2]
-      ┃   ┗━ Mesh[3]
-      ┣━ Shared Armatures
-      ┃   ┗━ Armature[0]
-      ┗━ Models Templates
-          ┣━ Model[0], using Meshes[0,2] and Armature[0]
-          ┣━ Model[1], using Meshes[1,2] and Armature[0]
-          ┗━ Model[2], using Meshes[1,3] and Armature[0]
-```
-
-Since glTF (and probably other newer 3D formats) supports this resource management
-architecture, I think it's very convenient to support it too at runtime level.
-
-It is also worth to notice that currently, the classes are designed so all the
-disposable resources are concentrated in the shared MeshCollection, so it's the
-only part of the whole ModelCollection that needs to be disposed. Armatures and
-ModelTemplates are made of regular managed classes, so they don't need to
-implement IDisposable. In fact, it is possible to switch the Meshes of a
-ModelCollection at runtime.
-
-
-### ModelInstance overview
-
-The first thing you'll notice when looking at the new classes is that
-for some classes, there's two class variants
-
-|Template class| Instance class|
-|-|-|
-|[ModelTemplate](ModelGraph/ModelTemplate.cs)|[ModelInstance](ModelGraph/ModelInstance.cs)|
-|[DrawableTemplate](ModelGraph/DrawableTemplate.cs)|[DrawableInstance](ModelGraph/DrawableInstance.cs)|
-|[ArmatureTemplate](ModelGraph/ArmatureTemplate.cs)|[ArmatureInstance](ModelGraph/ArmatureInstance.cs)|
-|[NodeTemplate](ModelGraph/NodeTemplate.cs)|[NodeInstance](ModelGraph/NodeInstance.cs)|
-
-
-So what's this about?
-
-Well, if you compare with the original MonoGame's Model, it was essentially a content object, _a resource_
-so in order to draw it on screen multiple times, you need to keep the world transforms of the instances
-of that model somewhere, typically in custom structures. This is fine because in general, MG's Model has
-been used most of the time as a rigid object, and it only needs a matrix to be rendered somewhere. It is
-true that MG's Model supports skinning, but it was rarely used because it depended on the developer to
-keep the skeleton transform state in custom structures that need to match the skeleton of the model. Because
-MG's Model didn't hold the actual animations nor the structures to animate them, MG's Model was essentially
-crippled to support animations out of the box.
-
-To support animations _out of the box_ you need some state object that keeps the animation state of every
-instance of the object on screen. You might have a model resource representing a _soldier_ and you want to
-render it on screen multiple times, but each model drawing will be in a different state; one drawing will
-represent the soldier walking, while another will represent it running.
-
-In essence, the Template classes are _stateless resources_, and the Instance classes are the _state_ of a specific
-representation of the source TemplateModel on screen.
-
-Some might say that this is too much, that in order to render an animated character on screen you only need
-the world transform, the animation track name, and the animation time, which can easily be passed to an
-extended Draw call. This might be true for basic scenarios, but for more complex use cases it does not hold.
-
-by having a specific set of objects that hold the state of a rigged character I can set a pose from a track
-and time, but also blend multiple tracks, and after that, programatically modify the local transform of a
-specific bone, or apply IK to some bone chains. This is the sort of stuff real games need to do, for which
-you need a _state_ object.
-
-In practice you load a TemplateModel as a resource, from which you create an instance every time you need
-to draw it on screen (no need to create it per frame, you keep it as that instance is active)
-
-The instance classes are designed to be as lightweight as possible, they don't have disposable objects, so
-they're easy to create. But even is that is a problem to the GC, pooling strategies can be explored.
-
-Now, a ModelInstance is
-
-### Armatures and Animations
-
-[Armatures](ModelGraph/ArmatureTemplate.cs) (or Skeletons), define a collection of Nodes with a hierarchical relationship.
-
-Within an Armature you can find a table of nodes, the table has been flattened and sorted so parent nodes
-must always appear before their children. This makes armature evaluation easier since there's no need
-to traverse a complex hierarchy.
-
-A Specific node can contain either one of these two:
-- A Fixed Transform Matrix
-- A set of properties representing:
-  - S: A Vector3 Scale
-  - R: A Quaternion Rotation
-  - T: A Vector3 Translation
-
-When using the SRT properties, each property can have a list of curves, one for every animation track.
-
-Notice that Armatures are defined by two classes:
-
-- [ArmatureTemplate](ModelGraph/ArmatureTemplate.cs)
-  - Stateless
-  - Represents the initial state of the model
-  - Define the hierarchical relationship between parent and child nodes.
-  - Contains the animation curves for all the animation tracks (which in same cases, can be a LOT of data)
-  - 
-- [ArmatureInstance](ModelGraph/ArmatureInstance.cs)
-  - Statefull and lightwight.
-  - Reference a ArmatureTemplate from where it gets the [ICurveEvaluator interface](../ICurveEvaluator.cs) evaluators.
-  - Contains the evaluated local and world transform matrices for each node.  
-  - Expose APIs to set the node matrices individually, or as a whole, defining an animation track and time.
-  - The collection of world transforms represent the current, evaluated "pose" of the skeleton at a given time.
-  - This object is the one from where the mesh skinning takes the world transform matrices.
-
-
-### Drawing Commands
-
-Each ModelTemplate has a list of [DrawableTemplate](ModelGraph/DrawableTemplate.cs) objects.
-
-A [DrawableTemplate](ModelGraph/DrawableTemplate.cs) can be seen as drawing command, and it basically
-tells to render a specific Mesh from a MeshCollection, in a specific location in the scene, defined
-by one or more nodes:
-
-```c
-MeshCollection
-                ⬊
-                  DrawableTemplate ⮕ DrawableInstance: Draw Mesh[3] at Node[4].WorldTransform
-                ⬈
-ArmatureInstance
-```
-
-
-
-
-
-
-
+### ModelTemplate Overview
+
+When comparing MonoGame with the new architecture, the object that would mirror [MonoGame´s Model class](https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Graphics/Model.cs),
+is the [ModelTemplate class](ModelGraph/ModelTemplate.cs), it represents essentially the same thing;
+a _stateless resource_ representing a 3D object.
+
+But, unlike MonoGame's Model, [ModelTemplate class](ModelGraph/ModelTemplate.cs) is,
+by default, contained within a [ModelCollection class](ModelGraph/ModelCollection.cs), so when
+you load a glTF, you're not loading a single ModelTemplate, but a ModelCollection. In most
+cases, loading a glTF will produce a ModelCollection with a single ModelTemplate inside, so,
+why making it so complicated?
+
+The answer is _resource management_: glTF has a very smart architecture that allows reusing
+textures, materials, meshes and animations across multiple models, up to the point that
+we can have multiple models stored in a single file, all of them sharing the same index and
+vertex buffer.
+
+That's why, the ModelCollection is more than a simple list of ModelTemplate objects, it also
+holds the resources shared by the models.
+
+```c#
+    ModelCollection
+      ┣━ Shared Meshes (n)
+      ┣━ Shared Armatures (n)
+      ┗━ Models Templates (n)
+```
+
+With this design, we can have many models reusing expensive resources like meshes and armatures.
+
+BTW, Armatures are the skeletons for animated characters, they're expensive resources because they
+also hold the animation curves of each node.
+
+So an example of resource reusing, we can have something like this:
+
+```c#
+    ModelCollection
+      ┣━ Shared Meshes
+      ┃   ┣━ Mesh[0]
+      ┃   ┣━ Mesh[1]
+      ┃   ┣━ Mesh[2]
+      ┃   ┗━ Mesh[3]
+      ┣━ Shared Armatures
+      ┃   ┗━ Armature[0]
+      ┗━ Models Templates
+          ┣━ Model[0], using Meshes[0,2] and Armature[0]
+          ┣━ Model[1], using Meshes[1,2] and Armature[0]
+          ┗━ Model[2], using Meshes[1,3] and Armature[0]
+```
+
+Since glTF (and probably other newer 3D formats) supports this resource management
+architecture, I think it's very convenient to support it too at runtime level.
+
+It is also worth to notice that currently, the classes are designed so all the
+disposable resources are concentrated in the shared MeshCollection, so it's the
+only part of the whole ModelCollection that needs to be disposed. Armatures and
+ModelTemplates are made of regular managed classes, so they don't need to
+implement IDisposable. In fact, it is possible to switch the Meshes of a
+ModelCollection at runtime.
+
+
+### ModelInstance overview
+
+The first thing you'll notice when looking at the new classes is that
+for some classes, there's two class variants
+
+|Template class| Instance class|
+|-|-|
+|[ModelTemplate](ModelGraph/ModelTemplate.cs)|[ModelInstance](ModelGraph/ModelInstance.cs)|
+|[DrawableTemplate](ModelGraph/DrawableTemplate.cs)|[DrawableInstance](ModelGraph/DrawableInstance.cs)|
+|[ArmatureTemplate](ModelGraph/ArmatureTemplate.cs)|[ArmatureInstance](ModelGraph/ArmatureInstance.cs)|
+|[NodeTemplate](ModelGraph/NodeTemplate.cs)|[NodeInstance](ModelGraph/NodeInstance.cs)|
+
+
+So what's this about?
+
+Well, if you compare with the original MonoGame's Model, it was essentially a content object, _a resource_
+so in order to draw it on screen multiple times, you need to keep the world transforms of the instances
+of that model somewhere, typically in custom structures. This is fine because in general, MG's Model has
+been used most of the time as a rigid object, and it only needs a matrix to be rendered somewhere. It is
+true that MG's Model supports skinning, but it was rarely used because it depended on the developer to
+keep the skeleton transform state in custom structures that need to match the skeleton of the model. Because
+MG's Model didn't hold the actual animations nor the structures to animate them, MG's Model was essentially
+crippled to support animations out of the box.
+
+To support animations _out of the box_ you need some state object that keeps the animation state of every
+instance of the object on screen. You might have a model resource representing a _soldier_ and you want to
+render it on screen multiple times, but each model drawing will be in a different state; one drawing will
+represent the soldier walking, while another will represent it running.
+
+In essence, the Template classes are _stateless resources_, and the Instance classes are the _state_ of a specific
+representation of the source TemplateModel on screen.
+
+Some might say that this is too much, that in order to render an animated character on screen you only need
+the world transform, the animation track name, and the animation time, which can easily be passed to an
+extended Draw call. This might be true for basic scenarios, but for more complex use cases it does not hold.
+
+by having a specific set of objects that hold the state of a rigged character I can set a pose from a track
+and time, but also blend multiple tracks, and after that, programatically modify the local transform of a
+specific bone, or apply IK to some bone chains. This is the sort of stuff real games need to do, for which
+you need a _state_ object.
+
+In practice you load a TemplateModel as a resource, from which you create an instance every time you need
+to draw it on screen (no need to create it per frame, you keep it as that instance is active)
+
+The instance classes are designed to be as lightweight as possible, they don't have disposable objects, so
+they're easy to create. But even is that is a problem to the GC, pooling strategies can be explored.
+
+Now, a ModelInstance is
+
+### Armatures and Animations
+
+[Armatures](ModelGraph/ArmatureTemplate.cs) (or Skeletons), define a collection of Nodes with a hierarchical relationship.
+
+Within an Armature you can find a table of nodes, the table has been flattened and sorted so parent nodes
+must always appear before their children. This makes armature evaluation easier since there's no need
+to traverse a complex hierarchy.
+
+A Specific node can contain either one of these two:
+- A Fixed Transform Matrix
+- A set of properties representing:
+  - S: A Vector3 Scale
+  - R: A Quaternion Rotation
+  - T: A Vector3 Translation
+
+When using the SRT properties, each property can have a list of curves, one for every animation track.
+
+Notice that Armatures are defined by two classes:
+
+- [ArmatureTemplate](ModelGraph/ArmatureTemplate.cs)
+  - Stateless
+  - Represents the initial state of the model
+  - Define the hierarchical relationship between parent and child nodes.
+  - Contains the animation curves for all the animation tracks (which in same cases, can be a LOT of data)
+  - 
+- [ArmatureInstance](ModelGraph/ArmatureInstance.cs)
+  - Statefull and lightwight.
+  - Reference a ArmatureTemplate from where it gets the [ICurveEvaluator interface](../ICurveEvaluator.cs) evaluators.
+  - Contains the evaluated local and world transform matrices for each node.  
+  - Expose APIs to set the node matrices individually, or as a whole, defining an animation track and time.
+  - The collection of world transforms represent the current, evaluated "pose" of the skeleton at a given time.
+  - This object is the one from where the mesh skinning takes the world transform matrices.
+
+
+### Drawing Commands
+
+Each ModelTemplate has a list of [DrawableTemplate](ModelGraph/DrawableTemplate.cs) objects.
+
+A [DrawableTemplate](ModelGraph/DrawableTemplate.cs) can be seen as drawing command, and it basically
+tells to render a specific Mesh from a MeshCollection, in a specific location in the scene, defined
+by one or more nodes:
+
+```c
+MeshCollection
+                ⬊
+                  DrawableTemplate ⮕ DrawableInstance: Draw Mesh[3] at Node[4].WorldTransform
+                ⬈
+ArmatureInstance
+```
+
+
+
+
+
+
+

+ 16 - 16
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/AnimationTrackInfo.cs → src/MonoScene.Runtime.Model3D/ModelGraph/AnimationTrackInfo.cs

@@ -1,16 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace MonoScene.Graphics
-{
-    public class AnimationTrackInfo : BaseTemplate
-    {
-        public AnimationTrackInfo(string name, object tag, float duration)
-            : base(name, tag)
-        {            
-            Duration = duration;
-        }
-        public float Duration { get; private set; }
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MonoScene.Graphics
+{
+    public class AnimationTrackInfo : Content.BaseContent
+    {
+        public AnimationTrackInfo(string name, object tag, float duration)
+            : base(name, tag)
+        {            
+            Duration = duration;
+        }
+        public float Duration { get; private set; }
+    }
+}

+ 144 - 144
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/ArmatureInstance.cs → src/MonoScene.Runtime.Model3D/ModelGraph/ArmatureInstance.cs

@@ -1,144 +1,144 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-using XFORM = Microsoft.Xna.Framework.Matrix;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Represents a specific and independent state of a <see cref="ArmatureTemplate"/>.
-    /// </summary>
-    /// <remarks>
-    /// An <see cref="ArmatureTemplate"/> represents the layout, graph and initial state of a skeleton, and it's a READ ONLY OBJECT.
-    /// Moving forward, an <see cref="ArmatureInstance"/> represents a live instance of that skeleton within a 3D scene, so each
-    /// joint can be rotated independently from other instances, without affecting each other.
-    /// </remarks>
-    public class ArmatureInstance
-    {
-        #region lifecycle
-
-        internal ArmatureInstance(ArmatureTemplate armature)
-        {
-            _Template = armature;
-            _NodeInstances = new NodeInstance[armature.Count];
-
-            // no need to check arguments since they're supposedly pre-checked by ArmatureTemplate's constructor.
-
-            for (var i = 0; i < _NodeInstances.Length; ++i)
-            {
-                var n = armature[i];
-                var pidx = n.ParentIndex;
-                var p = pidx < 0 ? null : _NodeInstances[pidx];
-                _NodeInstances[i] = new NodeInstance(n, p);
-            }            
-        }
-
-        #endregion
-
-        #region data
-
-        private ArmatureTemplate _Template;
-        private NodeInstance[] _NodeInstances;              
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets a list of all the <see cref="NodeInstance"/> nodes used by this <see cref="ModelInstance"/>.
-        /// </summary>
-        public IReadOnlyList<NodeInstance> LogicalNodes => _NodeInstances;
-
-        /// <summary>
-        /// Gets all the <see cref="NodeInstance"/> roots used by this <see cref="ModelInstance"/>.
-        /// </summary>
-        public IEnumerable<NodeInstance> VisualNodes => _NodeInstances.Where(item => item.VisualParent == null);
-
-        /// <summary>
-        /// Gets the total number of animation tracks for this instance.
-        /// </summary>
-        public int AnimationTracksCount => _Template.Animations.Count;        
-
-        #endregion
-
-        #region API
-
-        public int IndexOfNode(string nodeName)
-        {
-            for (int i = 0; i < _NodeInstances.Length; ++i)
-            {
-                if (_NodeInstances[i].Name == nodeName) return i;
-            }
-
-            return -1;
-        }
-
-        public void SetLocalMatrix(string name, XFORM localMatrix)
-        {
-            var n = LogicalNodes.FirstOrDefault(item => item.Name == name);
-            if (n == null) return;
-            n.LocalMatrix = localMatrix;
-        }
-
-        public void SetModelMatrix(string name, XFORM modelMatrix)
-        {
-            var n = LogicalNodes.FirstOrDefault(item => item.Name == name);
-            if (n == null) return;
-            n.ModelMatrix = modelMatrix;
-        }
-
-        public void SetPoseTransforms()
-        {
-            foreach (var n in _NodeInstances) n.SetPoseTransform();
-        }
-
-        public string NameOfTrack(int trackIndex) { return _Template.Animations[trackIndex].Name; }
-
-        public int IndexOfTrack(string name) { return _Template.IndexOfTrack(name); }
-
-        public float GetAnimationDuration(int trackIndex) { return _Template.GetTrackDuration(trackIndex); }
-
-        public void SetAnimationFrame(int trackIndex, float time, bool looped = true)
-        {
-            if (looped)
-            {
-                var duration = GetAnimationDuration(trackIndex);
-                if (duration > 0) time %= duration;
-            }
-
-            foreach (var n in _NodeInstances) n.SetAnimationFrame(trackIndex, time);
-        }
-
-        public void SetAnimationFrame(params (int trackIndex, float Time, float Weight)[] blended)
-        {
-            SetAnimationFrame(_NodeInstances, blended);
-        }
-
-        public static void SetAnimationFrame(IEnumerable<NodeInstance> nodes, params (int TrackIdx, float Time, float Weight)[] blended)
-        {
-            if (nodes == null) throw new ArgumentNullException(nameof(nodes));
-
-            Span<int> tracks = stackalloc int[blended.Length];
-            Span<float> times = stackalloc float[blended.Length];
-            Span<float> weights = stackalloc float[blended.Length];
-
-            float w = blended.Sum(item => item.Weight);
-
-            w = w == 0 ? 1 : 1 / w;
-
-            for (int i = 0; i < blended.Length; ++i)
-            {
-                tracks[i] = blended[i].TrackIdx;
-                times[i] = blended[i].Time;
-                weights[i] = blended[i].Weight * w;
-            }
-
-            foreach (var n in nodes) n.SetAnimationFrame(tracks, times, weights);
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using XFORM = Microsoft.Xna.Framework.Matrix;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Represents a specific and independent state of a <see cref="ArmatureTemplate"/>.
+    /// </summary>
+    /// <remarks>
+    /// An <see cref="ArmatureTemplate"/> represents the layout, graph and initial state of a skeleton, and it's a READ ONLY OBJECT.
+    /// Moving forward, an <see cref="ArmatureInstance"/> represents a live instance of that skeleton within a 3D scene, so each
+    /// joint can be rotated independently from other instances, without affecting each other.
+    /// </remarks>
+    public class ArmatureInstance
+    {
+        #region lifecycle
+
+        internal ArmatureInstance(ArmatureTemplate armature)
+        {
+            _Template = armature;
+            _NodeInstances = new NodeInstance[armature.Count];
+
+            // no need to check arguments since they're supposedly pre-checked by ArmatureTemplate's constructor.
+
+            for (var i = 0; i < _NodeInstances.Length; ++i)
+            {
+                var n = armature[i];
+                var pidx = n.ParentIndex;
+                var p = pidx < 0 ? null : _NodeInstances[pidx];
+                _NodeInstances[i] = new NodeInstance(n, p);
+            }            
+        }
+
+        #endregion
+
+        #region data
+
+        private ArmatureTemplate _Template;
+        private NodeInstance[] _NodeInstances;              
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets a list of all the <see cref="NodeInstance"/> nodes used by this <see cref="ModelInstance"/>.
+        /// </summary>
+        public IReadOnlyList<NodeInstance> LogicalNodes => _NodeInstances;
+
+        /// <summary>
+        /// Gets all the <see cref="NodeInstance"/> roots used by this <see cref="ModelInstance"/>.
+        /// </summary>
+        public IEnumerable<NodeInstance> VisualNodes => _NodeInstances.Where(item => item.VisualParent == null);
+
+        /// <summary>
+        /// Gets the total number of animation tracks for this instance.
+        /// </summary>
+        public int AnimationTracksCount => _Template.Animations.Count;        
+
+        #endregion
+
+        #region API
+
+        public int IndexOfNode(string nodeName)
+        {
+            for (int i = 0; i < _NodeInstances.Length; ++i)
+            {
+                if (_NodeInstances[i].Name == nodeName) return i;
+            }
+
+            return -1;
+        }
+
+        public void SetLocalMatrix(string name, XFORM localMatrix)
+        {
+            var n = LogicalNodes.FirstOrDefault(item => item.Name == name);
+            if (n == null) return;
+            n.LocalMatrix = localMatrix;
+        }
+
+        public void SetModelMatrix(string name, XFORM modelMatrix)
+        {
+            var n = LogicalNodes.FirstOrDefault(item => item.Name == name);
+            if (n == null) return;
+            n.ModelMatrix = modelMatrix;
+        }
+
+        public void SetPoseTransforms()
+        {
+            foreach (var n in _NodeInstances) n.SetPoseTransform();
+        }
+
+        public string NameOfTrack(int trackIndex) { return _Template.Animations[trackIndex].Name; }
+
+        public int IndexOfTrack(string name) { return _Template.IndexOfTrack(name); }
+
+        public float GetAnimationDuration(int trackIndex) { return _Template.GetTrackDuration(trackIndex); }
+
+        public void SetAnimationFrame(int trackIndex, float time, bool looped = true)
+        {
+            if (looped)
+            {
+                var duration = GetAnimationDuration(trackIndex);
+                if (duration > 0) time %= duration;
+            }
+
+            foreach (var n in _NodeInstances) n.SetAnimationFrame(trackIndex, time);
+        }
+
+        public void SetAnimationFrame(params (int trackIndex, float Time, float Weight)[] blended)
+        {
+            SetAnimationFrame(_NodeInstances, blended);
+        }
+
+        public static void SetAnimationFrame(IEnumerable<NodeInstance> nodes, params (int TrackIdx, float Time, float Weight)[] blended)
+        {
+            if (nodes == null) throw new ArgumentNullException(nameof(nodes));
+
+            Span<int> tracks = stackalloc int[blended.Length];
+            Span<float> times = stackalloc float[blended.Length];
+            Span<float> weights = stackalloc float[blended.Length];
+
+            float w = blended.Sum(item => item.Weight);
+
+            w = w == 0 ? 1 : 1 / w;
+
+            for (int i = 0; i < blended.Length; ++i)
+            {
+                tracks[i] = blended[i].TrackIdx;
+                times[i] = blended[i].Time;
+                weights[i] = blended[i].Weight * w;
+            }
+
+            foreach (var n in nodes) n.SetAnimationFrame(tracks, times, weights);
+        }
+
+        #endregion
+    }
+}

+ 77 - 77
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/ArmatureTemplate.cs → src/MonoScene.Runtime.Model3D/ModelGraph/ArmatureTemplate.cs

@@ -1,77 +1,77 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace MonoScene.Graphics
-{
-    public class ArmatureTemplate
-    {
-        #region lifecycle
-
-        /// <summary>
-        /// Creates an armature from an array of <see cref="NodeTemplate"/>
-        /// </summary>
-        /// <param name="nodes">a flattened array of <see cref="NodeTemplate"/> objects.</param>
-        /// <param name="atracks">animation tracks info</param>
-        internal ArmatureTemplate(NodeTemplate[] nodes, AnimationTrackInfo[] atracks)
-        {
-            if (nodes == null) throw new ArgumentNullException(nameof(nodes));
-
-            // check that child nodes always follow parent nodes
-
-            for(int i=0; i < nodes.Length; ++i)
-            {
-                var n = nodes[i];
-
-                if (n == null) throw new ArgumentNullException(nameof(nodes));                
-                if (n.ParentIndex >= i) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ParentIndex must be lower than {i}, but found {n.ParentIndex}");
-
-                for(int j=0; j < n.ChildIndices.Count; ++j)
-                {
-                    var cidx = n.ChildIndices[j];
-                    if (cidx >= nodes.Length) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ChildIndices[{j}] must be lower than {nodes.Length}, but found {cidx}");
-                    if (cidx <= i) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ChildIndices[{j}] must be heigher than {i}, but found {cidx}");
-                }                
-            }
-
-            _NodeTemplates = nodes;
-            _AnimationTracks = atracks ?? (new AnimationTrackInfo[0]);
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly NodeTemplate[] _NodeTemplates;
-        private readonly AnimationTrackInfo[] _AnimationTracks;
-
-        #endregion
-
-        #region properties
-
-        public int Count => _NodeTemplates.Length;
-
-        public NodeTemplate this[int index] => _NodeTemplates[index];
-
-        public IReadOnlyList<AnimationTrackInfo> Animations => _AnimationTracks;
-
-        #endregion
-
-        #region API
-
-        public int IndexOfTrack(string name)
-        {
-            return Array.FindIndex(_AnimationTracks, item => item.Name == name);
-        }
-
-        public float GetTrackDuration(int trackLogicalIndex)
-        {            
-            if (trackLogicalIndex < 0) return 0;
-            if (trackLogicalIndex >= _AnimationTracks.Length) return 0;
-            return _AnimationTracks[trackLogicalIndex].Duration;
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MonoScene.Graphics
+{
+    public class ArmatureTemplate
+    {
+        #region lifecycle
+
+        /// <summary>
+        /// Creates an armature from an array of <see cref="NodeTemplate"/>
+        /// </summary>
+        /// <param name="nodes">a flattened array of <see cref="NodeTemplate"/> objects.</param>
+        /// <param name="atracks">animation tracks info</param>
+        internal ArmatureTemplate(NodeTemplate[] nodes, AnimationTrackInfo[] atracks)
+        {
+            if (nodes == null) throw new ArgumentNullException(nameof(nodes));
+
+            // check that child nodes always follow parent nodes
+
+            for(int i=0; i < nodes.Length; ++i)
+            {
+                var n = nodes[i];
+
+                if (n == null) throw new ArgumentNullException(nameof(nodes));                
+                if (n.ParentIndex >= i) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ParentIndex must be lower than {i}, but found {n.ParentIndex}");
+
+                for(int j=0; j < n.ChildIndices.Count; ++j)
+                {
+                    var cidx = n.ChildIndices[j];
+                    if (cidx >= nodes.Length) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ChildIndices[{j}] must be lower than {nodes.Length}, but found {cidx}");
+                    if (cidx <= i) throw new ArgumentOutOfRangeException(nameof(nodes), $"[{i}].ChildIndices[{j}] must be heigher than {i}, but found {cidx}");
+                }                
+            }
+
+            _NodeTemplates = nodes;
+            _AnimationTracks = atracks ?? (new AnimationTrackInfo[0]);
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly NodeTemplate[] _NodeTemplates;
+        private readonly AnimationTrackInfo[] _AnimationTracks;
+
+        #endregion
+
+        #region properties
+
+        public int Count => _NodeTemplates.Length;
+
+        public NodeTemplate this[int index] => _NodeTemplates[index];
+
+        public IReadOnlyList<AnimationTrackInfo> Animations => _AnimationTracks;
+
+        #endregion
+
+        #region API
+
+        public int IndexOfTrack(string name)
+        {
+            return Array.FindIndex(_AnimationTracks, item => item.Name == name);
+        }
+
+        public float GetTrackDuration(int trackLogicalIndex)
+        {            
+            if (trackLogicalIndex < 0) return 0;
+            if (trackLogicalIndex >= _AnimationTracks.Length) return 0;
+            return _AnimationTracks[trackLogicalIndex].Duration;
+        }
+
+        #endregion
+    }
+}

+ 26 - 26
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/DrawableInstance.cs → src/MonoScene.Runtime.Model3D/ModelGraph/DrawableInstance.cs

@@ -1,26 +1,26 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace MonoScene.Graphics
-{
-    [System.Diagnostics.DebuggerDisplay("{Template.Name} {MeshIndex}")]
-    public readonly struct DrawableInstance
-    {
-        internal DrawableInstance(IDrawableTemplate t, IMeshTransform xform)
-        {
-            Template = t;
-            Transform = xform;
-        }
-
-        /// <summary>
-        /// Defines "what to draw"
-        /// </summary>
-        public readonly IDrawableTemplate Template;
-
-        /// <summary>
-        /// Defines "where to draw"
-        /// </summary>
-        public readonly IMeshTransform Transform;
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MonoScene.Graphics
+{
+    [System.Diagnostics.DebuggerDisplay("{Template.Name} {MeshIndex}")]
+    public readonly struct DrawableInstance
+    {
+        internal DrawableInstance(IDrawableTemplate t, IMeshTransform xform)
+        {
+            Template = t;
+            Transform = xform;
+        }
+
+        /// <summary>
+        /// Defines "what to draw"
+        /// </summary>
+        public readonly IDrawableTemplate Template;
+
+        /// <summary>
+        /// Defines "where to draw"
+        /// </summary>
+        public readonly IMeshTransform Transform;
+    }
+}

+ 167 - 167
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/DrawableTemplate.cs → src/MonoScene.Runtime.Model3D/ModelGraph/DrawableTemplate.cs

@@ -1,167 +1,167 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-using XNAMAT = Microsoft.Xna.Framework.Matrix;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Defines a drawable object within a model.
-    /// </summary>
-    /// <remarks>
-    /// A <see cref="ModelTemplate"/> has a collection of <see cref="IDrawableTemplate"/> that can be
-    /// seen as a sequence of drawing commands. So to some degree, it binds a visual resource (a mesh)
-    /// with a scene location (the node transform).
-    /// </remarks>
-    public interface IDrawableTemplate
-    {
-        /// <summary>
-        /// Typically this is the name of the content node that contained the mesh.
-        /// </summary>
-        string Name { get; }
-
-        Object Tag { get; }
-
-        /// <summary>
-        /// An index into <see cref="ModelTemplate.Meshes"/>
-        /// </summary>
-        int MeshIndex { get; }
-
-        IMeshTransform CreateGeometryTransform();
-
-        void UpdateGeometryTransform(IMeshTransform rigidTransform, ArmatureInstance armature);
-    }
-       
-
-    /// <summary>
-    /// Defines a reference to a drawable mesh
-    /// </summary>
-    /// <remarks>
-    /// This class is the 'glue' that binds a mesh with a <see cref="NodeTemplate"/> so we
-    /// can calculate the local transform matrix of the mesh we want to render.
-    /// </remarks>    
-    abstract class DrawableTemplate : BaseTemplate, IDrawableTemplate
-    {
-        #region lifecycle
-
-        protected DrawableTemplate(string name, int logicalMeshIndex)
-            : base(name)
-        {            
-            _LogicalMeshIndex = logicalMeshIndex;            
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly int _LogicalMeshIndex;
-
-        #endregion
-
-        #region properties
-        
-        /// <summary>
-        /// An index into a <see cref="MeshCollection"/>
-        /// </summary>
-        public int MeshIndex => _LogicalMeshIndex;
-
-        #endregion
-
-        #region API
-
-        public abstract IMeshTransform CreateGeometryTransform();
-
-        public abstract void UpdateGeometryTransform(IMeshTransform geoxform, ArmatureInstance armature);
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Defines a reference to a drawable rigid mesh
-    /// </summary>
-    sealed class RigidDrawableTemplate : DrawableTemplate
-    {
-        #region lifecycle
-
-        public RigidDrawableTemplate(int meshIndex, NodeTemplate node)
-            : base(node.Name, meshIndex)
-        {
-            _NodeIndex = node.ThisIndex;
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly int _NodeIndex;
-
-        #endregion
-
-        #region API
-
-        public override IMeshTransform CreateGeometryTransform() { return new MeshRigidTransform(); }
-
-        public override void UpdateGeometryTransform(IMeshTransform rigidTransform, ArmatureInstance armature)
-        {
-            var node = armature.LogicalNodes[_NodeIndex];
-
-            var statxform = (MeshRigidTransform)rigidTransform;
-            statxform.Update(node.ModelMatrix);
-            // statxform.Update(node.MorphWeights, false);
-        }
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Defines a reference to a drawable skinned mesh
-    /// </summary>
-    sealed class SkinnedDrawableTemplate : DrawableTemplate
-    {
-        #region lifecycle
-
-        public SkinnedDrawableTemplate(int meshIndex, NodeTemplate morphNode, string ownerNname, (NodeTemplate, XNAMAT)[] skinNodes)
-            : base(ownerNname, meshIndex)
-        {
-            // _MorphNodeIndex = indexFunc(morphNode);
-
-            _JointsNodeIndices = new int[skinNodes.Length];
-            _BindMatrices = new XNAMAT[skinNodes.Length];
-
-            for (int i = 0; i < _JointsNodeIndices.Length; ++i)
-            {
-                var (j, ibm) = skinNodes[i];
-
-                _JointsNodeIndices[i] = j.ThisIndex;
-                _BindMatrices[i] = ibm;
-            }
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly int _MorphNodeIndex;
-        private readonly int[] _JointsNodeIndices;
-        private readonly XNAMAT[] _BindMatrices;
-
-        #endregion
-
-        #region API
-
-        public override IMeshTransform CreateGeometryTransform() { return new MeshSkinTransform(); }
-
-        public override void UpdateGeometryTransform(IMeshTransform skinnedTransform, ArmatureInstance armature)
-        {
-            var skinxform = (MeshSkinTransform)skinnedTransform;
-
-            skinxform.Update(_JointsNodeIndices.Length, idx => _BindMatrices[idx], idx => armature.LogicalNodes[_JointsNodeIndices[idx]].ModelMatrix);
-
-            // skinxform.Update(instances[_MorphNodeIndex].MorphWeights, false);
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using XNAMAT = Microsoft.Xna.Framework.Matrix;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Defines a drawable object within a model.
+    /// </summary>
+    /// <remarks>
+    /// A <see cref="ModelTemplate"/> has a collection of <see cref="IDrawableTemplate"/> that can be
+    /// seen as a sequence of drawing commands. So to some degree, it binds a visual resource (a mesh)
+    /// with a scene location (the node transform).
+    /// </remarks>
+    public interface IDrawableTemplate
+    {
+        /// <summary>
+        /// Typically this is the name of the content node that contained the mesh.
+        /// </summary>
+        string Name { get; }
+
+        Object Tag { get; }
+
+        /// <summary>
+        /// An index into <see cref="ModelTemplate.Meshes"/>
+        /// </summary>
+        int MeshIndex { get; }
+
+        IMeshTransform CreateGeometryTransform();
+
+        void UpdateGeometryTransform(IMeshTransform rigidTransform, ArmatureInstance armature);
+    }
+       
+
+    /// <summary>
+    /// Defines a reference to a drawable mesh
+    /// </summary>
+    /// <remarks>
+    /// This class is the 'glue' that binds a mesh with a <see cref="NodeTemplate"/> so we
+    /// can calculate the local transform matrix of the mesh we want to render.
+    /// </remarks>    
+    abstract class DrawableTemplate : Content.BaseContent, IDrawableTemplate
+    {
+        #region lifecycle
+
+        protected DrawableTemplate(string name, int logicalMeshIndex)
+            : base(name)
+        {            
+            _LogicalMeshIndex = logicalMeshIndex;            
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _LogicalMeshIndex;
+
+        #endregion
+
+        #region properties
+        
+        /// <summary>
+        /// An index into a <see cref="MeshCollection"/>
+        /// </summary>
+        public int MeshIndex => _LogicalMeshIndex;
+
+        #endregion
+
+        #region API
+
+        public abstract IMeshTransform CreateGeometryTransform();
+
+        public abstract void UpdateGeometryTransform(IMeshTransform geoxform, ArmatureInstance armature);
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a reference to a drawable rigid mesh
+    /// </summary>
+    sealed class RigidDrawableTemplate : DrawableTemplate
+    {
+        #region lifecycle
+
+        public RigidDrawableTemplate(int meshIndex, NodeTemplate node)
+            : base(node.Name, meshIndex)
+        {
+            _NodeIndex = node.ThisIndex;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _NodeIndex;
+
+        #endregion
+
+        #region API
+
+        public override IMeshTransform CreateGeometryTransform() { return new MeshRigidTransform(); }
+
+        public override void UpdateGeometryTransform(IMeshTransform rigidTransform, ArmatureInstance armature)
+        {
+            var node = armature.LogicalNodes[_NodeIndex];
+
+            var statxform = (MeshRigidTransform)rigidTransform;
+            statxform.Update(node.ModelMatrix);
+            // statxform.Update(node.MorphWeights, false);
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a reference to a drawable skinned mesh
+    /// </summary>
+    sealed class SkinnedDrawableTemplate : DrawableTemplate
+    {
+        #region lifecycle
+
+        public SkinnedDrawableTemplate(int meshIndex, NodeTemplate morphNode, string ownerNname, (NodeTemplate, XNAMAT)[] skinNodes)
+            : base(ownerNname, meshIndex)
+        {
+            // _MorphNodeIndex = indexFunc(morphNode);
+
+            _JointsNodeIndices = new int[skinNodes.Length];
+            _BindMatrices = new XNAMAT[skinNodes.Length];
+
+            for (int i = 0; i < _JointsNodeIndices.Length; ++i)
+            {
+                var (j, ibm) = skinNodes[i];
+
+                _JointsNodeIndices[i] = j.ThisIndex;
+                _BindMatrices[i] = ibm;
+            }
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly int _MorphNodeIndex;
+        private readonly int[] _JointsNodeIndices;
+        private readonly XNAMAT[] _BindMatrices;
+
+        #endregion
+
+        #region API
+
+        public override IMeshTransform CreateGeometryTransform() { return new MeshSkinTransform(); }
+
+        public override void UpdateGeometryTransform(IMeshTransform skinnedTransform, ArmatureInstance armature)
+        {
+            var skinxform = (MeshSkinTransform)skinnedTransform;
+
+            skinxform.Update(_JointsNodeIndices.Length, idx => _BindMatrices[idx], idx => armature.LogicalNodes[_JointsNodeIndices[idx]].ModelMatrix);
+
+            // skinxform.Update(instances[_MorphNodeIndex].MorphWeights, false);
+        }
+
+        #endregion
+    }
+}

+ 408 - 408
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/MeshTransforms.cs → src/MonoScene.Runtime.Model3D/ModelGraph/MeshTransforms.cs

@@ -1,408 +1,408 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using V3 = Microsoft.Xna.Framework.Vector3;
-using V4 = Microsoft.Xna.Framework.Vector4;
-using TRANSFORM = Microsoft.Xna.Framework.Matrix;
-using VERTEXINFLUENCES = Microsoft.Xna.Framework.Graphics.PackedVector.VertexInfluences;
-using MORPHINFLUENCES = Microsoft.Xna.Framework.Graphics.PackedVector.VertexInfluences;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Interface for a mesh transform object
-    /// </summary>
-    public interface IMeshTransform
-    {
-        /// <summary>
-        /// Gets a value indicating whether the current <see cref="IMeshTransform"/> will render visible geometry.
-        /// </summary>
-        /// <remarks>
-        /// When this value is false, a runtime should skip rendering any geometry using
-        /// this <see cref="IMeshTransform"/> instance, since it will not be visible anyway.
-        /// </remarks>
-        bool Visible { get; }
-
-        /// <summary>
-        /// Gets a value indicating whether the triangles need to be flipped to render correctly.
-        /// </summary>
-        /// <remarks>
-        /// When this value is true, a runtime rendering triangles should inverse the face culling.
-        /// </remarks>
-        bool FlipFaces { get; }
-
-        
-        /// <summary>
-        /// Transforms a vertex position from local mesh space to world space.
-        /// </summary>
-        /// <param name="position">The local position of the vertex.</param>
-        /// <param name="positionDeltas">The local position deltas of the vertex, one for each morph target, or null.</param>
-        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
-        /// <returns>A position in world space.</returns>
-        V3 TransformPosition(V3 position, IReadOnlyList<V3> positionDeltas, in VERTEXINFLUENCES skinWeights);
-
-        /// <summary>
-        /// Transforms a vertex normal from local mesh space to world space.
-        /// </summary>
-        /// <param name="normal">The local normal of the vertex.</param>
-        /// <param name="normalDeltas">The local normal deltas of the vertex, one for each morph target, or null.</param>
-        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
-        /// <returns>A normal in world space.</returns>
-        V3 TransformNormal(V3 normal, IReadOnlyList<V3> normalDeltas, in VERTEXINFLUENCES skinWeights);
-
-        /// <summary>
-        /// Transforms a vertex tangent from local mesh space to world space.
-        /// </summary>
-        /// <param name="tangent">The tangent normal of the vertex.</param>
-        /// <param name="tangentDeltas">The local tangent deltas of the vertex, one for each morph target, or null.</param>
-        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
-        /// <returns>A tangent in world space.</returns>
-        V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in VERTEXINFLUENCES skinWeights);
-
-        V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
-        
-    }
-
-    abstract class MeshMorphTransform
-    {
-        #region constructor
-
-        protected MeshMorphTransform()
-        {
-            Update(default, false);
-        }
-
-        protected MeshMorphTransform(MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
-        {
-            Update(morphWeights, useAbsoluteMorphTargets);
-        }
-
-        #endregion
-
-        #region data
-
-        /// <summary>
-        /// Represents a sparse collection of weights where:
-        /// - Index of value <see cref="COMPLEMENT_INDEX"/> points to the Mesh master positions.
-        /// - All other indices point to Mesh MorphTarget[index] positions.
-        /// </summary>
-        private MORPHINFLUENCES _Weights;
-
-        public const int COMPLEMENT_INDEX = 65536;
-
-        /// <summary>
-        /// True if morph targets represent absolute values.
-        /// False if morph targets represent values relative to master value.
-        /// </summary>
-        private bool _AbsoluteMorphTargets;
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets the current morph weights to use for morph target blending. <see cref="COMPLEMENT_INDEX"/> represents the index for the base geometry.
-        /// </summary>
-        public MORPHINFLUENCES MorphWeights => _Weights;
-
-        /// <summary>
-        /// Gets a value indicating whether morph target values are absolute, and not relative to the master value.
-        /// </summary>
-        public bool AbsoluteMorphTargets => _AbsoluteMorphTargets;
-
-        #endregion
-
-        #region API
-
-        public void Update(MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets = false)
-        {
-            /*
-            _AbsoluteMorphTargets = useAbsoluteMorphTargets;
-
-            if (morphWeights.IsWeightless)
-            {
-                _Weights = MORPHINFLUENCES.Create((COMPLEMENT_INDEX, 1));
-                return;
-            }
-
-            _Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
-            */
-        }
-
-        protected V3 MorphVectors(V3 value, IReadOnlyList<V3> morphTargets)
-        {
-            return value;
-
-            /*
-            if (morphTargets == null || morphTargets.Count == 0) return value;
-            
-            if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
-
-            var p = V3.Zero;
-
-            if (_AbsoluteMorphTargets)
-            {
-                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
-                {
-                    var val = index == COMPLEMENT_INDEX ? value : morphTargets[index];
-                    p += val * weight;
-                }
-            }
-            else
-            {
-                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
-                {
-                    var val = index == COMPLEMENT_INDEX ? value : value + morphTargets[index];
-                    p += val * weight;
-                }
-            }
-
-            return p;
-            */
-        }
-
-        protected V4 MorphVectors(V4 value, IReadOnlyList<V4> morphTargets)
-        {
-            return value;
-
-            /*
-            if (morphTargets == null || morphTargets.Count == 0) return value;
-
-            if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
-
-            var p = V4.Zero;
-
-            if (_AbsoluteMorphTargets)
-            {
-                foreach (var pair in _Weights.GetNonZeroWeights())
-                {
-                    var val = pair.Index == COMPLEMENT_INDEX ? value : morphTargets[pair.Index];
-                    p += val * pair.Weight;
-                }
-            }
-            else
-            {
-                foreach (var pair in _Weights.GetNonZeroWeights())
-                {
-                    var val = pair.Index == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Index];
-                    p += val * pair.Weight;
-                }
-            }
-
-            return p;
-            */
-        }
-
-        public V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets)
-        {
-            return MorphVectors(color, morphTargets);
-        }
-
-        #endregion
-    }
-
-    class MeshRigidTransform : MeshMorphTransform, IMeshTransform
-    {
-        #region constructor
-
-        public MeshRigidTransform()
-        {
-            Update(TRANSFORM.Identity);
-        }
-
-        public MeshRigidTransform(TRANSFORM worldMatrix)
-        {
-            Update(default, false);
-            Update(worldMatrix);
-        }
-
-        public MeshRigidTransform(TRANSFORM worldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphs)
-        {
-            Update(morphWeights, useAbsoluteMorphs);
-            Update(worldMatrix);
-        }
-
-        #endregion
-
-        #region data
-
-        private TRANSFORM _WorldMatrix;
-        private Boolean _Visible;
-        private Boolean _FlipFaces;
-
-        #endregion
-
-        #region properties
-
-        public Boolean Visible => _Visible;
-
-        public Boolean FlipFaces => _FlipFaces;
-
-        public TRANSFORM WorldMatrix => _WorldMatrix;
-
-        #endregion
-
-        #region API
-
-        public void Update(TRANSFORM worldMatrix)
-        {
-            _WorldMatrix = worldMatrix;
-
-            // http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
-
-            float determinant3x3 =
-                +(worldMatrix.M13 * worldMatrix.M21 * worldMatrix.M32)
-                + (worldMatrix.M11 * worldMatrix.M22 * worldMatrix.M33)
-                + (worldMatrix.M12 * worldMatrix.M23 * worldMatrix.M31)
-                - (worldMatrix.M12 * worldMatrix.M21 * worldMatrix.M33)
-                - (worldMatrix.M13 * worldMatrix.M22 * worldMatrix.M31)
-                - (worldMatrix.M11 * worldMatrix.M23 * worldMatrix.M32);
-
-            _Visible = Math.Abs(determinant3x3) > float.Epsilon;
-            _FlipFaces = determinant3x3 < 0;
-        }
-
-        
-        public V3 TransformPosition(V3 position, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            position = MorphVectors(position, morphTargets);
-
-            return V3.Transform(position, _WorldMatrix);
-        }
-
-        public V3 TransformNormal(V3 normal, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            normal = MorphVectors(normal, morphTargets);
-
-            return V3.Normalize(V3.TransformNormal(normal, _WorldMatrix));
-        }
-
-        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            var t = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
-
-            t = V3.Normalize(V3.TransformNormal(t, _WorldMatrix));
-
-            return new V4(t, tangent.W);
-        }
-
-        #endregion
-    }
-
-    class MeshSkinTransform : MeshMorphTransform, IMeshTransform
-    {
-        #region constructor
-
-        public MeshSkinTransform() { }
-
-        public MeshSkinTransform(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
-        {
-            Update(morphWeights, useAbsoluteMorphTargets);
-            Update(invBindMatrix, currWorldMatrix);
-        }
-
-        public MeshSkinTransform(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
-        {
-            Update(morphWeights, useAbsoluteMorphTargets);
-            Update(count, invBindMatrix, currWorldMatrix);
-        }
-
-        #endregion
-
-        #region data
-
-        private TRANSFORM[] _SkinTransforms;
-
-        #endregion
-
-        #region properties
-
-        public bool Visible => true;
-        public bool FlipFaces => false;
-
-        /// <summary>
-        /// Gets the collection of the current, final matrices to use for skinning
-        /// </summary>
-        public IReadOnlyList<TRANSFORM> SkinMatrices => _SkinTransforms;        
-
-        #endregion
-
-        #region API
-
-        public void Update(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix)
-        {
-            // Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
-            // Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
-            // Guard.IsTrue(invBindMatrix.Length == currWorldMatrix.Length, nameof(currWorldMatrix), $"{invBindMatrix} and {currWorldMatrix} length mismatch.");
-
-            if (_SkinTransforms == null || _SkinTransforms.Length != invBindMatrix.Length) _SkinTransforms = new TRANSFORM[invBindMatrix.Length];
-
-            for (int i = 0; i < _SkinTransforms.Length; ++i)
-            {
-                _SkinTransforms[i] = invBindMatrix[i] * currWorldMatrix[i];
-            }
-        }
-
-        public void Update(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix)
-        {
-            // Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
-            // Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
-
-            if (_SkinTransforms == null || _SkinTransforms.Length != count) _SkinTransforms = new TRANSFORM[count];
-
-            for (int i = 0; i < _SkinTransforms.Length; ++i)
-            {
-                _SkinTransforms[i] = invBindMatrix(i) * currWorldMatrix(i);
-            }
-        }        
-        
-        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            localPosition = MorphVectors(localPosition, morphTargets);
-
-            var worldPosition = V3.Zero;
-
-            var wnrm = 1.0f / skinWeights.WeightSum;
-
-            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
-            {
-                worldPosition += V3.Transform(localPosition, _SkinTransforms[jidx]) * jweight * wnrm;
-            }
-
-            return worldPosition;
-        }
-
-        public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            localNormal = MorphVectors(localNormal, morphTargets);
-
-            var worldNormal = V3.Zero;
-
-            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
-            {
-                worldNormal += V3.TransformNormal(localNormal, _SkinTransforms[jidx]) * jweight;
-            }
-
-            return V3.Normalize(localNormal);
-        }
-
-        public V4 TransformTangent(V4 localTangent, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
-        {
-            var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
-
-            var worldTangent = V3.Zero;
-
-            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
-            {
-                worldTangent += V3.TransformNormal(localTangentV, _SkinTransforms[jidx]) * jweight;
-            }
-
-            worldTangent = V3.Normalize(worldTangent);
-
-            return new V4(worldTangent, localTangent.W);
-        }
-
-        #endregion        
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using V3 = Microsoft.Xna.Framework.Vector3;
+using V4 = Microsoft.Xna.Framework.Vector4;
+using TRANSFORM = Microsoft.Xna.Framework.Matrix;
+using VERTEXINFLUENCES = Microsoft.Xna.Framework.Graphics.PackedVector.VertexInfluences;
+using MORPHINFLUENCES = Microsoft.Xna.Framework.Graphics.PackedVector.VertexInfluences;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Interface for a mesh transform object
+    /// </summary>
+    public interface IMeshTransform
+    {
+        /// <summary>
+        /// Gets a value indicating whether the current <see cref="IMeshTransform"/> will render visible geometry.
+        /// </summary>
+        /// <remarks>
+        /// When this value is false, a runtime should skip rendering any geometry using
+        /// this <see cref="IMeshTransform"/> instance, since it will not be visible anyway.
+        /// </remarks>
+        bool Visible { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether the triangles need to be flipped to render correctly.
+        /// </summary>
+        /// <remarks>
+        /// When this value is true, a runtime rendering triangles should inverse the face culling.
+        /// </remarks>
+        bool FlipFaces { get; }
+
+        
+        /// <summary>
+        /// Transforms a vertex position from local mesh space to world space.
+        /// </summary>
+        /// <param name="position">The local position of the vertex.</param>
+        /// <param name="positionDeltas">The local position deltas of the vertex, one for each morph target, or null.</param>
+        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
+        /// <returns>A position in world space.</returns>
+        V3 TransformPosition(V3 position, IReadOnlyList<V3> positionDeltas, in VERTEXINFLUENCES skinWeights);
+
+        /// <summary>
+        /// Transforms a vertex normal from local mesh space to world space.
+        /// </summary>
+        /// <param name="normal">The local normal of the vertex.</param>
+        /// <param name="normalDeltas">The local normal deltas of the vertex, one for each morph target, or null.</param>
+        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
+        /// <returns>A normal in world space.</returns>
+        V3 TransformNormal(V3 normal, IReadOnlyList<V3> normalDeltas, in VERTEXINFLUENCES skinWeights);
+
+        /// <summary>
+        /// Transforms a vertex tangent from local mesh space to world space.
+        /// </summary>
+        /// <param name="tangent">The tangent normal of the vertex.</param>
+        /// <param name="tangentDeltas">The local tangent deltas of the vertex, one for each morph target, or null.</param>
+        /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
+        /// <returns>A tangent in world space.</returns>
+        V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in VERTEXINFLUENCES skinWeights);
+
+        V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
+        
+    }
+
+    abstract class MeshMorphTransform
+    {
+        #region constructor
+
+        protected MeshMorphTransform()
+        {
+            Update(default, false);
+        }
+
+        protected MeshMorphTransform(MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Represents a sparse collection of weights where:
+        /// - Index of value <see cref="COMPLEMENT_INDEX"/> points to the Mesh master positions.
+        /// - All other indices point to Mesh MorphTarget[index] positions.
+        /// </summary>
+        private MORPHINFLUENCES _Weights;
+
+        public const int COMPLEMENT_INDEX = 65536;
+
+        /// <summary>
+        /// True if morph targets represent absolute values.
+        /// False if morph targets represent values relative to master value.
+        /// </summary>
+        private bool _AbsoluteMorphTargets;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the current morph weights to use for morph target blending. <see cref="COMPLEMENT_INDEX"/> represents the index for the base geometry.
+        /// </summary>
+        public MORPHINFLUENCES MorphWeights => _Weights;
+
+        /// <summary>
+        /// Gets a value indicating whether morph target values are absolute, and not relative to the master value.
+        /// </summary>
+        public bool AbsoluteMorphTargets => _AbsoluteMorphTargets;
+
+        #endregion
+
+        #region API
+
+        public void Update(MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets = false)
+        {
+            /*
+            _AbsoluteMorphTargets = useAbsoluteMorphTargets;
+
+            if (morphWeights.IsWeightless)
+            {
+                _Weights = MORPHINFLUENCES.Create((COMPLEMENT_INDEX, 1));
+                return;
+            }
+
+            _Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
+            */
+        }
+
+        protected V3 MorphVectors(V3 value, IReadOnlyList<V3> morphTargets)
+        {
+            return value;
+
+            /*
+            if (morphTargets == null || morphTargets.Count == 0) return value;
+            
+            if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
+
+            var p = V3.Zero;
+
+            if (_AbsoluteMorphTargets)
+            {
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
+                {
+                    var val = index == COMPLEMENT_INDEX ? value : morphTargets[index];
+                    p += val * weight;
+                }
+            }
+            else
+            {
+                foreach (var (index, weight) in _Weights.GetNonZeroWeights())
+                {
+                    var val = index == COMPLEMENT_INDEX ? value : value + morphTargets[index];
+                    p += val * weight;
+                }
+            }
+
+            return p;
+            */
+        }
+
+        protected V4 MorphVectors(V4 value, IReadOnlyList<V4> morphTargets)
+        {
+            return value;
+
+            /*
+            if (morphTargets == null || morphTargets.Count == 0) return value;
+
+            if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
+
+            var p = V4.Zero;
+
+            if (_AbsoluteMorphTargets)
+            {
+                foreach (var pair in _Weights.GetNonZeroWeights())
+                {
+                    var val = pair.Index == COMPLEMENT_INDEX ? value : morphTargets[pair.Index];
+                    p += val * pair.Weight;
+                }
+            }
+            else
+            {
+                foreach (var pair in _Weights.GetNonZeroWeights())
+                {
+                    var val = pair.Index == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Index];
+                    p += val * pair.Weight;
+                }
+            }
+
+            return p;
+            */
+        }
+
+        public V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets)
+        {
+            return MorphVectors(color, morphTargets);
+        }
+
+        #endregion
+    }
+
+    class MeshRigidTransform : MeshMorphTransform, IMeshTransform
+    {
+        #region constructor
+
+        public MeshRigidTransform()
+        {
+            Update(TRANSFORM.Identity);
+        }
+
+        public MeshRigidTransform(TRANSFORM worldMatrix)
+        {
+            Update(default, false);
+            Update(worldMatrix);
+        }
+
+        public MeshRigidTransform(TRANSFORM worldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphs)
+        {
+            Update(morphWeights, useAbsoluteMorphs);
+            Update(worldMatrix);
+        }
+
+        #endregion
+
+        #region data
+
+        private TRANSFORM _WorldMatrix;
+        private Boolean _Visible;
+        private Boolean _FlipFaces;
+
+        #endregion
+
+        #region properties
+
+        public Boolean Visible => _Visible;
+
+        public Boolean FlipFaces => _FlipFaces;
+
+        public TRANSFORM WorldMatrix => _WorldMatrix;
+
+        #endregion
+
+        #region API
+
+        public void Update(TRANSFORM worldMatrix)
+        {
+            _WorldMatrix = worldMatrix;
+
+            // http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
+
+            float determinant3x3 =
+                +(worldMatrix.M13 * worldMatrix.M21 * worldMatrix.M32)
+                + (worldMatrix.M11 * worldMatrix.M22 * worldMatrix.M33)
+                + (worldMatrix.M12 * worldMatrix.M23 * worldMatrix.M31)
+                - (worldMatrix.M12 * worldMatrix.M21 * worldMatrix.M33)
+                - (worldMatrix.M13 * worldMatrix.M22 * worldMatrix.M31)
+                - (worldMatrix.M11 * worldMatrix.M23 * worldMatrix.M32);
+
+            _Visible = Math.Abs(determinant3x3) > float.Epsilon;
+            _FlipFaces = determinant3x3 < 0;
+        }
+
+        
+        public V3 TransformPosition(V3 position, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            position = MorphVectors(position, morphTargets);
+
+            return V3.Transform(position, _WorldMatrix);
+        }
+
+        public V3 TransformNormal(V3 normal, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            normal = MorphVectors(normal, morphTargets);
+
+            return V3.Normalize(V3.TransformNormal(normal, _WorldMatrix));
+        }
+
+        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            var t = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
+
+            t = V3.Normalize(V3.TransformNormal(t, _WorldMatrix));
+
+            return new V4(t, tangent.W);
+        }
+
+        #endregion
+    }
+
+    class MeshSkinTransform : MeshMorphTransform, IMeshTransform
+    {
+        #region constructor
+
+        public MeshSkinTransform() { }
+
+        public MeshSkinTransform(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
+            Update(invBindMatrix, currWorldMatrix);
+        }
+
+        public MeshSkinTransform(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix, MORPHINFLUENCES morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
+            Update(count, invBindMatrix, currWorldMatrix);
+        }
+
+        #endregion
+
+        #region data
+
+        private TRANSFORM[] _SkinTransforms;
+
+        #endregion
+
+        #region properties
+
+        public bool Visible => true;
+        public bool FlipFaces => false;
+
+        /// <summary>
+        /// Gets the collection of the current, final matrices to use for skinning
+        /// </summary>
+        public IReadOnlyList<TRANSFORM> SkinMatrices => _SkinTransforms;        
+
+        #endregion
+
+        #region API
+
+        public void Update(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix)
+        {
+            // Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
+            // Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
+            // Guard.IsTrue(invBindMatrix.Length == currWorldMatrix.Length, nameof(currWorldMatrix), $"{invBindMatrix} and {currWorldMatrix} length mismatch.");
+
+            if (_SkinTransforms == null || _SkinTransforms.Length != invBindMatrix.Length) _SkinTransforms = new TRANSFORM[invBindMatrix.Length];
+
+            for (int i = 0; i < _SkinTransforms.Length; ++i)
+            {
+                _SkinTransforms[i] = invBindMatrix[i] * currWorldMatrix[i];
+            }
+        }
+
+        public void Update(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix)
+        {
+            // Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
+            // Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
+
+            if (_SkinTransforms == null || _SkinTransforms.Length != count) _SkinTransforms = new TRANSFORM[count];
+
+            for (int i = 0; i < _SkinTransforms.Length; ++i)
+            {
+                _SkinTransforms[i] = invBindMatrix(i) * currWorldMatrix(i);
+            }
+        }        
+        
+        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            localPosition = MorphVectors(localPosition, morphTargets);
+
+            var worldPosition = V3.Zero;
+
+            var wnrm = 1.0f / skinWeights.WeightSum;
+
+            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
+            {
+                worldPosition += V3.Transform(localPosition, _SkinTransforms[jidx]) * jweight * wnrm;
+            }
+
+            return worldPosition;
+        }
+
+        public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            localNormal = MorphVectors(localNormal, morphTargets);
+
+            var worldNormal = V3.Zero;
+
+            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
+            {
+                worldNormal += V3.TransformNormal(localNormal, _SkinTransforms[jidx]) * jweight;
+            }
+
+            return V3.Normalize(localNormal);
+        }
+
+        public V4 TransformTangent(V4 localTangent, IReadOnlyList<V3> morphTargets, in VERTEXINFLUENCES skinWeights)
+        {
+            var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
+
+            var worldTangent = V3.Zero;
+
+            foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
+            {
+                worldTangent += V3.TransformNormal(localTangentV, _SkinTransforms[jidx]) * jweight;
+            }
+
+            worldTangent = V3.Normalize(worldTangent);
+
+            return new V4(worldTangent, localTangent.W);
+        }
+
+        #endregion        
+    }
+}

+ 117 - 117
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/ModelCollection.cs → src/MonoScene.Runtime.Model3D/ModelGraph/ModelCollection.cs

@@ -1,117 +1,117 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using MODELMESH = MonoScene.Graphics.Mesh;
-
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Defines a <see cref="ModelCollection"/> super class that has ownership over
-    /// <see cref="MeshCollection"/> resources and it's responsible of disposing them.
-    /// </summary>
-    public class DeviceModelCollection : ModelCollection, IDisposable
-    {
-        #region lifecycle
-
-        public DeviceModelCollection(MeshCollection meshes, ArmatureTemplate[] armatures, ModelTemplate[] models, int defaultModelIndex) :base(meshes, armatures, models,defaultModelIndex)
-        {
-            SharedMeshes = meshes;
-        }
-
-        public void Dispose()
-        {
-            _SharedMeshes?.Dispose();
-            _SharedMeshes = null;
-        }
-
-        #endregion
-
-        #region data
-
-        /// <summary>
-        /// Meshes shared by all the <see cref="ModelCollection.Models"/>.
-        /// </summary>
-        private MeshCollection _SharedMeshes;
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Defines a collection of <see cref="ModelTemplate"/> objects and their shared resources.
-    /// </summary>
-    public class ModelCollection
-    {
-        #region lifecycle
-
-        public ModelCollection(IMeshCollection meshes, ArmatureTemplate[] armatures, ModelTemplate[] models, int defaultModelIndex)
-        {
-            _SharedArmatures = armatures;
-            _DefaultModelIndex = defaultModelIndex;
-            _Models = models;
-
-            // must be set after _Models;
-            SharedMeshes = meshes;            
-        }
-
-        #endregion
-
-        #region data
-
-        /// <summary>
-        /// Multiple <see cref="ModelTemplate"/> at <see cref="_Models"/> might share the same meshes.
-        /// </summary>
-        private IMeshCollection _SharedMeshes;
-
-        /// <summary>
-        /// Multiple <see cref="ModelTemplate"/> at <see cref="_Models"/> might share the same <see cref="ArmatureTemplate"/>.
-        /// </summary>
-        private ArmatureTemplate[] _SharedArmatures;
-
-        /// <summary>
-        /// Models available in this collection.
-        /// </summary>
-        private ModelTemplate[] _Models;        
-
-        /// <summary>
-        /// Default model index
-        /// </summary>
-        private readonly int _DefaultModelIndex;
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets or sets the collection of meshes.
-        /// </summary>        
-        public IMeshCollection SharedMeshes
-        {
-            get => _SharedMeshes;
-            set
-            {
-                _SharedMeshes = value;
-                foreach (var model in _Models) model.Meshes = _SharedMeshes;
-            }
-        }
-
-        /// <summary>
-        /// Gets the list of models.
-        /// </summary>
-        public IReadOnlyList<ModelTemplate> Models => _Models;
-
-        /// <summary>
-        /// Gets the default model.
-        /// </summary>
-        public ModelTemplate DefaultModel => _Models[_DefaultModelIndex];        
-
-        #endregion
-
-        #region API
-
-        public int IndexOfModel(string modelName) => Array.FindIndex(_Models, item => item.Name == modelName);       
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using MODELMESH = MonoScene.Graphics.Mesh;
+
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Defines a <see cref="ModelCollection"/> super class that has ownership over
+    /// <see cref="MeshCollection"/> resources and it's responsible of disposing them.
+    /// </summary>
+    public class DeviceModelCollection : ModelCollection, IDisposable
+    {
+        #region lifecycle
+
+        public DeviceModelCollection(MeshCollection meshes, ArmatureTemplate[] armatures, ModelTemplate[] models, int defaultModelIndex) :base(meshes, armatures, models,defaultModelIndex)
+        {
+            SharedMeshes = meshes;
+        }
+
+        public void Dispose()
+        {
+            _SharedMeshes?.Dispose();
+            _SharedMeshes = null;
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Meshes shared by all the <see cref="ModelCollection.Models"/>.
+        /// </summary>
+        private MeshCollection _SharedMeshes;
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a collection of <see cref="ModelTemplate"/> objects and their shared resources.
+    /// </summary>
+    public class ModelCollection
+    {
+        #region lifecycle
+
+        public ModelCollection(IMeshCollection meshes, ArmatureTemplate[] armatures, ModelTemplate[] models, int defaultModelIndex)
+        {
+            _SharedArmatures = armatures;
+            _DefaultModelIndex = defaultModelIndex;
+            _Models = models;
+
+            // must be set after _Models;
+            SharedMeshes = meshes;            
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Multiple <see cref="ModelTemplate"/> at <see cref="_Models"/> might share the same meshes.
+        /// </summary>
+        private IMeshCollection _SharedMeshes;
+
+        /// <summary>
+        /// Multiple <see cref="ModelTemplate"/> at <see cref="_Models"/> might share the same <see cref="ArmatureTemplate"/>.
+        /// </summary>
+        private ArmatureTemplate[] _SharedArmatures;
+
+        /// <summary>
+        /// Models available in this collection.
+        /// </summary>
+        private ModelTemplate[] _Models;        
+
+        /// <summary>
+        /// Default model index
+        /// </summary>
+        private readonly int _DefaultModelIndex;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets or sets the collection of meshes.
+        /// </summary>        
+        public IMeshCollection SharedMeshes
+        {
+            get => _SharedMeshes;
+            set
+            {
+                _SharedMeshes = value;
+                foreach (var model in _Models) model.Meshes = _SharedMeshes;
+            }
+        }
+
+        /// <summary>
+        /// Gets the list of models.
+        /// </summary>
+        public IReadOnlyList<ModelTemplate> Models => _Models;
+
+        /// <summary>
+        /// Gets the default model.
+        /// </summary>
+        public ModelTemplate DefaultModel => _Models[_DefaultModelIndex];        
+
+        #endregion
+
+        #region API
+
+        public int IndexOfModel(string modelName) => Array.FindIndex(_Models, item => item.Name == modelName);       
+
+        #endregion
+    }
+}

+ 306 - 306
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/ModelInstance.cs → src/MonoScene.Runtime.Model3D/ModelGraph/ModelInstance.cs

@@ -1,306 +1,306 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using Microsoft.Xna.Framework.Graphics;
-
-using XNAV3 = Microsoft.Xna.Framework.Vector3;
-using XNAMAT = Microsoft.Xna.Framework.Matrix;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Represents the state machine of a specific model instance on screen.    
-    /// </summary>
-    /// <remarks>
-    /// For each <see cref="ModelTemplate"/> you can create
-    /// multiple <see cref="ModelInstance"/> objects.
-    /// </remarks>
-    public class ModelInstance
-    {
-        #region lifecycle
-
-        internal ModelInstance(ModelTemplate parent)
-        {
-            _Parent = parent;
-            
-            _Armature = new ArmatureInstance(_Parent._Armature);
-            _Armature.SetPoseTransforms();
-
-            _WorldMatrix = XNAMAT.Identity;            
-
-            _DrawableTemplates = _Parent._DrawableReferences;
-            _DrawableTransforms = new IMeshTransform[_DrawableTemplates.Length];
-
-            for (int i = 0; i < _DrawableTransforms.Length; ++i)
-            {
-                _DrawableTransforms[i] = _DrawableTemplates[i].CreateGeometryTransform();
-            }            
-        }
-
-        #endregion
-
-        #region data        
-
-        private readonly ModelTemplate _Parent;
-
-        private IMeshCollection _Meshes => _Parent.Meshes;
-
-        private readonly ArmatureInstance _Armature;
-
-        private readonly IDrawableTemplate[] _DrawableTemplates;
-        private readonly IMeshTransform[] _DrawableTransforms;
-
-        private XNAMAT _WorldMatrix;
-
-        #endregion
-
-        #region properties
-
-        public string Name => _Parent.Name;
-
-        public Object Tag => _Parent.Tag;        
-
-        public ArmatureInstance Armature => _Armature;
-
-        public XNAMAT WorldMatrix
-        {
-            get => _WorldMatrix;
-            set => _WorldMatrix = value;
-        }
-
-        public Microsoft.Xna.Framework.BoundingSphere ModelBounds => _Parent.ModelBounds;
-
-        public Microsoft.Xna.Framework.BoundingSphere WorldBounds => _Parent.ModelBounds.Transform(_WorldMatrix);
-
-        /// <summary>
-        /// Gets the number of drawable instances.
-        /// </summary>
-        public int DrawableInstancesCount => _DrawableTransforms.Length;
-
-        /// <summary>
-        /// Gets the current sequence of drawing commands.
-        /// </summary>
-        public IEnumerable<DrawableInstance> DrawableInstances
-        {
-            get
-            {
-                for (int i = 0; i < _DrawableTemplates.Length; ++i)
-                {
-                    yield return GetDrawableInstance(i);
-                }
-            }
-        }
-
-
-        public IEnumerable<Effect> SharedEffects => _Parent.SharedEffects;
-
-        #endregion
-
-        #region API - Armature
-
-        public int IndexOfNode(string nodeName) { return _Armature.IndexOfNode(nodeName); }
-
-        /// <summary>
-        /// Gets the matrix of a given node/bone in Model Space.
-        /// </summary>
-        /// <param name="nodeIndex">The index of the node/bone.</param>
-        /// <returns>A matrix in model space.</returns>
-        public XNAMAT GetModelMatrix(int nodeIndex) { return _Armature.LogicalNodes[nodeIndex].ModelMatrix; }
-
-        /// <summary>
-        /// Gets the matrix of a given node/bone in World Space.
-        /// </summary>
-        /// <param name="nodeIndex">The index of the node/bone.</param>
-        /// <returns>A matrix in world space.</returns>
-        public XNAMAT GetWorldMatrix(int nodeIndex) { return GetModelMatrix(nodeIndex) * _WorldMatrix; }
-
-        #endregion
-
-        #region API - Drawing
-
-        /// <summary>
-        /// Gets a <see cref="DrawableInstance"/> object, where:
-        /// - Name is the name of this drawable instance. Originally, it was the name of <see cref="Schema2.Node"/>.
-        /// - MeshIndex is the logical Index of a <see cref="Schema2.Mesh"/> in <see cref="Schema2.ModelRoot.LogicalMeshes"/>.
-        /// - Transform is an <see cref="IMeshTransform"/> 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="DrawableInstancesCount"/></param>
-        /// <returns><see cref="DrawableInstance"/> object.</returns>
-        public DrawableInstance GetDrawableInstance(int index)
-        {
-            var dref = _DrawableTemplates[index];
-
-            dref.UpdateGeometryTransform(_DrawableTransforms[index], _Armature);
-
-            return new DrawableInstance(dref, _DrawableTransforms[index]);
-        }
-
-        /// <summary>
-        /// Draws this <see cref="MonoGameModelInstance"/> into the current <see cref="GraphicsDevice"/>.
-        /// </summary>
-        /// <param name="projection">The projection matrix.</param>
-        /// <param name="view">The view matrix.</param>        
-        public void DrawAllParts(XNAMAT projection, XNAMAT view)
-        {
-            foreach (var e in this.SharedEffects)
-            {
-                UpdateProjViewTransforms(e, projection, view);
-            }
-
-            // first we draw all the opaque meshes
-            DrawOpaqueParts();
-
-            // next, we draw all the translucid meshes
-            DrawTranslucidParts();
-        }
-
-        public void DrawTranslucidParts()
-        {
-            foreach (var d in DrawableInstances)
-            {
-                var mesh = _Meshes[d.Template.MeshIndex];
-                if (mesh.TranslucidEffects.Count == 0) continue;
-
-                SetEffectsTransforms(mesh.TranslucidEffects, _WorldMatrix, d.Transform);
-
-                mesh.DrawTranslucid();
-            }
-        }
-
-        public void DrawOpaqueParts()
-        {
-            foreach (var d in DrawableInstances)
-            {
-                var mesh = _Meshes[d.Template.MeshIndex];
-                if (mesh.OpaqueEffects.Count == 0) continue;
-
-                SetEffectsTransforms(mesh.OpaqueEffects, _WorldMatrix, d.Transform);
-
-                mesh.DrawOpaque();
-            }
-        }
-
-        /// <summary>
-        /// Sets the effects transforms.
-        /// </summary>
-        /// <param name="effects">The target effects</param>
-        /// <param name="projectionXform">The current projection matrix</param>
-        /// <param name="viewXform">The current view matrix</param>
-        /// <param name="worldXform">The current world matrix</param>
-        /// <param name="meshXform">The mesh local transform provided by the runtime</param>
-        private void SetEffectsTransforms(IReadOnlyCollection<Effect> effects, XNAMAT worldXform, IMeshTransform meshXform)
-        {
-            if (meshXform is MeshSkinTransform skinnedXform)
-            {
-                // skinned transforms don't have a single "local transform" instead, they deform the mesh using multiple meshes.
-
-                var skinTransforms = UseArray(skinnedXform.SkinMatrices.Count);
-
-                for (int i = 0; i < skinTransforms.Length; ++i)
-                {
-                    skinTransforms[i] = skinnedXform.SkinMatrices[i];
-                }
-
-                foreach (var effect in effects)
-                {
-                    UpdateWorldTransforms(effect, worldXform, skinTransforms);
-                }
-            }
-
-            if (meshXform is MeshRigidTransform rigidXform)
-            {
-                var statTransform = rigidXform.WorldMatrix;
-
-                worldXform = XNAMAT.Multiply(statTransform, worldXform);
-
-                foreach (var effect in effects)
-                {
-                    UpdateWorldTransforms(effect, worldXform);
-                }
-            }
-        }
-
-        #endregion
-
-        #region effect utils
-
-        // pre-allocated bone arrays to update the IEffectBones
-        private static readonly List<XNAMAT[]> _BoneArrays = new List<XNAMAT[]>();
-
-        // Since SkinnedEffect has such a flexible and GC friendly API,
-        // we have to do this to have a reusable bone matrix pool.
-        private static XNAMAT[] UseArray(int count)
-        {
-            while (_BoneArrays.Count <= count) _BoneArrays.Add(null);
-
-            if (_BoneArrays[count] == null) _BoneArrays[count] = new XNAMAT[count];
-
-            return _BoneArrays[count];
-
-        }
-
-
-        public static void UpdateProjViewTransforms(Effect effect, XNAMAT projectionXform, XNAMAT viewXform)
-        {
-            if (effect is IEffectMatrices matrices)
-            {
-                matrices.Projection = projectionXform;
-                matrices.View = viewXform;
-            }
-        }
-
-        public static void UpdateWorldTransforms(Effect effect, XNAMAT worldXform, XNAMAT[] skinTransforms = null)
-        {
-            if (effect is IEffectMatrices matrices)
-            {
-                matrices.World = worldXform;
-            }
-
-            if (skinTransforms != null)
-            {
-                if (effect is SkinnedEffect skin)
-                {
-                    skin.SetBoneTransforms(skinTransforms);
-                }
-                else if (effect is IEffectBones iskin)
-                {
-                    iskin.SetBoneTransforms(skinTransforms);
-                }
-            }
-            else
-            {
-                if (effect is IEffectBones iskin)
-                {
-                    iskin.SetBoneTransforms(null);
-                }
-            }
-        }
-
-        #endregion
-
-        #region nested types
-
-        public static IComparer<ModelInstance> GetDistanceComparer(XNAV3 origin)
-        {
-            return new _DistanceComparer(origin);
-        }
-
-        private struct _DistanceComparer : IComparer<ModelInstance>
-        {
-            public _DistanceComparer(XNAV3 origin) { _Origin = origin; }
-
-            private readonly XNAV3 _Origin;
-
-            public int Compare(ModelInstance x, ModelInstance y)
-            {
-                var xDist = (x.WorldMatrix.Translation - _Origin).LengthSquared();
-                var yDist = (y.WorldMatrix.Translation - _Origin).LengthSquared();
-
-                return xDist.CompareTo(yDist);
-            }
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using XNAV3 = Microsoft.Xna.Framework.Vector3;
+using XNAMAT = Microsoft.Xna.Framework.Matrix;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Represents the state machine of a specific model instance on screen.    
+    /// </summary>
+    /// <remarks>
+    /// For each <see cref="ModelTemplate"/> you can create
+    /// multiple <see cref="ModelInstance"/> objects.
+    /// </remarks>
+    public class ModelInstance
+    {
+        #region lifecycle
+
+        internal ModelInstance(ModelTemplate parent)
+        {
+            _Parent = parent;
+            
+            _Armature = new ArmatureInstance(_Parent._Armature);
+            _Armature.SetPoseTransforms();
+
+            _WorldMatrix = XNAMAT.Identity;            
+
+            _DrawableTemplates = _Parent._DrawableReferences;
+            _DrawableTransforms = new IMeshTransform[_DrawableTemplates.Length];
+
+            for (int i = 0; i < _DrawableTransforms.Length; ++i)
+            {
+                _DrawableTransforms[i] = _DrawableTemplates[i].CreateGeometryTransform();
+            }            
+        }
+
+        #endregion
+
+        #region data        
+
+        private readonly ModelTemplate _Parent;
+
+        private IMeshCollection _Meshes => _Parent.Meshes;
+
+        private readonly ArmatureInstance _Armature;
+
+        private readonly IDrawableTemplate[] _DrawableTemplates;
+        private readonly IMeshTransform[] _DrawableTransforms;
+
+        private XNAMAT _WorldMatrix;
+
+        #endregion
+
+        #region properties
+
+        public string Name => _Parent.Name;
+
+        public Object Tag => _Parent.Tag;        
+
+        public ArmatureInstance Armature => _Armature;
+
+        public XNAMAT WorldMatrix
+        {
+            get => _WorldMatrix;
+            set => _WorldMatrix = value;
+        }
+
+        public Microsoft.Xna.Framework.BoundingSphere ModelBounds => _Parent.ModelBounds;
+
+        public Microsoft.Xna.Framework.BoundingSphere WorldBounds => _Parent.ModelBounds.Transform(_WorldMatrix);
+
+        /// <summary>
+        /// Gets the number of drawable instances.
+        /// </summary>
+        public int DrawableInstancesCount => _DrawableTransforms.Length;
+
+        /// <summary>
+        /// Gets the current sequence of drawing commands.
+        /// </summary>
+        public IEnumerable<DrawableInstance> DrawableInstances
+        {
+            get
+            {
+                for (int i = 0; i < _DrawableTemplates.Length; ++i)
+                {
+                    yield return GetDrawableInstance(i);
+                }
+            }
+        }
+
+
+        public IEnumerable<Effect> SharedEffects => _Parent.SharedEffects;
+
+        #endregion
+
+        #region API - Armature
+
+        public int IndexOfNode(string nodeName) { return _Armature.IndexOfNode(nodeName); }
+
+        /// <summary>
+        /// Gets the matrix of a given node/bone in Model Space.
+        /// </summary>
+        /// <param name="nodeIndex">The index of the node/bone.</param>
+        /// <returns>A matrix in model space.</returns>
+        public XNAMAT GetModelMatrix(int nodeIndex) { return _Armature.LogicalNodes[nodeIndex].ModelMatrix; }
+
+        /// <summary>
+        /// Gets the matrix of a given node/bone in World Space.
+        /// </summary>
+        /// <param name="nodeIndex">The index of the node/bone.</param>
+        /// <returns>A matrix in world space.</returns>
+        public XNAMAT GetWorldMatrix(int nodeIndex) { return GetModelMatrix(nodeIndex) * _WorldMatrix; }
+
+        #endregion
+
+        #region API - Drawing
+
+        /// <summary>
+        /// Gets a <see cref="DrawableInstance"/> object, where:
+        /// - Name is the name of this drawable instance. Originally, it was the name of <see cref="Schema2.Node"/>.
+        /// - MeshIndex is the logical Index of a <see cref="Schema2.Mesh"/> in <see cref="Schema2.ModelRoot.LogicalMeshes"/>.
+        /// - Transform is an <see cref="IMeshTransform"/> 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="DrawableInstancesCount"/></param>
+        /// <returns><see cref="DrawableInstance"/> object.</returns>
+        public DrawableInstance GetDrawableInstance(int index)
+        {
+            var dref = _DrawableTemplates[index];
+
+            dref.UpdateGeometryTransform(_DrawableTransforms[index], _Armature);
+
+            return new DrawableInstance(dref, _DrawableTransforms[index]);
+        }
+
+        /// <summary>
+        /// Draws this <see cref="MonoGameModelInstance"/> into the current <see cref="GraphicsDevice"/>.
+        /// </summary>
+        /// <param name="projection">The projection matrix.</param>
+        /// <param name="view">The view matrix.</param>        
+        public void DrawAllParts(XNAMAT projection, XNAMAT view)
+        {
+            foreach (var e in this.SharedEffects)
+            {
+                UpdateProjViewTransforms(e, projection, view);
+            }
+
+            // first we draw all the opaque meshes
+            DrawOpaqueParts();
+
+            // next, we draw all the translucid meshes
+            DrawTranslucidParts();
+        }
+
+        public void DrawTranslucidParts()
+        {
+            foreach (var d in DrawableInstances)
+            {
+                var mesh = _Meshes[d.Template.MeshIndex];
+                if (mesh.TranslucidEffects.Count == 0) continue;
+
+                SetEffectsTransforms(mesh.TranslucidEffects, _WorldMatrix, d.Transform);
+
+                mesh.DrawTranslucid();
+            }
+        }
+
+        public void DrawOpaqueParts()
+        {
+            foreach (var d in DrawableInstances)
+            {
+                var mesh = _Meshes[d.Template.MeshIndex];
+                if (mesh.OpaqueEffects.Count == 0) continue;
+
+                SetEffectsTransforms(mesh.OpaqueEffects, _WorldMatrix, d.Transform);
+
+                mesh.DrawOpaque();
+            }
+        }
+
+        /// <summary>
+        /// Sets the effects transforms.
+        /// </summary>
+        /// <param name="effects">The target effects</param>
+        /// <param name="projectionXform">The current projection matrix</param>
+        /// <param name="viewXform">The current view matrix</param>
+        /// <param name="worldXform">The current world matrix</param>
+        /// <param name="meshXform">The mesh local transform provided by the runtime</param>
+        private void SetEffectsTransforms(IReadOnlyCollection<Effect> effects, XNAMAT worldXform, IMeshTransform meshXform)
+        {
+            if (meshXform is MeshSkinTransform skinnedXform)
+            {
+                // skinned transforms don't have a single "local transform" instead, they deform the mesh using multiple meshes.
+
+                var skinTransforms = UseArray(skinnedXform.SkinMatrices.Count);
+
+                for (int i = 0; i < skinTransforms.Length; ++i)
+                {
+                    skinTransforms[i] = skinnedXform.SkinMatrices[i];
+                }
+
+                foreach (var effect in effects)
+                {
+                    UpdateWorldTransforms(effect, worldXform, skinTransforms);
+                }
+            }
+
+            if (meshXform is MeshRigidTransform rigidXform)
+            {
+                var statTransform = rigidXform.WorldMatrix;
+
+                worldXform = XNAMAT.Multiply(statTransform, worldXform);
+
+                foreach (var effect in effects)
+                {
+                    UpdateWorldTransforms(effect, worldXform);
+                }
+            }
+        }
+
+        #endregion
+
+        #region effect utils
+
+        // pre-allocated bone arrays to update the IEffectBones
+        private static readonly List<XNAMAT[]> _BoneArrays = new List<XNAMAT[]>();
+
+        // Since SkinnedEffect has such a flexible and GC friendly API,
+        // we have to do this to have a reusable bone matrix pool.
+        private static XNAMAT[] UseArray(int count)
+        {
+            while (_BoneArrays.Count <= count) _BoneArrays.Add(null);
+
+            if (_BoneArrays[count] == null) _BoneArrays[count] = new XNAMAT[count];
+
+            return _BoneArrays[count];
+
+        }
+
+
+        public static void UpdateProjViewTransforms(Effect effect, XNAMAT projectionXform, XNAMAT viewXform)
+        {
+            if (effect is IEffectMatrices matrices)
+            {
+                matrices.Projection = projectionXform;
+                matrices.View = viewXform;
+            }
+        }
+
+        public static void UpdateWorldTransforms(Effect effect, XNAMAT worldXform, XNAMAT[] skinTransforms = null)
+        {
+            if (effect is IEffectMatrices matrices)
+            {
+                matrices.World = worldXform;
+            }
+
+            if (skinTransforms != null)
+            {
+                if (effect is SkinnedEffect skin)
+                {
+                    skin.SetBoneTransforms(skinTransforms);
+                }
+                else if (effect is IEffectBones iskin)
+                {
+                    iskin.SetBoneTransforms(skinTransforms);
+                }
+            }
+            else
+            {
+                if (effect is IEffectBones iskin)
+                {
+                    iskin.SetBoneTransforms(null);
+                }
+            }
+        }
+
+        #endregion
+
+        #region nested types
+
+        public static IComparer<ModelInstance> GetDistanceComparer(XNAV3 origin)
+        {
+            return new _DistanceComparer(origin);
+        }
+
+        private struct _DistanceComparer : IComparer<ModelInstance>
+        {
+            public _DistanceComparer(XNAV3 origin) { _Origin = origin; }
+
+            private readonly XNAV3 _Origin;
+
+            public int Compare(ModelInstance x, ModelInstance y)
+            {
+                var xDist = (x.WorldMatrix.Translation - _Origin).LengthSquared();
+                var yDist = (y.WorldMatrix.Translation - _Origin).LengthSquared();
+
+                return xDist.CompareTo(yDist);
+            }
+        }
+
+        #endregion
+    }
+}

+ 77 - 77
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/ModelTemplate.cs → src/MonoScene.Runtime.Model3D/ModelGraph/ModelTemplate.cs

@@ -1,77 +1,77 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-using System.Threading.Tasks;
-
-using Microsoft.Xna.Framework.Graphics;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Defines a templatized representation of a <see cref="Schema2.Scene"/> that can be used
-    /// to create <see cref="ModelInstance"/>, which can help render a scene on a client application.
-    /// </summary>
-    public class ModelTemplate : BaseTemplate
-    {
-        #region lifecycle        
-
-        public ModelTemplate(string modelName, ArmatureTemplate armature, IDrawableTemplate[] drawables)
-            : base(modelName)
-        {            
-            _Armature = armature;
-            _DrawableReferences = drawables;            
-        }
-
-        #endregion
-
-        #region data        
-
-        internal readonly ArmatureTemplate _Armature;
-        
-        private IMeshCollection _Meshes;        
-
-        // this is the collection of "what needs to be rendered", and it binds meshes with armatures
-        internal readonly IDrawableTemplate[] _DrawableReferences;
-
-        private Effect[] _SharedEffects;
-
-        #endregion
-
-        #region properties        
-
-        public Microsoft.Xna.Framework.BoundingSphere ModelBounds { get; set; }
-
-        public IMeshCollection Meshes
-        {
-            get => _Meshes;
-            set
-            {
-                _Meshes = value;
-                _SharedEffects = null;
-            }
-        }
-
-        public IReadOnlyCollection<Effect> SharedEffects
-        {
-            get
-            {
-                if (_SharedEffects != null) return _SharedEffects;
-
-                var meshIndices = _DrawableReferences.Select(item => item.MeshIndex);
-                _SharedEffects = _Meshes.GetSharedEffects(meshIndices);
-
-                return _SharedEffects;
-            }
-        }
-
-        #endregion
-
-        #region API
-
-        public ModelInstance CreateInstance() => new ModelInstance(this);
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Defines a templatized representation of a <see cref="Schema2.Scene"/> that can be used
+    /// to create <see cref="ModelInstance"/>, which can help render a scene on a client application.
+    /// </summary>
+    public class ModelTemplate : Content.BaseContent
+    {
+        #region lifecycle        
+
+        public ModelTemplate(string modelName, ArmatureTemplate armature, IDrawableTemplate[] drawables)
+            : base(modelName)
+        {            
+            _Armature = armature;
+            _DrawableReferences = drawables;            
+        }
+
+        #endregion
+
+        #region data        
+
+        internal readonly ArmatureTemplate _Armature;
+        
+        private IMeshCollection _Meshes;        
+
+        // this is the collection of "what needs to be rendered", and it binds meshes with armatures
+        internal readonly IDrawableTemplate[] _DrawableReferences;
+
+        private Effect[] _SharedEffects;
+
+        #endregion
+
+        #region properties        
+
+        public Microsoft.Xna.Framework.BoundingSphere ModelBounds { get; set; }
+
+        public IMeshCollection Meshes
+        {
+            get => _Meshes;
+            set
+            {
+                _Meshes = value;
+                _SharedEffects = null;
+            }
+        }
+
+        public IReadOnlyCollection<Effect> SharedEffects
+        {
+            get
+            {
+                if (_SharedEffects != null) return _SharedEffects;
+
+                var meshIndices = _DrawableReferences.Select(item => item.MeshIndex);
+                _SharedEffects = _Meshes.GetSharedEffects(meshIndices);
+
+                return _SharedEffects;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public ModelInstance CreateInstance() => new ModelInstance(this);
+
+        #endregion
+    }
+}

+ 127 - 127
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/NodeInstance.cs → src/MonoScene.Runtime.Model3D/ModelGraph/NodeInstance.cs

@@ -1,127 +1,127 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using XFORM = Microsoft.Xna.Framework.Matrix;
-// using SPARSE8 = Microsoft.Xna.Framework.Vector4;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Defines a node of a scene graph in <see cref="ModelInstance"/>
-    /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Name}")]
-    public sealed 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? _ModelMatrix;
-
-        // private SPARSE8 _MorphWeights;
-
-        #endregion
-
-        #region properties
-
-        public String Name => _Template.Name;
-
-        public Object Tag => _Template.Tag;
-
-        /// <summary>
-        /// Parent node.
-        /// </summary>
-        public NodeInstance VisualParent => _Parent;                
-
-        /// <summary>
-        /// Transform matrix in local space
-        /// </summary>
-        public XFORM LocalMatrix
-        {
-            get => _LocalMatrix;
-            set
-            {
-                _LocalMatrix = value;
-                _ModelMatrix = null;
-            }
-        }
-
-        /// <summary>
-        /// Transform matrix in world space
-        /// </summary>
-        public XFORM ModelMatrix
-        {
-            get => _GetModelMatrix();
-            set => _SetModelMatrix(value);
-        }
-
-        // public SPARSE8 MorphWeights { get => _MorphWeights; set => _MorphWeights = value; }
-
-        /// <summary>
-        /// Gets a value indicating whether any of the transforms down the node tree graph has been modified.
-        /// </summary>
-        private bool _TransformChainIsDirty
-        {
-            get
-            {
-                if (!_ModelMatrix.HasValue) return true;
-
-                return _Parent == null ? false : _Parent._TransformChainIsDirty;
-            }
-        }
-
-        #endregion
-
-        #region API
-
-        private XFORM _GetModelMatrix()
-        {
-            if (!_TransformChainIsDirty) return _ModelMatrix.Value;
-
-            _ModelMatrix = _Parent == null ? _LocalMatrix : XFORM.Multiply(_LocalMatrix, _Parent.ModelMatrix);
-
-            return _ModelMatrix.Value;
-        }
-
-        private void _SetModelMatrix(XFORM xform)
-        {
-            if (_Parent == null) { LocalMatrix = xform; return; }
-
-            var pxform = _Parent._GetModelMatrix();
-            XFORM.Invert(ref pxform, out XFORM ipwm);
-
-            LocalMatrix = XFORM.Multiply(xform, ipwm);
-        }
-
-        public void SetPoseTransform() { SetAnimationFrame(-1, 0); }
-
-        public void SetAnimationFrame(int trackLogicalIndex, float time)
-        {            
-            this.LocalMatrix = _Template.GetLocalMatrix(trackLogicalIndex, time);
-
-            // this.MorphWeights = _Template.GetMorphWeights(trackLogicalIndex, time);
-        }
-
-        public void SetAnimationFrame(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
-        {            
-            this.LocalMatrix = _Template.GetLocalMatrix(track, time, weight);
-
-            // this.MorphWeights = _Template.GetMorphWeights(track, time, weight);
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using XFORM = Microsoft.Xna.Framework.Matrix;
+// using SPARSE8 = Microsoft.Xna.Framework.Vector4;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Defines a node of a scene graph in <see cref="ModelInstance"/>
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{Name}")]
+    public sealed 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? _ModelMatrix;
+
+        // private SPARSE8 _MorphWeights;
+
+        #endregion
+
+        #region properties
+
+        public String Name => _Template.Name;
+
+        public Object Tag => _Template.Tag;
+
+        /// <summary>
+        /// Parent node.
+        /// </summary>
+        public NodeInstance VisualParent => _Parent;                
+
+        /// <summary>
+        /// Transform matrix in local space
+        /// </summary>
+        public XFORM LocalMatrix
+        {
+            get => _LocalMatrix;
+            set
+            {
+                _LocalMatrix = value;
+                _ModelMatrix = null;
+            }
+        }
+
+        /// <summary>
+        /// Transform matrix in world space
+        /// </summary>
+        public XFORM ModelMatrix
+        {
+            get => _GetModelMatrix();
+            set => _SetModelMatrix(value);
+        }
+
+        // public SPARSE8 MorphWeights { get => _MorphWeights; set => _MorphWeights = value; }
+
+        /// <summary>
+        /// Gets a value indicating whether any of the transforms down the node tree graph has been modified.
+        /// </summary>
+        private bool _TransformChainIsDirty
+        {
+            get
+            {
+                if (!_ModelMatrix.HasValue) return true;
+
+                return _Parent == null ? false : _Parent._TransformChainIsDirty;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        private XFORM _GetModelMatrix()
+        {
+            if (!_TransformChainIsDirty) return _ModelMatrix.Value;
+
+            _ModelMatrix = _Parent == null ? _LocalMatrix : XFORM.Multiply(_LocalMatrix, _Parent.ModelMatrix);
+
+            return _ModelMatrix.Value;
+        }
+
+        private void _SetModelMatrix(XFORM xform)
+        {
+            if (_Parent == null) { LocalMatrix = xform; return; }
+
+            var pxform = _Parent._GetModelMatrix();
+            XFORM.Invert(ref pxform, out XFORM ipwm);
+
+            LocalMatrix = XFORM.Multiply(xform, ipwm);
+        }
+
+        public void SetPoseTransform() { SetAnimationFrame(-1, 0); }
+
+        public void SetAnimationFrame(int trackLogicalIndex, float time)
+        {            
+            this.LocalMatrix = _Template.GetLocalMatrix(trackLogicalIndex, time);
+
+            // this.MorphWeights = _Template.GetMorphWeights(trackLogicalIndex, time);
+        }
+
+        public void SetAnimationFrame(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
+        {            
+            this.LocalMatrix = _Template.GetLocalMatrix(track, time, weight);
+
+            // this.MorphWeights = _Template.GetMorphWeights(track, time, weight);
+        }
+
+        #endregion
+    }
+}

+ 175 - 175
src/MonoScene.Runtime.Model3D/Graphics/ModelGraph/NodeTemplate.cs → src/MonoScene.Runtime.Model3D/ModelGraph/NodeTemplate.cs

@@ -1,175 +1,175 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Linq;
-
-using XNAV3 = Microsoft.Xna.Framework.Vector3;
-using XNAV4 = Microsoft.Xna.Framework.Vector4;
-using XNAQUAT = Microsoft.Xna.Framework.Quaternion;
-using XNAMAT = Microsoft.Xna.Framework.Matrix;
-using SPARSE8 = Microsoft.Xna.Framework.Vector4;
-
-namespace MonoScene.Graphics
-{
-    /// <summary>
-    /// Defines a hierarchical transform node of a scene graph tree.
-    /// </summary>
-    [System.Diagnostics.DebuggerDisplay("[{LogicalNodeIndex}] {Name}")]
-    public class NodeTemplate : BaseTemplate
-    {
-        #region lifecycle
-
-        internal NodeTemplate(int thisIndex, int parentIndex, int[] childIndices)
-        {
-            if (parentIndex >= thisIndex) throw new ArgumentOutOfRangeException(nameof(parentIndex));
-            if (childIndices.Any(item => item <= thisIndex)) throw new ArgumentOutOfRangeException(nameof(childIndices));
-
-            _ThisIndex = thisIndex;
-            _ParentIndex = parentIndex;
-            _ChildIndices = childIndices;
-        }
-
-        #endregion
-
-        #region data
-
-        /// <summary>
-        /// the index of this node within <see cref="ModelTemplate._NodeTemplates"/>
-        /// </summary>
-        private readonly int _ThisIndex;
-
-        /// <summary>
-        /// the index of the parent node within <see cref="ModelTemplate._NodeTemplates"/>
-        /// </summary>
-        private readonly int _ParentIndex;
-        private readonly int[] _ChildIndices;
-
-        private XNAMAT _LocalMatrix;        
-
-        private bool _UseAnimatedTransforms;
-
-        private AffineTransform _LocalTransform;
-        private AnimatableProperty<XNAV3> _Scale;
-        private AnimatableProperty<XNAQUAT> _Rotation;
-        private AnimatableProperty<XNAV3> _Translation;
-
-        // private AnimatableProperty<SPARSE8> _Morphing;
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets the index of the source <see cref="Schema2.Node"/> in <see cref="ModelTemplate._NodeTemplates"/>
-        /// </summary>
-        public int ThisIndex => _ThisIndex;
-
-        /// <summary>
-        /// Gets the index of the parent <see cref="NodeTemplate"/> in <see cref="ModelTemplate._NodeTemplates"/>
-        /// </summary>
-        public int ParentIndex => _ParentIndex;
-
-        /// <summary>
-        /// Gets the list of indices of the children <see cref="NodeTemplate"/> in <see cref="ModelTemplate._NodeTemplates"/>
-        /// </summary>
-        public IReadOnlyList<int> ChildIndices => _ChildIndices;
-
-        public XNAMAT LocalMatrix => _LocalMatrix;        
-
-        #endregion
-
-        #region API
-
-        public void SetLocalMatrix(XNAMAT matrix)
-        {
-            _LocalMatrix = matrix;
-            _UseAnimatedTransforms = false;
-        }
-
-        public void SetLocalTransform(AnimatableProperty<XNAV3> s, AnimatableProperty<XNAQUAT> r, AnimatableProperty<XNAV3> t)
-        {            
-            var ss = s != null && s.IsAnimated;
-            var rr = r != null && r.IsAnimated;
-            var tt = t != null && t.IsAnimated;
-
-            if (!(ss || rr || tt))
-            {
-                _UseAnimatedTransforms = false;
-                _Scale = null;
-                _Rotation = null;
-                _Translation = null;
-                return;
-            }
-
-            _UseAnimatedTransforms = true;
-            _Scale = s;
-            _Rotation = r;
-            _Translation = t;
-
-            _LocalMatrix = AffineTransform.CreateFromAny(null, s.Value, r.Value, t.Value).Matrix;
-        }
-
-        /*
-        public SPARSE8 GetMorphWeights(int trackLogicalIndex, float time)
-        {
-            if (trackLogicalIndex < 0) return _Morphing.Value;
-
-            return _Morphing.GetValueAt(trackLogicalIndex, time);
-        }
-        
-        public SPARSE8 GetMorphWeights(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
-        {
-            if (!_Morphing.IsAnimated) return _Morphing.Value;
-
-            Span<SPARSE8> xforms = stackalloc SPARSE8[track.Length];
-
-            for (int i = 0; i < xforms.Length; ++i)
-            {
-                xforms[i] = GetMorphWeights(track[i], time[i]);
-            }
-
-            return SPARSE8.Blend(xforms, weight);
-        }*/
-
-        public AffineTransform GetLocalTransform(int trackIndex, float time)
-        {
-            if (!_UseAnimatedTransforms || trackIndex < 0) return _LocalTransform;
-
-            var s = _Scale?.GetValueAt(trackIndex, time);
-            var r = _Rotation?.GetValueAt(trackIndex, time);
-            var t = _Translation?.GetValueAt(trackIndex, time);
-
-            return new AffineTransform(s, r, t);
-        }
-
-        public AffineTransform GetLocalTransform(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
-        {
-            if (!_UseAnimatedTransforms) return _LocalTransform;
-
-            Span<AffineTransform> xforms = stackalloc AffineTransform[track.Length];
-
-            for (int i = 0; i < xforms.Length; ++i)
-            {
-                xforms[i] = GetLocalTransform(track[i], time[i]);
-            }
-
-            return AffineTransform.Blend(xforms, weight);
-        }
-
-        public XNAMAT GetLocalMatrix(int trackIndex, float time)
-        {
-            if (!_UseAnimatedTransforms || trackIndex < 0) return _LocalMatrix;
-
-            return GetLocalTransform(trackIndex, time).Matrix;
-        }
-
-        public XNAMAT GetLocalMatrix(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
-        {
-            if (!_UseAnimatedTransforms) return _LocalMatrix;
-
-            return GetLocalTransform(track, time, weight).Matrix;
-        }
-
-        #endregion
-    }
-}
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+
+using XNAV3 = Microsoft.Xna.Framework.Vector3;
+using XNAV4 = Microsoft.Xna.Framework.Vector4;
+using XNAQUAT = Microsoft.Xna.Framework.Quaternion;
+using XNAMAT = Microsoft.Xna.Framework.Matrix;
+using SPARSE8 = Microsoft.Xna.Framework.Vector4;
+
+namespace MonoScene.Graphics
+{
+    /// <summary>
+    /// Defines a hierarchical transform node of a scene graph tree.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("[{LogicalNodeIndex}] {Name}")]
+    public class NodeTemplate : Content.BaseContent
+    {
+        #region lifecycle
+
+        internal NodeTemplate(int thisIndex, int parentIndex, int[] childIndices)
+        {
+            if (parentIndex >= thisIndex) throw new ArgumentOutOfRangeException(nameof(parentIndex));
+            if (childIndices.Any(item => item <= thisIndex)) throw new ArgumentOutOfRangeException(nameof(childIndices));
+
+            _ThisIndex = thisIndex;
+            _ParentIndex = parentIndex;
+            _ChildIndices = childIndices;
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// the index of this node within <see cref="ModelTemplate._NodeTemplates"/>
+        /// </summary>
+        private readonly int _ThisIndex;
+
+        /// <summary>
+        /// the index of the parent node within <see cref="ModelTemplate._NodeTemplates"/>
+        /// </summary>
+        private readonly int _ParentIndex;
+        private readonly int[] _ChildIndices;
+
+        private XNAMAT _LocalMatrix;        
+
+        private bool _UseAnimatedTransforms;
+
+        private AffineTransform _LocalTransform;
+        private AnimatableProperty<XNAV3> _Scale;
+        private AnimatableProperty<XNAQUAT> _Rotation;
+        private AnimatableProperty<XNAV3> _Translation;
+
+        // private AnimatableProperty<SPARSE8> _Morphing;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the index of the source <see cref="Schema2.Node"/> in <see cref="ModelTemplate._NodeTemplates"/>
+        /// </summary>
+        public int ThisIndex => _ThisIndex;
+
+        /// <summary>
+        /// Gets the index of the parent <see cref="NodeTemplate"/> in <see cref="ModelTemplate._NodeTemplates"/>
+        /// </summary>
+        public int ParentIndex => _ParentIndex;
+
+        /// <summary>
+        /// Gets the list of indices of the children <see cref="NodeTemplate"/> in <see cref="ModelTemplate._NodeTemplates"/>
+        /// </summary>
+        public IReadOnlyList<int> ChildIndices => _ChildIndices;
+
+        public XNAMAT LocalMatrix => _LocalMatrix;        
+
+        #endregion
+
+        #region API
+
+        public void SetLocalMatrix(XNAMAT matrix)
+        {
+            _LocalMatrix = matrix;
+            _UseAnimatedTransforms = false;
+        }
+
+        public void SetLocalTransform(AnimatableProperty<XNAV3> s, AnimatableProperty<XNAQUAT> r, AnimatableProperty<XNAV3> t)
+        {            
+            var ss = s != null && s.IsAnimated;
+            var rr = r != null && r.IsAnimated;
+            var tt = t != null && t.IsAnimated;
+
+            if (!(ss || rr || tt))
+            {
+                _UseAnimatedTransforms = false;
+                _Scale = null;
+                _Rotation = null;
+                _Translation = null;
+                return;
+            }
+
+            _UseAnimatedTransforms = true;
+            _Scale = s;
+            _Rotation = r;
+            _Translation = t;
+
+            _LocalMatrix = AffineTransform.CreateFromAny(null, s.Value, r.Value, t.Value).Matrix;
+        }
+
+        /*
+        public SPARSE8 GetMorphWeights(int trackLogicalIndex, float time)
+        {
+            if (trackLogicalIndex < 0) return _Morphing.Value;
+
+            return _Morphing.GetValueAt(trackLogicalIndex, time);
+        }
+        
+        public SPARSE8 GetMorphWeights(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
+        {
+            if (!_Morphing.IsAnimated) return _Morphing.Value;
+
+            Span<SPARSE8> xforms = stackalloc SPARSE8[track.Length];
+
+            for (int i = 0; i < xforms.Length; ++i)
+            {
+                xforms[i] = GetMorphWeights(track[i], time[i]);
+            }
+
+            return SPARSE8.Blend(xforms, weight);
+        }*/
+
+        public AffineTransform GetLocalTransform(int trackIndex, float time)
+        {
+            if (!_UseAnimatedTransforms || trackIndex < 0) return _LocalTransform;
+
+            var s = _Scale?.GetValueAt(trackIndex, time);
+            var r = _Rotation?.GetValueAt(trackIndex, time);
+            var t = _Translation?.GetValueAt(trackIndex, time);
+
+            return new AffineTransform(s, r, t);
+        }
+
+        public AffineTransform GetLocalTransform(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
+        {
+            if (!_UseAnimatedTransforms) return _LocalTransform;
+
+            Span<AffineTransform> xforms = stackalloc AffineTransform[track.Length];
+
+            for (int i = 0; i < xforms.Length; ++i)
+            {
+                xforms[i] = GetLocalTransform(track[i], time[i]);
+            }
+
+            return AffineTransform.Blend(xforms, weight);
+        }
+
+        public XNAMAT GetLocalMatrix(int trackIndex, float time)
+        {
+            if (!_UseAnimatedTransforms || trackIndex < 0) return _LocalMatrix;
+
+            return GetLocalTransform(trackIndex, time).Matrix;
+        }
+
+        public XNAMAT GetLocalMatrix(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
+        {
+            if (!_UseAnimatedTransforms) return _LocalMatrix;
+
+            return GetLocalTransform(track, time, weight).Matrix;
+        }
+
+        #endregion
+    }
+}

+ 1 - 1
src/MonoScene.Runtime.Model3D/MonoScene.Runtime.Model3D.csproj

@@ -18,7 +18,7 @@
   </ItemGroup>  
 
   <ItemGroup>
-    <ProjectReference Include="..\MonoScene.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj" />
   </ItemGroup>  
 
 </Project>

+ 161 - 161
src/MonoScene.Runtime.Model3D/Graphics/Skinning.MD → src/MonoScene.Runtime.Model3D/Skinning.MD

@@ -1,162 +1,162 @@
-#### Skinning considerations
-
-First of all, it's important to undestand that glTF skinning architecture is quite versatile, and allows
-defining a skinned rig in ways that might not be compatible with pre-existing engines skeletal systems.
-
-In glTF, a skinned mesh is usually defined like this:
-
-
-```
-(fig 1)
-Scene  
- ┣━🐒 SkinnedMesh     
- ┗━🦴 Hip
-    ┗━🦴 Chest
-       ┗━🦴 Neck
-          ┣━🦴 LeftShoulder
-          ┣━🦴 RightShoulder
-          ┗━🦴 Head
-```
-
-But it can also be defined like this:
-
-```
-(fig 2)
-Scene  
- ┗━🐒 SkinnedMesh     
-    ┗━🦴 Hip
-       ┗━🦴 Chest
-          ┗━🦴 Neck
-             ┣━🦴 LeftShoulder
-             ┣━🦴 RightShoulder
-             ┗━🦴 Head
-```
-
-or like this:
-
-```
-(fig 3)
-Scene
- ┗━ RootNode
-    ┣━🐒 SkinnedMesh     
-    ┗━🦴 Hip
-       ┗━🦴 Chest
-          ┗━🦴 Neck
-             ┣━🦴 LeftShoulder
-             ┣━🦴 RightShoulder
-             ┗━🦴 Head
-```
-
-And then, the `Mesh` object contains a  `Skin` definition that maps which nodes
-of the scene graph influence the skin of this particular mesh.
-
-Many engines expect the mesh to be the parent node of the skeleton, and the skeleton bones to
-be child nodes of that root node containing the mesh, and also expect all the nodes to be used
-as part of the skinning _in the same order as they're defined_, so there's no need for any kind
-of skin mapping. Although this skeleton architecture is certainly possible with glTF, it's a
-naive approach to skinning and also very limiting.
-
-Here's a few use cases that are possible with glTF that would not be possible with a basic
-skinning architecture without a skin mapping.
-
-Case 1, Remember the RayMan character? 🖐👀
-
-It was made of _floating parts_, and it did not have any elbows or knees in the _geometry_ , but
-it certainly needed elbows and knees to support the whole skeleton animation hierarchy.
-
-```
-RayMan scene
- ┣━👀 RaymanMesh
- ┗━⚡ Hip
-    ┗━👚 Chest
-       ┗━⚙ Neck
-          ┣━⚙ LeftShoulder
-          ┃  ┗━⚙ LeftElbow
-          ┃     ┗━🤚 LeftHand
-          ┣━⚙ RightShoulder
-          ┃  ┗━⚙ RightElbow
-          ┃     ┗━🤚 RightHand
-          ┗━👀 Head
-
-Nodes with symbol ⚙ represent bones in the skeleton graph NOT used by the mesh skin.
-```
-
-Case 2: Variations
-
-What if we want to have multiple sets of the same character, sharing the same skeleton and animations
-to save memory?  with (fig 1) is not possible, because the skeleton and animations are
-children of the root node which holds the mesh, so we either switch the mesh at runtime, which is ugly,
-or we duplicate the whole tree.
-
-glTF solves this problem by storing all the required resources like this:
-
-```
-glTF resources
- ┣━🐒 MonkeyMesh     
- ┣━🦧 OrangutanMesh     
- ┗━🦴 Hip
-    ┗━🦴 Chest
-       ┗━🦴 Neck
-          ┣━🦴 LeftShoulder
-          ┣━🦴 RightShoulder
-          ┗━🦴 Head
-
-```
-
-And at render time, it uses _Scenes_ that map into the resources, saving a lot of memory:
-
-```
-Scene 0
- ┣━🐒 MonkeyMesh     
- ┗━🦴 Hip
-    ┗━🦴 Chest
-       ┗━🦴 Neck
-          ┣━🦴 LeftShoulder
-          ┣━🦴 RightShoulder
-          ┗━🦴 Head
-Scene 1
- ┣━🦧 OrangutanMesh     
- ┗━🦴 Hip
-    ┗━🦴 Chest
-       ┗━🦴 Neck
-          ┣━🦴 LeftShoulder
-          ┣━🦴 RightShoulder
-          ┗━🦴 Head
-```
-
-This architecture can be useful in a number of scenarios:
-- Different LOD levels of the same character.
-- Different states of the same character: a fresh character and a half beaten character.
-
-
-Case 3: Multiple Skins
-
-Consider a skinned character that uses two meshes:
-- naked body mesh
-- clothes
-
-Both meshes share the same skeleton, but although there's some bone overlapping,
-the actual skinning mapping might not be exactly the same:
-
-```
-Scene 0
- ┣━🐒⓵ MonkeyMesh 
- ┣━🥼⓶ ClothesMesh
- ┗━⓵⓶ Hip
-    ┗━⓵⓶ Chest
-       ┗━⓵ Neck
-          ┣━⓵⓶ LeftShoulder
-          ┣━⓵⓶ RightShoulder
-          ┗━⓵ Head
-```
-
-Furthermore, since MonkeyMesh⓵ and ClothesMesh⓶ might have been bound to a skin at different times during authoring,
-their respective _Inverse Bind Matrices_ might be completely different.
-
-Why an artist might want to do something that ugly?
-
-One reason might simply be because he doesn't care and, after all, all authoring application do support this workflow.
-
-The other is to have tight control over how joints bend at acute angles. This is a well known problem with human
-articulations, it's specially noticeable on knees and elbows when they're bent over 90 degrees, looking like the
+#### Skinning considerations
+
+First of all, it's important to undestand that glTF skinning architecture is quite versatile, and allows
+defining a skinned rig in ways that might not be compatible with pre-existing engines skeletal systems.
+
+In glTF, a skinned mesh is usually defined like this:
+
+
+```
+(fig 1)
+Scene  
+ ┣━🐒 SkinnedMesh     
+ ┗━🦴 Hip
+    ┗━🦴 Chest
+       ┗━🦴 Neck
+          ┣━🦴 LeftShoulder
+          ┣━🦴 RightShoulder
+          ┗━🦴 Head
+```
+
+But it can also be defined like this:
+
+```
+(fig 2)
+Scene  
+ ┗━🐒 SkinnedMesh     
+    ┗━🦴 Hip
+       ┗━🦴 Chest
+          ┗━🦴 Neck
+             ┣━🦴 LeftShoulder
+             ┣━🦴 RightShoulder
+             ┗━🦴 Head
+```
+
+or like this:
+
+```
+(fig 3)
+Scene
+ ┗━ RootNode
+    ┣━🐒 SkinnedMesh     
+    ┗━🦴 Hip
+       ┗━🦴 Chest
+          ┗━🦴 Neck
+             ┣━🦴 LeftShoulder
+             ┣━🦴 RightShoulder
+             ┗━🦴 Head
+```
+
+And then, the `Mesh` object contains a  `Skin` definition that maps which nodes
+of the scene graph influence the skin of this particular mesh.
+
+Many engines expect the mesh to be the parent node of the skeleton, and the skeleton bones to
+be child nodes of that root node containing the mesh, and also expect all the nodes to be used
+as part of the skinning _in the same order as they're defined_, so there's no need for any kind
+of skin mapping. Although this skeleton architecture is certainly possible with glTF, it's a
+naive approach to skinning and also very limiting.
+
+Here's a few use cases that are possible with glTF that would not be possible with a basic
+skinning architecture without a skin mapping.
+
+Case 1, Remember the RayMan character? 🖐👀
+
+It was made of _floating parts_, and it did not have any elbows or knees in the _geometry_ , but
+it certainly needed elbows and knees to support the whole skeleton animation hierarchy.
+
+```
+RayMan scene
+ ┣━👀 RaymanMesh
+ ┗━⚡ Hip
+    ┗━👚 Chest
+       ┗━⚙ Neck
+          ┣━⚙ LeftShoulder
+          ┃  ┗━⚙ LeftElbow
+          ┃     ┗━🤚 LeftHand
+          ┣━⚙ RightShoulder
+          ┃  ┗━⚙ RightElbow
+          ┃     ┗━🤚 RightHand
+          ┗━👀 Head
+
+Nodes with symbol ⚙ represent bones in the skeleton graph NOT used by the mesh skin.
+```
+
+Case 2: Variations
+
+What if we want to have multiple sets of the same character, sharing the same skeleton and animations
+to save memory?  with (fig 1) is not possible, because the skeleton and animations are
+children of the root node which holds the mesh, so we either switch the mesh at runtime, which is ugly,
+or we duplicate the whole tree.
+
+glTF solves this problem by storing all the required resources like this:
+
+```
+glTF resources
+ ┣━🐒 MonkeyMesh     
+ ┣━🦧 OrangutanMesh     
+ ┗━🦴 Hip
+    ┗━🦴 Chest
+       ┗━🦴 Neck
+          ┣━🦴 LeftShoulder
+          ┣━🦴 RightShoulder
+          ┗━🦴 Head
+
+```
+
+And at render time, it uses _Scenes_ that map into the resources, saving a lot of memory:
+
+```
+Scene 0
+ ┣━🐒 MonkeyMesh     
+ ┗━🦴 Hip
+    ┗━🦴 Chest
+       ┗━🦴 Neck
+          ┣━🦴 LeftShoulder
+          ┣━🦴 RightShoulder
+          ┗━🦴 Head
+Scene 1
+ ┣━🦧 OrangutanMesh     
+ ┗━🦴 Hip
+    ┗━🦴 Chest
+       ┗━🦴 Neck
+          ┣━🦴 LeftShoulder
+          ┣━🦴 RightShoulder
+          ┗━🦴 Head
+```
+
+This architecture can be useful in a number of scenarios:
+- Different LOD levels of the same character.
+- Different states of the same character: a fresh character and a half beaten character.
+
+
+Case 3: Multiple Skins
+
+Consider a skinned character that uses two meshes:
+- naked body mesh
+- clothes
+
+Both meshes share the same skeleton, but although there's some bone overlapping,
+the actual skinning mapping might not be exactly the same:
+
+```
+Scene 0
+ ┣━🐒⓵ MonkeyMesh 
+ ┣━🥼⓶ ClothesMesh
+ ┗━⓵⓶ Hip
+    ┗━⓵⓶ Chest
+       ┗━⓵ Neck
+          ┣━⓵⓶ LeftShoulder
+          ┣━⓵⓶ RightShoulder
+          ┗━⓵ Head
+```
+
+Furthermore, since MonkeyMesh⓵ and ClothesMesh⓶ might have been bound to a skin at different times during authoring,
+their respective _Inverse Bind Matrices_ might be completely different.
+
+Why an artist might want to do something that ugly?
+
+One reason might simply be because he doesn't care and, after all, all authoring application do support this workflow.
+
+The other is to have tight control over how joints bend at acute angles. This is a well known problem with human
+articulations, it's specially noticeable on knees and elbows when they're bent over 90 degrees, looking like the
 vertices are collapsing over themselves.

+ 2 - 2
src/MonoScene.Runtime.Scene3D/MonoScene.Runtime.Scene3D.csproj

@@ -12,8 +12,8 @@
   </ItemGroup>  
 
   <ItemGroup>
-    <ProjectReference Include="..\MonoScene.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj" />
-    <ProjectReference Include="..\MonoScene.Model3D\MonoScene.Runtime.Model3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Model3D\MonoScene.Runtime.Model3D.csproj" />
   </ItemGroup>
 
 </Project>

+ 4 - 4
src/MonoScene.sln

@@ -8,13 +8,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo1", "Demo1\Demo1.csproj
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Pipeline.GLTF", "MonoScene.Pipeline.GLTF\MonoScene.Pipeline.GLTF.csproj", "{C8AC1020-FE76-4BF5-9767-F63554900091}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.EffectsPBR", "MonoScene.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj", "{2563EE5B-0B4A-4EC1-BF78-8F270CACAB59}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.EffectsPBR", "MonoScene.Runtime.EffectsPBR\MonoScene.Runtime.EffectsPBR.csproj", "{2563EE5B-0B4A-4EC1-BF78-8F270CACAB59}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Content", "MonoScene.Content3D\MonoScene.Runtime.Content.csproj", "{41112F36-3213-4C3F-8ECC-616F1B502B6A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Content", "MonoScene.Runtime.Content\MonoScene.Runtime.Content.csproj", "{41112F36-3213-4C3F-8ECC-616F1B502B6A}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Model3D", "MonoScene.Model3D\MonoScene.Runtime.Model3D.csproj", "{7B9B380E-D9AA-44F4-ABD3-36255EEE9F4A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Model3D", "MonoScene.Runtime.Model3D\MonoScene.Runtime.Model3D.csproj", "{7B9B380E-D9AA-44F4-ABD3-36255EEE9F4A}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Scene3D", "MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj", "{486A02BA-D0C2-4EBB-B17A-DAC9C32A8C99}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoScene.Runtime.Scene3D", "MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj", "{486A02BA-D0C2-4EBB-B17A-DAC9C32A8C99}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo2", "Demo2\Demo2.csproj", "{B3D0B09B-1DF1-49CB-B84F-6047C49550CC}"
 EndProject

+ 1 - 1
src/MonoSceneViewer/MonoGameViewer.csproj

@@ -15,7 +15,7 @@
   <ItemGroup>
     <ProjectReference Include="..\MonoScene.Pipeline.GLTF\MonoScene.Pipeline.GLTF.csproj" />
     <ProjectReference Include="..\MonoScene.Pipeline.Assimp\MonoScene.Pipeline.Assimp.csproj" />
-    <ProjectReference Include="..\MonoScene.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
+    <ProjectReference Include="..\MonoScene.Runtime.Scene3D\MonoScene.Runtime.Scene3D.csproj" />
 
   </ItemGroup>