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.Pipeline { /// /// Reads the vertex and index buffer data from glTF /// public 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 } }