Przeglądaj źródła

Initial Commit.

Vicente Penades 5 lat temu
rodzic
commit
60b7f15bd7

+ 1 - 0
README.md

@@ -1,2 +1,3 @@
 # SharpGLTF.Monogame.Example
+
 Example of using Monogame and SharpGLTF to load animated glTF models at runtime.

+ 31 - 0
src/MonoGameScene.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29806.167
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGameScene", "MonoGameScene\MonoGameScene.csproj", "{3479C8CD-AE00-429E-BA88-1332B1E75F97}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Runtime.MonoGame", "SharpGLTF.Runtime.MonoGame\SharpGLTF.Runtime.MonoGame.csproj", "{83A2BD11-5186-4595-8B7E-191B5DE0BEFE}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3479C8CD-AE00-429E-BA88-1332B1E75F97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3479C8CD-AE00-429E-BA88-1332B1E75F97}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3479C8CD-AE00-429E-BA88-1332B1E75F97}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3479C8CD-AE00-429E-BA88-1332B1E75F97}.Release|Any CPU.Build.0 = Release|Any CPU
+		{83A2BD11-5186-4595-8B7E-191B5DE0BEFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{83A2BD11-5186-4595-8B7E-191B5DE0BEFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{83A2BD11-5186-4595-8B7E-191B5DE0BEFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{83A2BD11-5186-4595-8B7E-191B5DE0BEFE}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {A5475DEB-D7F5-4A81-84EB-5C61A84246EB}
+	EndGlobalSection
+EndGlobal

+ 6 - 0
src/MonoGameScene/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
+    </startup>
+</configuration>

+ 163 - 0
src/MonoGameScene/Game1.cs

@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace MonoGameScene
+{
+    /// <summary>
+    /// This is the main type for your game.
+    /// </summary>
+    public class Game1 : Game
+    {
+        #region lifecycle
+
+        public Game1()
+        {
+            _Graphics = new GraphicsDeviceManager(this);
+
+            Content.RootDirectory = "Content";
+        }
+        
+        protected override void Initialize()
+        {
+            // TODO: Add your initialization logic here
+
+            this.Window.Title = "SharpGLTF - MonoGame Scene";
+            this.Window.AllowUserResizing = true;
+            this.Window.AllowAltF4 = true;
+
+            base.Initialize();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+        }
+
+        #endregion
+
+        #region resources
+
+        private readonly GraphicsDeviceManager _Graphics;
+        
+        // these are the actual hardware resources that represent every model's geometry.
+        
+        SharpGLTF.Runtime.MonoGameDeviceContent<SharpGLTF.Runtime.MonoGameModelTemplate> _AvodadoTemplate;
+        SharpGLTF.Runtime.MonoGameDeviceContent<SharpGLTF.Runtime.MonoGameModelTemplate> _BrainStemTemplate;
+        SharpGLTF.Runtime.MonoGameDeviceContent<SharpGLTF.Runtime.MonoGameModelTemplate> _CesiumManTemplate;
+        SharpGLTF.Runtime.MonoGameDeviceContent<SharpGLTF.Runtime.MonoGameModelTemplate> _HauntedHouseTemplate;
+
+        #endregion
+
+        #region content loading
+        
+        protected override void LoadContent()
+        {
+            _AvodadoTemplate = SharpGLTF.Runtime.MonoGameModelTemplate.LoadDeviceModel(this.GraphicsDevice, "Models\\Avocado.glb");
+            _BrainStemTemplate = SharpGLTF.Runtime.MonoGameModelTemplate.LoadDeviceModel(this.GraphicsDevice, "Models\\BrainStem.glb");
+            _CesiumManTemplate = SharpGLTF.Runtime.MonoGameModelTemplate.LoadDeviceModel(this.GraphicsDevice, "Models\\CesiumMan.glb");
+            _HauntedHouseTemplate = SharpGLTF.Runtime.MonoGameModelTemplate.LoadDeviceModel(this.GraphicsDevice, "Models\\haunted_house.glb");
+        }
+        
+        protected override void UnloadContent()
+        {
+            _AvodadoTemplate?.Dispose();
+            _AvodadoTemplate = null;
+
+            _BrainStemTemplate?.Dispose();
+            _BrainStemTemplate = null;
+
+            _CesiumManTemplate?.Dispose();
+            _CesiumManTemplate = null;
+
+            _HauntedHouseTemplate?.Dispose();
+            _HauntedHouseTemplate = null;
+        }
+
+        #endregion
+
+        #region game loop
+
+        // these are the scene instances we create for every glTF model we want to render on screen.
+        // Instances are designed to be as lightweight as possible, so it should not be a problem to
+        // create as many of them as you need at runtime.
+        private SharpGLTF.Runtime.MonoGameModelInstance _HauntedHouse;
+        private SharpGLTF.Runtime.MonoGameModelInstance _BrainStem;
+        private SharpGLTF.Runtime.MonoGameModelInstance _Avocado;
+        private SharpGLTF.Runtime.MonoGameModelInstance _CesiumMan1;
+        private SharpGLTF.Runtime.MonoGameModelInstance _CesiumMan2;
+        private SharpGLTF.Runtime.MonoGameModelInstance _CesiumMan3;
+        private SharpGLTF.Runtime.MonoGameModelInstance _CesiumMan4;       
+
+
+        protected override void Update(GameTime gameTime)
+        {
+            // For Mobile devices, this logic will close the Game when the Back button is pressed
+            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+            {
+                Exit();
+            }
+
+            // create as many instances as we need from the templates
+
+            if (_Avocado == null) _Avocado = _AvodadoTemplate.Instance.CreateInstance();
+            if (_HauntedHouse == null) _HauntedHouse = _HauntedHouseTemplate.Instance.CreateInstance();
+            if (_BrainStem == null) _BrainStem = _BrainStemTemplate.Instance.CreateInstance();
+
+            if (_CesiumMan1 == null) _CesiumMan1 = _CesiumManTemplate.Instance.CreateInstance();
+            if (_CesiumMan2 == null) _CesiumMan2 = _CesiumManTemplate.Instance.CreateInstance();
+            if (_CesiumMan3 == null) _CesiumMan3 = _CesiumManTemplate.Instance.CreateInstance();
+            if (_CesiumMan4 == null) _CesiumMan4 = _CesiumManTemplate.Instance.CreateInstance();
+
+            // animate each instance individually.
+
+            var animTime = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            _BrainStem.Controller.SetAnimationFrame(0, 0.7f* animTime);
+            _CesiumMan1.Controller.SetAnimationFrame(0, 0.3f);
+            _CesiumMan2.Controller.SetAnimationFrame(0, 0.5f * animTime);
+            _CesiumMan3.Controller.SetAnimationFrame(0, 1.0f * animTime);
+            _CesiumMan4.Controller.SetAnimationFrame(0, 1.5f * animTime);
+
+            base.Update(gameTime);
+        }        
+
+        
+        protected override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice.Clear(Color.DarkSlateGray);
+
+            base.Draw(gameTime);
+
+            var animTime = (float)gameTime.TotalGameTime.TotalSeconds;
+
+            var lookAt = new Vector3(0, 2, 0);
+            var camPos = new Vector3((float)Math.Sin(animTime*0.5f) * 2, 2, 12);
+
+            var camera = Matrix.CreateWorld(camPos, lookAt - camPos, Vector3.UnitY);                      
+
+            // draw all the instances.
+
+            var ctx = new ModelDrawContext(_Graphics, camera);
+
+            ctx.DrawModelInstance(_Avocado, Matrix.CreateScale(30) * Matrix.CreateRotationY(animTime*0.3f) * Matrix.CreateTranslation(-5,5,-5));
+
+            ctx.DrawModelInstance(_HauntedHouse, Matrix.CreateScale(20) * Matrix.CreateRotationY(1));
+
+            ctx.DrawModelInstance(_BrainStem, Matrix.CreateTranslation(0,0.5f,8));
+
+            ctx.DrawModelInstance(_CesiumMan1, Matrix.CreateTranslation(-3, 0, 5));
+            ctx.DrawModelInstance(_CesiumMan2, Matrix.CreateTranslation(-2, 0, 5));
+            ctx.DrawModelInstance(_CesiumMan3, Matrix.CreateTranslation( 2, 0, 5));
+            ctx.DrawModelInstance(_CesiumMan4, Matrix.CreateTranslation( 3, 0, 5));
+        }
+        
+        #endregion
+    }
+}

+ 69 - 0
src/MonoGameScene/ModelDrawContext.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using SharpGLTF.Runtime;
+
+namespace MonoGameScene
+{
+    /// <summary>
+    /// Small helper for rendering MonoGame models.
+    /// </summary>
+    class ModelDrawContext
+    {
+        #region lifecycle
+
+        public ModelDrawContext(GraphicsDeviceManager graphics, Matrix cameraMatrix)
+        {
+            _Device = graphics.GraphicsDevice;
+
+            _Device.DepthStencilState = DepthStencilState.Default;
+
+            _View = Matrix.Invert(cameraMatrix);            
+            
+            float fieldOfView = MathHelper.PiOver4;            
+            float nearClipPlane = 0.01f;            
+            float farClipPlane = 1000;
+
+            _Projection = Matrix.CreatePerspectiveFieldOfView(fieldOfView, graphics.GraphicsDevice.Viewport.AspectRatio, nearClipPlane, farClipPlane);            
+        }
+
+        #endregion
+
+        #region data
+
+        private GraphicsDevice _Device;
+        private Matrix _Projection;
+        private Matrix _View;        
+
+        #endregion        
+
+        #region API
+
+        public void DrawModelInstance(MonoGameModelInstance model, Matrix world)
+        {
+            foreach (var e in model.Template.Effects) UpdateMaterial(e);
+
+            model.Draw(_Projection, _View, world);
+        }
+
+        public static void UpdateMaterial(Effect effect)
+        {
+            if (effect is IEffectLights lights)
+            {
+                lights.EnableDefaultLighting();
+            }
+
+            if (effect is IEffectFog fog)
+            {
+                fog.FogEnabled = false;
+            }
+        }
+
+        #endregion
+    }
+}

BIN
src/MonoGameScene/Models/Avocado.glb


BIN
src/MonoGameScene/Models/BrainStem.glb


BIN
src/MonoGameScene/Models/CesiumMan.glb


BIN
src/MonoGameScene/Models/haunted_house.glb


BIN
src/MonoGameScene/MonoGameDemo.jpg


+ 38 - 0
src/MonoGameScene/MonoGameScene.csproj

@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net471</TargetFramework>
+    <OutputType>WinExe</OutputType>
+    <LangVersion>7.1</LangVersion>    
+    <MonoGamePlatform>Windows</MonoGamePlatform>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.7.1.189" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SharpGLTF.Runtime.Monogame\SharpGLTF.Runtime.MonoGame.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="System.Windows.Forms" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="Models\Avocado.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Models\BrainStem.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Models\CesiumMan.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Models\haunted_house.glb">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+
+</Project>

+ 14 - 0
src/MonoGameScene/Program.cs

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

+ 13 - 0
src/MonoGameScene/readme.md

@@ -0,0 +1,13 @@
+# MonoGame realtime rendering demo
+
+![MonoGame Demo](MonoGameDemo.jpg)
+
+Notes on the demo:
+
+MonoGame typically preprocesses all graphics resources through its content pipeline, and all assets are converted to XNB files, which is what the runtime is able to load.
+
+This is not the case of this demo; the glTF files are loaded at runtime without any preprocessing.
+
+Also for simplicity, the demo uses the default in-built BasicEffect and SkinnedEffect shaders, which date from the years of DirectX9, so PBR rendering is not supported.
+
+The project depends on [SharpGLTF.Runtime.MonoGame](../SharpGLTF.Runtime.MonoGame), which also depends on [SharpGLTF.Core](../../src/SharpGLTF.Core), that's everything you need to load and render glTF models into MonoGame.

+ 148 - 0
src/SharpGLTF.Runtime.MonoGame/LoaderContext.cs

@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// Helper class used to import a glTF model into MonoGame
+    /// </summary>
+    class LoaderContext
+    {
+        #region lifecycle
+
+        public LoaderContext(GraphicsDevice device)
+        {
+            _Device = device;
+            _MatFactory = new MaterialFactory(device, _Disposables);
+        }
+
+        #endregion
+
+        #region data
+
+        private GraphicsDevice _Device;
+
+        private readonly GraphicsResourceTracker _Disposables = new GraphicsResourceTracker();
+        private readonly MaterialFactory _MatFactory;        
+
+        private readonly Dictionary<Mesh, ModelMesh> _StaticMeshes = new Dictionary<Mesh, ModelMesh>();
+        private readonly Dictionary<Mesh, ModelMesh> _SkinnedMeshes = new Dictionary<Mesh, ModelMesh>();
+        
+        #endregion
+
+        #region properties
+
+        public IReadOnlyList<GraphicsResource> Disposables => _Disposables.Disposables;
+
+        #endregion                  
+
+        #region Mesh API
+
+        private static IEnumerable<Schema2.MeshPrimitive> GetValidPrimitives(Schema2.Mesh srcMesh)
+        {
+            foreach (var srcPrim in srcMesh.Primitives)
+            {
+                var ppp = srcPrim.GetVertexAccessor("POSITION");
+                if (ppp.Count < 3) continue;
+
+                if (srcPrim.DrawPrimitiveType == Schema2.PrimitiveType.POINTS) continue;
+                if (srcPrim.DrawPrimitiveType == Schema2.PrimitiveType.LINES) continue;
+                if (srcPrim.DrawPrimitiveType == Schema2.PrimitiveType.LINE_LOOP) continue;
+                if (srcPrim.DrawPrimitiveType == Schema2.PrimitiveType.LINE_STRIP) continue;
+
+                yield return srcPrim;
+            }
+        }
+
+        public ModelMesh CreateMesh(Schema2.Mesh srcMesh, int maxBones = 72)
+        {
+            if (_Device == null) throw new InvalidOperationException();            
+
+            var srcPrims = GetValidPrimitives(srcMesh).ToList();            
+
+            var dstMesh = new ModelMesh(_Device, Enumerable.Range(0, srcPrims.Count).Select(item => new ModelMeshPart()).ToList());
+
+            dstMesh.Name = srcMesh.Name;
+            dstMesh.BoundingSphere = srcMesh.CreateBoundingSphere();
+
+            var srcNormals = new MeshNormalsFallback(srcMesh);
+
+            var idx = 0;
+            foreach (var srcPrim in srcPrims)
+            {
+                CreateMeshPart(dstMesh.MeshParts[idx++], srcPrim, srcNormals, maxBones);
+            }
+
+            return dstMesh;
+        }
+
+        private void CreateMeshPart(ModelMeshPart dstPart, MeshPrimitive srcPart, MeshNormalsFallback normalsFunc, int maxBones)
+        {
+            var doubleSided = srcPart.Material?.DoubleSided ?? false;
+
+            var srcGeometry = new MeshPrimitiveReader(srcPart, doubleSided, normalsFunc);
+
+            var eff = srcGeometry.IsSkinned ? _MatFactory.UseSkinnedEffect(srcPart.Material) : _MatFactory.UseStaticEffect(srcPart.Material);
+
+            dstPart.Effect = eff;            
+
+            var vb = srcGeometry.IsSkinned ? CreateVertexBuffer(srcGeometry.ToXnaSkinned()) : CreateVertexBuffer(srcGeometry.ToXnaStatic());
+
+            dstPart.VertexBuffer = vb;
+            dstPart.NumVertices = srcGeometry.VertexCount;
+            dstPart.VertexOffset = 0;
+
+            dstPart.IndexBuffer = CreateIndexBuffer(srcGeometry.TriangleIndices);
+            dstPart.PrimitiveCount = srcGeometry.TriangleIndices.Length;
+            dstPart.StartIndex = 0;
+        }
+        
+        #endregion
+
+        #region resources API
+
+        private VertexBuffer CreateVertexBuffer<T>(T[] dstVertices) where T:struct, IVertexType
+        {
+            var vb = new VertexBuffer(_Device, typeof(T), dstVertices.Length, BufferUsage.None);
+            _Disposables.AddDisposable(vb);
+
+            vb.SetData(dstVertices);
+            return vb;
+        }
+
+        private IndexBuffer CreateIndexBuffer(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);
+                _Disposables.AddDisposable(indices);
+
+                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);
+                _Disposables.AddDisposable(indices);
+
+                indices.SetData(sequence16);
+                return indices;
+            }
+        }        
+
+        #endregion
+    }    
+}

