Explorar el Código

Complete refactor of runtime classes so they can be used with or without glTF

Vicente Penades hace 5 años
padre
commit
1dd62da88d
Se han modificado 60 ficheros con 4738 adiciones y 29 borrados
  1. BIN
      src/Demo1/Content/WaterBottle.glb
  2. 36 0
      src/Demo1/Demo1.csproj
  3. 80 0
      src/Demo1/Game1.cs
  4. BIN
      src/Demo1/Icon.bmp
  5. BIN
      src/Demo1/Icon.ico
  6. 14 0
      src/Demo1/Program.cs
  7. 43 0
      src/Demo1/app.manifest
  8. BIN
      src/Demo2/Content/CesiumMan.glb
  9. BIN
      src/Demo2/Content/WaterBottle.glb
  10. 40 0
      src/Demo2/Demo2.csproj
  11. 83 0
      src/Demo2/Game1.cs
  12. BIN
      src/Demo2/Icon.bmp
  13. BIN
      src/Demo2/Icon.ico
  14. 14 0
      src/Demo2/Program.cs
  15. 43 0
      src/Demo2/app.manifest
  16. 68 0
      src/MonoGame.Framework.Graphics.GLTF/Converters.cs
  17. 22 0
      src/MonoGame.Framework.Graphics.GLTF/Decoders/CurveDecoders.cs
  18. 114 0
      src/MonoGame.Framework.Graphics.GLTF/Decoders/MeshDecoders.cs
  19. 117 0
      src/MonoGame.Framework.Graphics.GLTF/Factories/ArmatureFactory.GLTF.cs
  20. 119 0
      src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.Basic.cs
  21. 283 0
      src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.GLTF.cs
  22. 89 0
      src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.PBR.cs
  23. 33 0
      src/MonoGame.Framework.Graphics.GLTF/Factories/TextureFactory.GLTF.cs
  24. 62 0
      src/MonoGame.Framework.Graphics.GLTF/FormatGLTF.cs
  25. 18 0
      src/MonoGame.Framework.Graphics.GLTF/MonoGame.Framework.Pipeline.GLTF.csproj
  26. 2 2
      src/MonoGame.Framework.Graphics.PBR/Effects/AnimatedEffect.cs
  27. 1 1
      src/MonoGame.Framework.Graphics.PBR/Effects/UnlitEffect.cs
  28. 10 0
      src/MonoGame.Framework.Graphics.PBR/Resources.BRDF.cs
  29. 19 7
      src/MonoGame.Framework.Graphics.PBR/Resources.cs
  30. 195 0
      src/MonoGame.Framework.Graphics.Toolkit3D/AffineTransform.cs
  31. 77 0
      src/MonoGame.Framework.Graphics.Toolkit3D/AnimatableProperty.cs
  32. 40 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/GraphicsResourceTracker.cs
  33. 132 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/Mesh.cs
  34. 56 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/MeshCollection.cs
  35. 121 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/MeshPart.cs
  36. 142 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/VertexTypes.cs
  37. 157 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelDrawingContext.cs
  38. 18 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/AnimationTrackInfo.cs
  39. 142 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ArmatureInstance.cs
  40. 70 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ArmatureTemplate.cs
  41. 26 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/DrawableInstance.cs
  42. 160 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/DrawableTemplate.cs
  43. 415 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/MeshTransforms.cs
  44. 292 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelLayerInstance.cs
  45. 85 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelLayerTemplate.cs
  46. 74 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelTemplate.cs
  47. 125 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/NodeInstance.cs
  48. 176 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/NodeTemplate.cs
  49. 30 0
      src/MonoGame.Framework.Graphics.Toolkit3D/ICurveEvaluator.cs
  50. 22 0
      src/MonoGame.Framework.Graphics.Toolkit3D/MonoGame.Framework.Graphics.Toolkit3D.csproj
  51. 109 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/ArmatureFactory.cs
  52. 99 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshDecoder.cs
  53. 119 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshFactory.cs
  54. 207 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshPrimitiveBuilder.cs
  55. 106 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/TextureFactory.cs
  56. 200 0
      src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/VertexDecoder.cs
  57. 24 0
      src/MonoGameScene.sln
  58. 7 11
      src/MonoGameViewer/MainScene.cs
  59. 1 2
      src/MonoGameViewer/MonoGameViewer.csproj
  60. 1 6
      src/SharpGLTF.Runtime.MonoGame/Content/LoaderContext.PBREffects.cs

BIN
src/Demo1/Content/WaterBottle.glb


+ 36 - 0
src/Demo1/Demo1.csproj

@@ -0,0 +1,36 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>Icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Content\WaterBottle.glb" />
+    <None Remove="Icon.ico" />
+    <None Remove="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Content\WaterBottle.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Icon.ico" />
+    <EmbeddedResource Include="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <TrimmerRootAssembly Include="Microsoft.Xna.Framework.Content.ContentTypeReader" Visible="false" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.GLTF\MonoGame.Framework.Pipeline.GLTF.csproj" />
+  </ItemGroup>
+</Project>

+ 80 - 0
src/Demo1/Game1.cs

@@ -0,0 +1,80 @@
+using System;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Demo1
+{
+    public class Game1 : Game
+    {
+        #region lifecycle
+        public Game1()
+        {
+            _Graphics = new GraphicsDeviceManager(this);            
+            IsMouseVisible = true;
+        }
+
+        protected override void Initialize()
+        {
+            base.Initialize();
+        }
+
+        protected override void LoadContent()
+        {
+            var gltfModel = SharpGLTF.Schema2.ModelRoot.Load("Content\\WaterBottle.glb");
+
+            var factory = new Microsoft.Xna.Framework.Content.Pipeline.Graphics.PBRMeshFactory(this.GraphicsDevice);
+
+            _MeshCollection = factory.CreateMeshCollection(gltfModel.LogicalMeshes);
+        }
+
+        protected override void UnloadContent()
+        {
+            base.UnloadContent();
+
+            _MeshCollection?.Dispose();
+            _MeshCollection = null;            
+        }
+
+        #endregion
+
+        #region data
+
+        private GraphicsDeviceManager _Graphics;
+
+        private PBREnvironment _LightsAndFog = PBREnvironment.CreateDefault();
+
+        private MeshCollection _MeshCollection;        
+
+        #endregion
+
+        #region Game Loop
+
+        protected override void Update(GameTime gameTime)
+        {
+            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();            
+
+            base.Update(gameTime);
+        }
+
+        protected override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice.Clear(Color.CornflowerBlue);            
+
+            var camPos = Vector3.Zero;
+            var mdlPos = new Vector3(0.5f, 0, 0);
+
+            var camX = Matrix.CreateWorld(Vector3.Zero, mdlPos - camPos, Vector3.UnitY);
+            var mdlX = Matrix.CreateRotationY(0.25f * (float)gameTime.TotalGameTime.TotalSeconds) * Matrix.CreateTranslation(mdlPos);
+
+            var dc = new ModelDrawingContext(_Graphics.GraphicsDevice);
+            dc.SetCamera(camX);
+            dc.DrawMesh(_LightsAndFog, _MeshCollection[0], mdlX);
+
+            base.Draw(gameTime);
+        }
+
+        #endregion
+    }
+}

BIN
src/Demo1/Icon.bmp


BIN
src/Demo1/Icon.ico


+ 14 - 0
src/Demo1/Program.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Demo1
+{
+    public static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            using (var game = new Game1())
+                game.Run();
+        }
+    }
+}

+ 43 - 0
src/Demo1/app.manifest

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="Demo1"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+    </windowsSettings>
+  </application>
+
+</assembly>

BIN
src/Demo2/Content/CesiumMan.glb


BIN
src/Demo2/Content/WaterBottle.glb


+ 40 - 0
src/Demo2/Demo2.csproj

@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>Icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Content\CesiumMan.glb" />
+    <None Remove="Content\WaterBottle.glb" />
+    <None Remove="Icon.ico" />
+    <None Remove="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Content\CesiumMan.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\WaterBottle.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Icon.ico" />
+    <EmbeddedResource Include="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <TrimmerRootAssembly Include="Microsoft.Xna.Framework.Content.ContentTypeReader" Visible="false" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.GLTF\MonoGame.Framework.Pipeline.GLTF.csproj" />
+  </ItemGroup>
+</Project>

+ 83 - 0
src/Demo2/Game1.cs

@@ -0,0 +1,83 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Demo2
+{
+    public class Game1 : Game
+    {
+        #region lifecycle
+        public Game1()
+        {
+            _Graphics = new GraphicsDeviceManager(this);
+            IsMouseVisible = true;
+        }
+
+        protected override void Initialize()
+        {
+            base.Initialize();
+        }
+
+        protected override void LoadContent()
+        {
+            _ModelTemplate = Microsoft.Xna.Framework.Content.Pipeline.Graphics.FormatGLTF.LoadModel("Content\\CesiumMan.glb", this.GraphicsDevice);            
+        }
+
+        protected override void UnloadContent()
+        {
+            base.UnloadContent();
+
+            _ModelTemplate?.Dispose();
+            _ModelTemplate = null;
+        }
+
+        #endregion
+
+        #region data
+
+        private GraphicsDeviceManager _Graphics;
+
+        private PBREnvironment _LightsAndFog = PBREnvironment.CreateDefault();
+
+        private ModelTemplate _ModelTemplate;
+
+        #endregion
+
+        #region Game Loop
+
+        private ModelLayerInstance _ModelView1;
+
+        protected override void Update(GameTime gameTime)
+        {
+            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
+
+            if (_ModelView1 == null) _ModelView1 = _ModelTemplate.DefaultLayer.CreateInstance();
+
+            var mdlPos = new Vector3(3.5f, 0, 0);
+
+            _ModelView1.WorldMatrix = Matrix.CreateRotationY(0.25f * (float)gameTime.TotalGameTime.TotalSeconds) * Matrix.CreateTranslation(mdlPos);
+            _ModelView1.Armature.SetAnimationFrame(0, (float)gameTime.TotalGameTime.TotalSeconds);
+
+            base.Update(gameTime);
+        }
+
+        protected override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice.Clear(Color.CornflowerBlue);
+
+            var camPos = Vector3.Zero;            
+
+            var camX = Matrix.CreateWorld(Vector3.Zero, _ModelView1.WorldBounds.Center - camPos, Vector3.UnitY);
+
+            var dc = new ModelDrawingContext(_Graphics.GraphicsDevice);
+
+            dc.SetCamera(camX);
+
+            dc.DrawModelInstance(_LightsAndFog, _ModelView1);
+
+            base.Draw(gameTime);
+        }
+
+        #endregion
+    }
+}

BIN
src/Demo2/Icon.bmp


BIN
src/Demo2/Icon.ico


+ 14 - 0
src/Demo2/Program.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Demo2
+{
+    public static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            using (var game = new Game1())
+                game.Run();
+        }
+    }
+}

+ 43 - 0
src/Demo2/app.manifest

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="Demo2"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+    </windowsSettings>
+  </application>
+
+</assembly>

+ 68 - 0
src/MonoGame.Framework.Graphics.GLTF/Converters.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Graphics.Graphics.ModelGraph;
+
+using SharpGLTF.Runtime;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    static class Converters
+    {
+        public static Vector2 ToXna(this System.Numerics.Vector2 v) { return new Vector2(v.X, v.Y); }
+
+        public static Vector3 ToXna(this System.Numerics.Vector3 v) { return new Vector3(v.X, v.Y, v.Z); }
+
+        public static Vector4 ToXna(this System.Numerics.Vector4 v) { return new Vector4(v.X, v.Y, v.Z, v.W); }
+
+        public static Quaternion ToXna(this System.Numerics.Quaternion q) { return new Quaternion(q.X, q.Y, q.Z, q.W); }
+
+        public static BoundingSphere ToXna(this (System.Numerics.Vector3 center, float radius) sphere)
+        {
+            return new BoundingSphere(sphere.center.ToXna(), sphere.radius);
+        }
+
+        public static Matrix ToXna(this System.Numerics.Matrix4x4 m)
+        {
+            return new Matrix
+                (m.M11,m.M12,m.M13,m.M14
+                ,m.M21,m.M22,m.M23,m.M24
+                ,m.M31,m.M32,m.M33,m.M34
+                ,m.M41,m.M42,m.M43,m.M44);
+        }
+
+        public static AffineTransform ToXna(this SharpGLTF.Transforms.AffineTransform xform)
+        {
+            return new AffineTransform(xform.Scale.ToXna(), xform.Rotation.ToXna(), xform.Translation.ToXna());
+        }
+
+        public static TextureAddressMode ToXna(this SharpGLTF.Schema2.TextureWrapMode mode)
+        {
+            switch (mode)
+            {
+                case SharpGLTF.Schema2.TextureWrapMode.CLAMP_TO_EDGE: return TextureAddressMode.Clamp;
+                case SharpGLTF.Schema2.TextureWrapMode.MIRRORED_REPEAT: return TextureAddressMode.Mirror;
+                default: return TextureAddressMode.Wrap;
+            }
+        }
+
+        public static ICurveEvaluator<Vector3> ToXna(this SharpGLTF.Animations.ICurveSampler<System.Numerics.Vector3> curve)
+        {
+            if (curve == null) return null;
+            return new _GltfSamplerVector3(curve);
+        }
+
+        public static ICurveEvaluator<Quaternion> ToXna(this SharpGLTF.Animations.ICurveSampler<System.Numerics.Quaternion> curve)
+        {
+            if (curve == null) return null;
+            return new _GltfSamplerQuaternion(curve);
+        }
+
+           
+
+            
+    }
+}

+ 22 - 0
src/MonoGame.Framework.Graphics.GLTF/Decoders/CurveDecoders.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    readonly struct _GltfSamplerVector3 : ICurveEvaluator<Vector3>
+    {
+        public _GltfSamplerVector3(SharpGLTF.Animations.ICurveSampler<System.Numerics.Vector3> source) { _Source = source; }
+
+        private readonly SharpGLTF.Animations.ICurveSampler<System.Numerics.Vector3> _Source;
+        public Vector3 Evaluate(float offset) { return _Source.GetPoint(offset).ToXna(); }
+    }
+
+    readonly struct _GltfSamplerQuaternion : ICurveEvaluator<Quaternion>
+    {
+        public _GltfSamplerQuaternion(SharpGLTF.Animations.ICurveSampler<System.Numerics.Quaternion> source) { _Source = source; }
+
+        private readonly SharpGLTF.Animations.ICurveSampler<System.Numerics.Quaternion> _Source;
+        public Quaternion Evaluate(float offset) { return _Source.GetPoint(offset).ToXna(); }
+    }
+}

