using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using SharpGLTF.Runtime.Template;
using SharpGLTF.Schema2;
using MODELMESH = SharpGLTF.Runtime.Template.RuntimeModelMesh;
using SRCMATERIAL = SharpGLTF.Schema2.Material;
using SRCMESH = SharpGLTF.Schema2.Mesh;
using SRCPRIM = SharpGLTF.Schema2.MeshPrimitive;
namespace SharpGLTF.Runtime.Pipeline
{
///
/// Helper class used to import a glTF meshes and materials into MonoGame
///
///
/// derived types:
///
public abstract class MeshesFactory
{
#region lifecycle
///
/// Register here your own derived class to override mesh creation
///
public static Func InstanceBuilder { get; set; }
public static MeshesFactory Create(GraphicsDevice device)
{
ArgumentNullException.ThrowIfNull(device);
var mf = InstanceBuilder?.Invoke(device);
mf ??= new DefaultMeshesFactory(device);
return mf;
}
protected MeshesFactory(GraphicsDevice device)
{
_Device = device;
}
#endregion
#region data
private GraphicsDevice _Device;
private GraphicsResourceTracker _Disposables;
private EffectsFactory _EffectsFactory;
// gathers all meshes using shared vertex and index buffers whenever possible.
private MeshPrimitiveWriter _MeshWriter;
private int _CurrentMeshIndex;
// used as a container to a default material;
private ModelRoot _DummyModel;
#endregion
#region properties
///
/// Gets the collection of disposables accumulated by processing meshes, effects and textures.
///
internal IReadOnlyList Disposables => _Disposables.Disposables;
#endregion
#region API
internal void Reset()
{
_Disposables = new GraphicsResourceTracker();
_EffectsFactory = EffectsFactory.Create(_Device, _Disposables);
}
internal IReadOnlyDictionary CreateRuntimeMeshes(IEnumerable srcMeshes)
{
_MeshWriter = new MeshPrimitiveWriter();
foreach (var srcMesh in srcMeshes)
{
_WriteMesh(srcMesh);
}
return _MeshWriter.GetRuntimeMeshes(_Device, _Disposables);
}
#endregion
#region Mesh API
private void _WriteMesh(SRCMESH srcMesh)
{
if (_Device == null) throw new InvalidOperationException();
var srcPrims = _GetValidPrimitives(srcMesh)
.ToDictionary(item => item, item => new MeshPrimitiveReader(item, item.Material?.DoubleSided ?? false));
VertexNormalsFactory.CalculateSmoothNormals(srcPrims.Values.ToList());
VertexTangentsFactory.CalculateTangents(srcPrims.Values.ToList());
foreach (var srcPrim in srcPrims)
{
_CurrentMeshIndex = srcMesh.LogicalIndex;
_WriteMeshPrimitive(srcPrim.Value, srcPrim.Key.Material);
}
}
private static IEnumerable _GetValidPrimitives(SRCMESH 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;
}
}
private void _WriteMeshPrimitive(MeshPrimitiveReader srcPrim, SRCMATERIAL srcMaterial)
{
srcMaterial ??= GetDefaultMaterial();
var effect = _EffectsFactory.UseEffect(srcMaterial, srcPrim.IsSkinned);
WriteMeshPrimitive(srcPrim, effect);
}
protected void WriteMeshPrimitive(Effect effect, MeshPrimitiveReader primitive)
where TVertex : unmanaged, IVertexType
{
_MeshWriter.WriteMeshPrimitive(_CurrentMeshIndex, effect, primitive);
}
private SRCMATERIAL GetDefaultMaterial()
{
if (_DummyModel != null)
{
_DummyModel = ModelRoot.CreateModel();
_DummyModel.CreateMaterial("Default");
}
return _DummyModel.LogicalMaterials[0];
}
#endregion
#region overridable API
///
/// Converts a to a device primitive.
///
/// The mesh primitive to read and convert
/// The used by the primitive.
protected abstract void WriteMeshPrimitive(MeshPrimitiveReader srcPrimitive, Effect effect);
#endregion
}
class DefaultMeshesFactory : MeshesFactory
{
#region lifecycle
public DefaultMeshesFactory(GraphicsDevice device) : base(device) { }
#endregion
#region meshes creation
protected override void WriteMeshPrimitive(MeshPrimitiveReader srcPrimitive, Effect effect)
{
if (srcPrimitive.IsSkinned) WriteMeshPrimitive(effect, srcPrimitive);
else WriteMeshPrimitive(effect, srcPrimitive);
}
#endregion
}
}