Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
fe3105350This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Numerics;
using SharpGLTF.Collections;
using SharpGLTF.Geometry.VertexTypes;
namespace SharpGLTF.Geometry
{
public interface IPrimitiveReader<TMaterial>
{
/// <summary>
/// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
/// </summary>
Type VertexType { get; }
/// <summary>
/// Gets the current <typeparamref name="TMaterial"/> instance used by this primitive.
/// </summary>
TMaterial Material { get; }
/// <summary>
/// Gets the number of vertices used by each primitive shape.
/// </summary>
int VerticesPerPrimitive { get; }
/// <summary>
/// Gets the list of <see cref="IVertexBuilder"/> vertices.
/// </summary>
IReadOnlyList<IVertexBuilder> Vertices { get; }
/// <summary>
/// Gets the list of <see cref="IPrimitiveMorphTargetReader"/>.
/// </summary>
IReadOnlyList<IPrimitiveMorphTargetReader> MorphTargets { get; }
/// <summary>
/// Gets the indices of all points, given that <see cref="VerticesPerPrimitive"/> is 1.
/// </summary>
IReadOnlyList<int> Points { get; }
/// <summary>
/// Gets the indices of all lines, given that <see cref="VerticesPerPrimitive"/> is 2.
/// </summary>
IReadOnlyList<(int A, int B)> Lines { get; }
/// <summary>
/// Gets the indices of all the surfaces as triangles, given that <see cref="VerticesPerPrimitive"/> is 3.
/// </summary>
IReadOnlyList<(int A, int B, int C)> Triangles { get; }
/// <summary>
/// Gets the indices of all the surfaces, given that <see cref="VerticesPerPrimitive"/> is 3.
/// </summary>
IReadOnlyList<(int A, int B, int C, int? D)> Surfaces { get; }
/// <summary>
/// Calculates the raw list of indices to use for this primitive.
/// </summary>
/// <returns>a list of indices.</returns>
IReadOnlyList<int> GetIndices();
}
public interface IPrimitiveBuilder
{
/// <summary>
/// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
/// </summary>
Type VertexType { get; }
void SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta delta);
int AddPoint(IVertexBuilder a);
(int A, int B) AddLine(IVertexBuilder a, IVertexBuilder b);
(int A, int B, int C) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c);
(int A, int B, int C, int D) AddQuadrangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c, IVertexBuilder d);
}
/// <summary>
/// Represents an utility class to help build mesh primitives by adding triangles
/// </summary>
/// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/> instance.</typeparam>
/// <typeparam name="TvG">
/// The vertex fragment type with Position, Normal and Tangent.
/// Valid types are:
/// <see cref="VertexPosition"/>,
/// <see cref="VertexPositionNormal"/>,
/// <see cref="VertexPositionNormalTangent"/>.
/// </typeparam>
/// <typeparam name="TvM">
/// The vertex fragment type with Colors and Texture Coordinates.
/// Valid types are:
/// <see cref="VertexEmpty"/>,
/// <see cref="VertexColor1"/>,
/// <see cref="VertexTexture1"/>,
/// <see cref="VertexColor1Texture1"/>.
/// <see cref="VertexColor1Texture2"/>.
/// <see cref="VertexColor2Texture2"/>.
/// </typeparam>
/// <typeparam name="TvS">
/// The vertex fragment type with Skin Joint Weights.
/// Valid types are:
/// <see cref="VertexEmpty"/>,
/// <see cref="VertexJoints4"/>,
/// <see cref="VertexJoints8"/>.
/// </typeparam>
public abstract class PrimitiveBuilder<TMaterial, TvG, TvM, TvS> : IPrimitiveBuilder, IPrimitiveReader<TMaterial>
where TvG : struct, IVertexGeometry
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
{
#region lifecycle
internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
{
this._Mesh = mesh;
this._Material = material;
}
protected PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, PrimitiveBuilder<TMaterial, TvG, TvM, TvS> other, TMaterial material)
{
Guard.NotNull(mesh, nameof(mesh));
Guard.NotNull(other, nameof(other));
this._Mesh = mesh;
this._Material = material != null ? material : other.Material;
other._Vertices.CopyTo(this._Vertices);
foreach (var otherMT in other._MorphTargets)
{
var thisMT = new PrimitiveMorphTargetBuilder<TvG>(idx => this._Vertices[idx].Geometry, otherMT);
this._MorphTargets.Add(otherMT);
}
}
internal abstract PrimitiveBuilder<TMaterial, TvG, TvM, TvS> Clone(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material);
#endregion
#region data
private readonly MeshBuilder<TMaterial, TvG, TvM, TvS> _Mesh;
private readonly TMaterial _Material;
private readonly VertexListWrapper _Vertices = new VertexListWrapper();
private readonly List<PrimitiveMorphTargetBuilder<TvG>> _MorphTargets = new List<PrimitiveMorphTargetBuilder<TvG>>();
#endregion
#region properties
public MeshBuilder<TMaterial, TvG, TvM, TvS> Mesh => _Mesh;
public TMaterial Material => _Material;
/// <summary>
/// Gets the number of vertices used by each primitive:
/// 1 - Points
/// 2 - Lines
/// 3 - Triangles
/// </summary>
public abstract int VerticesPerPrimitive { get; }
public Type VertexType => typeof(VertexBuilder<TvG, TvM, TvS>);
public IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> Vertices => _Vertices;
IReadOnlyList<IVertexBuilder> IPrimitiveReader<TMaterial>.Vertices => _Vertices;
IReadOnlyList<IPrimitiveMorphTargetReader> IPrimitiveReader<TMaterial>.MorphTargets => _MorphTargets;
public virtual IReadOnlyList<int> Points => Array.Empty<int>();
public virtual IReadOnlyList<(int A, int B)> Lines => Array.Empty<(int, int)>();
public virtual IReadOnlyList<(int A, int B, int C)> Triangles => Array.Empty<(int, int, int)>();
public virtual IReadOnlyList<(int A, int B, int C, int? D)> Surfaces => Array.Empty<(int, int, int, int?)>();
#endregion
#region API - morph targets
internal PrimitiveMorphTargetBuilder<TvG> _UseMorphTarget(int morphTargetIndex)
{
while (this._MorphTargets.Count <= morphTargetIndex) this._MorphTargets.Add(new PrimitiveMorphTargetBuilder<TvG>(idx => _Vertices[idx].Geometry));
return this._MorphTargets[morphTargetIndex];
}
#endregion
#region API
public void Validate()
{
foreach (var v in _Vertices)
{
v.Validate();
}
}
/// <summary>
/// Checks if <paramref name="vertex"/> is a compatible vertex and casts it, or converts it if it is not.
/// </summary>
/// <param name="vertex">Any vertex</param>
/// <returns>A vertex compatible with this primitive.</returns>
private static VertexBuilder<TvG, TvM, TvS> ConvertVertex(IVertexBuilder vertex)
{
return VertexBuilder<TvG, TvM, TvS>.CreateFrom(vertex);
}
/// <summary>
/// Adds or reuses a vertex.
/// </summary>
/// <param name="vertex">
/// A vertex formed by
/// <typeparamref name="TvG"/>,
/// <typeparamref name="TvM"/> and
/// <typeparamref name="TvS"/> fragments.
/// </param>
/// <returns>The index of the vertex.</returns>
protected int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
{
return _Vertices.Use(vertex);
}
void IPrimitiveBuilder.SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta delta)
{
_UseMorphTarget(morphTargetIndex).SetVertexDelta(vertexIndex, delta);
}
/// <summary>
/// Adds a point.
/// </summary>
/// <param name="a">vertex for this point.</param>
/// <returns>The indices of the vertices.</returns>
public int AddPoint(IVertexBuilder a)
{
Guard.NotNull(a, nameof(a));
return AddPoint(ConvertVertex(a));
}
/// <summary>
/// Adds a line.
/// </summary>
/// <param name="a">First corner of the line.</param>
/// <param name="b">Second corner of the line.</param>
/// <returns>The indices of the vertices, or, in case the line is degenerated, (-1,-1).</returns>
public (int A, int B) AddLine(IVertexBuilder a, IVertexBuilder b)
{
Guard.NotNull(a, nameof(a));
Guard.NotNull(b, nameof(b));
return AddLine(ConvertVertex(a), ConvertVertex(b));
}
/// <summary>
/// Adds a triangle.
/// </summary>
/// <param name="a">First corner of the triangle.</param>
/// <param name="b">Second corner of the triangle.</param>
/// <param name="c">Third corner of the triangle.</param>
/// <returns>The indices of the vertices, or, in case the triangle is degenerated, (-1,-1,-1).</returns>
public (int A, int B, int C) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c)
{
Guard.NotNull(a, nameof(a));
Guard.NotNull(b, nameof(b));
Guard.NotNull(c, nameof(c));
return AddTriangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c));
}
/// <summary>
/// Adds a quadrangle.
/// </summary>
/// <param name="a">First corner of the quadrangle.</param>
/// <param name="b">Second corner of the quadrangle.</param>
/// <param name="c">Third corner of the quadrangle.</param>
/// <param name="d">Fourth corner of the quadrangle.</param>
/// <returns>The indices of the vertices, or, in case the quadrangle is degenerated, (-1,-1,-1,-1).</returns>
/// <remarks>
/// If only one of the vertices is degenerated, leading to a single triangle, the resulting indices would
/// have just one negative index, like this: (16,-1,17,18)
/// </remarks>
public (int A, int B, int C, int D) AddQuadrangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c, IVertexBuilder d)
{
Guard.NotNull(a, nameof(a));
Guard.NotNull(b, nameof(b));
Guard.NotNull(c, nameof(c));
Guard.NotNull(d, nameof(d));
return AddQuadrangle(ConvertVertex(a), ConvertVertex(b), ConvertVertex(c), ConvertVertex(d));
}
internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Converter<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
{
if (primitive == null) return;
if (vertexTransformFunc == null) vertexTransformFunc = v => v;
AddPrimitive<TMaterial>(primitive, v => vertexTransformFunc((VertexBuilder<TvG, TvM, TvS>)v));
}
internal void AddPrimitive<TAnyMaterial>(IPrimitiveReader<TAnyMaterial> primitive, Converter<IVertexBuilder, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
{
if (primitive == null) return;
Guard.NotNull(vertexTransformFunc, nameof(vertexTransformFunc));
// if the input primitive has morph targets, we need to keep track of the new index location
// of the vertices in the destination primitive, so we can retarget the mopth target indices.
var vmap = primitive.MorphTargets.Count == 0 ? null : new Dictionary<int, int>();
if (this.VerticesPerPrimitive == 1)
{
foreach (var src in primitive.Points)
{
var vrt = vertexTransformFunc(primitive.Vertices[src]);
var idx = AddPoint(vrt);
if (vmap != null) vmap[src] = idx;
}
}
if (this.VerticesPerPrimitive == 2)
{
foreach (var (srcA, srcB) in primitive.Lines)
{
var vrtA = vertexTransformFunc(primitive.Vertices[srcA]);
var vrtB = vertexTransformFunc(primitive.Vertices[srcB]);
var (dstA, dstB) = AddLine(vrtA, vrtB);
if (vmap != null)
{
vmap[srcA] = dstA;
vmap[srcB] = dstB;
}
}
}
if (this.VerticesPerPrimitive == 3)
{
foreach (var (srcA, srcB, srcC, srcD) in primitive.Surfaces)
{
var vrtA = vertexTransformFunc(primitive.Vertices[srcA]);
var vrtB = vertexTransformFunc(primitive.Vertices[srcB]);
var vrtC = vertexTransformFunc(primitive.Vertices[srcC]);
if (srcD.HasValue)
{
var vrtD = vertexTransformFunc(primitive.Vertices[srcD.Value]);
var (dstA, dstB, dstC, dstD) = AddQuadrangle(vrtA, vrtB, vrtC, vrtD);
if (vmap != null)
{
vmap[srcA] = dstA;
vmap[srcB] = dstB;
vmap[srcC] = dstC;
vmap[srcD.Value] = dstD;
}
}
else
{
var (dstA, dstB, dstC) = AddTriangle(vrtA, vrtB, vrtC);
if (vmap != null)
{
vmap[srcA] = dstA;
vmap[srcB] = dstB;
vmap[srcC] = dstC;
}
}
}
}
if (vmap != null)
{
TvG geoTransformFunc(IVertexGeometry g) => vertexTransformFunc(new VertexBuilder(g)).Geometry;
for (int i = 0; i < primitive.MorphTargets.Count; ++i)
{
var smt = primitive.MorphTargets[i];
_UseMorphTarget(i).SetMorphTargets(smt, vmap, geoTransformFunc);
}
}
}
public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransformFunc)
{
Guard.NotNull(vertexTransformFunc, nameof(vertexTransformFunc));
_Vertices.TransformVertices(vertexTransformFunc);
TvG geoFunc(TvG g) => vertexTransformFunc(new VertexBuilder<TvG, TvM, TvS>(g, default, default(TvS))).Geometry;
foreach (var mt in _MorphTargets) mt.TransformVertices(geoFunc);
}
#endregion
#region abstract API
public abstract IReadOnlyList<int> GetIndices();
/// <summary>
/// Adds a point.
/// </summary>
/// <param name="a">vertex for this point.</param>
/// <returns>The index of the vertex.</returns>
public virtual int AddPoint(VertexBuilder<TvG, TvM, TvS> a)
{
throw new NotSupportedException("Points are not supported for this primitive");
}
/// <summary>
/// Adds a line.
/// </summary>
/// <param name="a">First corner of the line.</param>
/// <param name="b">Second corner of the line.</param>
/// <returns>The indices of the vertices, or, in case the line is degenerated, (-1,-1).</returns>
public virtual (int A, int B) AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
{
throw new NotSupportedException("Lines are not supported for this primitive");
}
/// <summary>
/// Adds a triangle.
/// </summary>
/// <param name="a">First corner of the triangle.</param>
/// <param name="b">Second corner of the triangle.</param>
/// <param name="c">Third corner of the triangle.</param>
/// <returns>The indices of the vertices, or, in case the triangle is degenerated, (-1,-1,-1).</returns>
public virtual (int A, int B, int C) AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
{
throw new NotSupportedException("Triangles are not supported for this primitive");
}
/// <summary>
/// Adds a quadrangle.
/// </summary>
/// <param name="a">First corner of the quadrangle.</param>
/// <param name="b">Second corner of the quadrangle.</param>
/// <param name="c">Third corner of the quadrangle.</param>
/// <param name="d">Fourth corner of the quadrangle.</param>
/// <returns>The indices of the vertices, or, in case the quadrangle is degenerated, (-1,-1,-1,-1).</returns>
public virtual (int A, int B, int C, int D) AddQuadrangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c, VertexBuilder<TvG, TvM, TvS> d)
{
throw new NotSupportedException("Quadrangles are not supported for this primitive");
}
#endregion
#region helper types
private sealed class VertexListWrapper : VertexList<VertexBuilder<TvG, TvM, TvS>>, IReadOnlyList<IVertexBuilder>
{
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists
IVertexBuilder IReadOnlyList<IVertexBuilder>.this[int index] => base[index];
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists
IEnumerator<IVertexBuilder> IEnumerable<IVertexBuilder>.GetEnumerator()
{
foreach (var item in this) yield return item;
}
}
#endregion
}
[System.Diagnostics.DebuggerDisplay("Points[{Points.Count}] {_Material}")]
sealed class PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
where TvG : struct, IVertexGeometry
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
{
#region lifecycle
internal PointsPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
: base(mesh, material)
{
}
internal override PrimitiveBuilder<TMaterial, TvG, TvM, TvS> Clone(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
{
return new PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(mesh, this, material);
}
private PointsPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS> other, TMaterial material)
: base(mesh, other, material)
{
}
#endregion
#region properties
public override int VerticesPerPrimitive => 1;
public override IReadOnlyList<int> Points => new PointListWrapper<VertexBuilder<TvG, TvM, TvS>>(this.Vertices);
#endregion
#region API
public override int AddPoint(VertexBuilder<TvG, TvM, TvS> a)
{
if (Mesh.VertexPreprocessor != null)
{
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return -1;
}
return UseVertex(a);
}
public override IReadOnlyList<int> GetIndices() { return Array.Empty<int>(); }
#endregion
#region types
struct PointListWrapper<T> : IReadOnlyList<int>
{
public PointListWrapper(IReadOnlyList<T> vertices)
{
_Vertices = vertices;
}
private readonly IReadOnlyList<T> _Vertices;
public int this[int index] => index;
public int Count => _Vertices.Count;
public IEnumerator<int> GetEnumerator() { return Enumerable.Range(0, _Vertices.Count).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return Enumerable.Range(0, _Vertices.Count).GetEnumerator(); }
}
#endregion
}
[System.Diagnostics.DebuggerDisplay("Lines[{Lines.Count}] {_Material}")]
sealed class LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
where TvG : struct, IVertexGeometry
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
{
#region lifecycle
internal LinesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
: base(mesh, material)
{
}
internal override PrimitiveBuilder<TMaterial, TvG, TvM, TvS> Clone(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
{
return new LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(mesh, this, material);
}
private LinesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> other, TMaterial material)
: base(mesh, other, material)
{
this._Indices.AddRange(other._Indices);
}
#endregion
#region data
private readonly List<(int A, int B)> _Indices = new List<(int A, int B)>();
#endregion
#region properties
public override int VerticesPerPrimitive => 2;
public override IReadOnlyList<(int A, int B)> Lines => _Indices;
#endregion
#region API
public override (int A, int B) AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
{
if (Mesh.VertexPreprocessor != null)
{
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1);
}
// check for degenerated line
if (a.Position == b.Position) return (-1, -1);
var aa = UseVertex(a);
var bb = UseVertex(b);
System.Diagnostics.Debug.Assert(aa != bb, "unexpected degenerated line");
_Indices.Add((aa, bb));
return (aa, bb);
}
public override IReadOnlyList<int> GetIndices()
{
return _Indices
.SelectMany(item => new[] { item.A, item.B })
.ToList();
}
#endregion
}
[System.Diagnostics.DebuggerDisplay("Triangles[{Triangles.Count}] {_Material}")]
sealed class TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
where TvG : struct, IVertexGeometry
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
{
#region lifecycle
internal TrianglesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
: base(mesh, material)
{
}
internal override PrimitiveBuilder<TMaterial, TvG, TvM, TvS> Clone(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material)
{
return new TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS>(mesh, this, material);
}
private TrianglesPrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> other, TMaterial material)
: base(mesh, other, material)
{
this._TriIndices.AddRange(other._TriIndices);
this._QuadIndices.AddRange(other._QuadIndices);
}
#endregion
#region data
private readonly List<(int A, int B, int C)> _TriIndices = new List<(int A, int B, int C)>();
private readonly List<(int A, int B, int C, int D)> _QuadIndices = new List<(int A, int B, int C, int D)>();
#endregion
#region properties
public override int VerticesPerPrimitive => 3;
public override IReadOnlyList<(int A, int B, int C)> Triangles => new TriangleList(_TriIndices, _QuadIndices);
public override IReadOnlyList<(int A, int B, int C, int? D)> Surfaces => new SurfaceList(_TriIndices, _QuadIndices);
#endregion
#region API
public override (int A, int B, int C) AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
{
if (Mesh.VertexPreprocessor != null)
{
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1);
}
return _AddTriangle(a, b, c);
}
public override (int A, int B, int C, int D) AddQuadrangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c, VertexBuilder<TvG, TvM, TvS> d)
{
if (Mesh.VertexPreprocessor != null)
{
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1, -1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1, -1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref c)) return (-1, -1, -1, -1);
if (!Mesh.VertexPreprocessor.PreprocessVertex(ref d)) return (-1, -1, -1, -1);
}
// if diagonals are degenerated, the whole quad is degenerated.
if (a.Position == c.Position || b.Position == d.Position) return (-1, -1, -1, -1);
// A-B degenerated
if (a.Position == b.Position)
{
var tri = _AddTriangle(b, c, d);
return (-1, tri.A, tri.B, tri.C);
}
// B-C degenerated
if (b.Position == c.Position)
{
var tri = _AddTriangle(a, c, d);
return (tri.A, -1, tri.B, tri.C);
}
// C-D degenerated
if (c.Position == d.Position)
{
var tri = _AddTriangle(a, b, d);
return (tri.A, tri.B, -1, tri.C);
}
// D-A degenerated
if (d.Position == a.Position)
{
var tri = _AddTriangle(a, b, c);
return (tri.A, tri.B, tri.C, -1);
}
// at some points it could be interesting to comply with https://github.com/KhronosGroup/glTF/pull/1620
var aa = UseVertex(a);
var bb = UseVertex(b);
var cc = UseVertex(c);
var dd = UseVertex(d);
System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc && cc != dd, "unexpected degenerated triangle");
var oddEven = MeshBuilderToolkit.GetQuadrangleDiagonal(a.Position, b.Position, c.Position, d.Position);
if (oddEven)
{
_QuadIndices.Add((aa, bb, cc, dd));
return (aa, bb, cc, dd);
}
else
{
_QuadIndices.Add((bb, cc, dd, aa));
return (aa, bb, cc, dd); // notice that we return the indices in the same order we got them.
}
}
private (int A, int B, int C) _AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
{
// check for degenerated triangle
if (a.Position == b.Position || a.Position == c.Position || b.Position == c.Position) return (-1, -1, -1);
var aa = UseVertex(a);
var bb = UseVertex(b);
var cc = UseVertex(c);
System.Diagnostics.Debug.Assert(aa != bb && aa != cc && bb != cc, "unexpected degenerated triangle");
// TODO: check if a triangle with indices aa-bb-cc, bb-cc-aa or cc-aa-bb already exists, since there's no point in having the same polygon twice.
_TriIndices.Add((aa, bb, cc));
return (aa, bb, cc);
}
public override IReadOnlyList<int> GetIndices()
{
return Triangles
.SelectMany(item => new[] { item.A, item.B, item.C })
.ToList();
}
#endregion
#region Types
private struct TriangleList : IReadOnlyList<(int A, int B, int C)>
{
public TriangleList(IReadOnlyList<(int, int, int)> tris, IReadOnlyList<(int, int, int, int)> quads)
{
_Tris = tris;
_Quads = quads;
}
private readonly IReadOnlyList<(int A, int B, int C)> _Tris;
private readonly IReadOnlyList<(int A, int B, int C, int D)> _Quads;
public int Count => _Tris.Count + (_Quads.Count * 2);
public (int A, int B, int C) this[int index]
{
get
{
if (index < _Tris.Count)
{
var tri = _Tris[index];
return tri;
}
index -= _Tris.Count;
var quad = _Quads[index / 2];
return (index & 1) == 0 ? (quad.A, quad.B, quad.C) : (quad.A, quad.C, quad.D);
}
}
public IEnumerator<(int A, int B, int C)> GetEnumerator()
{
var c = this.Count;
for (int i = 0; i < c; ++i) yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
var c = this.Count;
for (int i = 0; i < c; ++i) yield return this[i];
}
}
private struct SurfaceList : IReadOnlyList<(int A, int B, int C, int? D)>
{
public SurfaceList(IReadOnlyList<(int, int, int)> tris, IReadOnlyList<(int, int, int, int)> quads)
{
_Tris = tris;
_Quads = quads;
}
private readonly IReadOnlyList<(int A, int B, int C)> _Tris;
private readonly IReadOnlyList<(int A, int B, int C, int D)> _Quads;
public int Count => _Tris.Count + _Quads.Count;
public (int A, int B, int C, int? D) this[int index]
{
get
{
if (index < _Tris.Count)
{
var tri = _Tris[index];
return (tri.A, tri.B, tri.C, null);
}
index -= _Tris.Count;
var quad = _Quads[index];
return (quad.A, quad.B, quad.C, quad.D);
}
}
public IEnumerator<(int A, int B, int C, int? D)> GetEnumerator()
{
var c = this.Count;
for (int i = 0; i < c; ++i) yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
var c = this.Count;
for (int i = 0; i < c; ++i) yield return this[i];
}
}
#endregion
}
/// <summary>
/// Helper class used to calculate Normals and Tangents of missing meshes.
/// </summary>
/// <typeparam name="TMaterial">default material</typeparam>
class MeshPrimitiveNormalsAndTangents<TMaterial> : VertexNormalsFactory.IMeshPrimitive, VertexTangentsFactory.IMeshPrimitive
{
#region constructor
// TODO: we need a solution for morph targets.
public static IReadOnlyDictionary<IPrimitiveReader<TMaterial>, MeshPrimitiveNormalsAndTangents<TMaterial>> GenerateNormalsTangents(IMeshBuilder<TMaterial> mesh)
{
var pairs = mesh.Primitives
.Where(item => item.VerticesPerPrimitive > 2)
.ToDictionary(p => p, p => new MeshPrimitiveNormalsAndTangents<TMaterial>(p));
// we can safaly generate both sets because MeshPrimitiveNormalsAndTangents will still return the good normals and tangets if they exist.
VertexNormalsFactory.CalculateSmoothNormals(pairs.Values.ToList());
VertexTangentsFactory.CalculateTangents(pairs.Values.ToList());
return pairs;
}
public MeshPrimitiveNormalsAndTangents(IPrimitiveReader<TMaterial> source)
{
_Source = source;
}
#endregion
#region data
private readonly IPrimitiveReader<TMaterial> _Source;
private Vector3[] _Normals;
private Vector4[] _Tangents;
#endregion
#region API
public int VertexCount => _Source.Vertices.Count;
public IEnumerable<(int A, int B, int C)> GetTriangleIndices()
{
return _Source.Triangles;
}
public Vector3 GetVertexPosition(int idx)
{
var v = _Source.Vertices[idx];
return v.GetGeometry().GetPosition();
}
public Vector3 GetVertexNormal(int idx)
{
var v = _Source.Vertices[idx];
return v.GetGeometry().TryGetNormal(out Vector3 normal) ? normal : _Normals[idx];
}
public Vector4 GetVertexTangent(int idx)
{
var v = _Source.Vertices[idx];
return v.GetGeometry().TryGetTangent(out Vector4 tangent) ? tangent : _Tangents[idx];
}
public Vector2 GetVertexTexCoord(int idx)
{
var v = _Source.Vertices[idx];
var m = v.GetMaterial();
return m.MaxTextCoords > 0 ? m.GetTexCoord(0) : Vector2.Zero;
}
public void SetVertexNormal(int idx, Vector3 normal)
{
if (_Normals == null) _Normals = new Vector3[VertexCount];
_Normals[idx] = normal;
}
public void SetVertexTangent(int idx, Vector4 tangent)
{
if (_Tangents == null) _Tangents = new Vector4[VertexCount];
_Tangents[idx] = tangent;
}
#endregion
}
}