+ 114 - 0
src/MonoGame.Framework.Graphics.GLTF/Decoders/MeshDecoders.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    readonly struct _MeshDecoder<TMaterial> : IMeshDecoder<TMaterial>
+        where TMaterial : class
+    {
+        #region constructor
+        public _MeshDecoder(SharpGLTF.Runtime.IMeshDecoder<TMaterial> mesh)
+        {
+            _Source = mesh;
+            _Primitives = mesh.Primitives.Select(item => _MeshPrimitiveDecoder<TMaterial>.Create(item)).ToArray();
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly SharpGLTF.Runtime.IMeshDecoder<TMaterial> _Source;
+        private readonly IMeshPrimitiveDecoder<TMaterial>[] _Primitives;
+
+        #endregion
+
+        #region properties
+
+        public string Name => _Source.Name;       
+
+        public IReadOnlyList<IMeshPrimitiveDecoder<TMaterial>> Primitives => _Primitives;
+
+        public object Tag => null; // might return Extras.
+
+        #endregion
+    }
+
+    readonly struct _MeshPrimitiveDecoder<TMaterial> : IMeshPrimitiveDecoder<TMaterial>
+    where TMaterial:class
+    {
+        #region constructor
+
+        public static IMeshPrimitiveDecoder<TMaterial> Create(SharpGLTF.Runtime.IMeshPrimitiveDecoder<TMaterial> primitive)
+        {
+            return new _MeshPrimitiveDecoder<TMaterial>(primitive);
+        }
+        public _MeshPrimitiveDecoder(SharpGLTF.Runtime.IMeshPrimitiveDecoder<TMaterial> primitive)
+        {
+            _Source = primitive;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly SharpGLTF.Runtime.IMeshPrimitiveDecoder<TMaterial> _Source;
+
+        #endregion
+
+        #region properties
+
+        public TMaterial Material => _Source.Material;
+
+        public int VertexCount => _Source.VertexCount;
+
+        public int MorphTargetsCount => 0;
+
+        public int ColorsCount => _Source.ColorsCount;
+
+        public int TexCoordsCount => _Source.TexCoordsCount;
+
+        public int JointsWeightsCount => _Source.JointsWeightsCount == 0 ? 0 : 4;
+
+        public IEnumerable<(int A, int B)> LineIndices => Enumerable.Empty<(int A, int B)>();
+
+        public IEnumerable<(int A, int B, int C)> TriangleIndices => _Source.TriangleIndices;
+
+        #endregion
+
+        #region vertex API
+
+        public Vector3 GetPosition(int vertexIndex) { return _Source.GetPosition(vertexIndex).ToXna(); }
+        public Vector3 GetNormal(int vertexIndex) { return _Source.GetNormal(vertexIndex).ToXna(); }
+        public Vector4 GetTangent(int vertexIndex) { return _Source.GetTangent(vertexIndex).ToXna(); }
+        public VertexSkinning GetSkinWeights(int vertexIndex)
+        {
+            var sparse = _Source
+                .GetSkinWeights(vertexIndex)
+                .GetReducedWeights(4);
+
+            var indices = new Vector4(sparse.Index0, sparse.Index1, sparse.Index2, sparse.Index3);
+            var weights = new Vector4(sparse.Weight0, sparse.Weight1, sparse.Weight2, sparse.Weight3);
+
+            return new VertexSkinning
+            {
+                Indices = new Framework.Graphics.PackedVector.Short4(indices),
+                Weights = weights
+            };
+        }
+
+        
+
+        public Vector4 GetColor(int vertexIndex, int colorSetIndex) { return _Source.GetColor(vertexIndex, colorSetIndex).ToXna(); }
+        public Vector2 GetTextureCoord(int vertexIndex, int textureSetIndex) { return _Source.GetTextureCoord(vertexIndex, textureSetIndex).ToXna(); }
+
+
+        public IReadOnlyList<Vector3> GetNormalDeltas(int vertexIndex) { throw new NotImplementedException(); }
+        public IReadOnlyList<Vector3> GetPositionDeltas(int vertexIndex) { throw new NotImplementedException(); }
+        public IReadOnlyList<Vector3> GetTangentDeltas(int vertexIndex) { throw new NotImplementedException(); }
+
+        #endregion
+    }
+}

+ 117 - 0
src/MonoGame.Framework.Graphics.GLTF/Factories/ArmatureFactory.GLTF.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using GLTFNODE = SharpGLTF.Schema2.Node;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    class GLTFArmatureFactory : ArmatureFactory<GLTFNODE>
+    {
+        #region overrides
+
+        protected override string GetName(GLTFNODE node) { return node.Name; }
+
+        protected override IEnumerable<GLTFNODE> GetChildren(GLTFNODE node) { return node.VisualChildren; }
+
+        protected override Matrix GetLocalMatrix(GLTFNODE node) { return node.LocalMatrix.ToXna(); }        
+
+        protected override AnimatableProperty<Vector3> GetScale(GLTFNODE node)
+        {
+            var lxform = node.LocalTransform.ToXna();
+
+            var s = new AnimatableProperty<Vector3>(lxform.Scale);
+
+            foreach (var anim in node.LogicalParent.LogicalAnimations)
+            {
+                var sAnim = anim.FindScaleSampler(node)?.CreateCurveSampler(true).ToXna();
+                if (sAnim != null) s.SetCurve(anim.LogicalIndex, sAnim);
+            }
+
+            return s;
+        }
+
+        protected override AnimatableProperty<Quaternion> GetRotation(GLTFNODE node)
+        {
+            var lxform = node.LocalTransform.ToXna();
+
+            var r = new AnimatableProperty<Quaternion>(lxform.Rotation);
+
+            foreach (var anim in node.LogicalParent.LogicalAnimations)
+            {
+                var rAnim = anim.FindRotationSampler(node)?.CreateCurveSampler(true).ToXna();
+                if (rAnim != null) r.SetCurve(anim.LogicalIndex, rAnim);
+            }
+
+            return r;
+        }
+
+        protected override AnimatableProperty<Vector3> GetTranslation(GLTFNODE node)
+        {
+            var lxform = node.LocalTransform.ToXna();
+
+            var t = new AnimatableProperty<Vector3>(lxform.Translation);
+
+            foreach (var anim in node.LogicalParent.LogicalAnimations)
+            {
+                var tAnim = anim.FindTranslationSampler(node)?.CreateCurveSampler(true).ToXna();
+                if (tAnim != null) t.SetCurve(anim.LogicalIndex, tAnim);
+            }
+
+            return t;
+        }
+
+        #endregion
+
+        #region API
+
+        public ModelLayerTemplate CreateModelLayer(SharpGLTF.Schema2.Scene scene)
+        {
+            AddSceneRoot(scene);
+            var armature = CreateArmature();
+
+            var drawables = GLTFNODE.Flatten(scene)
+                .Where(item => item.Mesh != null)
+                .Select(item => CreateDrawable(item))
+                .ToArray();
+
+            return new ModelLayerTemplate(scene.Name, armature, drawables);
+        }
+
+        public void AddSceneRoot(SharpGLTF.Schema2.Scene scene)
+        {
+            foreach (var root in scene.VisualChildren)
+            {
+                AddRoot(root);
+            }
+        }
+
+        public IDrawableTemplate CreateDrawable(GLTFNODE node)
+        {
+            if (node == null) throw new ArgumentNullException(nameof(GLTFNODE));
+            if (node.Mesh == null) throw new ArgumentNullException(nameof(GLTFNODE.Mesh));
+
+            if (node.Skin == null)
+            {
+                return CreateRigidDrawable(node.Mesh.LogicalIndex, node);
+            }
+            else
+            {
+                var bones = new (GLTFNODE, Matrix)[node.Skin.JointsCount];
+                for (int i = 0; i < bones.Length; ++i)
+                {
+                    var (joint, inverseBindMatrix) = node.Skin.GetJoint(i);
+
+                    bones[i] = (joint, inverseBindMatrix.ToXna());
+                }
+
+                return CreateSkinnedDrawable(node.Mesh.LogicalIndex, node, bones);
+            }
+        }        
+
+        #endregion
+    }
+}

+ 119 - 0
src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.Basic.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using GLTFMATERIAL = SharpGLTF.Schema2.Material;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public class BasicMeshFactory : GLTFMeshFactory
+    {
+        public BasicMeshFactory(GraphicsDevice device) : base(device)
+        {
+        }
+
+        // Monogame's BasicEffect uses Phong's shading, while glTF uses PBR shading, so
+        // given monogame's limitations, we try to guess the most appropiate values
+        // to have a reasonably good looking renders.
+
+        protected override Type GetPreferredVertexType(IMeshPrimitiveDecoder<GLTFMATERIAL> srcPrim)
+        {
+            return base.GetPreferredVertexType(srcPrim);
+        }
+
+        protected override MeshPrimitiveMaterial ConvertMaterial(GLTFMATERIAL srcMaterial, bool mustSupportSkinning)
+        {
+            if (srcMaterial == null) srcMaterial = GetDefaultMaterial();
+
+            var effect = mustSupportSkinning ? CreateSkinnedEffect(srcMaterial) : CreateRigidEffect(srcMaterial);
+
+            return new MeshPrimitiveMaterial
+            {
+                Effect = effect,
+                DoubleSided = srcMaterial.DoubleSided,
+                Blend = BlendState.Opaque
+            };
+        }
+
+        #region effects creation
+        
+
+        protected virtual Effect CreateRigidEffect(GLTFMATERIAL srcMaterial)
+        {
+            var dstMaterial = srcMaterial.Alpha == SharpGLTF.Schema2.AlphaMode.MASK
+                ? CreateAlphaTestEffect(srcMaterial)
+                : CreateBasicEffect(srcMaterial);
+
+            return dstMaterial;
+        }
+
+        protected virtual Effect CreateBasicEffect(GLTFMATERIAL srcMaterial)
+        {
+            var dstMaterial = new BasicEffect(Device);
+
+            dstMaterial.Name = srcMaterial.Name;
+
+            dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
+            dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
+            dstMaterial.SpecularColor = GetSpecularColor(srcMaterial);
+            dstMaterial.SpecularPower = GetSpecularPower(srcMaterial);
+            dstMaterial.EmissiveColor = GeEmissiveColor(srcMaterial);
+            dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
+
+            if (srcMaterial.Unlit)
+            {
+                dstMaterial.EmissiveColor = dstMaterial.DiffuseColor;
+                dstMaterial.SpecularColor = Vector3.Zero;
+                dstMaterial.SpecularPower = 16;
+            }
+
+            dstMaterial.PreferPerPixelLighting = true;
+            dstMaterial.TextureEnabled = dstMaterial.Texture != null;
+
+            return dstMaterial;
+        }
+
+        protected virtual Effect CreateAlphaTestEffect(GLTFMATERIAL srcMaterial)
+        {
+            var dstMaterial = new AlphaTestEffect(Device);
+
+            dstMaterial.Name = srcMaterial.Name;
+
+            dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
+            //dstMaterial.AlphaFunction = CompareFunction.GreaterEqual;
+            dstMaterial.ReferenceAlpha = (int)(srcMaterial.AlphaCutoff * 255);
+
+            dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
+
+            dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
+
+            return dstMaterial;
+        }
+
+        protected virtual Effect CreateSkinnedEffect(GLTFMATERIAL srcMaterial)
+        {
+            var dstMaterial = new SkinnedEffect(Device);
+
+            dstMaterial.Name = srcMaterial.Name;
+
+            dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
+            dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
+            dstMaterial.SpecularColor = GetSpecularColor(srcMaterial);
+            dstMaterial.SpecularPower = GetSpecularPower(srcMaterial);
+            dstMaterial.EmissiveColor = GeEmissiveColor(srcMaterial);
+            dstMaterial.Texture = UseDiffuseTexture(srcMaterial);
+
+            dstMaterial.WeightsPerVertex = 4;
+            dstMaterial.PreferPerPixelLighting = true;
+
+            // apparently, SkinnedEffect does not support disabling textures, so we set a white texture here.
+            if (dstMaterial.Texture == null) dstMaterial.Texture = UseTexture(null); // creates a dummy white texture.
+
+            return dstMaterial;
+        }
+
+        #endregion
+    }
+}

+ 283 - 0
src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.GLTF.cs

