using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using SharpGLTF.Schema2;
namespace SharpGLTF.Runtime
{
///
/// Helper class used to import a glTF model into MonoGame
///
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 _StaticMeshes = new Dictionary();
private readonly Dictionary _SkinnedMeshes = new Dictionary();
#endregion
#region properties
public IReadOnlyList Disposables => _Disposables.Disposables;
#endregion
#region Mesh API
private static IEnumerable 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[] 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, int, int)> triangles)
{
var sequence32 = triangles
.SelectMany(item => new[] { (UInt32)item.Item3, (UInt32)item.Item2, (UInt32)item.Item1 })
.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
}
}