using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using SharpGLTF.Schema2;
using SRCMESH = SharpGLTF.Schema2.Mesh;
using SRCPRIM = SharpGLTF.Schema2.MeshPrimitive;
using SRCMATERIAL = SharpGLTF.Schema2.Material;
using MODELMESH = SharpGLTF.Runtime.RuntimeModelMesh;
namespace SharpGLTF.Runtime
{
///
/// Helper class used to import a glTF meshes and materials into MonoGame
///
public abstract class LoaderContext
{
#region lifecycle
public LoaderContext(GraphicsDevice device)
{
_Device = device;
}
#endregion
#region data
private GraphicsDevice _Device;
private GraphicsResourceTracker _Disposables;
private EffectsFactory _MatFactory;
// 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 SharpGLTF.Schema2.ModelRoot _DummyModel;
#endregion
#region properties
protected GraphicsDevice Device => _Device;
internal IReadOnlyList Disposables => _Disposables.Disposables;
#endregion
#region API
internal void Reset()
{
_Disposables = new GraphicsResourceTracker();
_MatFactory = new EffectsFactory(_Device, _Disposables);
_MeshWriter = new MeshPrimitiveWriter();
}
#endregion
#region Mesh API
internal 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 = _MatFactory.GetMaterial(srcMaterial, srcPrim.IsSkinned);
if (effect == null)
{
effect = CreateEffect(srcMaterial, srcPrim.IsSkinned);
_MatFactory.Register(srcMaterial, srcPrim.IsSkinned, effect);
}
WriteMeshPrimitive(srcPrim, effect);
}
protected abstract void WriteMeshPrimitive(MeshPrimitiveReader srcPrimitive, Effect effect);
protected void WriteMeshPrimitive(Effect effect, MeshPrimitiveReader primitive)
where TVertex : unmanaged, IVertexType
{
_MeshWriter.WriteMeshPrimitive(_CurrentMeshIndex, effect, primitive);
}
#endregion
#region EFfects API
///
/// Called when finding a new material that needs to be converted to an Effect.
///
/// The material to convert.
/// Indicates that the material is used in a skinned mesh.
/// An effect to be used in place of .
protected abstract Effect CreateEffect(Material srcMaterial, bool isSkinned);
protected virtual Texture2D UseTexture(MaterialChannel? channel, string name)
{
return _MatFactory.UseTexture(channel, name);
}
#endregion
#region resources API
internal IReadOnlyDictionary CreateRuntimeModels()
{
return _MeshWriter.GetRuntimeMeshes(_Device, _Disposables);
}
private Material GetDefaultMaterial()
{
if (_DummyModel != null)
{
_DummyModel = ModelRoot.CreateModel();
_DummyModel.CreateMaterial("Default");
}
return _DummyModel.LogicalMaterials[0];
}
#endregion
}
}