@@ -0,0 +1,283 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using GLTFMATERIAL = SharpGLTF.Schema2.Material;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public abstract class GLTFMeshFactory : MeshFactory<GLTFMATERIAL>
+    {
+        #region lifecycle
+
+        protected GLTFMeshFactory(GraphicsDevice device) : base(device)
+        {
+            _TextureFactory = new GLTFTextureFactory(device);
+            _SolidTextureFactory = new SolidColorTextureFactory(device);
+        }
+
+        private readonly GLTFTextureFactory _TextureFactory;
+        private readonly SolidColorTextureFactory _SolidTextureFactory;
+
+        private static SharpGLTF.Schema2.ModelRoot _DummyModel;
+
+        #endregion
+
+        #region mesh converters
+
+        public MeshCollection CreateMeshCollection(IEnumerable<SharpGLTF.Schema2.Mesh> logicalMeshes)
+        {
+            var meshes = logicalMeshes
+                .Select(item => new _MeshDecoder<GLTFMATERIAL>( SharpGLTF.Runtime.MeshDecoder.Decode(item)))
+                .Cast<IMeshDecoder<GLTFMATERIAL>>()
+                .ToArray();
+
+            return CreateMeshCollection(meshes);
+        }
+
+        #endregion
+
+        #region texture helpers        
+
+        protected Texture2D UseTexture(SharpGLTF.Schema2.Texture texture)
+        {
+            return _TextureFactory.UseTexture(texture.PrimaryImage.Content, texture.Name);
+        }
+
+        protected Texture2D UseTexture(Color color)
+        {
+            return _SolidTextureFactory.UseTexture(color);
+        }
+
+        protected SamplerState UseSampler(SharpGLTF.Schema2.TextureSampler sampler)
+        {
+            if (sampler == null) return SamplerState.LinearWrap;
+
+            return _TextureFactory.UseSampler(sampler.WrapS.ToXna(), sampler.WrapS.ToXna());
+        }
+
+        #endregion
+
+        #region gltf basic helpers
+
+        protected GLTFMATERIAL GetDefaultMaterial()
+        {
+            if (_DummyModel == null)  // !=
+            {
+                _DummyModel = SharpGLTF.Schema2.ModelRoot.CreateModel();
+                _DummyModel.CreateMaterial("Default");
+            }
+
+            return _DummyModel.LogicalMaterials[0];
+        }
+
+        
+
+        protected static float GetAlphaLevel(GLTFMATERIAL srcMaterial)
+        {
+            if (srcMaterial.Alpha == SharpGLTF.Schema2.AlphaMode.OPAQUE) return 1;
+
+            var baseColor = srcMaterial.FindChannel("BaseColor");
+
+            if (baseColor == null) return 1;
+
+            return baseColor.Value.Parameter.W;
+        }
+
+        protected static Vector3 GetDiffuseColor(GLTFMATERIAL srcMaterial)
+        {
+            var diffuse = srcMaterial.FindChannel("Diffuse");
+
+            if (diffuse == null) diffuse = srcMaterial.FindChannel("BaseColor");
+
+            if (diffuse == null) return Vector3.One;
+
+            return new Vector3(diffuse.Value.Parameter.X, diffuse.Value.Parameter.Y, diffuse.Value.Parameter.Z);
+        }
+
+        protected static Vector3 GetSpecularColor(GLTFMATERIAL srcMaterial)
+        {
+            var mr = srcMaterial.FindChannel("MetallicRoughness");
+
+            if (mr == null) return Vector3.One; // default value 16
+
+            var diffuse = GetDiffuseColor(srcMaterial);
+            var metallic = mr.Value.Parameter.X;
+            var roughness = mr.Value.Parameter.Y;
+
+            var k = Vector3.Zero;
+            k += Vector3.Lerp(diffuse, Vector3.Zero, roughness);
+            k += Vector3.Lerp(diffuse, Vector3.One, metallic);
+            k *= 0.5f;
+
+            return k;
+        }
+
+        protected static float GetSpecularPower(GLTFMATERIAL srcMaterial)
+        {
+            var mr = srcMaterial.FindChannel("MetallicRoughness");
+
+            if (mr == null) return 16; // default value = 16
+
+            var metallic = mr.Value.Parameter.X;
+            var roughness = mr.Value.Parameter.Y;
+
+            return 4 + 16 * metallic;
+        }
+
+        protected static Vector3 GeEmissiveColor(GLTFMATERIAL srcMaterial)
+        {
+            var emissive = srcMaterial.FindChannel("Emissive");
+
+            if (emissive == null) return Vector3.Zero;
+
+            return new Vector3(emissive.Value.Parameter.X, emissive.Value.Parameter.Y, emissive.Value.Parameter.Z);
+        }
+
+        protected Texture2D UseDiffuseTexture(GLTFMATERIAL srcMaterial)
+        {
+            var diffuse = srcMaterial.FindChannel("Diffuse");
+
+            if (diffuse == null) diffuse = srcMaterial.FindChannel("BaseColor");
+            if (diffuse == null) return null;
+            if (diffuse.Value.Texture == null) return null;
+
+            return UseTexture(diffuse.Value.Texture);
+        }
+
+        #endregion
+
+        #region gltf advanced helpers
+
+        protected void TransferChannel(EffectTexture2D.Scalar1 dst, GLTFMATERIAL src, string name, float defval)
+        {
+            dst.Texture = UseChannelTexture(src, name);
+            dst.Sampler = UseChannelSampler(src, name);
+            dst.Scale = GetScaler(src, name, defval);
+            dst.SetIndex = GetTextureSet(src, name);
+            dst.Transform = GetTransform(src, name);
+        }
+
+        protected void TransferChannel(EffectTexture2D.Scalar2 dst, GLTFMATERIAL src, string name, Vector2 defval)
+        {
+            dst.Texture = UseChannelTexture(src, name);
+            dst.Sampler = UseChannelSampler(src, name);
+            dst.Scale = GetScaler(src, name, defval);
+            dst.SetIndex = GetTextureSet(src, name);
+            dst.Transform = GetTransform(src, name);
+        }
+
+        protected void TransferChannel(EffectTexture2D.Scalar3 dst, GLTFMATERIAL src, string name, Vector3 defval)
+        {
+            dst.Texture = UseChannelTexture(src, name);
+            dst.Sampler = UseChannelSampler(src, name);
+            dst.Scale = GetScaler(src, name, defval);
+            dst.SetIndex = GetTextureSet(src, name);
+            dst.Transform = GetTransform(src, name);
+        }
+
+        protected void TransferChannel(EffectTexture2D.Scalar4 dst, GLTFMATERIAL src, string name, Vector4 defval)
+        {
+            dst.Texture = UseChannelTexture(src, name);
+            dst.Sampler = UseChannelSampler(src, name);
+            dst.Scale = GetScaler(src, name, defval);
+            dst.SetIndex = GetTextureSet(src, name);
+            dst.Transform = GetTransform(src, name);
+        }
+
+        protected float GetScaler(GLTFMATERIAL srcMaterial, string name, float defval)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return defval;
+            var param = channel.Value.Parameter;
+
+            return param.X;
+        }
+
+        protected (Vector3 u, Vector3 v) GetTransform(GLTFMATERIAL srcMaterial, string name)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return (Vector3.UnitX, Vector3.UnitY);
+
+            if (channel.Value.TextureTransform == null) return (Vector3.UnitX, Vector3.UnitY);
+
+            var S = System.Numerics.Matrix3x2.CreateScale(channel.Value.TextureTransform.Scale);
+            var R = System.Numerics.Matrix3x2.CreateRotation(-channel.Value.TextureTransform.Rotation);
+            var T = System.Numerics.Matrix3x2.CreateTranslation(channel.Value.TextureTransform.Offset);
+
+            var X = S * R * T;
+
+            return (new Vector3(X.M11, X.M21, X.M31), new Vector3(X.M12, X.M22, X.M32));
+        }
+
+        protected int GetTextureSet(GLTFMATERIAL srcMaterial, string name)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return 0;
+
+            if (channel.Value.TextureTransform == null) return channel.Value.TextureCoordinate;
+
+            return channel.Value.TextureTransform.TextureCoordinateOverride ?? channel.Value.TextureCoordinate;
+        }
+
+        protected Vector2 GetScaler(GLTFMATERIAL srcMaterial, string name, Vector2 defval)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return defval;
+            var param = channel.Value.Parameter;
+
+            return new Vector2(param.X, param.Y);
+        }
+
+        protected Vector3 GetScaler(GLTFMATERIAL srcMaterial, string name, Vector3 defval)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return defval;
+            var param = channel.Value.Parameter;
+
+            return new Vector3(param.X, param.Y, param.Z);
+        }
+
+        protected Vector4 GetScaler(GLTFMATERIAL srcMaterial, string name, Vector4 defval)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return defval;
+            var param = channel.Value.Parameter;
+
+            return new Vector4(param.X, param.Y, param.Z, param.W);
+        }
+
+        protected Texture2D UseChannelTexture(GLTFMATERIAL srcMaterial, string channelName)
+        {
+            var channel = srcMaterial.FindChannel(channelName);
+
+            if (!channel.HasValue) return null;
+            if (channel.Value.Texture == null) return null;
+            if (channel.Value.Texture.PrimaryImage == null) return null;
+            if (channel.Value.Texture.PrimaryImage.Content.IsEmpty) return null;
+
+            return UseTexture(channel.Value.Texture);
+        }
+
+        protected SamplerState UseChannelSampler(GLTFMATERIAL srcMaterial, string name)
+        {
+            var channel = srcMaterial.FindChannel(name);
+
+            if (!channel.HasValue) return null;
+            if (channel.Value.Texture == null) return null;
+
+            return UseSampler(channel.Value.TextureSampler);
+        }
+
+        #endregion
+    }
+}

+ 89 - 0
src/MonoGame.Framework.Graphics.GLTF/Factories/MeshFactory.PBR.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using GLTFMATERIAL = SharpGLTF.Schema2.Material;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public class PBRMeshFactory : GLTFMeshFactory
+    {
+        public PBRMeshFactory(GraphicsDevice device) : base(device) { }
+
+        protected override MeshPrimitiveMaterial ConvertMaterial(GLTFMATERIAL srcMaterial, bool isSkinned)
+        {
+            if (srcMaterial == null) srcMaterial = GetDefaultMaterial();
+
+            var effect = CreateEffect(srcMaterial, isSkinned);            
+
+            var blending = BlendState.Opaque;            
+
+            if (srcMaterial.Alpha == SharpGLTF.Schema2.AlphaMode.BLEND)
+            {
+                blending = BlendState.NonPremultiplied;
+                effect.AlphaBlend = true;
+            }
+
+            if (srcMaterial.Alpha == SharpGLTF.Schema2.AlphaMode.MASK)
+            {
+                effect.AlphaCutoff = srcMaterial.AlphaCutoff;
+            }            
+
+            if (effect is PBREffect pbrEffect)
+            {
+                pbrEffect.NormalMode = srcMaterial.DoubleSided ? GeometryNormalMode.DoubleSided : GeometryNormalMode.Reverse;
+            }
+
+            var material = new MeshPrimitiveMaterial();
+
+            material.Effect = effect;
+            material.DoubleSided = srcMaterial.DoubleSided;
+            material.Blend = blending;            
+
+            return material;
+        }
+
+        private AnimatedEffect CreateEffect(GLTFMATERIAL srcMaterial, bool isSkinned)
+        {
+            if (srcMaterial.Unlit)
+            {
+                var ueffect = new UnlitEffect(this.Device);
+
+                TransferChannel(ueffect.BaseColorMap, srcMaterial, "BaseColor", Vector4.One);
+                TransferChannel(ueffect.EmissiveMap, srcMaterial, "Emissive", Vector3.Zero);
+                TransferChannel(ueffect.OcclusionMap, srcMaterial, "Occlusion", 0);
+                if (ueffect.OcclusionMap.Texture == null) ueffect.OcclusionMap.Scale = 0;
+
+                return ueffect;
+            }
+
+            PBREffect effect = null;
+
+            if (srcMaterial.FindChannel("SpecularGlossiness") != null)
+            {
+                var xeffect = new PBRSpecularGlossinessEffect(this.Device);
+                effect = xeffect;
+
+                TransferChannel(xeffect.DiffuseMap, srcMaterial, "Diffuse", Vector4.One);
+                TransferChannel(xeffect.SpecularGlossinessMap, srcMaterial, "SpecularGlossiness", Vector4.Zero);
+            }
+            else
+            {
+                var xeffect = new PBRMetallicRoughnessEffect(this.Device);
+                effect = xeffect;
+
+                TransferChannel(xeffect.BaseColorMap, srcMaterial, "BaseColor", Vector4.One);
+                TransferChannel(xeffect.MetalRoughnessMap, srcMaterial, "MetallicRoughness", Vector2.One);
+            }
+
+            TransferChannel(effect.NormalMap, srcMaterial, "Normal", 1);
+            TransferChannel(effect.EmissiveMap, srcMaterial, "Emissive", Vector3.Zero);
+            TransferChannel(effect.OcclusionMap, srcMaterial, "Occlusion", 0);
+            if (effect.OcclusionMap.Texture == null) effect.OcclusionMap.Scale = 0;            
+
+            return effect;
+        }
+    }
+}

+ 33 - 0
src/MonoGame.Framework.Graphics.GLTF/Factories/TextureFactory.GLTF.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+
+using GLTFIMAGE = SharpGLTF.Memory.MemoryImage;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    class GLTFTextureFactory : TextureFactory<GLTFIMAGE>
+    {
+        public GLTFTextureFactory(GraphicsDevice device)
+            : base(device) { }
+
+        protected override Texture2D ConvertTexture(GLTFIMAGE image)
+        {
+            if (Device == null) throw new InvalidOperationException();
+
+            if (!image.IsValid) return null;
+
+            using (var m = image.Open())
+            {
+                var tex = Texture2D.FromStream(Device, m);               
+
+                // tex.Name = name;                
+
+                return tex;
+            }
+        }
+    }
+}

+ 62 - 0
src/MonoGame.Framework.Graphics.GLTF/FormatGLTF.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using SharpGLTF.Runtime;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public static class FormatGLTF
+    {
+        public static ModelTemplate LoadModel(string filePath, GraphicsDevice graphics, bool useBasicEffects = false)
+        {
+            var model = SharpGLTF.Schema2.ModelRoot.Load(filePath, SharpGLTF.Validation.ValidationMode.TryFix);
+
+            return ReadModel(model, graphics, useBasicEffects);
+        }
+
+        public static ModelTemplate LoadModel(System.IO.FileInfo finfo, GraphicsDevice graphics, bool useBasicEffects = false)
+        {
+            var model = SharpGLTF.Schema2.ModelRoot.Load(finfo.FullName, SharpGLTF.Validation.ValidationMode.TryFix);
+
+            return ReadModel(model, graphics, useBasicEffects);
+        }
+
+        public static ModelTemplate ReadModel(SharpGLTF.Schema2.ModelRoot model, GraphicsDevice graphics, bool useBasicEffects = false)
+        {
+            var factory = useBasicEffects ? (GLTFMeshFactory)new BasicMeshFactory(graphics) : new PBRMeshFactory(graphics);
+
+            return ConvertToXna(model, factory);
+        }
+
+        public static ModelTemplate ConvertToXna(SharpGLTF.Schema2.ModelRoot srcModel, GLTFMeshFactory meshFactory)
+        {
+            if (meshFactory == null) throw new ArgumentNullException();
+
+            var meshCollection = meshFactory.CreateMeshCollection(srcModel.LogicalMeshes);
+
+            var layers = new List<ModelLayerTemplate>();
+
+            foreach (var scene in srcModel.LogicalScenes)
+            {
+                var armatureFactory = new GLTFArmatureFactory();
+
+                for (int i = 0; i < srcModel.LogicalAnimations.Count; ++i)
+                {
+                    var track = srcModel.LogicalAnimations[i];
+                    armatureFactory.SetAnimationTrack(i, track.Name, track.Duration);
+                }
+
+                var layer = armatureFactory.CreateModelLayer(scene);
+
+                layer.ModelBounds = scene.EvaluateBoundingSphere().ToXna();
+
+                layers.Add(layer);
+            }
+
+            return new ModelTemplate(meshCollection, layers.ToArray(), srcModel.DefaultScene.LogicalIndex);
+        }       
+    }
+}

+ 18 - 0
src/MonoGame.Framework.Graphics.GLTF/MonoGame.Framework.Pipeline.GLTF.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <RootNamespace>Microsoft.Xna.Framework.Content.Pipeline.Graphics</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.PBR\MonoGame.Framework.Graphics.PBR.csproj" />
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.Toolkit3D\MonoGame.Framework.Graphics.Toolkit3D.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1375-develop" PrivateAssets="all" />
+    <PackageReference Include="SharpGLTF.Core" Version="1.0.0-Preview-20200921-1654" />    
+  </ItemGroup>
+
+</Project>

+ 2 - 2
src/MonoGame.Framework.Graphics.PBR/Effects/AnimatedEffect.cs

@@ -38,8 +38,8 @@ namespace Microsoft.Xna.Framework.Graphics
 
 
         #region properties - material
         #region properties - material
 
 
-        public bool AlphaBlend { get; set; }
-        public float AlphaCutoff { get; set; }
+        public bool AlphaBlend { get; set; } = false;
+        public float AlphaCutoff { get; set; } = -1;
 
 
         #endregion
         #endregion
 
 

+ 1 - 1
src/MonoGame.Framework.Graphics.PBR/Effects/UnlitEffect.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 
 