+ 63 - 0
src/SharpGLTF.Runtime.MonoGame/MeshNormalsFallback.cs

@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+
+using XYZ = System.Numerics.Vector3;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// Helper class used to calculate smooth Normals on glTF meshes with missing normals.
+    /// </summary>
+    class MeshNormalsFallback
+    {
+        #region lifecycle
+
+        public MeshNormalsFallback(Schema2.Mesh mesh)
+        {
+            foreach (var srcPrim in mesh.Primitives)
+            {
+                var accessor = srcPrim.GetVertexAccessor("POSITION");
+                if (accessor == null) continue;
+
+                var positions = accessor.AsVector3Array();
+
+                foreach (var srcTri in srcPrim.GetTriangleIndices())
+                {
+                    var a = positions[srcTri.A];
+                    var b = positions[srcTri.B];
+                    var c = positions[srcTri.C];
+                    var d = XYZ.Cross(b - a, c - a);
+
+                    AddWeightedNormal(a, d);
+                    AddWeightedNormal(b, d);
+                    AddWeightedNormal(c, d);
+                }
+            }
+        }
+
+        #endregion
+
+        #region data
+        
+        private readonly Dictionary<XYZ, XYZ> _WeightedNormals = new Dictionary<XYZ, XYZ>();
+
+        #endregion
+
+        #region API
+
+        private void AddWeightedNormal(XYZ p, XYZ d)
+        {
+            if (_WeightedNormals.TryGetValue(p, out XYZ ddd)) ddd += d;
+            else ddd = d;
+
+            _WeightedNormals[p] = ddd;
+        }
+
+        public XYZ GetNormal(XYZ position)
+        {
+            if (!_WeightedNormals.TryGetValue(position, out XYZ normal)) normal = position;
+            return normal == XYZ.Zero ? XYZ.UnitX : XYZ.Normalize(normal);
+        }
+        
+        #endregion
+    }
+}

