using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XY = System.Numerics.Vector2;
using XYZ = System.Numerics.Vector3;
using XYZW = System.Numerics.Vector4;
namespace SharpGLTF.Runtime
{
///
/// Exposes an API to allow decoding a geometry mesh.
///
/// The primitive's material type
///
/// Implemented by
///
public interface IMeshDecoder
where TMaterial : class
{
string Name { get; }
Object Extras { get; }
int LogicalIndex { get; }
IReadOnlyList> Primitives { get; }
}
///
/// Exposes an API to get geometry data from a mesh primitive
///
///
/// Implemented by
///
public interface IMeshPrimitiveDecoder
{
#region properties
///
/// Gets a value indicating the total number of vertices for this primitive.
///
int VertexCount { get; }
///
/// Gets a value indicating the total number of morph targets for this primitive.
///
int MorphTargetsCount { get; }
///
/// Gets a value indicating the number of color vertex attributes.
/// In the range of 0 to 2.
///
int ColorsCount { get; }
///
/// Gets a value indicating the number of texture coordinate vertex attributes.
/// In the range of 0 to 2.
///
int TexCoordsCount { get; }
///
/// Gets a value indicating the number of skinning joint-weight attributes.
/// The values can be 0, 4 or 8.
///
int JointsWeightsCount { get; }
///
/// Gets a sequence of tuples where each item represents the vertex indices of a line.
///
IEnumerable<(int A, int B)> LineIndices { get; }
///
/// Gets a sequence of tuples where each item represents the vertex indices of a triangle.
///
IEnumerable<(int A, int B, int C)> TriangleIndices { get; }
#endregion
#region API
XYZ GetPosition(int vertexIndex);
XYZ GetNormal(int vertexIndex);
XYZW GetTangent(int vertexIndex);
XY GetTextureCoord(int vertexIndex, int textureSetIndex);
XYZW GetColor(int vertexIndex, int colorSetIndex);
Transforms.SparseWeight8 GetSkinWeights(int vertexIndex);
IReadOnlyList GetPositionDeltas(int vertexIndex);
IReadOnlyList GetNormalDeltas(int vertexIndex);
IReadOnlyList GetTangentDeltas(int vertexIndex);
IReadOnlyList GetTextureCoordDeltas(int vertexIndex, int textureSetIndex);
IReadOnlyList GetColorDeltas(int vertexIndex, int colorSetIndex);
#endregion
}
///
/// Exposes an API to get geometry data from a mesh primitive
///
/// The material type used by the primitive
///
/// Implemented by
///
public interface IMeshPrimitiveDecoder : IMeshPrimitiveDecoder
where TMaterial : class
{
TMaterial Material { get; }
}
///
/// Utility methods to help decode Meshes.
///
public static class MeshDecoder
{
public static IMeshDecoder Decode(this Schema2.Mesh mesh, RuntimeOptions options = null)
{
if (mesh == null) return null;
var meshDecoder = new _MeshDecoder(mesh, options);
meshDecoder.GenerateNormalsAndTangents();
return meshDecoder;
}
public static IMeshDecoder[] Decode(this IReadOnlyList meshes, RuntimeOptions options = null)
{
Guard.NotNull(meshes, nameof(meshes));
return meshes.Select(item => item.Decode(options)).ToArray();
}
public static XYZ GetPosition(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
{
Guard.NotNull(primitive, nameof(primitive));
Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
Guard.NotNull(xform, nameof(xform));
var p = primitive.GetPosition(idx);
var d = primitive.GetPositionDeltas(idx);
var w = primitive.GetSkinWeights(idx);
return xform.TransformPosition(p, d, w);
}
public static XYZ GetNormal(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
{
Guard.NotNull(primitive, nameof(primitive));
Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
Guard.NotNull(xform, nameof(xform));
var n = primitive.GetNormal(idx);
var d = primitive.GetNormalDeltas(idx);
var w = primitive.GetSkinWeights(idx);
return xform.TransformNormal(n, d, w);
}
public static XYZW GetTangent(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
{
Guard.NotNull(primitive, nameof(primitive));
Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
Guard.NotNull(xform, nameof(xform));
var t = primitive.GetTangent(idx);
var d = primitive.GetTangentDeltas(idx);
var w = primitive.GetSkinWeights(idx);
return xform.TransformTangent(t, d, w);
}
public static (XYZ Min, XYZ Max) EvaluateBoundingBox(this Schema2.Scene scene, float samplingTimeStep = 1.0f)
{
Guard.NotNull(scene, nameof(scene));
var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
var sceneTemplate = SceneTemplate.Create(scene);
var sceneInstance = sceneTemplate.CreateInstance();
var armatureInst = sceneInstance.Armature;
if (armatureInst.AnimationTracks.Count == 0)
{
armatureInst.SetPoseTransforms();
return sceneInstance.EvaluateBoundingBox(decodedMeshes);
}
var min = new XYZ(float.PositiveInfinity);
var max = new XYZ(float.NegativeInfinity);
for (int trackIdx = 0; trackIdx < armatureInst.AnimationTracks.Count; ++trackIdx)
{
var duration = armatureInst.AnimationTracks[trackIdx].Duration;
for (float time = 0; time < duration; time += samplingTimeStep)
{
armatureInst.SetAnimationFrame(trackIdx, time);
var (fMin, fMax) = sceneInstance.EvaluateBoundingBox(decodedMeshes);
min = XYZ.Min(min, fMin);
max = XYZ.Max(max, fMax);
}
}
return (min, max);
}
public static (XYZ Center, Single Radius) EvaluateBoundingSphere(this Schema2.Scene scene, float samplingTimeStep = 1.0f)
{
Guard.NotNull(scene, nameof(scene));
var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
var sceneTemplate = SceneTemplate.Create(scene);
var sceneInstance = sceneTemplate.CreateInstance();
var armatureInst = sceneInstance.Armature;
if (armatureInst.AnimationTracks.Count == 0)
{
armatureInst.SetPoseTransforms();
return sceneInstance.EvaluateBoundingSphere(decodedMeshes);
}
var center = XYZ.Zero;
float radius = -1f;
for (int trackIdx = 0; trackIdx < armatureInst.AnimationTracks.Count; ++trackIdx)
{
var duration = armatureInst.AnimationTracks[trackIdx].Duration;
for (float time = 0; time < duration; time += samplingTimeStep)
{
armatureInst.SetAnimationFrame(trackIdx, time);
var (fc, fr) = sceneInstance.EvaluateBoundingSphere(decodedMeshes);
_MergeSphere(ref center, ref radius, fc, fr);
}
}
return (center, radius);
}
public static (XYZ Min, XYZ Max) EvaluateBoundingBox(this SceneInstance instance, IReadOnlyList> meshes)
where TMaterial : class
{
Guard.NotNull(instance, nameof(instance));
Guard.NotNull(meshes, nameof(meshes));
var min = new XYZ(float.PositiveInfinity);
var max = new XYZ(float.NegativeInfinity);
foreach (var pos in instance.GetWorldVertices(meshes))
{
min = XYZ.Min(min, pos);
max = XYZ.Max(max, pos);
}
return (min, max);
}
public static (XYZ Center, Single Radius) EvaluateBoundingSphere(this SceneInstance instance, IReadOnlyList> meshes)
where TMaterial : class
{
Guard.NotNull(instance, nameof(instance));
Guard.NotNull(meshes, nameof(meshes));
var center = XYZ.Zero;
var radius = -1f;
foreach (var p1 in instance.GetWorldVertices(meshes))
{
_AddPointToSphere(ref center, ref radius, p1);
}
return (center, radius);
}
private static void _AddPointToSphere(ref XYZ c1, ref float r1, XYZ c2)
{
if (r1 < 0) { c1 = c2; r1 = 0; return; }
var dir = c2 - c1;
var len = dir.Length();
if (len <= r1) return; // if inside, exit.
dir /= len;
var p1 = c1 - (dir * r1);
c1 = (p1 + c2) / 2;
r1 = (p1 - c2).Length() / 2;
#if DEBUG
var dist = (c2 - c1).Length();
System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
dist = (p1 - c1).Length();
System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
#endif
}
private static void _MergeSphere(ref XYZ c1, ref float r1, XYZ c2, float r2)
{
if (r1 < 0) { c1 = c2; r1 = r2; return; }
var dir = c2 - c1;
var len = dir.Length();
if (r1 >= (r2 + len)) return; // new inside current, exit.
if (r2 >= (r1 + len)) { c1 = c2; r1 = r2; return; } // current inside new, update & exit.
// combine
dir /= len;
var p1 = c1 - (dir * r1);
var p2 = c2 + (dir * r2);
c1 = (p1 + p2) / 2;
r1 = (p1 - p2).Length() / 2;
#if DEBUG
var dist = (c2 - c1).Length() + r2;
System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
#endif
}
public static IEnumerable GetWorldVertices(this SceneInstance instance, IReadOnlyList> meshes)
where TMaterial : class
{
Guard.NotNull(instance, nameof(instance));
Guard.NotNull(meshes, nameof(meshes));
for (int i = 0; i < meshes.Count; ++i)
{
Guard.MustBeEqualTo(meshes[i].LogicalIndex, i, nameof(meshes) + $"[{i}]");
}
return instance
.Where(item => item.Transform.Visible)
.SelectMany(item => meshes[item.Template.LogicalMeshIndex].GetWorldVertices(item.Transform));
}
public static IEnumerable GetWorldVertices(this IMeshDecoder mesh, Transforms.IGeometryTransform xform)
where TMaterial : class
{
Guard.NotNull(mesh, nameof(mesh));
Guard.NotNull(xform, nameof(xform));
foreach (var childXform in Transforms.InstancingTransform.Evaluate(xform))
{
foreach (var primitive in mesh.Primitives)
{
for (int i = 0; i < primitive.VertexCount; ++i)
{
yield return primitive.GetPosition(i, childXform);
}
}
}
}
}
}