-namespace Microsoft.Xna.Framework.Graphics.Effects
+namespace Microsoft.Xna.Framework.Graphics
 {
 {
     public class UnlitEffect : AnimatedEffect , IEffectFog
     public class UnlitEffect : AnimatedEffect , IEffectFog
     {
     {

+ 10 - 0
src/MonoGame.Framework.Graphics.PBR/Resources.BRDF.cs

@@ -109,6 +109,16 @@ namespace Microsoft.Xna.Framework.Graphics
             return new Vector2(A / (float)samples, B / (float)samples);
             return new Vector2(A / (float)samples, B / (float)samples);
         }
         }
 
 
+        /// <summary>
+        /// Generates the pixels of the BRDF LUT texture.
+        /// </summary>
+        /// <typeparam name="TPixel">The pixel format</typeparam>
+        /// <param name="size">The width and height size of the square texture.</param>
+        /// <param name="pixelConverter">a converter from Vector2 to your final pixel format</param>
+        /// <returns>A size x size array with the final pixels</returns>
+        /// <remarks>
+        /// The end result must look exactly as the image of <see href="https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf"/> on page 13.
+        /// </remarks>
         public static TPixel[] Generate<TPixel>(int size, Func<Vector2,TPixel> pixelConverter)
         public static TPixel[] Generate<TPixel>(int size, Func<Vector2,TPixel> pixelConverter)
         {
         {
             uint samples = 1024;
             uint samples = 1024;

+ 19 - 7
src/MonoGame.Framework.Graphics.PBR/Resources.cs

@@ -10,6 +10,8 @@ namespace Microsoft.Xna.Framework.Graphics
 {
 {
     static class Resources
     static class Resources
     {
     {
+        private static GraphicsDevice _Device;
+
         private static readonly Dictionary<string, Byte[]> _Shaders = new Dictionary<string, byte[]>();
         private static readonly Dictionary<string, Byte[]> _Shaders = new Dictionary<string, byte[]>();
 
 
         public static Byte[] GetShaderByteCode(string name)
         public static Byte[] GetShaderByteCode(string name)
@@ -54,11 +56,25 @@ namespace Microsoft.Xna.Framework.Graphics
         public static Texture2D BlackTransparentDotTexture { get { if (blackTransparentDotTexture != null) return blackTransparentDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D BlackTransparentDotTexture { get { if (blackTransparentDotTexture != null) return blackTransparentDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D AoRoughMetalDefaltDotTexture { get { if (aoRoughMetalDefaltDotTexture != null) return aoRoughMetalDefaltDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
         public static Texture2D AoRoughMetalDefaltDotTexture { get { if (aoRoughMetalDefaltDotTexture != null) return aoRoughMetalDefaltDotTexture; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
 
 
-        public static Texture2D IblGGX { get { if (bdrf_ibl_ggx != null) return bdrf_ibl_ggx; else { throw new NullReferenceException("SharpGLTF.Runtime.Generated.BlackTransparentDotTexture()  ... The Generated dot texture was requested but never created. Make sure you have called Initialize(,,) on gltf in monogames load function first."); } } }
+        public static Texture2D IblGGX
+        {
+            get
+            {
+                if (bdrf_ibl_ggx != null) return bdrf_ibl_ggx;
+
+                var pixels = BRDFGenerator.Generate(128, val => new Rg32(val));
+                bdrf_ibl_ggx = new Texture2D(_Device, 128, 128, false, SurfaceFormat.Rg32);
+                bdrf_ibl_ggx.SetData(pixels);
+
+                return bdrf_ibl_ggx;
+            }
+        }
 
 
         public static void GenerateDotTextures(GraphicsDevice device)
         public static void GenerateDotTextures(GraphicsDevice device)
         {
         {
-            if (whiteDotTexture != null) return;
+            if (_Device != null) return;
+
+            _Device = device;           
             
             
             whiteDotTexture = new Texture2D(device, 1, 1);
             whiteDotTexture = new Texture2D(device, 1, 1);
             whiteDotTexture.SetData(new Color[] { Color.White });
             whiteDotTexture.SetData(new Color[] { Color.White });
@@ -67,11 +83,7 @@ namespace Microsoft.Xna.Framework.Graphics
             blackTransparentDotTexture.SetData(new Color[] { Color.Transparent });
             blackTransparentDotTexture.SetData(new Color[] { Color.Transparent });
                 
                 
             aoRoughMetalDefaltDotTexture = new Texture2D(device, 1, 1);
             aoRoughMetalDefaltDotTexture = new Texture2D(device, 1, 1);
-            aoRoughMetalDefaltDotTexture.SetData(new Color[] { new Color(1.0f, 0.97f, 0.03f, 0.0f) });
-
-            var pixels = BRDFGenerator.Generate(128, val => new Rg32(val));
-            bdrf_ibl_ggx = new Texture2D(device, 128, 128, false, SurfaceFormat.Rg32);
-            bdrf_ibl_ggx.SetData(pixels);
+            aoRoughMetalDefaltDotTexture.SetData(new Color[] { new Color(1.0f, 0.97f, 0.03f, 0.0f) });            
         }
         }
     }
     }
 }
 }

+ 195 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/AffineTransform.cs

@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using TRANSFORM = Microsoft.Xna.Framework.Matrix;
+using V3 = Microsoft.Xna.Framework.Vector3;
+using V4 = Microsoft.Xna.Framework.Vector4;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Represents an affine transform in 3D space, defined by:
+    /// - A <see cref="Vector3"/> scale.
+    /// - A <see cref="Quaternion"/> rotation.
+    /// - A <see cref="Vector3"/> translation.
+    /// </summary>
+    /// <remarks>
+    /// <see cref="AffineTransform"/> cannot represent skewed matrices. This means
+    /// that it can be used to represent <see cref="Schema2.Node"/> local transforms,
+    /// but since chained transforms can become skewed, a world transform cannot be
+    /// represented by a <see cref="AffineTransform"/>.
+    /// </remarks>
+    /// <see href="https://github.com/vpenades/SharpGLTF/issues/41"/>
+    [System.Diagnostics.DebuggerDisplay("AffineTransform 𝐒:{Scale} 𝐑:{Rotation} 𝚻:{Translation}")]
+    public struct AffineTransform
+    {
+        private const float _UnitLengthThresholdVec4 = 0.00769f;
+
+        #region lifecycle
+
+        public static implicit operator AffineTransform(TRANSFORM matrix) { return new AffineTransform(matrix); }
+
+        public AffineTransform(TRANSFORM matrix)
+        {
+            if (!matrix.Decompose(out this.Scale, out this.Rotation, out this.Translation))
+            {
+                throw new ArgumentException("matrix is invalid or skewed.", nameof(matrix));
+            }
+        }
+
+        public AffineTransform(Vector3? scale, Quaternion? rotation, Vector3? translation)
+        {
+            this.Scale = scale ?? Vector3.One;
+            this.Rotation = rotation ?? Quaternion.Identity;
+            this.Translation = translation ?? Vector3.Zero;
+        }
+
+        public static AffineTransform CreateFromAny(TRANSFORM? matrix, Vector3? scale, Quaternion? rotation, Vector3? translation)
+        {
+            if (matrix.HasValue)
+            {
+                return new AffineTransform(matrix.Value);
+            }
+            else
+            {
+                return new AffineTransform(scale, rotation, translation);
+            }
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Rotation
+        /// </summary>
+        public Quaternion Rotation;
+
+        /// <summary>
+        /// Scale
+        /// </summary>
+        public Vector3 Scale;
+
+        /// <summary>
+        /// Translation
+        /// </summary>
+        public Vector3 Translation;
+
+        #endregion
+
+        #region properties
+
+        public static AffineTransform Identity => new AffineTransform { Rotation = Quaternion.Identity, Scale = Vector3.One, Translation = Vector3.Zero };
+
+        /// <summary>
+        /// Gets the <see cref="Matrix4x4"/> transform of the current <see cref="AffineTransform"/>
+        /// </summary>
+        public TRANSFORM Matrix
+        {
+            get
+            {
+                var m = TRANSFORM.CreateScale(this.Scale) * TRANSFORM.CreateFromQuaternion(Sanitized(this.Rotation));
+                m.Translation = this.Translation;
+                return m;
+            }
+        }
+
+        public bool IsIdentity
+        {
+            get
+            {
+                if (Scale != Vector3.One) return false;
+                if (Rotation != Quaternion.Identity) return false;
+                if (Translation != Vector3.Zero) return false;
+                return true;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public static AffineTransform Blend(ReadOnlySpan<AffineTransform> transforms, ReadOnlySpan<float> weights)
+        {
+            var s = Vector3.Zero;
+            var r = default(Quaternion);
+            var t = Vector3.Zero;
+
+            for (int i = 0; i < transforms.Length; ++i)
+            {
+                var w = weights[i];
+
+                s += transforms[i].Scale * w;
+                r += transforms[i].Rotation * w;
+                t += transforms[i].Translation * w;
+            }
+
+            r = Quaternion.Normalize(r);
+
+            return new AffineTransform(s, r, t);
+        }
+
+        public static AffineTransform operator *(in AffineTransform a, in AffineTransform b)
+        {
+            return Multiply(a, b);
+        }
+
+        public static AffineTransform Multiply(in AffineTransform a, in AffineTransform b)
+        {
+            AffineTransform r;
+
+            r.Scale = Vector3Transform(b.Scale * Vector3Transform(a.Scale, a.Rotation), Quaternion.Inverse(a.Rotation));
+
+            r.Rotation = Quaternion.Multiply(b.Rotation, a.Rotation);
+
+            r.Translation
+                = b.Translation
+                + Vector3Transform(a.Translation * b.Scale, b.Rotation);
+
+            return r;
+        }
+
+        /// <summary>
+        /// This method is equivalent to System.Numerics.Vector3.Transform(Vector3 v, Quaternion q)
+        /// </summary>
+        /// <param name="v">The vector to transform</param>
+        /// <param name="q">The transform rotation</param>
+        /// <returns>The rotated vector</returns>
+        private static Vector3 Vector3Transform(Vector3 v, Quaternion q)
+        {
+            // Extract the vector part of the quaternion
+            var u = new Vector3(q.X, q.Y, q.Z);
+
+            // Extract the scalar part of the quaternion
+            var s = q.W;
+
+            // Do the math
+            return (2.0f * Vector3.Dot(u, v) * u)
+                + (((s * s) - Vector3.Dot(u, u)) * v)
+                + (2.0f * s * Vector3.Cross(u, v));
+        }
+
+
+
+
+        internal static Quaternion AsQuaternion(Vector4 v)
+        {
+            return new Quaternion(v.X, v.Y, v.Z, v.W);
+        }
+
+        internal static Quaternion Sanitized(Quaternion q)
+        {
+            return IsNormalized(q) ? q : Quaternion.Normalize(q);
+        }
+
+        internal static Boolean IsNormalized(Quaternion rotation)
+        {
+            // if (!rotation._IsFinite()) return false;
+
+            return Math.Abs(rotation.Length() - 1) <= _UnitLengthThresholdVec4;
+        }
+
+        #endregion
+    }
+}

+ 77 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/AnimatableProperty.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.Xna.Framework
+{
+    /// <summary>
+    /// Defines an animatable property with a default value and a collection of animation curve tracks.
+    /// </summary>
+    /// <typeparam name="T">A type that can be interpolated with <see cref="ICurveEvaluator{T}"/></typeparam>
+    [System.Diagnostics.DebuggerDisplay("{Value} with {CurveCount} curves.")]
+    public sealed class AnimatableProperty<T>
+       where T : struct
+    {
+        #region lifecycle
+
+        public AnimatableProperty(T defaultValue)
+        {
+            Value = defaultValue;
+        }
+
+        #endregion
+
+        #region data
+
+        private List<ICurveEvaluator<T>> _Curves;
+
+        /// <summary>
+        /// Gets the default value of this instance.
+        /// When animations are disabled, or there's no animation track available, this will be the returned value.
+        /// </summary>
+        public T Value { get; set; }
+
+        #endregion
+
+        #region properties
+
+        public bool IsAnimated => _Curves == null ? false : _Curves.Count > 0;
+
+        public int CurveCount => _Curves.Count;
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Evaluates the value of this <see cref="AnimatableProperty{T}"/> at a given <paramref name="offset"/> for a given <paramref name="curveIndex"/>.
+        /// </summary>
+        /// <param name="curveIndex">The index of the animation track</param>
+        /// <param name="offset">The time offset within the curve</param>
+        /// <returns>The evaluated value taken from the animation <paramref name="curveIndex"/>, or <see cref="Value"/> if a track was not found.</returns>
+        public T GetValueAt(int curveIndex, float offset)
+        {
+            if (_Curves == null) return this.Value;
+
+            if (curveIndex < 0 || curveIndex >= _Curves.Count) return this.Value;
+
+            return _Curves[curveIndex]?.Evaluate(offset) ?? this.Value;
+        }
+
+        public void SetCurve(int curveIndex, ICurveEvaluator<T> sampler)
+        {
+            if (curveIndex < 0) throw new ArgumentOutOfRangeException(nameof(curveIndex));
+            if (sampler == null) throw new ArgumentNullException(nameof(sampler));
+
+            if (_Curves == null) _Curves = new List<ICurveEvaluator<T>>();
+
+            while (_Curves.Count <= curveIndex) _Curves.Add(null);
+
+            _Curves[curveIndex] = sampler;
+        }
+
+        #endregion
+    }
+}

+ 40 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/GraphicsResourceTracker.cs

@@ -0,0 +1,40 @@
+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);
+        }
+
+        #endregion
+    }
+}

+ 132 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/Mesh.cs

@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Replaces <see cref="ModelMesh"/>
+    /// </summary>
+    public sealed class RuntimeModelMesh
+    {
+        #region lifecycle
+
+        public RuntimeModelMesh(GraphicsDevice graphicsDevice)
+        {
+            this._GraphicsDevice = graphicsDevice;
+        }        
+
+        #endregion
+
+        #region data
+
+        internal GraphicsDevice _GraphicsDevice;
+
+        private readonly List<RuntimeModelMeshPart> _Primitives = new List<RuntimeModelMeshPart>();
+        private IReadOnlyList<Effect> _Effects;
+
+        private IReadOnlyList<RuntimeModelMeshPart> _OpaquePrimitives;
+        private IReadOnlyList<Effect> _OpaqueEffects;
+
+        private IReadOnlyList<RuntimeModelMeshPart> _TranslucidPrimitives;
+        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;
+            }
+        }        
+
+        #endregion
+
+        #region API
+
+        public RuntimeModelMeshPart CreateMeshPart()
+        {
+            var primitive = new RuntimeModelMeshPart(this);
+
+            _Primitives.Add(primitive);
+
+            _OpaquePrimitives = null;
+            _TranslucidPrimitives = null;
+
+            InvalidateEffectCollection();            
+
+            return primitive;
+        }
+
+        internal void InvalidateEffectCollection()
+        {
+            _OpaqueEffects = null;
+            _TranslucidEffects = null;
+        }
+
+        private IReadOnlyList<RuntimeModelMeshPart> GetOpaqueParts()
+        {
+            if (_OpaquePrimitives != null) return _OpaquePrimitives;
+            _OpaquePrimitives = _Primitives.Where(item => item.Blending == BlendState.Opaque).ToArray();
+            return _OpaquePrimitives;
+        }
+
+        private IReadOnlyList<RuntimeModelMeshPart> 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);
+        }
+
+        #endregion
+    }
+}

+ 56 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/MeshCollection.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    [System.Diagnostics.DebuggerDisplay("{Count} Meshes {SharedEffects.Count} Shared effects.")]
+    public class MeshCollection : IDisposable
+    {
+        #region lifecycle
+
+        internal MeshCollection(RuntimeModelMesh[] 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 RuntimeModelMesh[] _Meshes;
+
+        private readonly Effect[] _SharedEffects;
+
+        #endregion
+
+        #region properties
+
+        public int Count => _Meshes.Length;
+
+        public RuntimeModelMesh this[int index] => _Meshes[index];
+
+        public IReadOnlyCollection<Effect> SharedEffects => _SharedEffects;
+
+        #endregion
+    }
+}

+ 121 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/MeshPart.cs

@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Replaces <see cref="ModelMeshPart"/>.
+    /// </summary>    
+    public sealed class RuntimeModelMeshPart
+    {
+        #region lifecycle
+
+        internal RuntimeModelMeshPart(RuntimeModelMesh parent)
+        {
+            _Parent = parent;
+        }
+
+        internal void SetVertexBuffer(VertexBuffer vb, int offset, int count)
+        {
+            this._SharedVertexBuffer = vb;
+            this._VertexOffset = offset;
+            this._VertexCount = count;
+        }
+
+        internal void SetIndexBuffer(IndexBuffer ib, int offset, int count)
+        {
+            this._SharedIndexBuffer = ib;
+            this._IndexOffset = offset;
+            this._PrimitiveCount = count;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly RuntimeModelMesh _Parent;
+
+        private Effect _Effect;
+        private BlendState _Blend = BlendState.Opaque;        
+
+        // state used for normal rendering
+        private RasterizerState _FrontRasterizer = RasterizerState.CullCounterClockwise;
+
+        // state used for mirrored tranform. This must be the same as _FrontRasterizer with reversed CullMode.
+        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 GraphicsDevice Device => _Parent._GraphicsDevice;
+
+        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 RasterizerState FrontRasterizer
+        {
+            get => _FrontRasterizer;
+            set => _FrontRasterizer = value;
+        }
+
+        public RasterizerState BackRasterizer
+        {
+            get => _BackRasterizer;
+            set => _BackRasterizer = value;
+        }
+
+        #endregion
+
+        #region API
+
+        public void Draw(GraphicsDevice device)
+        {
+            bool isMirrorTransform = _Effect is AnimatedEffect animEffect && animEffect.WorldIsMirror;
+
+            if (_PrimitiveCount > 0)
+            {
+                device.SetVertexBuffer(_SharedVertexBuffer);
+                device.Indices = _SharedIndexBuffer;
+
+                device.BlendState = _Blend;
+                device.RasterizerState = isMirrorTransform ? _BackRasterizer : _FrontRasterizer;
+
+                foreach(var pass in _Effect.CurrentTechnique.Passes)
+                {
+                    pass.Apply();
+                    device.DrawIndexedPrimitives(PrimitiveType.TriangleList, _VertexOffset, _IndexOffset, _PrimitiveCount);
+                }
+            }
+        }
+
+        #endregion
+    }
+
+    
+}

+ 142 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/Meshes/VertexTypes.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    [System.Diagnostics.DebuggerDisplay("{_ToDebugString(),nq}")]
+    struct VertexRigid : IVertexType
+    {
+        #region debug
+
+        private string _ToDebugString()
+        {
+            var p = $"{Position.X:N5} {Position.Y:N5} {Position.Z:N5}";
+            var n = $"{Normal.X:N2} {Normal.Y:N2} {Normal.Z:N2}";
+            var t = $"{Tangent.X:N2} {Tangent.Y:N2} {Tangent.Z:N2} {Tangent.W:N1}";
+            var uv0 = $"{TextureCoordinate0.X:N3} {TextureCoordinate0.Y:N3}";
+            var uv1 = $"{TextureCoordinate1.X:N3} {TextureCoordinate1.Y:N3}";
+
+            return $"𝐏:{p}   𝚴:{n}   𝚻:{t}   𝐂:{Color.PackedValue:X}   𝐔𝐕₀:{uv0}   𝐔𝐕₁:{uv1}";
+        }
+
+        #endregion
+
+        #region static
+
+        private static VertexDeclaration _VDecl = CreateVertexDeclaration();
+
+        public static VertexDeclaration CreateVertexDeclaration()
+        {
+            int offset = 0;
+
+            var a = new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Position, 0);
+            offset += 3 * 4;
+
+            var b = new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0);
+            offset += 3 * 4;
+
+            var c = new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.Tangent, 0);
+            offset += 4 * 4;
+
+            var d = new VertexElement(offset, VertexElementFormat.Color, VertexElementUsage.Color, 0);
+            offset += 4 * 1;
+
+            var e = new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0);
+            offset += 2 * 4;
+
+            var f = new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 1);
+            offset += 2 * 4;
+
+            return new VertexDeclaration(a, b, c, d, e, f);
+        }
+
+        #endregion
+
+        #region data
+
+        public VertexDeclaration VertexDeclaration => _VDecl;
+
+        public Vector3 Position;
+        public Vector3 Normal;
+        public Vector4 Tangent;
+        public Color Color;
+        public Vector2 TextureCoordinate0;
+        public Vector2 TextureCoordinate1;
+
+        #endregion
+    }
+
+    [System.Diagnostics.DebuggerDisplay("{_ToDebugString(),nq}")]
+    struct VertexSkinned : IVertexType
+    {
+        #region debug
+
+        private string _ToDebugString()
+        {
+            var p = $"{Position.X:N5} {Position.Y:N5} {Position.Z:N5}";
+            var n = $"{Normal.X:N2} {Normal.Y:N2} {Normal.Z:N2}";
+            var t = $"{Tangent.X:N2} {Tangent.Y:N2} {Tangent.Z:N2} {Tangent.W:N1}";
+            var uv0 = $"{TextureCoordinate0.X:N3} {TextureCoordinate0.Y:N3}";
+            var uv1 = $"{TextureCoordinate1.X:N3} {TextureCoordinate1.Y:N3}";
+            var jv = BlendIndices.ToVector4();
+            var j = $"{jv.X:N3} {jv.Y:N3} {jv.Z:N3} {jv.W:N3}";
+
+            return $"𝐏:{p}   𝚴:{n}   𝚻:{t}   𝐂:{Color.PackedValue:X}   𝐔𝐕₀:{uv0}   𝐔𝐕₁:{uv1}   𝐉𝐖:{j}";
+        }
+
+        #endregion
+
+        #region static
+
+        private static VertexDeclaration _VDecl = CreateVertexDeclaration();
+
+        public static VertexDeclaration CreateVertexDeclaration()
+        {
+            int offset = 0;
+
+            var a = new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Position, 0);
+            offset += 3 * 4;
+
+            var b = new VertexElement(offset, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0);
+            offset += 3 * 4;
+
+            var c = new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.Tangent, 0);
+            offset += 4 * 4;
+
+            var d = new VertexElement(offset, VertexElementFormat.Color, VertexElementUsage.Color, 0);
+            offset += 4 * 1;
+
+            var e = new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0);
+            offset += 2 * 4;
+
+            var f = new VertexElement(offset, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 1);
+            offset += 2 * 4;
+
+            var g = new VertexElement(offset, VertexElementFormat.Byte4, VertexElementUsage.BlendIndices, 0);
+            offset += 4 * 1;
+
+            var h = new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0);
+            offset += 4 * 4;
+
+            return new VertexDeclaration(a, b, c, d, e, f, g, h);
+        }
+
+        #endregion
+
+        #region data
+
+        public VertexDeclaration VertexDeclaration => _VDecl;
+
+        public Vector3 Position;
+        public Vector3 Normal;
+        public Vector4 Tangent;
+        public Color Color;
+        public Vector2 TextureCoordinate0;
+        public Vector2 TextureCoordinate1;
+        public PackedVector.Byte4 BlendIndices;
+        public Vector4 BlendWeight;
+
+        #endregion
+    }    
+}

