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
{
///
/// Reads the content of a glTF object into a structure that's easier to consume by MonoGame.
///
public sealed class MeshPrimitiveReader
: VertexNormalsFactory.IMeshPrimitive
, VertexTangentsFactory.IMeshPrimitive
{
#region lifecycle
internal MeshPrimitiveReader(MeshPrimitive srcPrim, bool doubleSided)
{
_Positions = srcPrim.GetVertexAccessor("POSITION")?.AsVector3Array();
_Normals = srcPrim.GetVertexAccessor("NORMAL")?.AsVector3Array();
_Tangents = srcPrim.GetVertexAccessor("TANGENT")?.AsVector4Array();
_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;
}
}
_TrianglesSource = srcPrim.GetTriangleIndices().ToArray();
if (doubleSided) // Monogame's effect material does not support double sided materials, so we simulate it by adding reverse faces
{
var back = _TrianglesSource.Select(item => (item.A, item.C, item.B));
_Triangles = _TrianglesSource.Concat(back).ToArray();
}
else
{
_Triangles = _TrianglesSource;
}
}
#endregion
#region data
private readonly (int A, int B, int C)[] _TrianglesSource;
private readonly (int A, int B, int C)[] _Triangles;
private readonly IList _Positions;
private IList _Normals;
private IList _Tangents;
private readonly IList _Color0;
private readonly IList _TexCoord0;
private readonly IList _Joints0;
private readonly IList _Joints1;
private readonly IList _Weights0;
private readonly IList _Weights1;
#endregion
#region properties
public bool IsSkinned => _Joints0 != null;
public int VertexCount => _Positions?.Count ?? 0;
public (int A, int B, int C)[] TriangleIndices => _Triangles;
#endregion
#region API
public XYZ GetPosition(int idx) { return _Positions[idx]; }
public XYZ GetNormal(int idx) { return _Normals[idx]; }
public XYZW GetTangent(int idx) { return _Tangents[idx]; }
public XY GetTextureCoord(int idx, int set)
{
if (set == 0 && _TexCoord0 != null) return _TexCoord0[idx];
return XY.Zero;
}
public XYZW GetColor(int idx, int set)
{
if (set == 0 && _Color0 != null) return _Color0[idx];
return XYZW.One;
}
public XYZW GetIndices(int idx)
{
if (_Joints0 != null) return _Joints0[idx];
return XYZW.Zero;
}
public XYZW GetWeights(int idx)
{
if (_Weights0 != null) return _Weights0[idx];
return XYZW.UnitX;
}
///
/// Gets the current Vertex attributes as an array of vertices.
///
/// A Vertex type implementing .
/// A array
public unsafe TVertex[] ToXnaVertices()
where TVertex:unmanaged, IVertexType
{
var declaration = default(TVertex).VertexDeclaration;
if (sizeof(TVertex) != declaration.VertexStride) throw new ArgumentException(nameof(TVertex));
var dst = new TVertex[_Positions.Count];
for (int i = 0; i < dst.Length; ++i)
{
var v = _VertexWriter.CreateFromArray(dst, i);
foreach(var element in declaration.GetVertexElements())
{
switch(element.VertexElementUsage)
{
case VertexElementUsage.Position: v.SetValue(element, GetPosition(i)); break;
case VertexElementUsage.Normal: v.SetValue(element, GetNormal(i)); break;
case VertexElementUsage.Tangent: v.SetValue(element, GetTangent(i), true); break;
case VertexElementUsage.TextureCoordinate: v.SetValue(element, GetTextureCoord(i,element.UsageIndex)); break;
case VertexElementUsage.Color: v.SetValue(element, GetColor(i, element.UsageIndex) , true); break;
case VertexElementUsage.BlendIndices: v.SetValue(element, GetIndices(i), false); break;
case VertexElementUsage.BlendWeight: v.SetValue(element, GetWeights(i), true); break;
}
}
}
return dst;
}
#endregion
#region nested types
readonly ref struct _VertexWriter
{
#region constructor
public static _VertexWriter CreateFromArray(TVertex[] vvv, int idx)
where TVertex : unmanaged, IVertexType
{
var v = vvv.AsSpan().Slice(idx, 1);
var d = System.Runtime.InteropServices.MemoryMarshal.Cast(v);
return new _VertexWriter(d);
}
public _VertexWriter(Span vertex)
{
_Vertex = vertex;
}
#endregion
#region data
private readonly Span _Vertex;
#endregion
#region API
public unsafe void SetValue(VertexElement element, XY value)
{
if (element.VertexElementFormat == VertexElementFormat.Vector2)
{
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
return;
}
throw new NotImplementedException();
}
public unsafe void SetValue(VertexElement element, XYZ value)
{
if (element.VertexElementFormat == VertexElementFormat.Vector3)
{
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
return;
}
throw new NotImplementedException();
}
public unsafe void SetValue(VertexElement element, XYZW value, bool valueIsUnitLength)
{
var dst = _Vertex.Slice(element.Offset);
switch (element.VertexElementFormat)
{
case VertexElementFormat.Vector4:
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
return;
case VertexElementFormat.Byte4:
if (valueIsUnitLength)
{
SetValue(element, new Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedByte4(value.ToXna()));
}
else
{
SetValue(element, new Microsoft.Xna.Framework.Graphics.PackedVector.Byte4(value.ToXna()));
}
return;
case VertexElementFormat.Short4:
SetValue(element, new Microsoft.Xna.Framework.Graphics.PackedVector.Short4(value.ToXna()));
return;
case VertexElementFormat.NormalizedShort4:
SetValue(element, new Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedShort4(value.ToXna()));
return;
}
throw new NotImplementedException();
}
public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.Byte4 value)
{
if (element.VertexElementFormat != VertexElementFormat.Byte4) throw new ArgumentException(nameof(element));
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
}
public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedByte4 value)
{
if (element.VertexElementFormat != VertexElementFormat.Byte4) throw new ArgumentException(nameof(element));
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
}
public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.Short4 value)
{
if (element.VertexElementFormat != VertexElementFormat.Short4) throw new ArgumentException(nameof(element));
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
}
public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedShort4 value)
{
if (element.VertexElementFormat != VertexElementFormat.NormalizedShort4) throw new ArgumentException(nameof(element));
var dst = _Vertex.Slice(element.Offset);
System.Runtime.InteropServices.MemoryMarshal.Cast(dst)[0] = value;
}
#endregion
}
#endregion
#region Support methods for VertexNormalsFactory and VertexTangentsFactory
IEnumerable<(int A, int B, int C)> VertexNormalsFactory.IMeshPrimitive.GetTriangleIndices() { return _TrianglesSource; }
IEnumerable<(int A, int B, int C)> VertexTangentsFactory.IMeshPrimitive.GetTriangleIndices() { return _TrianglesSource; }
XYZ VertexNormalsFactory.IMeshPrimitive.GetVertexPosition(int idx) { return GetPosition(idx); }
XYZ VertexTangentsFactory.IMeshPrimitive.GetVertexPosition(int idx) { return GetPosition(idx); }
XYZ VertexTangentsFactory.IMeshPrimitive.GetVertexNormal(int idx) { return GetNormal(idx); }
XY VertexTangentsFactory.IMeshPrimitive.GetVertexTexCoord(int idx) { return GetTextureCoord(idx, 0); }
void VertexNormalsFactory.IMeshPrimitive.SetVertexNormal(int idx, XYZ normal)
{
_Normals ??= new XYZ[VertexCount];
if (!(_Normals is XYZ[])) return; // if it's not a plain array, it's a glTF source, so we prevent writing existing normals.
_Normals[idx] = normal;
}
void VertexTangentsFactory.IMeshPrimitive.SetVertexTangent(int idx, XYZW tangent)
{
_Tangents ??= new XYZW[VertexCount];
if (!(_Tangents is XYZW[])) return; // if it's not a plain array, it's a glTF source, so we prevent writing existing tangents.
_Tangents[idx] = tangent;
}
#endregion
}
sealed class MeshPrimitiveWriter
{
#region data
// shared buffers
private readonly Dictionary _Buffers = new Dictionary();
// primitives
private readonly List<_MeshPrimitive> _MeshPrimitives = new List<_MeshPrimitive>();
#endregion
#region API
public void WriteMeshPrimitive(int logicalMeshIndex, Effect effect, MeshPrimitiveReader primitive)
where TVertex : unmanaged, IVertexType
{
if (!_Buffers.TryGetValue(typeof(TVertex), out IPrimitivesBuffers pb))
{
_Buffers[typeof(TVertex)] = pb = new _PrimitivesBuffers();
}
var part = (pb as _PrimitivesBuffers).Append(logicalMeshIndex, effect, primitive);
_MeshPrimitives.Add(part);
}
internal IReadOnlyDictionary GetRuntimeMeshes(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.PrimitiveEffect;
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);
}
///
/// Contains the shared vertex/index buffers of all the mesh primitive that share the same vertex type.
///
///
sealed class _PrimitivesBuffers : IPrimitivesBuffers
where TVertex : unmanaged, IVertexType
{
#region data
private readonly List _Vertices = new List();
private readonly List<(int,int,int)> _Triangles = new List<(int, int, int)>();
#endregion
#region API
public _MeshPrimitive Append(int meshKey, Effect effect, MeshPrimitiveReader primitive)
{
var partVertices = primitive.ToXnaVertices();
var partTriangles = primitive.TriangleIndices;
var part = new _MeshPrimitive
{
LogicalMeshIndex = meshKey,
PrimitiveEffect = effect,
PrimitiveBuffers = this,
VertexOffset = _Vertices.Count,
VertexCount = partVertices.Length,
TriangleOffset = _Triangles.Count,
TriangleCount = partTriangles.Length
};
_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
}
///
/// Represents a mesh primitive
///
struct _MeshPrimitive
{
public int LogicalMeshIndex;
public Effect PrimitiveEffect;
public IPrimitivesBuffers PrimitiveBuffers;
public int VertexOffset;
public int VertexCount;
public int TriangleOffset;
public int TriangleCount;
}
#endregion
}
}