+ 137 - 0
src/SharpGLTF.Runtime.MonoGame/MeshPrimitiveReader.cs

@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework.Graphics;
+
+using SharpGLTF.Schema2;
+
+using XY = System.Numerics.Vector2;
+using XYZ = System.Numerics.Vector3;
+using XYZW = System.Numerics.Vector4;
+
+namespace SharpGLTF.Runtime
+{
+    class MeshPrimitiveReader
+    {
+        #region lifecycle
+
+        public MeshPrimitiveReader(MeshPrimitive srcPrim, bool doubleSided, MeshNormalsFallback fallbackNormals)
+        {
+            _Positions = srcPrim.GetVertexAccessor("POSITION")?.AsVector3Array();
+            _Normals = srcPrim.GetVertexAccessor("NORMAL")?.AsVector3Array();
+
+            if (_Normals == null)
+            {
+                _Normals = new XYZ[_Positions.Count];
+
+                for (int i = 0; i < _Normals.Count; ++i)
+                {
+                    _Normals[i] = fallbackNormals.GetNormal(_Positions[i]);
+                }
+            }
+
+            _Color0 = srcPrim.GetVertexAccessor("COLOR_0")?.AsColorArray();
+            _TexCoord0 = srcPrim.GetVertexAccessor("TEXCOORD_0")?.AsVector2Array();
+
+            _Joints0 = srcPrim.GetVertexAccessor("JOINTS_0")?.AsVector4Array();
+            _Joints1 = srcPrim.GetVertexAccessor("JOINTS_1")?.AsVector4Array();
+            _Weights0 = srcPrim.GetVertexAccessor("WEIGHTS_0")?.AsVector4Array();
+            _Weights1 = srcPrim.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array();
+
+            if (_Joints0 == null || _Weights0 == null) { _Joints0 = _Joints1 = _Weights0 = _Weights1 = null; }
+            if (_Joints1 == null || _Weights1 == null) { _Joints1 = _Weights1 = null; }
+
+            if (_Weights0 != null)
+            {
+                _Weights0 = _Weights0.ToArray(); // isolate memory to prevent overwriting source glTF.
+
+                for (int i = 0; i < _Weights0.Count; ++i)
+                {
+                    var r = XYZW.Dot(_Weights0[i], XYZW.One);
+                    _Weights0[i] /= r;
+                }
+            }
+
+            if (doubleSided) // Monogame's effect material does not support double sided materials, so we simulate it by adding reverse faces
+            {
+                var front = srcPrim.GetTriangleIndices();
+                var back = front.Select(item => (item.A, item.C, item.B));
+                _Triangles = front.Concat(back).ToArray();
+            }
+            else
+            {
+                _Triangles = srcPrim.GetTriangleIndices().ToArray();
+            }
+
+            
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly (int, int, int)[] _Triangles;
+
+        private readonly IList<XYZ> _Positions;
+        private readonly IList<XYZ> _Normals;
+
+        private readonly IList<XYZW> _Color0;
+        private readonly IList<XY> _TexCoord0;
+
+        private readonly IList<XYZW> _Joints0;
+        private readonly IList<XYZW> _Joints1;
+
+        private readonly IList<XYZW> _Weights0;
+        private readonly IList<XYZW> _Weights1;
+
+        #endregion
+
+        #region properties
+
+        public bool IsSkinned => _Joints0 != null;
+
+        public int VertexCount => _Positions?.Count ?? 0;
+
+        public (int, int, int)[] TriangleIndices => _Triangles;
+
+        #endregion
+
+        #region API        
+
+        public VertexPositionNormalTexture[] ToXnaStatic()
+        {
+            var dst = new VertexPositionNormalTexture[_Positions.Count];
+
+            for (int i = 0; i < dst.Length; ++i)
+            {
+                dst[i].Position = _Positions[i].ToXna();
+                dst[i].Normal = _Normals[i].ToXna();                
+
+                if (_TexCoord0 != null) dst[i].TextureCoordinate = _TexCoord0[i].ToXna();
+            }
+
+            return dst;
+        }
+
+        public VertexSkinned[] ToXnaSkinned()
+        {
+            var dst = new VertexSkinned[_Positions.Count];
+
+            for (int i = 0; i < dst.Length; ++i)
+            {
+                dst[i].Position = _Positions[i].ToXna();
+                dst[i].Normal = _Normals[i].ToXna();
+
+                if (_TexCoord0 != null) dst[i].TextureCoordinate = _TexCoord0[i].ToXna();
+
+                dst[i].BlendIndices = new Microsoft.Xna.Framework.Graphics.PackedVector.Byte4(_Joints0[i].ToXna());
+                dst[i].BlendWeight = _Weights0[i].ToXna();
+            }
+
+            return dst;
+        }
+
+        #endregion
+    }
+}

+ 58 - 0
src/SharpGLTF.Runtime.MonoGame/MonoGameDeviceContent.cs

@@ -0,0 +1,58 @@
+using System;
+
+namespace SharpGLTF.Runtime
+{
+    /// <summary>
+    /// A wrapper that contains an object with disposable resources.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class MonoGameDeviceContent<T> : IDisposable
+        where T:class
+    {
+        #region lifecycle
+
+        internal MonoGameDeviceContent(T instance, IDisposable[] disposables)
+        {
+            _Instance = instance;
+            _Disposables = disposables;
+        }
+
+        public void Dispose()
+        {
+            _Instance = null;
+            if (_Disposables == null) return;
+
+            foreach (var d in _Disposables) d.Dispose();
+            _Disposables = null;
+        }
+
+        ~MonoGameDeviceContent()
+        {
+            System.Diagnostics.Debug.Assert(_Disposables == null, "Not disposed correctly");
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// The actual object.
+        /// </summary>
+        private T _Instance;
+
+        /// <summary>
+        /// The disposable resources associated with <see cref="_Instance"/>.
+        /// </summary>
+        private IDisposable[] _Disposables;
+
+        #endregion
+
+        #region properties
+
+        public static implicit operator T(MonoGameDeviceContent<T> value) { return value?.Instance; }
+
+        public T Instance => _Instance;
+
+        #endregion       
+    }
+}

+ 105 - 0
src/SharpGLTF.Runtime.MonoGame/MonoGameModelInstance.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace SharpGLTF.Runtime
+{
+    public sealed class MonoGameModelInstance
+    {
+        #region lifecycle
+
+        internal MonoGameModelInstance(MonoGameModelTemplate template, SceneInstance instance)
+        {
+            _Template = template;
+            _Controller = instance;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly MonoGameModelTemplate _Template;
+        private readonly SceneInstance _Controller;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets a reference to the template used to create this <see cref="MonoGameModelInstance"/>.
+        /// </summary>
+        public MonoGameModelTemplate Template => _Template;
+
+        /// <summary>
+        /// Gets a reference to the animation controller of this <see cref="MonoGameModelInstance"/>.
+        /// </summary>
+        public SceneInstance Controller => _Controller;
+
+        #endregion
+
+        #region API
+
+        /// <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>
+        /// <param name="world">The world matrix.</param>
+        public void Draw(Matrix projection, Matrix view, Matrix world)
+        {
+            foreach (var d in _Controller.DrawableReferences)
+            {
+                Draw(_Template._Meshes[d.MeshIndex], projection, view, world, d.Transform);
+            }
+        }
+
+        private void Draw(ModelMesh mesh, Matrix projectionXform, Matrix viewXform, Matrix worldXform, Transforms.IGeometryTransform modelXform)
+        {
+            if (modelXform is Transforms.SkinnedTransform skinXform)
+            {
+                var skinTransforms = skinXform.SkinMatrices.Select(item => item.ToXna()).ToArray();
+
+                foreach (var effect in mesh.Effects)
+                {
+                    UpdateTransforms(effect, projectionXform, viewXform, worldXform, skinTransforms);
+                }
+            }
+
+            if (modelXform is Transforms.RigidTransform statXform)
+            {
+                var statTransform = statXform.WorldMatrix.ToXna();
+
+                worldXform = Matrix.Multiply(statTransform, worldXform);
+
+                foreach (var effect in mesh.Effects)
+                {
+                    UpdateTransforms(effect, projectionXform, viewXform, worldXform);
+                }
+            }
+
+            mesh.Draw();
+        }
+
+        private static void UpdateTransforms(Effect effect, Matrix projectionXform, Matrix viewXform, Matrix worldXform, Matrix[] skinTransforms = null)
+        {
+            if (effect is IEffectMatrices matrices)
+            {
+                matrices.Projection = projectionXform;
+                matrices.View = viewXform;
+                matrices.World = worldXform;
+            }
+
+            if (effect is SkinnedEffect skin && skinTransforms != null)
+            {
+                var xposed = skinTransforms.Select(item => Matrix.Transpose(item)).ToArray();
+
+                skin.SetBoneTransforms(skinTransforms);
+            }
+        }
+
+        #endregion
+    }
+}

+ 139 - 0
src/SharpGLTF.Runtime.MonoGame/MonoGameModelTemplate.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace SharpGLTF.Runtime
+{
+    public class MonoGameModelTemplate
+    {
+        #region lifecycle
+
+        public static MonoGameDeviceContent<MonoGameModelTemplate> LoadDeviceModel(GraphicsDevice device, string filePath)
+        {
+            var model = Schema2.ModelRoot.Load(filePath, Validation.ValidationMode.TryFix);
+
+            return CreateDeviceModel(device, model);
+        }
+
+        public static MonoGameDeviceContent<MonoGameModelTemplate> CreateDeviceModel(GraphicsDevice device, Schema2.ModelRoot srcModel)
+        {
+            srcModel.FixTextureSampler();
+
+            var templates = srcModel.LogicalScenes
+                .Select(item => SceneTemplate.Create(item, true))
+                .ToArray();
+            
+            var context = new LoaderContext(device);
+
+            var meshes = templates
+                .SelectMany(item => item.LogicalMeshIds)                
+                .ToDictionary(k => k, k => context.CreateMesh(srcModel.LogicalMeshes[k]));            
+
+            var mdl = new MonoGameModelTemplate(templates,srcModel.DefaultScene.LogicalIndex, meshes);
+
+            return new MonoGameDeviceContent<MonoGameModelTemplate>(mdl, context.Disposables.ToArray());
+        }
+        
+        internal MonoGameModelTemplate(SceneTemplate[] scenes, int defaultSceneIndex, IReadOnlyDictionary<int, ModelMesh> meshes)
+        {
+            _Meshes = meshes;
+            _Effects = _Meshes.Values
+                .SelectMany(item => item.Effects)
+                .Distinct()
+                .ToArray();
+
+            _Scenes = scenes;
+            _Bounds = scenes
+                .Select(item => CalculateBounds(item))
+                .ToArray();
+
+            _DefaultSceneIndex = defaultSceneIndex;
+        }
+
+        #endregion
+
+        #region data
+        
+        /// <summary>
+        /// Meshes shared by all the scenes.
+        /// </summary>
+        internal readonly IReadOnlyDictionary<int, ModelMesh> _Meshes;
+
+        /// <summary>
+        /// Effects shared by all the meshes.
+        /// </summary>
+        private readonly Effect[] _Effects;
+
+
+        private readonly SceneTemplate[] _Scenes;
+        private readonly BoundingSphere[] _Bounds;
+
+        private readonly int _DefaultSceneIndex;
+
+        #endregion
+
+        #region properties
+
+        public int SceneCount => _Scenes.Length;
+
+        public IReadOnlyList<Effect> Effects => _Effects;
+
+        public BoundingSphere Bounds => GetBounds(_DefaultSceneIndex);
+
+        public IEnumerable<string> AnimationTracks => GetAnimationTracks(_DefaultSceneIndex);
+
+        #endregion
+
+        #region API
+
+        public int IndexOfScene(string sceneName) => Array.FindIndex(_Scenes, item => item.Name == sceneName);
+
+        public BoundingSphere GetBounds(int sceneIndex) => _Bounds[sceneIndex];
+
+        public IEnumerable<string> GetAnimationTracks(int sceneIndex) => _Scenes[sceneIndex].AnimationTracks;
+
+        public MonoGameModelInstance CreateInstance() => CreateInstance(_DefaultSceneIndex);
+
+        public MonoGameModelInstance CreateInstance(int sceneIndex)
+        {
+            return new MonoGameModelInstance(this, _Scenes[sceneIndex].CreateInstance());
+        }
+
+        private BoundingSphere CalculateBounds(SceneTemplate scene)
+        {
+            var instance = scene.CreateInstance();
+            instance.SetPoseTransforms();
+
+            var bounds = default(BoundingSphere);
+
+            foreach (var d in instance.DrawableReferences)
+            {
+                var b = _Meshes[d.MeshIndex].BoundingSphere;
+
+                if (d.Transform is Transforms.RigidTransform statXform) b = b.Transform(statXform.WorldMatrix.ToXna());
+
+                if (d.Transform is Transforms.SkinnedTransform skinXform)
+                {
+                    // this is a bit agressive and probably over-reaching, but with skins you never know the actual bounds
+                    // unless you calculate the bounds frame by frame.
+
+                    var bb = b;
+
+                    foreach (var xb in skinXform.SkinMatrices.Select(item => bb.Transform(item.ToXna())))
+                    {
+                        b = BoundingSphere.CreateMerged(b, xb);
+                    }
+                }
+
+                bounds = bounds.Radius == 0 ? b : BoundingSphere.CreateMerged(bounds, b);
+            }
+
+            return bounds;
+        }
+        
+        #endregion        
+    }    
+}

+ 337 - 0
src/SharpGLTF.Runtime.MonoGame/ResourceManager.cs

@@ -0,0 +1,337 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace SharpGLTF.Runtime
+{
+    class GraphicsResourceTracker
+    {
+        #region data
+
+        private readonly List<GraphicsResource> _Disposables = new List<GraphicsResource>();        
+
+        #endregion
+
+        #region properties
+
+        public IReadOnlyList<GraphicsResource> Disposables => _Disposables;
+
+        #endregion
+
+        #region API
+
+        public void AddDisposable(GraphicsResource resource)
+        {
+            if (resource == null) throw new ArgumentNullException();
+            if (_Disposables.Contains(resource)) throw new ArgumentException();
+            _Disposables.Add(resource);
+        }
+
+        #endregion
+    }
+
+    class TextureFactory
+    {
+        #region lifecycle
+
+        public TextureFactory(GraphicsDevice device, GraphicsResourceTracker disposables)
+        {
+            _Device = device;
+            _Disposables = disposables;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly GraphicsDevice _Device;
+        private readonly GraphicsResourceTracker _Disposables;
+
+        private readonly Dictionary<IReadOnlyList<Byte>, Texture2D> _Textures = new Dictionary<IReadOnlyList<byte>, Texture2D>(new ArraySegmentContentComparer());        
+
+        #endregion
+
+        #region API
+
+        public Texture2D UseTexture(ArraySegment<Byte> data, string name = null)
+        {
+            if (_Device == null) throw new InvalidOperationException();
+
+            if (data.Count == 0) return null;
+
+            if (_Textures.TryGetValue(data, out Texture2D tex)) return tex;
+
+            using (var m = new System.IO.MemoryStream(data.Array, data.Offset, data.Count, false))
+            {
+                tex = Texture2D.FromStream(_Device, m);
+                _Disposables.AddDisposable(tex);
+
+                tex.Name = name;
+
+                _Textures[data] = tex;
+
+                return tex;
+            }
+        }
+
+        public Texture2D UseWhiteImage()
+        {
+            const string solidWhitePNg = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFHpUWHRUaXRsZQAACJkrz8gsSQUABoACIippo0oAAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAAGklEQVQoz2P8//8/AymAiYFEMKphVMPQ0QAAVW0DHZ8uFaIAAAAASUVORK5CYII=";
+
+            var toBytes = Convert.FromBase64String(solidWhitePNg);
+
+            return UseTexture(new ArraySegment<byte>(toBytes), "_InternalSolidWhite");
+        }
+
+        #endregion
+
+        #region types
+
+        private class ArraySegmentContentComparer : IEqualityComparer<IReadOnlyList<Byte>>
+        {
+            public bool Equals(IReadOnlyList<byte> x, IReadOnlyList<byte> y)
+            {
+                return Enumerable.SequenceEqual(x, y);
+            }
+
+            public int GetHashCode(IReadOnlyList<byte> obj)
+            {
+                var h = 0;
+                for (int i = 0; i < obj.Count; ++i)
+                {
+                    h ^= obj[i].GetHashCode();
+                    h *= 17;
+                }
+                return h;
+            }
+        }
+
+        #endregion
+    }
+
+    class MaterialFactory
+    {
+        #region lifecycle
+
+        public MaterialFactory(GraphicsDevice device, GraphicsResourceTracker disposables)
+        {
+            _Device = device;
+            _TexFactory = new TextureFactory(device, disposables);
+            _Disposables = disposables;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly GraphicsDevice _Device;
+        private readonly TextureFactory _TexFactory;
+        private readonly GraphicsResourceTracker _Disposables;
+
+        private readonly Dictionary<Object, Effect> _StaticEffects = new Dictionary<Object, Effect>();
+        private readonly Dictionary<Object, SkinnedEffect> _SkinnedEffects = new Dictionary<Object, SkinnedEffect>();
+
+        private BasicEffect _DefaultStatic;
+        private SkinnedEffect _DefaultSkinned;
+
+        #endregion        
+
+        #region API - Schema
+
+        // 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.
+
+        public Effect UseStaticEffect(Schema2.Material srcMaterial)
+        {
+            if (_Device == null) throw new InvalidOperationException();
+
+            if (srcMaterial == null)
+            {
+                if (_DefaultStatic == null)
+                {
+                    _DefaultStatic = new BasicEffect(_Device);
+                    _Disposables.AddDisposable(_DefaultStatic);
+                }
+
+                return _DefaultStatic;
+            }
+
+            if (_StaticEffects.TryGetValue(srcMaterial, out Effect dstMaterial)) return dstMaterial;
+
+            dstMaterial = srcMaterial.Alpha == Schema2.AlphaMode.MASK ? CreateAlphaTestEffect(srcMaterial) : CreateBasicEffect(srcMaterial);
+
+            _StaticEffects[srcMaterial] = dstMaterial;
+
+            return dstMaterial;
+        }
+
+        private Effect CreateBasicEffect(Schema2.Material srcMaterial)
+        {
+            var dstMaterial = new BasicEffect(_Device);
+            _Disposables.AddDisposable(dstMaterial);           
+
+
+            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 = GetDiffuseTexture(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;
+        }
+
+        private Effect CreateAlphaTestEffect(Schema2.Material srcMaterial)
+        {
+            var dstMaterial = new AlphaTestEffect(_Device);
+            _Disposables.AddDisposable(dstMaterial);
+
+            dstMaterial.Name = srcMaterial.Name;
+
+            dstMaterial.Alpha = GetAlphaLevel(srcMaterial);
+            //dstMaterial.AlphaFunction = CompareFunction.GreaterEqual;
+            dstMaterial.ReferenceAlpha = (int)(srcMaterial.AlphaCutoff * 255);
+            
+
+            dstMaterial.DiffuseColor = GetDiffuseColor(srcMaterial);
+            
+            dstMaterial.Texture = GetDiffuseTexture(srcMaterial);            
+
+            return dstMaterial;
+        }
+
+        public Effect UseSkinnedEffect(Schema2.Material srcMaterial)
+        {
+            if (_Device == null) throw new InvalidOperationException();
+
+            if (srcMaterial == null)
+            {
+                if (_DefaultSkinned == null)
+                {
+                    _DefaultSkinned = new SkinnedEffect(_Device);
+                    _Disposables.AddDisposable(_DefaultStatic);
+                }
+
+                return _DefaultSkinned;
+            }
+
+            if (_SkinnedEffects.TryGetValue(srcMaterial, out SkinnedEffect dstMaterial)) return dstMaterial;
+
+            dstMaterial = new SkinnedEffect(_Device);
+            _SkinnedEffects[srcMaterial] = dstMaterial;
+            _Disposables.AddDisposable(dstMaterial);
+
+            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 = GetDiffuseTexture(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 = _TexFactory.UseWhiteImage();
+
+            return dstMaterial;
+        }
+
+        private static float GetAlphaLevel(Schema2.Material srcMaterial)
+        {
+            if (srcMaterial.Alpha == Schema2.AlphaMode.OPAQUE) return 1;
+
+            var baseColor = srcMaterial.FindChannel("BaseColor");
+
+            if (baseColor == null) return 1;
+
+            return baseColor.Value.Parameter.W;
+        }
+
+        private static Vector3 GetDiffuseColor(Schema2.Material 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);
+        }
+
+        private static Vector3 GetSpecularColor(Schema2.Material 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;
+        }
+
+        private static float GetSpecularPower(Schema2.Material 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;
+        }
+
+        private static Vector3 GeEmissiveColor(Schema2.Material 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);
+        }
+
+        private Texture2D GetDiffuseTexture(Schema2.Material srcMaterial)
+        {
+            var diffuse = srcMaterial.FindChannel("Diffuse");
+
+            if (diffuse == null) diffuse = srcMaterial.FindChannel("BaseColor");
+
+            if (diffuse == null) return null;
+
+            var name = srcMaterial.Name;
+            if (name == null) name = "null";
+            name += "-Diffuse";            
+
+            return _TexFactory.UseTexture(diffuse.Value.Texture?.PrimaryImage?.GetImageContent() ?? default, name);
+        }
+
+        #endregion
+    }
+
+
+}

+ 14 - 0
src/SharpGLTF.Runtime.MonoGame/SharpGLTF.Runtime.MonoGame.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net471</TargetFramework>
+    <RootNamespace>SharpGLTF.Runtime</RootNamespace>
+    <LangVersion>7.1</LangVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.7.1.189" />
+    <PackageReference Include="SharpGLTF.Core" Version="1.0.0-alpha0015" />    
+  </ItemGroup>  
+
+</Project>

+ 51 - 0
src/SharpGLTF.Runtime.MonoGame/VertexSkinned.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+
+namespace SharpGLTF.Runtime
+{
+    struct VertexSkinned : IVertexType
+    {
+        #region data
+
+        public Vector3 Position;
+        public Vector3 Normal;
+        public Vector2 TextureCoordinate;
+        public Microsoft.Xna.Framework.Graphics.PackedVector.Byte4 BlendIndices;
+        public Vector4 BlendWeight;
+        
+        #endregion
+
+        #region API
+
+        public VertexDeclaration VertexDeclaration => 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.Vector2, VertexElementUsage.TextureCoordinate, 0);
+            offset += 2 * 4;
+
+            var d = new VertexElement(offset, VertexElementFormat.Byte4, VertexElementUsage.BlendIndices, 0);
+            offset += 4 * 1;
+
+            var e = new VertexElement(offset, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0);
+            offset += 4 * 4;
+
+            return new VertexDeclaration(a, b, c, d, e);
+        }
+
+        #endregion
+    }
+}

+ 70 - 0
src/SharpGLTF.Runtime.MonoGame/_Extensions.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Microsoft.Xna.Framework;
+
+namespace SharpGLTF.Runtime
+{
+    static class _Extensions
+    {
+        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 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 void FixTextureSampler(this Schema2.ModelRoot root)
+        {
+            // SharpGLTF 1.0.0-Alpha10 has an issue with TextureSamplers, it's fixed in newer versions
+
+            foreach(var t in root.LogicalTextures)
+            {
+                if (t.Sampler == null)
+                {
+                    var sampler = root.UseTextureSampler
+                        (
+                        Schema2.TextureWrapMode.REPEAT,
+                        Schema2.TextureWrapMode.REPEAT,
+                        Schema2.TextureMipMapFilter.DEFAULT,
+                        Schema2.TextureInterpolationFilter.LINEAR
+                        );
+
+                    t.Sampler = sampler;
+                }
+            }
+        }
+
+        public static BoundingSphere CreateBoundingSphere(this Schema2.Mesh mesh)
+        {
+            var points = mesh
+                .Primitives
+                .Select(item => item.GetVertexAccessor("POSITION"))
+                .Where(item => item != null)
+                .SelectMany(item => item.AsVector3Array())
+                .Select(item => item.ToXna());
+
+            return BoundingSphere.CreateFromPoints(points);
+        }
+    }
+}