+ 157 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelDrawingContext.cs

@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Helper class for rendering <see cref="ModelLayerInstance"/> models.
+    /// </summary>
+    public struct ModelDrawingContext
+    {
+        #region lifecycle
+
+        public ModelDrawingContext(GraphicsDevice graphics)
+        {
+            _Device = graphics;
+
+            _Device.DepthStencilState = DepthStencilState.Default;            
+
+            float fieldOfView = MathHelper.PiOver4;
+            float nearClipPlane = 0.01f;
+            float farClipPlane = 1000;            
+
+            _Projection = Matrix.CreatePerspectiveFieldOfView(fieldOfView, graphics.Viewport.AspectRatio, nearClipPlane, farClipPlane);
+
+            _View = Matrix.Invert(Matrix.Identity);
+            _DistanceComparer = ModelLayerInstance.GetDistanceComparer(-_View.Translation);
+        }
+
+        #endregion
+
+        #region data
+
+        private GraphicsDevice _Device;
+        private Matrix _Projection;
+        private Matrix _View;
+        private IComparer<ModelLayerInstance> _DistanceComparer;
+
+        private static readonly HashSet<Effect> _SceneEffects = new HashSet<Effect>();
+        private static readonly List<ModelLayerInstance> _SceneInstances = new List<ModelLayerInstance>();
+
+        #endregion
+
+        #region API
+
+        public void SetCamera(Matrix cameraMatrix)
+        {
+            _View = Matrix.Invert(cameraMatrix);
+
+            _DistanceComparer = ModelLayerInstance.GetDistanceComparer(-_View.Translation);
+        }
+
+        public void SetProjection(Matrix projectionMatrix)
+        {
+            _Projection = projectionMatrix;
+        }
+
+        public void DrawMesh(PBREnvironment environment, RuntimeModelMesh mesh, Matrix worldMatrix)
+        {
+            foreach (var e in mesh.OpaqueEffects)
+            {               
+                ModelLayerInstance.UpdateProjViewTransforms(e, _Projection, _View);
+                ModelLayerInstance.UpdateWorldTransforms(e, worldMatrix);
+                environment.ApplyTo(e);
+            }
+
+            mesh.DrawOpaque();
+
+            foreach (var e in mesh.TranslucidEffects)
+            {                
+                ModelLayerInstance.UpdateProjViewTransforms(e, _Projection, _View);
+                ModelLayerInstance.UpdateWorldTransforms(e, worldMatrix);
+                environment.ApplyTo(e);
+            }
+
+            mesh.DrawTranslucid();
+        }
+
+        /// <summary>
+        /// Draw a single model instance
+        /// </summary>
+        /// <param name="environment">Defines the athmospheric and lighting environment to use for the render.</param>
+        /// <param name="modelInstance">Defines the instance that is going to be rendered.</param>
+        /// <remarks>
+        /// Rendering models one by one is accepted, but some features like translucent parts sortings will not work
+        /// unless you manually render the models in the correct order.
+        /// </remarks>
+        public void DrawModelInstance(PBREnvironment environment, ModelLayerInstance modelInstance)
+        {
+            foreach (var e in modelInstance.Template.SharedEffects)
+            {
+                environment.ApplyTo(e);
+                ModelLayerInstance.UpdateProjViewTransforms(e, _Projection, _View);
+            }
+
+            modelInstance.Draw(_Projection, _View);
+        }
+
+        /// <summary>
+        /// Draws a batch of model instances.
+        /// </summary>
+        /// <param name="environment">Defines the athmospheric and lighting environment to use for the render.</param>
+        /// <param name="modelInstances">A batch of model instances.</param>
+        /// <remarks>
+        /// Rendering multiple models in a batch has a number of advantages over rendering models one by one:
+        /// - It allows splitting the rendering between opaque and translucent parts, which are rendered in the correct
+        ///   order to preserve rendering correctness.
+        /// - Less redundant calls.
+        /// - Futher optimizations are possible, like batching instances that share the same template model in a single
+        ///   drawing call.
+        /// - Possibility to add shadows, where some instances cast shadows over others.
+        /// </remarks>
+        public void DrawSceneInstances(PBREnvironment environment, params ModelLayerInstance[] modelInstances)
+        {
+            // todo: fustrum culling goes here
+
+            _SceneInstances.Clear();
+            _SceneInstances.AddRange(modelInstances);
+            _SceneInstances.Sort(_DistanceComparer);
+
+            // gather all effects from all visible instances.
+            _SceneEffects.Clear();
+            _SceneEffects.UnionWith(_SceneInstances.SelectMany(item => item.Template.SharedEffects));
+
+            // set Projection & View on all visible effects.
+
+            foreach (var e in _SceneEffects)
+            {
+                ModelLayerInstance.UpdateProjViewTransforms(e, _Projection, _View);
+                // todo: set env.Exposure
+                // todo: set env.AmbientLight
+            }
+
+            // todo: find the closest lights for each visible instance.
+
+            // render opaque parts from closest to farthest
+
+            foreach (var instance in _SceneInstances)
+            {
+                foreach (var e in instance.Template.SharedEffects) environment.ApplyTo(e);
+                instance.DrawOpaqueParts();
+            }
+
+            // render translucid parts from farthest to closest
+
+            _SceneInstances.Reverse();
+
+            foreach (var instance in _SceneInstances)
+            {
+                foreach (var e in instance.Template.SharedEffects) environment.ApplyTo(e);
+                instance.DrawTranslucidParts();
+            }
+        }
+
+        #endregion
+    }    
+}

+ 18 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/AnimationTrackInfo.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Graphics.Graphics.ModelGraph
+{
+    public class AnimationTrackInfo
+    {
+        public AnimationTrackInfo(string name, float duration)
+        {
+            Name = name;
+            Duration = duration;
+        }
+
+        public string Name { get; private set; }
+        public float Duration { get; private set; }
+    }
+}

+ 142 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ArmatureInstance.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using XFORM = Microsoft.Xna.Framework.Matrix;
+
+namespace Microsoft.Xna.Framework.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];
+
+            for (var i = 0; i < _NodeInstances.Length; ++i)
+            {
+                var n = armature[i];
+                var pidx = armature[i].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="ModelLayerInstance"/>.
+        /// </summary>
+        public IReadOnlyList<NodeInstance> LogicalNodes => _NodeInstances;
+
+        /// <summary>
+        /// Gets all the <see cref="NodeInstance"/> roots used by this <see cref="ModelLayerInstance"/>.
+        /// </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
+    }
+}

+ 70 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ArmatureTemplate.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics.Graphics.ModelGraph;
+
+namespace Microsoft.Xna.Framework.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));
+
+            for(int i=0; i < nodes.Length; ++i)
+            {
+                var n = nodes[i];
+                if (n == null) throw new ArgumentNullException($"{nameof(nodes)}[{i}] is null.");
+
+                var parentIndex = n.ParentIndex;
+                if (parentIndex >= i) throw new ArgumentException($"{nameof(nodes)}[{i}].ParentIndex must point to a preceding node in the array.");
+            }
+
+            _NodeTemplates = nodes;
+            _AnimationTracks = atracks == null ? new AnimationTrackInfo[0] : atracks;
+        }
+
+        #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 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/DrawableInstance.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.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;
+    }
+}

+ 160 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/DrawableTemplate.cs

@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using TRANSFORM = Microsoft.Xna.Framework.Matrix;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    public interface IDrawableTemplate
+    {
+        /// <summary>
+        /// Typically this is the name of the content node that contained the mesh.
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// An index into <see cref="ModelLayerTemplate.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 : IDrawableTemplate
+    {
+        #region lifecycle
+
+        protected DrawableTemplate(string nodeName, int logicalMeshIndex)
+        {
+            _NodeName = nodeName;
+            _LogicalMeshIndex = logicalMeshIndex;            
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly String _NodeName;
+
+        private readonly int _LogicalMeshIndex;
+
+        #endregion
+
+        #region properties
+
+        public String Name => _NodeName;
+
+        /// <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
+
+        internal 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
+
+        internal SkinnedDrawableTemplate(int meshIndex, NodeTemplate morphNode, string ownerNname, (NodeTemplate,Matrix)[] skinNodes)
+            : base(ownerNname, meshIndex)
+        {
+            // _MorphNodeIndex = indexFunc(morphNode);
+
+            _JointsNodeIndices = new int[skinNodes.Length];
+            _BindMatrices = new TRANSFORM[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 TRANSFORM[] _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
+    }
+}

+ 415 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/MeshTransforms.cs

@@ -0,0 +1,415 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using TRANSFORM = Microsoft.Xna.Framework.Matrix;
+using V3 = Microsoft.Xna.Framework.Vector3;
+using V4 = Microsoft.Xna.Framework.Vector4;
+using SPARSE8 = Microsoft.Xna.Framework.Vector4;
+
+namespace Microsoft.Xna.Framework.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 SPARSE8 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 SPARSE8 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 SPARSE8 skinWeights);
+
+        V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
+        */
+    }
+
+    abstract class MeshMorphTransform
+    {
+        #region constructor
+
+        protected MeshMorphTransform()
+        {
+            Update(default, false);
+        }
+
+        protected MeshMorphTransform(SPARSE8 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 SPARSE8 _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 SPARSE8 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(SPARSE8 morphWeights, bool useAbsoluteMorphTargets = false)
+        {
+            /*
+            _AbsoluteMorphTargets = useAbsoluteMorphTargets;
+
+            if (morphWeights.IsWeightless)
+            {
+                _Weights = SPARSE8.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, SPARSE8 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 SPARSE8 skinWeights)
+        {
+            position = MorphVectors(position, morphTargets);
+
+            return V3.Transform(position, _WorldMatrix);
+        }
+
+        public V3 TransformNormal(V3 normal, IReadOnlyList<V3> morphTargets, in SPARSE8 skinWeights)
+        {
+            normal = MorphVectors(normal, morphTargets);
+
+            return V3.Normalize(V3.TransformNormal(normal, _WorldMatrix));
+        }
+
+        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> morphTargets, in SPARSE8 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, SPARSE8 morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
+            Update(invBindMatrix, currWorldMatrix);
+        }
+
+        public MeshSkinTransform(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix, SPARSE8 morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
+            Update(count, invBindMatrix, currWorldMatrix);
+        }
+
+        #endregion
+
+        #region data
+
+        private TRANSFORM[] _SkinTransforms;
+
+        #endregion
+
+        #region properties
+
+        /// <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 bool Visible => true;
+
+        public bool FlipFaces => false;
+
+        /*
+        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> morphTargets, in SPARSE8 skinWeights)
+        {
+            Guard.NotNull(skinWeights, nameof(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 SPARSE8 skinWeights)
+        {
+            Guard.NotNull(skinWeights, nameof(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 SPARSE8 skinWeights)
+        {
+            Guard.NotNull(skinWeights, nameof(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        
+    }
+}

+ 292 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelLayerInstance.cs

@@ -0,0 +1,292 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Represents the state machine of a specific model layer instance on screen.    
+    /// </summary>
+    /// <remarks>
+    /// For each <see cref="ModelLayerTemplate"/> you can create
+    /// multiple <see cref="ModelLayerInstance"/> objects.
+    /// </remarks>
+    public class ModelLayerInstance
+    {
+        #region lifecycle
+
+        internal ModelLayerInstance(ModelLayerTemplate parent)
+        {
+            _Parent = parent;
+
+            _Armature = new ArmatureInstance(_Parent._Armature);
+            _Armature.SetPoseTransforms();
+
+            _WorldMatrix = Matrix.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 ModelLayerTemplate _Parent;
+        private readonly ArmatureInstance _Armature;
+
+        private readonly IDrawableTemplate[] _DrawableTemplates;
+        private readonly IMeshTransform[] _DrawableTransforms;
+
+        private Matrix _WorldMatrix;
+
+        // pre-allocated bone arrays to update the IEffectBones
+        private static readonly List<Matrix[]> _BoneArrays = new List<Matrix[]>();
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets a reference to the template used to create this <see cref="MonoGameModelInstance"/>.
+        /// </summary>
+        public ModelLayerTemplate Template => _Parent;
+
+        public ArmatureInstance Armature => _Armature;
+
+        public Matrix WorldMatrix
+        {
+            get => _WorldMatrix;
+            set => _WorldMatrix = value;
+        }
+
+        public BoundingSphere ModelBounds => _Parent.ModelBounds;
+
+        public 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);
+                }
+            }
+        }
+
+        #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 Matrix 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 Matrix GetWorldMatrix(int nodeIndex) { return _Armature.LogicalNodes[nodeIndex].ModelMatrix * _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 Draw(Matrix projection, Matrix view)
+        {
+            foreach (var e in this.Template.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 = _Parent.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 = _Parent.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, Matrix 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 = Matrix.Multiply(statTransform, worldXform);
+
+                foreach (var effect in effects)
+                {
+                    UpdateWorldTransforms(effect, worldXform);
+                }
+            }
+        }
+
+        // Since SkinnedEffect has such a flexible and GC friendly API,
+        // we have to do this to have a reusable bone matrix pool.
+        private static Matrix[] UseArray(int count)
+        {
+            while (_BoneArrays.Count <= count) _BoneArrays.Add(null);
+
+            if (_BoneArrays[count] == null) _BoneArrays[count] = new Matrix[count];
+
+            return _BoneArrays[count];
+
+        }
+
+
+        public static void UpdateProjViewTransforms(Effect effect, Matrix projectionXform, Matrix viewXform)
+        {
+            if (effect is IEffectMatrices matrices)
+            {
+                matrices.Projection = projectionXform;
+                matrices.View = viewXform;
+            }
+        }
+
+        public static void UpdateWorldTransforms(Effect effect, Matrix worldXform, Matrix[] 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<ModelLayerInstance> GetDistanceComparer(Vector3 origin)
+        {
+            return new _DistanceComparer(origin);
+        }
+
+        private struct _DistanceComparer : IComparer<ModelLayerInstance>
+        {
+            public _DistanceComparer(Vector3 origin) { _Origin = origin; }
+
+            private readonly Vector3 _Origin;
+
+            public int Compare(ModelLayerInstance x, ModelLayerInstance y)
+            {
+                var xDist = (x.WorldMatrix.Translation - _Origin).LengthSquared();
+                var yDist = (y.WorldMatrix.Translation - _Origin).LengthSquared();
+
+                return xDist.CompareTo(yDist);
+            }
+        }
+
+        #endregion
+    }
+}

+ 85 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelLayerTemplate.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+
+
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Defines a templatized representation of a <see cref="Schema2.Scene"/> that can be used
+    /// to create <see cref="ModelLayerInstance"/>, which can help render a scene on a client application.
+    /// </summary>
+    public class ModelLayerTemplate
+    {
+        #region lifecycle        
+
+        public ModelLayerTemplate(string layerName, ArmatureTemplate armature, IDrawableTemplate[] drawables)
+        {
+            _LayerName = layerName;
+            _Armature = armature;
+            _DrawableReferences = drawables;            
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly String _LayerName;        
+
+        internal readonly ArmatureTemplate _Armature;
+        
+        private MeshCollection _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 String Name => _LayerName;        
+
+        public BoundingSphere ModelBounds { get; set; }
+
+        public MeshCollection Meshes
+        {
+            get => _Meshes;
+            set
+            {
+                _Meshes = value;
+                _SharedEffects = null;
+            }
+        }
+
+        public IReadOnlyCollection<Effect> SharedEffects
+        {
+            get
+            {
+                if (_SharedEffects != null) return _SharedEffects;
+
+                // gather all effects used by all the meshes used by all the drawable calls in this layer.
+                _SharedEffects = _DrawableReferences                    
+                    .Select(item => _Meshes[item.MeshIndex])
+                    .SelectMany(item => item.OpaqueEffects.Concat(item.TranslucidEffects))
+                    .Distinct()
+                    .ToArray();
+
+                return _SharedEffects;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public ModelLayerInstance CreateInstance() => new ModelLayerInstance(this);
+
+        #endregion
+    }
+}

+ 74 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/ModelTemplate.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using MODELMESH = Microsoft.Xna.Framework.Graphics.RuntimeModelMesh;
+
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    public class ModelTemplate : IDisposable
+    {
+        #region lifecycle
+
+        public ModelTemplate(MeshCollection meshes, ModelLayerTemplate[] layers, int defaultLayer)
+        {            
+            _Layers = layers;
+            _DefaultLayerIndex = defaultLayer;
+
+            SharedMeshes = meshes;
+        }
+
+        public void Dispose()
+        {
+            _SharedMeshes?.Dispose();
+            _SharedMeshes = null;
+            _Layers = null;
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Meshes shared by all the <see cref="_Layers"/>.
+        /// </summary>
+        internal MeshCollection _SharedMeshes;        
+
+        /// <summary>
+        /// Layers available in this template
+        /// </summary>
+        internal ModelLayerTemplate[] _Layers;        
+
+        /// <summary>
+        /// Default layer index
+        /// </summary>
+        private readonly int _DefaultLayerIndex;
+
+        #endregion
+
+        #region properties
+
+        public IReadOnlyList<ModelLayerTemplate> Layers => _Layers;
+
+        public ModelLayerTemplate DefaultLayer => _Layers[_DefaultLayerIndex];
+
+        public MeshCollection SharedMeshes
+        {
+            get => _SharedMeshes;
+            set
+            {
+                _SharedMeshes = value;
+                foreach (var layer in _Layers) layer.Meshes = _SharedMeshes;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public int IndexOfLayer(string layerName) => Array.FindIndex(_Layers, item => item.Name == layerName);       
+
+        #endregion
+    }
+}

+ 125 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/NodeInstance.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using XFORM = Microsoft.Xna.Framework.Matrix;
+// using SPARSE8 = Microsoft.Xna.Framework.Vector4;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Defines a node of a scene graph in <see cref="ModelLayerInstance"/>
+    /// </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;
+
+        /// <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
+    }
+}

+ 176 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Graphics/ModelGraph/NodeTemplate.cs

@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+
+using TRANSFORM = Microsoft.Xna.Framework.Matrix;
+using SPARSE8 = Microsoft.Xna.Framework.Vector4;
+
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+    /// <summary>
+    /// Defines a hierarchical transform node of a scene graph tree.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("[{LogicalNodeIndex}] {Name}")]
+    public class NodeTemplate
+    {
+        #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="ModelLayerTemplate._NodeTemplates"/>
+        /// </summary>
+        private readonly int _ThisIndex;
+
+        /// <summary>
+        /// the index of the parent node within <see cref="ModelLayerTemplate._NodeTemplates"/>
+        /// </summary>
+        private readonly int _ParentIndex;
+        private readonly int[] _ChildIndices;
+
+        private TRANSFORM _LocalMatrix;        
+
+        private bool _UseAnimatedTransforms;
+
+        private AffineTransform _LocalTransform;
+        private AnimatableProperty<Vector3> _Scale;
+        private AnimatableProperty<Quaternion> _Rotation;
+        private AnimatableProperty<Vector3> _Translation;
+
+        // private AnimatableProperty<SPARSE8> _Morphing;
+
+        #endregion
+
+        #region properties
+
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets the index of the source <see cref="Schema2.Node"/> in <see cref="ModelLayerTemplate._NodeTemplates"/>
+        /// </summary>
+        public int ThisIndex => _ThisIndex;
+
+        /// <summary>
+        /// Gets the index of the parent <see cref="NodeTemplate"/> in <see cref="ModelLayerTemplate._NodeTemplates"/>
+        /// </summary>
+        public int ParentIndex => _ParentIndex;
+
+        /// <summary>
+        /// Gets the list of indices of the children <see cref="NodeTemplate"/> in <see cref="ModelLayerTemplate._NodeTemplates"/>
+        /// </summary>
+        public IReadOnlyList<int> ChildIndices => _ChildIndices;
+
+        public TRANSFORM LocalMatrix => _LocalMatrix;
+
+        #endregion
+
+        #region API
+
+        public void SetLocalMatrix(TRANSFORM matrix)
+        {
+            _LocalMatrix = matrix;
+            _UseAnimatedTransforms = false;
+        }
+
+        public void SetLocalTransform(AnimatableProperty<Vector3> s, AnimatableProperty<Quaternion> r, AnimatableProperty<Vector3> 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 TRANSFORM GetLocalMatrix(int trackIndex, float time)
+        {
+            if (!_UseAnimatedTransforms || trackIndex < 0) return _LocalMatrix;
+
+            return GetLocalTransform(trackIndex, time).Matrix;
+        }
+
+        public TRANSFORM GetLocalMatrix(ReadOnlySpan<int> track, ReadOnlySpan<float> time, ReadOnlySpan<float> weight)
+        {
+            if (!_UseAnimatedTransforms) return _LocalMatrix;
+
+            return GetLocalTransform(track, time, weight).Matrix;
+        }
+
+        #endregion
+    }
+}

+ 30 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/ICurveEvaluator.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Xna.Framework
+{
+    /// <summary>
+    /// Interface over a curve generalization.
+    /// </summary>
+    /// <typeparam name="T">Any type that can be interpolated, like:
+    /// - <see cref="Single"/>
+    /// - <see cref="Vector3"/>
+    /// - <see cref="Quaternion"/>
+    /// - etc
+    /// </typeparam>
+    /// <remarks>
+    /// 1- This interface could be incorported into Monogame, and implemented in monogame's <see cref="Curve"/> class.
+    /// 
+    /// 2- Notice that a Vector3 curve can be simulated by wrapping 3 monogame's <see cref="Curve"/> instances.
+    /// but, for Quaternion curves, we need a full implementation.
+    /// 
+    /// 3- In general, curves can be implemented in so many ways that demand an abstract interface.
+    /// After all, animatable objects only need to evaluate the curve at a given time and don't care
+    /// about the internal logic of the curve.
+    /// </remarks>
+    public interface ICurveEvaluator<T>
+    {        
+        T Evaluate(Single position);
+    }
+}

+ 22 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/MonoGame.Framework.Graphics.Toolkit3D.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netstandard2.0;net452</TargetFrameworks>
+    <RootNamespace>Microsoft.Xna.Framework.Graphics</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>  
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1375-develop" PrivateAssets="all" />
+    <PackageReference Include="System.Memory" Version="4.5.4" />
+  </ItemGroup>
+
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
+    <PackageReference Include="System.ValueTuple" Version="4.5.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.PBR\MonoGame.Framework.Graphics.PBR.csproj" />
+  </ItemGroup>  
+
+</Project>

+ 109 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/ArmatureFactory.cs

@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Graphics.Graphics.ModelGraph;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public abstract class ArmatureFactory<TNode>
+    {
+        #region data
+
+        private readonly List<AnimationTrackInfo> _AnimationTracks = new List<AnimationTrackInfo>();
+
+        private readonly List<NodeTemplate> _Nodes = new List<NodeTemplate>();
+        private readonly Dictionary<TNode, NodeTemplate> _Map = new Dictionary<TNode, NodeTemplate>();
+
+        #endregion
+
+        #region API
+
+        public void SetAnimationTrack(int index, string name, float duration)
+        {
+            while (_AnimationTracks.Count <= index) _AnimationTracks.Add(new AnimationTrackInfo(null,0));
+
+            _AnimationTracks[index] = new AnimationTrackInfo(name, duration);
+        }
+
+        public void AddRoot(TNode node)
+        {
+            AddNodeRescursive(node, -1);
+        }
+
+        protected abstract string GetName(TNode node);
+        protected abstract IEnumerable<TNode> GetChildren(TNode node);
+        protected abstract Matrix GetLocalMatrix(TNode node);
+        protected abstract AnimatableProperty<Vector3> GetScale(TNode node);
+        protected abstract AnimatableProperty<Quaternion> GetRotation(TNode node);
+        protected abstract AnimatableProperty<Vector3> GetTranslation(TNode node);
+
+
+        public NodeTemplate GetNode(TNode srcNode) => _Map[srcNode];
+
+        public IDrawableTemplate CreateRigidDrawable(int meshIndex, TNode node)
+        {
+            var n = GetNode(node);
+            var d = new RigidDrawableTemplate(meshIndex, n);
+            return d;
+        }
+
+        public IDrawableTemplate CreateSkinnedDrawable(int meshIndex, TNode container, (TNode,Matrix)[] bones)
+        {
+            var xbones = bones
+                .Select(item => (GetNode(item.Item1), item.Item2))
+                .ToArray();
+
+            var d = new SkinnedDrawableTemplate(meshIndex, null, GetNode(container).Name, xbones);
+
+            return d;
+        }
+
+        public ArmatureTemplate CreateArmature() { return new ArmatureTemplate(_Nodes.ToArray(), _AnimationTracks.ToArray()); }
+
+        #endregion
+
+        #region core
+
+        private int AddNodeRescursive(TNode src, int parentIndex)
+        {
+            if (src == null) throw new ArgumentNullException(nameof(src));
+            if (_Map.ContainsKey(src)) throw new ArgumentException("already exists");
+            if (parentIndex >= _Nodes.Count) throw new ArgumentOutOfRangeException(nameof(parentIndex));
+
+            var thisIdx = _Nodes.Count;
+            _Nodes.Add(null);
+
+            var childIndices = new List<int>();
+            
+            
+            foreach(var child in GetChildren(src))
+            {
+                var childIndex = AddNodeRescursive(child, thisIdx);
+                childIndices.Add(childIndex);
+            }
+
+            
+            var dst = new NodeTemplate(thisIdx, parentIndex, childIndices.ToArray());
+            dst.Name = GetName(src);
+
+            dst.SetLocalMatrix(GetLocalMatrix(src));
+
+            var s = GetScale(src);
+            var r = GetRotation(src);
+            var t = GetTranslation(src);
+            dst.SetLocalTransform(s, r, t);
+
+            _Nodes[thisIdx] = dst;
+            _Map[src] = dst;
+
+            // TODO: ensure there's enough animation tracks.
+
+            return thisIdx;
+        }        
+
+        #endregion        
+    }
+}

+ 99 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshDecoder.cs

@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using XY = Microsoft.Xna.Framework.Vector2;
+using XYZ = Microsoft.Xna.Framework.Vector3;
+using XYZW = Microsoft.Xna.Framework.Vector4;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public interface IMeshDecoder<TMaterial>
+        where TMaterial : class
+    {
+        string Name { get; }        
+        IReadOnlyList<IMeshPrimitiveDecoder<TMaterial>> Primitives { get; }
+
+        Object Tag { get; }
+    }    
+
+    public interface IMeshPrimitiveDecoder<TMaterial> : IMeshPrimitiveDecoder
+        where TMaterial : class
+    {
+        TMaterial Material { get; }
+    }
+
+    public interface IMeshPrimitiveDecoder
+    {
+        #region properties
+
+        /// <summary>
+        /// Gets a value indicating the total number of vertices for this primitive.
+        /// </summary>
+        int VertexCount { get; }
+
+        /// <summary>
+        /// Gets a value indicating the total number of morph targets for this primitive.
+        /// </summary>
+        int MorphTargetsCount { get; }
+
+        /// <summary>
+        /// Gets a value indicating the number of color vertex attributes.
+        /// In the range of 0 to 2.
+        /// </summary>
+        int ColorsCount { get; }
+
+        /// <summary>
+        /// Gets a value indicating the number of texture coordinate vertex attributes.
+        /// In the range of 0 to 2.
+        /// </summary>
+        int TexCoordsCount { get; }
+
+        /// <summary>
+        /// Gets a value indicating the number of skinning joint-weight attributes.
+        /// The values can be 0, 4 or 8.
+        /// </summary>
+        int JointsWeightsCount { get; }
+
+        /// <summary>
+        /// Gets a sequence of tuples where each item represents the vertex indices of a line.
+        /// </summary>
+        IEnumerable<(int A, int B)> LineIndices { get; }
+
+        /// <summary>
+        /// Gets a sequence of tuples where each item represents the vertex indices of a triangle.
+        /// </summary>
+        IEnumerable<(int A, int B, int C)> TriangleIndices { get; }
+
+        #endregion
+
+        #region API
+
+        XYZ GetPosition(int vertexIndex);
+
+        XYZ GetNormal(int vertexIndex);
+
+        XYZW GetTangent(int vertexIndex);
+
+        IReadOnlyList<XYZ> GetPositionDeltas(int vertexIndex);
+
+        IReadOnlyList<XYZ> GetNormalDeltas(int vertexIndex);
+
+        IReadOnlyList<XYZ> GetTangentDeltas(int vertexIndex);
+
+        XY GetTextureCoord(int vertexIndex, int textureSetIndex);
+
+        XYZW GetColor(int vertexIndex, int colorSetIndex);
+
+        VertexSkinning GetSkinWeights(int vertexIndex);
+
+        #endregion
+    }
+
+    public struct VertexSkinning
+    {
+        public Framework.Graphics.PackedVector.Short4 Indices;
+        public XYZW Weights;
+    }
+}

+ 119 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshFactory.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using XY = Microsoft.Xna.Framework.Vector2;
+using XYZ = Microsoft.Xna.Framework.Vector3;
+using XYZW = Microsoft.Xna.Framework.Vector4;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public abstract class MeshFactory<TMaterial>
+        where TMaterial : class
+    {
+        #region lifecycle
+
+        public MeshFactory(GraphicsDevice device)
+        {
+            _Device = device;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly GraphicsDevice _Device;        
+
+        private readonly Dictionary<TMaterial, MeshPrimitiveMaterial> _Materials = new Dictionary<TMaterial, MeshPrimitiveMaterial>();
+
+        /// <summary>
+        /// Gathers all disposable resources shared by the collection of meshes:
+        /// - <see cref="VertexBuffer"/>
+        /// - <see cref="IndexBuffer"/>
+        /// - <see cref="Texture2D"/>
+        /// - <see cref="Effect"/>
+        /// - Custom <see cref="BlendState"/>
+        /// - Custom <see cref="SamplerState"/>
+        /// </summary>
+        private GraphicsResourceTracker _Disposables;
+
+        #endregion
+
+        #region properties
+        protected GraphicsDevice Device => _Device;
+
+        #endregion
+
+        #region API
+
+        public MeshCollection CreateMeshCollection(IEnumerable<IMeshDecoder<TMaterial>> srcMeshes)
+        {
+            _Disposables = new GraphicsResourceTracker();            
+
+            int meshIndex = 0;
+
+            var meshPrimitiveBuilder = new MeshPrimitiveBuilder();
+
+            // aggregate the primitives of all meshes, so the builder can determine the shared resources
+
+            foreach (var srcMesh in srcMeshes)
+            {
+                foreach (var srcPrim in srcMesh.Primitives)
+                {
+                    Type vertexType = GetPreferredVertexType(srcPrim);
+
+                    if (!_Materials.TryGetValue(srcPrim.Material, out MeshPrimitiveMaterial material))
+                    {
+                        material = ConvertMaterial(srcPrim.Material, srcPrim.JointsWeightsCount > 0);
+                        if (material == null) throw new NullReferenceException("Material conversion failed");
+                        _Materials[srcPrim.Material] = material;
+                    }                    
+
+                    meshPrimitiveBuilder.AppendMeshPrimitive(meshIndex, vertexType, srcPrim, material.Effect, material.Blend, material.DoubleSided);
+                }
+
+                ++meshIndex;
+            }
+
+            // Create the runtime meshes
+
+            var dstMeshes = meshPrimitiveBuilder.CreateRuntimeMeshes(_Device, _Disposables)
+                .OrderBy(item => item.Key)
+                .Select(item => item.Value)
+                .ToArray();
+
+            _Materials.Clear();
+
+            return new MeshCollection(dstMeshes, _Disposables.Disposables.ToArray());
+        }
+
+        protected virtual Type GetPreferredVertexType(IMeshPrimitiveDecoder<TMaterial> srcPrim)
+        {
+            return srcPrim.JointsWeightsCount > 0 ? typeof(VertexSkinned) : typeof(VertexRigid);
+        }
+
+        protected abstract MeshPrimitiveMaterial ConvertMaterial(TMaterial material, bool mustSupportSkinning);
+        
+        #endregion
+    }
+
+    public class MeshPrimitiveMaterial
+    {
+        public Effect Effect;
+        public BlendState Blend;
+        public bool DoubleSided;
+
+        public class MeshFactory : MeshFactory<MeshPrimitiveMaterial>
+        {
+            public MeshFactory(GraphicsDevice device) : base(device) { }
+
+            protected override MeshPrimitiveMaterial ConvertMaterial(MeshPrimitiveMaterial material, bool mustSupportSkinning)
+            {
+                return material;
+            }
+        }
+    }
+}

+ 207 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/MeshPrimitiveBuilder.cs

@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    /// <summary>
+    /// Accumulates all the primitives of all the meshes of a model, so they can be optimized/batched.
+    /// </summary>
+    sealed class MeshPrimitiveBuilder
+    {
+        #region data
+
+        // shared buffers
+        private readonly Dictionary<Type, IPrimitivesBuffers> _Buffers = new Dictionary<Type, IPrimitivesBuffers>();
+
+        // primitives
+        private readonly List<_MeshPrimitive> _MeshPrimitives = new List<_MeshPrimitive>();
+
+        #endregion
+
+        #region API
+
+        public void AppendMeshPrimitive(int logicalMeshIndex, Type vertexType, IMeshPrimitiveDecoder primitive, Effect effect, BlendState blending, bool doubleSided)
+        {
+            // this is a reflection hack to call a generic method from a non generic method.
+
+            var myMethod = typeof(MeshPrimitiveBuilder)
+              .GetMethods()
+              .FirstOrDefault(m => m.Name == nameof(AppendMeshPrimitive) && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1);
+
+            myMethod = myMethod.MakeGenericMethod(vertexType);
+            myMethod.Invoke(this, new Object[] { logicalMeshIndex, primitive, effect, blending, doubleSided });
+        }
+
+        public void AppendMeshPrimitive<TVertex>(int logicalMeshIndex, IMeshPrimitiveDecoder primitive, Effect effect, BlendState blending, bool doubleSided)
+            where TVertex : unmanaged, IVertexType
+        {
+            if (!_Buffers.TryGetValue(typeof(TVertex), out IPrimitivesBuffers pb))
+            {
+                _Buffers[typeof(TVertex)] = pb = new _PrimitivesBuffers<TVertex>();
+            }
+
+            var part = (pb as _PrimitivesBuffers<TVertex>).Append(logicalMeshIndex, effect, blending, doubleSided, primitive);
+
+            _MeshPrimitives.Add(part);
+        }
+
+        internal IReadOnlyDictionary<int, RuntimeModelMesh> CreateRuntimeMeshes(GraphicsDevice device, GraphicsResourceTracker disposables)
+        {
+            // create shared vertex/index buffers
+
+            var vbuffers = _Buffers.Values.ToDictionary(key => key, val => val.CreateVertexBuffer(device));
+            var ibuffers = _Buffers.Values.ToDictionary(key => key, val => val.CreateIndexBuffer(device));
+            
+            foreach (var vb in vbuffers.Values) disposables.AddDisposable(vb);
+            foreach (var ib in ibuffers.Values) disposables.AddDisposable(ib);            
+
+            // create RuntimeModelMesh
+
+            RuntimeModelMesh _convert(IEnumerable<_MeshPrimitive> srcParts)
+            {
+                var dstMesh = new RuntimeModelMesh(device);
+
+                foreach (var srcPart in srcParts)
+                {
+                    var vb = vbuffers[srcPart.PrimitiveBuffers];
+                    var ib = ibuffers[srcPart.PrimitiveBuffers];
+
+                    var dstPart = dstMesh.CreateMeshPart();
+                    dstPart.Effect = srcPart.Material.PrimitiveEffect;
+                    dstPart.Blending = srcPart.Material.PrimitiveBlending;
+                    dstPart.FrontRasterizer = srcPart.Material.DoubleSided ? RasterizerState.CullNone : RasterizerState.CullCounterClockwise;
+                    dstPart.BackRasterizer = srcPart.Material.DoubleSided ? RasterizerState.CullNone : RasterizerState.CullClockwise;                    
+                    dstPart.SetVertexBuffer(vb, srcPart.VertexOffset, srcPart.VertexCount);
+                    dstPart.SetIndexBuffer(ib, srcPart.TriangleOffset * 3, srcPart.TriangleCount);
+                }
+
+                return dstMesh;
+            }
+
+            return _MeshPrimitives
+                .GroupBy(item => item.LogicalMeshIndex)
+                .ToDictionary(k => k.Key, v => _convert(v));
+        }
+
+        #endregion
+
+        #region nested types
+
+        interface IPrimitivesBuffers
+        {
+            VertexBuffer CreateVertexBuffer(GraphicsDevice device);
+            IndexBuffer CreateIndexBuffer(GraphicsDevice device);
+        }
+
+        /// <summary>
+        /// Contains the shared vertex/index buffers of all the mesh primitive that share the same vertex type.
+        /// </summary>
+        /// <typeparam name="TVertex"></typeparam>
+        sealed class _PrimitivesBuffers<TVertex> : IPrimitivesBuffers
+            where TVertex : unmanaged, IVertexType
+        {
+            #region data
+
+            private readonly List<TVertex> _Vertices = new List<TVertex>();
+            private readonly List<(int, int, int)> _Triangles = new List<(int, int, int)>();
+
+            #endregion
+
+            #region API
+            public _MeshPrimitive Append(int meshKey, Effect effect, BlendState blending, bool doubleSided, IMeshPrimitiveDecoder primitive)
+            {
+                var partVertices = primitive.ToXnaVertices<TVertex>();
+                var partTriangles = primitive.TriangleIndices.ToList();
+
+                var material = new _Material
+                {
+                    PrimitiveEffect = effect,
+                    PrimitiveBlending = blending,
+                    DoubleSided = doubleSided
+                };
+
+                var part = new _MeshPrimitive
+                {
+                    LogicalMeshIndex = meshKey,
+                    Material = material,
+                    PrimitiveBuffers = this,
+                    VertexOffset = _Vertices.Count,
+                    VertexCount = partVertices.Length,
+                    TriangleOffset = _Triangles.Count,
+                    TriangleCount = partTriangles.Count
+                };
+
+                _Vertices.AddRange(partVertices);
+                _Triangles.AddRange(partTriangles);
+
+                return part;
+            }
+
+            public VertexBuffer CreateVertexBuffer(GraphicsDevice device)
+            {
+                var data = new VertexBuffer(device, typeof(TVertex), _Vertices.Count, BufferUsage.None);
+                data.SetData(_Vertices.ToArray());
+                return data;
+            }
+
+            public IndexBuffer CreateIndexBuffer(GraphicsDevice device)
+            {
+                return CreateIndexBuffer(device, _Triangles);
+            }
+
+            private static IndexBuffer CreateIndexBuffer(GraphicsDevice device, IEnumerable<(int A, int B, int C)> triangles)
+            {
+                var sequence32 = triangles
+                    .SelectMany(item => new[] { (UInt32)item.C, (UInt32)item.B, (UInt32)item.A })
+                    .ToArray();
+
+                var max = sequence32.Max();
+
+                if (max > 65535)
+                {
+                    var indices = new IndexBuffer(device, typeof(UInt32), sequence32.Length, BufferUsage.None);
+
+                    indices.SetData(sequence32);
+                    return indices;
+                }
+                else
+                {
+                    var sequence16 = sequence32.Select(item => (UInt16)item).ToArray();
+
+                    var indices = new IndexBuffer(device, typeof(UInt16), sequence16.Length, BufferUsage.None);
+
+                    indices.SetData(sequence16);
+                    return indices;
+                }
+            }
+
+            #endregion
+        }
+
+        /// <summary>
+        /// Represents a mesh primitive
+        /// </summary>
+        struct _MeshPrimitive
+        {
+            public int LogicalMeshIndex;
+            public _Material Material;
+            public IPrimitivesBuffers PrimitiveBuffers;
+            public int VertexOffset;
+            public int VertexCount;
+            public int TriangleOffset;
+            public int TriangleCount;            
+        }
+
+        struct _Material
+        {
+            public Effect PrimitiveEffect;
+            public BlendState PrimitiveBlending;
+            public bool DoubleSided;
+        }
+
+        #endregion
+    }
+}

+ 106 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/TextureFactory.cs

@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    public abstract class TextureFactory<TTexture>
+    {
+        #region lifecycle
+
+        public TextureFactory(GraphicsDevice device)
+        {
+            _Device = device;            
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly GraphicsDevice _Device;
+        // private readonly GraphicsResourceTracker _Disposables;
+
+        private readonly Dictionary<TTexture, Texture2D> _Textures = new Dictionary<TTexture, Texture2D>();
+
+        #endregion
+
+        #region API
+
+        protected GraphicsDevice Device => _Device;
+
+        public Texture2D UseTexture(TTexture image, string name = null)
+        {
+            if (_Device == null) throw new InvalidOperationException();            
+
+            if (_Textures.TryGetValue(image, out Texture2D tex)) return tex;
+
+            tex = ConvertTexture(image);
+
+            tex.Name = name;
+
+            _Textures[image] = tex;
+
+            return tex;
+        }
+
+        protected abstract Texture2D ConvertTexture(TTexture image);
+
+        public Texture2D UseWhiteImage()
+        {
+            throw new NotImplementedException();
+
+            /*
+            const string solidWhitePNg = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFHpUWHRUaXRsZQAACJkrz8gsSQUABoACIippo0oAAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAAGklEQVQoz2P8//8/AymAiYFEMKphVMPQ0QAAVW0DHZ8uFaIAAAAASUVORK5CYII=";
+            var toBytes = Convert.FromBase64String(solidWhitePNg);
+            return UseTexture(new ArraySegment<byte>(toBytes), "_InternalSolidWhite");
+            */
+        }
+
+        public SamplerState UseSampler(TextureAddressMode u, TextureAddressMode v)
+        {
+            if (u == v)
+            {
+                if (u == TextureAddressMode.Wrap) return SamplerState.LinearWrap;
+                if (u == TextureAddressMode.Clamp) return SamplerState.LinearClamp;
+            }
+
+            var dstSampler = new SamplerState();
+            // _TextureSamplers[gltfSampler] = dstSampler;
+            
+
+            dstSampler.AddressU = u;
+            dstSampler.AddressV = v;
+
+            // ToDo: we also need to set magnification and minification filters.
+
+            return dstSampler;
+        }
+
+        #endregion        
+    }
+
+    public sealed class SolidColorTextureFactory : TextureFactory<Color>
+    {
+        public SolidColorTextureFactory(GraphicsDevice device, int width = 4, int height = 4) : base(device)
+        {
+            _Width = width;
+            _Height = height;
+        }
+
+        private readonly int _Width;
+        private readonly int _Height;
+
+        protected override Texture2D ConvertTexture(Color color)
+        {
+            var data = new Color[_Width * _Height];
+            data.AsSpan().Fill(color);
+
+            var tex = new Texture2D(Device, _Width, _Height);
+            tex.SetData(data);
+
+            return tex;
+        }
+    }
+}

+ 200 - 0
src/MonoGame.Framework.Graphics.Toolkit3D/Pipeline/VertexDecoder.cs

@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using XY = Microsoft.Xna.Framework.Vector2;
+using XYZ = Microsoft.Xna.Framework.Vector3;
+using XYZW = Microsoft.Xna.Framework.Vector4;
+
+using PACKED = Microsoft.Xna.Framework.Graphics.PackedVector;
+
+
+namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
+{
+    static class VertexDecoder
+    {
+        #region API
+
+        /// <summary>
+        /// Gets the current Vertex attributes as an array of <see cref="{TVertex}"/> vertices.
+        /// </summary>
+        /// <typeparam name="TVertex">A Vertex type implementing <see cref="IVertexType"/>.</typeparam>
+        /// <returns>A <see cref="{TVertex}"/> array</returns>
+        public static unsafe TVertex[] ToXnaVertices<TVertex>(this IMeshPrimitiveDecoder srcMesh)
+            where TVertex : unmanaged, IVertexType
+        {
+            var declaration = default(TVertex).VertexDeclaration;
+
+            if (sizeof(TVertex) != declaration.VertexStride) throw new ArgumentException(nameof(TVertex));
+
+            var dst = new TVertex[srcMesh.VertexCount];
+
+            VertexSkinning skinning = default; // skin joints indices            
+
+            for (int i = 0; i < dst.Length; ++i)
+            {
+                var dstv = _VertexWriter.CreateFromArray(dst, i);
+
+                if (srcMesh.JointsWeightsCount > 0) skinning = srcMesh.GetSkinWeights(i);
+                
+                foreach (var element in declaration.GetVertexElements())
+                {
+                    switch (element.VertexElementUsage)
+                    {
+                        case VertexElementUsage.Position: dstv.SetValue(element, srcMesh.GetPosition(i)); break;
+                        case VertexElementUsage.Normal: dstv.SetValue(element, srcMesh.GetNormal(i)); break;
+                        case VertexElementUsage.Tangent: dstv.SetValue(element, srcMesh.GetTangent(i)); break;
+
+                        case VertexElementUsage.TextureCoordinate: dstv.SetValue(element, srcMesh.GetTextureCoord(i, element.UsageIndex)); break;
+                        case VertexElementUsage.Color: dstv.SetValue(element, srcMesh.GetColor(i, element.UsageIndex)); break;
+
+                        case VertexElementUsage.BlendIndices: dstv.SetValue(element, skinning.Indices); break;
+                        case VertexElementUsage.BlendWeight: dstv.SetValue(element, skinning.Weights); break;
+                    }
+                }
+            }
+
+            return dst;
+        }
+
+        #endregion
+
+        #region nested types
+        readonly ref struct _VertexWriter
+        {
+            #region constructor
+            public static _VertexWriter CreateFromArray<TVertex>(TVertex[] vvv, int idx)
+                where TVertex : unmanaged, IVertexType
+            {
+                var v = vvv.AsSpan().Slice(idx, 1);
+
+                var d = MemoryMarshal.Cast<TVertex, Byte>(v);
+
+                return new _VertexWriter(d);
+            }
+
+            public _VertexWriter(Span<Byte> vertex)
+            {
+                _Vertex = vertex;
+            }
+
+            #endregion
+
+            #region data
+
+            private readonly Span<Byte> _Vertex;
+
+            #endregion
+
+            #region API            
+
+            public unsafe void SetValue(VertexElement element, XY value)
+            {
+                if (element.VertexElementFormat == VertexElementFormat.Vector2)
+                {
+                    var dst = _Vertex.Slice(element.Offset, sizeof(XY));
+                    MemoryMarshal.Write(dst, ref value);
+                    return;
+                }
+
+                throw new NotImplementedException();
+            }
+
+            public unsafe void SetValue(VertexElement element, XYZ value)
+            {
+                if (element.VertexElementFormat == VertexElementFormat.Vector3)
+                {
+                    var dst = _Vertex.Slice(element.Offset, sizeof(XYZ));
+                    MemoryMarshal.Write(dst, ref value);
+                    return;
+                }
+
+                throw new NotImplementedException();
+            }
+
+            public unsafe void SetValue(VertexElement element, XYZW value)
+            {
+                var dst = _Vertex.Slice(element.Offset);
+
+                switch (element.VertexElementFormat)
+                {
+                    case VertexElementFormat.Vector4:
+                        MemoryMarshal.Write(dst, ref value);
+                        return;
+
+                    case VertexElementFormat.Color:
+                        SetValue(element, new Color(value));
+                        return;
+
+                    case VertexElementFormat.Byte4:
+                        SetValue(element, new PACKED.Byte4(value));
+                        return;
+
+                    case VertexElementFormat.Short4:
+                        SetValue(element, new PACKED.Short4(value));
+                        return;
+
+                    case VertexElementFormat.NormalizedShort4:
+                        SetValue(element, new PACKED.NormalizedShort4(value));
+                        return;
+                }
+
+                throw new NotImplementedException();
+            }
+
+            public unsafe void SetValue(VertexElement element, PACKED.Byte4 value)
+            {
+                if (element.VertexElementFormat != VertexElementFormat.Byte4) throw new ArgumentException(nameof(element));
+
+                var dst = _Vertex.Slice(element.Offset, sizeof(PACKED.Byte4));
+                MemoryMarshal.Write(dst, ref value);
+            }
+
+            public unsafe void SetValue(VertexElement element, Color value)
+            {
+                if (element.VertexElementFormat != VertexElementFormat.Color) throw new ArgumentException(nameof(element));
+
+                var dst = _Vertex.Slice(element.Offset, sizeof(PACKED.Byte4));
+                MemoryMarshal.Write(dst, ref value);
+            }
+
+            public unsafe void SetValue(VertexElement element, PACKED.Short4 value)
+            {
+                if (element.VertexElementFormat == VertexElementFormat.Short4)
+                {
+                    var dst = _Vertex.Slice(element.Offset, sizeof(PACKED.Short4));
+                    MemoryMarshal.Write(dst, ref value);
+                    return;
+                }
+
+                if (element.VertexElementFormat == VertexElementFormat.Byte4)
+                {
+                    var xval = new PACKED.Byte4(value.ToVector4());
+
+                    var dst = _Vertex.Slice(element.Offset, sizeof(PACKED.Byte4));
+                    MemoryMarshal.Write(dst, ref xval);
+                    return;
+                }
+
+                throw new ArgumentException(nameof(element));
+            }
+
+            public unsafe void SetValue(VertexElement element, PACKED.NormalizedShort4 value)
+            {
+                if (element.VertexElementFormat != VertexElementFormat.NormalizedShort4) throw new ArgumentException(nameof(element));
+
+                var dst = _Vertex.Slice(element.Offset, sizeof(PACKED.NormalizedShort4));
+                MemoryMarshal.Write(dst, ref value);
+            }
+
+            #endregion
+        }
+
+        #endregion  
+    }
+}

+ 24 - 0
src/MonoGameScene.sln

@@ -11,6 +11,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGameViewer", "MonoGameV
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Graphics.PBR", "MonoGame.Framework.Graphics.PBR\MonoGame.Framework.Graphics.PBR.csproj", "{6D3141B5-4747-4D58-B773-4EA96DA8887B}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Graphics.PBR", "MonoGame.Framework.Graphics.PBR\MonoGame.Framework.Graphics.PBR.csproj", "{6D3141B5-4747-4D58-B773-4EA96DA8887B}"
 EndProject
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Graphics.Toolkit3D", "MonoGame.Framework.Graphics.Toolkit3D\MonoGame.Framework.Graphics.Toolkit3D.csproj", "{50241C0B-7275-4B2D-AA65-6D2EB643D138}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Pipeline.GLTF", "MonoGame.Framework.Graphics.GLTF\MonoGame.Framework.Pipeline.GLTF.csproj", "{B3E6CDA2-F721-430E-BD3C-55D78E6B8C6D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo1", "Demo1\Demo1.csproj", "{2C4B5022-8BA0-44CB-A612-995518C216D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo2", "Demo2\Demo2.csproj", "{9B0C0AF4-F505-4EFA-A707-6AE469224A98}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -33,6 +41,22 @@ Global
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Release|Any CPU.Build.0 = Release|Any CPU
 		{6D3141B5-4747-4D58-B773-4EA96DA8887B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{50241C0B-7275-4B2D-AA65-6D2EB643D138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{50241C0B-7275-4B2D-AA65-6D2EB643D138}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{50241C0B-7275-4B2D-AA65-6D2EB643D138}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{50241C0B-7275-4B2D-AA65-6D2EB643D138}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B3E6CDA2-F721-430E-BD3C-55D78E6B8C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B3E6CDA2-F721-430E-BD3C-55D78E6B8C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B3E6CDA2-F721-430E-BD3C-55D78E6B8C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B3E6CDA2-F721-430E-BD3C-55D78E6B8C6D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2C4B5022-8BA0-44CB-A612-995518C216D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2C4B5022-8BA0-44CB-A612-995518C216D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2C4B5022-8BA0-44CB-A612-995518C216D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2C4B5022-8BA0-44CB-A612-995518C216D2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9B0C0AF4-F505-4EFA-A707-6AE469224A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9B0C0AF4-F505-4EFA-A707-6AE469224A98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9B0C0AF4-F505-4EFA-A707-6AE469224A98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9B0C0AF4-F505-4EFA-A707-6AE469224A98}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE

+ 7 - 11
src/MonoGameViewer/MainScene.cs

@@ -18,11 +18,11 @@ namespace MonoGameViewer
         #region data
         #region data
 
 
         SharpGLTF.Schema2.ModelRoot _Model;
         SharpGLTF.Schema2.ModelRoot _Model;
-        SharpGLTF.Runtime.MonoGameDeviceContent<SharpGLTF.Runtime.MonoGameModelTemplate> _ModelTemplate;        
+        ModelTemplate _ModelTemplate;
 
 
         private bool _UseClassicEffects;
         private bool _UseClassicEffects;
 
 
-        private SharpGLTF.Runtime.MonoGameModelInstance _ModelInstance;
+        private ModelLayerInstance _ModelInstance;
 
 
         private Quaternion _Rotation = Quaternion.Identity;
         private Quaternion _Rotation = Quaternion.Identity;
 
 
@@ -78,11 +78,7 @@ namespace MonoGameViewer
             if (_Model == null) return;
             if (_Model == null) return;
             if (_ModelTemplate != null) { _ModelTemplate.Dispose(); _ModelTemplate = null; }
             if (_ModelTemplate != null) { _ModelTemplate.Dispose(); _ModelTemplate = null; }
 
 
-            var loader = _UseClassicEffects
-                ? new SharpGLTF.Runtime.Content.BasicEffectsLoaderContext(this.GraphicsDevice)
-                : SharpGLTF.Runtime.Content.LoaderContext.CreateLoaderContext(this.GraphicsDevice);
-
-            _ModelTemplate = loader.CreateDeviceModel(_Model);
+            _ModelTemplate = Microsoft.Xna.Framework.Content.Pipeline.Graphics.FormatGLTF.ReadModel(_Model, GraphicsDevice, _UseClassicEffects);
             _ModelInstance = null;
             _ModelInstance = null;
         }
         }
 
 
@@ -102,7 +98,7 @@ namespace MonoGameViewer
 
 
         public override void Update(GameTime gameTime)
         public override void Update(GameTime gameTime)
         {
         {
-            if (_ModelInstance == null && _ModelTemplate != null) _ModelInstance = _ModelTemplate.Content.CreateInstance();
+            if (_ModelInstance == null && _ModelTemplate != null) _ModelInstance = _ModelTemplate.DefaultLayer.CreateInstance();
         }
         }
 
 
         public override void Draw(GameTime gameTime)
         public override void Draw(GameTime gameTime)
@@ -111,9 +107,9 @@ namespace MonoGameViewer
 
 
             if (_ModelInstance == null) return;
             if (_ModelInstance == null) return;
 
 
-            _ModelInstance.Controller.SetAnimationFrame(0, (float)gameTime.TotalGameTime.TotalSeconds);
+            _ModelInstance.Armature.SetAnimationFrame(0, (float)gameTime.TotalGameTime.TotalSeconds);
 
 
-            var bounds = _ModelInstance.Template.Bounds;
+            var bounds = _ModelInstance.ModelBounds;
 
 
             var lookAt = bounds.Center;
             var lookAt = bounds.Center;
             var camPos = bounds.Center + new Vector3(0, 0, bounds.Radius * 3);
             var camPos = bounds.Center + new Vector3(0, 0, bounds.Radius * 3);
@@ -133,7 +129,7 @@ namespace MonoGameViewer
             {
             {
                 _ModelInstance.WorldMatrix = Matrix.CreateFromQuaternion(_Rotation);
                 _ModelInstance.WorldMatrix = Matrix.CreateFromQuaternion(_Rotation);
 
 
-                var ctx = new SharpGLTF.Runtime.MonoGameDrawingContext(this.GraphicsDevice);
+                var ctx = new ModelDrawingContext(this.GraphicsDevice);
                 ctx.SetCamera(camera);                
                 ctx.SetCamera(camera);                
                 ctx.DrawModelInstance(env, _ModelInstance);
                 ctx.DrawModelInstance(env, _ModelInstance);
             }
             }

+ 1 - 2
src/MonoGameViewer/MonoGameViewer.csproj

@@ -13,8 +13,7 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\MonoGame.Framework.Graphics.PBR\MonoGame.Framework.Graphics.PBR.csproj" />
-    <ProjectReference Include="..\SharpGLTF.Runtime.MonoGame\SharpGLTF.Runtime.MonoGame.csproj" />
+    <ProjectReference Include="..\MonoGame.Framework.Graphics.GLTF\MonoGame.Framework.Pipeline.GLTF.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
 
 

+ 1 - 6
src/SharpGLTF.Runtime.MonoGame/Content/LoaderContext.PBREffects.cs

@@ -7,7 +7,6 @@ using System.Threading.Tasks;
 
 
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework.Graphics;
 using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Graphics.Effects;
 
 
 using GLTFMATERIAL = SharpGLTF.Schema2.Material;
 using GLTFMATERIAL = SharpGLTF.Schema2.Material;
 
 
@@ -61,11 +60,7 @@ namespace SharpGLTF.Runtime.Content
             TransferChannel(effect.NormalMap, srcMaterial, "Normal", 1);
             TransferChannel(effect.NormalMap, srcMaterial, "Normal", 1);
             TransferChannel(effect.EmissiveMap, srcMaterial, "Emissive", Vector3.Zero);
             TransferChannel(effect.EmissiveMap, srcMaterial, "Emissive", Vector3.Zero);
             TransferChannel(effect.OcclusionMap, srcMaterial, "Occlusion", 0);            
             TransferChannel(effect.OcclusionMap, srcMaterial, "Occlusion", 0);            
-            if (effect.OcclusionMap.Texture == null) effect.OcclusionMap.Scale = 0;
-
-            // effect.AlphaMode = srcMaterial.Alpha;
-            // effect.AlphaCutoff = srcMaterial.AlphaCutoff;
-            // effect.DoubleSided = srcMaterial.DoubleSided;
+            if (effect.OcclusionMap.Texture == null) effect.OcclusionMap.Scale = 0;            
 
 
             return effect;
             return effect;
         }
         }