| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Numerics;
- using NUnit.Framework;
- using SharpGLTF.Geometry.VertexTypes;
- using SharpGLTF.Geometry.Parametric;
- using SharpGLTF.Schema2;
- using SharpGLTF.Scenes;
- namespace SharpGLTF.Geometry
- {
- using VERTEX1 = VertexBuilder<VertexPosition, VertexColor1Texture1, VertexJoints4>;
- using VERTEX2 = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexJoints4>;
- [Category("Toolkit.Geometry")]
- public class MeshBuilderTests
- {
- [Test]
- public void CreateInvalidTriangles()
- {
- var m = new Materials.MaterialBuilder();
- var mb = VERTEX2.CreateCompatibleMesh();
- // replaces default preprocessor with a debug preprocessor that throws exceptions at the slightest issue.
- mb.VertexPreprocessor.SetValidationPreprocessors();
- int TriangleCounter() { return mb.Primitives.Sum(item => item.Triangles.Count); }
- var prim = mb.UsePrimitive(m);
- var a = VERTEX2
- .Create(Vector3.Zero,Vector3.UnitX)
- .WithMaterial(Vector4.One,Vector2.Zero)
- .WithSkinning((0,1));
- var b = VERTEX2
- .Create(Vector3.UnitX, Vector3.UnitX)
- .WithMaterial(Vector4.One, Vector2.Zero)
- .WithSkinning((0, 1));
- var c = VERTEX2
- .Create(Vector3.UnitY, Vector3.UnitX)
- .WithMaterial(Vector4.One, Vector2.Zero)
- .WithSkinning((0, 1));
- prim.AddTriangle(a, b, c);
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- var v2nan = new Vector2(float.NaN, float.NaN);
- var v3nan = new Vector3(float.NaN, float.NaN, float.NaN);
- var v4nan = new Vector4(float.NaN, float.NaN, float.NaN, float.NaN);
- Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(v3nan), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, v3nan), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.Zero), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
-
- Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.UnitX * 0.8f), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v2nan), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v4nan), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(Vector4.One*2), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(-Vector4.One), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithSkinning((0,0)), b, c));
- Assert.That(TriangleCounter(), Is.EqualTo(1));
- }
- [Test]
- public void CreateMeshInSanitizedMode()
- {
- var mesh = VERTEX2.CreateCompatibleMesh();
- mesh.VertexPreprocessor.SetSanitizerPreprocessors();
- var prim = mesh.UsePrimitive(Materials.MaterialBuilder.CreateDefault(), 1);
- var p = new VertexPositionNormal(Vector3.UnitX, new Vector3(float.NaN));
- var m = new VertexColor1Texture1(Vector4.One * 7, new Vector2(float.NaN));
- var s = new VertexJoints4((0, 2), (1, 7), (2, 6), (3, 5));
-
- var v1Idx = prim.AddPoint(new VERTEX2(p, m, s));
- var v1Bis = prim.Vertices[v1Idx];
- NumericsAssert.AreEqual(v1Bis.Geometry.Position, Vector3.UnitX);
- NumericsAssert.AreEqual(v1Bis.Geometry.Normal, Vector3.UnitX);
- NumericsAssert.AreEqual(v1Bis.Material.Color, Vector4.One);
- NumericsAssert.AreEqual(v1Bis.Material.TexCoord, Vector2.Zero);
- NumericsAssert.AreEqual(v1Bis.Skinning.Joints, new Vector4(1, 2, 3, 0));
- NumericsAssert.AreEqual(v1Bis.Skinning.Weights, new Vector4(7, 6, 5, 2) / (7f + 6f + 5f + 2f));
- }
-
- [Test]
- public static void CreateWithDegeneratedTriangle()
- {
- // create materials
- var material1 = new Materials.MaterialBuilder()
- .WithMetallicRoughnessShader()
- .WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.5f);
- var material2 = new Materials.MaterialBuilder()
- .WithMetallicRoughnessShader()
- .WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.7f);
- // create a mesh with degenerated triangles
- var validTriangle =
- (
- new Vector3(4373.192624189425f, 5522.678275192156f, -359.8238015332605f),
- new Vector3(4370.978060142137f, 5522.723320999183f, -359.89184701762827f),
- new Vector3(4364.615741107147f, 5511.510615546256f, -359.08922455413233f)
- );
- var degeneratedTriangle =
- (
- new Vector3(4374.713581837248f, 5519.741978117265f, -360.87014389818034f),
- new Vector3(4373.187151107471f, 5521.493282925338f, -355.70835120644153f),
- new Vector3(4373.187151107471f, 5521.493282925338f, -355.70835120644153f)
- );
- var mesh = new MeshBuilder<VertexPosition>("mesh");
- mesh.VertexPreprocessor.SetValidationPreprocessors();
- var validIndices = mesh.UsePrimitive(material1)
- .AddTriangle
- (
- new VertexPosition(validTriangle.Item1),
- new VertexPosition(validTriangle.Item2),
- new VertexPosition(validTriangle.Item3)
- );
- Assert.That(validIndices.A, Is.GreaterThanOrEqualTo(0));
- Assert.That(validIndices.B, Is.GreaterThanOrEqualTo(0));
- Assert.That(validIndices.C, Is.GreaterThanOrEqualTo(0));
- var degenIndices = mesh.UsePrimitive(material2)
- .AddTriangle
- (
- new VertexPosition(degeneratedTriangle.Item1),
- new VertexPosition(degeneratedTriangle.Item2),
- new VertexPosition(degeneratedTriangle.Item3)
- );
- Assert.That(degenIndices.A, Is.LessThan(0));
- Assert.That(degenIndices.B, Is.LessThan(0));
- Assert.That(degenIndices.C, Is.LessThan(0));
- // create scene:
- var scene = new SceneBuilder();
- scene.AddRigidMesh(mesh, Matrix4x4.Identity);
- // check gltf2
- var model = scene.ToGltf2();
- Assert.That(model.LogicalMeshes[0].Primitives, Has.Count.EqualTo(1));
- }
- [Test]
- public static void CreateWithMutableSharedMaterial()
- {
- // create materials
- var material1 = Materials.MaterialBuilder.CreateDefault();
- var material2 = Materials.MaterialBuilder.CreateDefault();
- var material3 = Materials.MaterialBuilder.CreateDefault();
- Assert.That(Materials.MaterialBuilder.AreEqualByContent(material1, material2), Is.True);
- Assert.That(Materials.MaterialBuilder.AreEqualByContent(material1, material3), Is.True);
- Assert.That(material2 != material1);
- Assert.That(material3 != material1);
- // MeshBuilder should split primitives by material reference,
- // because in general, materials will not be immutable.
- var mesh = new MeshBuilder<VertexPosition>();
- mesh.UsePrimitive(material1, 1).AddPoint(default);
- mesh.UsePrimitive(material2, 1).AddPoint(default);
- mesh.UsePrimitive(material3, 1).AddPoint(default);
- Assert.That(mesh.Primitives, Has.Count.EqualTo(3));
- // create scene
- var scene = new SceneBuilder();
- scene.AddRigidMesh(mesh, Matrix4x4.Identity);
- // check gltf
- // The build process should identify that, at this point, material1, material2 and material3
- // represent the same material, and coalesce to a single material.
- var gltfModel = scene.ToGltf2();
- Assert.That(gltfModel.LogicalMaterials, Has.Count.EqualTo(1));
- // since Materials.MaterialBuilder is not immutable we can change the contents,
- // so now, material1, material2 and material3 no longer represent the same material
- material1.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.2f);
- material2.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.4f);
- material3.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.6f);
- gltfModel = scene.ToGltf2();
- Assert.That(gltfModel.LogicalMaterials, Has.Count.EqualTo(3));
- }
- [Test]
- public void CreateMeshWithTriangleAndQuad()
- {
- var dmat = Materials.MaterialBuilder.CreateDefault();
- var mesh = VERTEX1.CreateCompatibleMesh();
- var prim = mesh.UsePrimitive(dmat);
- var triIdx = prim.AddTriangle(new VERTEX1(Vector3.Zero), new VERTEX1(Vector3.UnitX * 2) , new VERTEX1(Vector3.UnitY * 2));
- var qadIdx = prim.AddQuadrangle(new VERTEX1(-Vector3.UnitX), new VERTEX1(Vector3.UnitY), new VERTEX1(Vector3.UnitX), new VERTEX1(-Vector3.UnitY));
- Assert.That(prim.Vertices, Has.Count.EqualTo(7));
- Assert.That(prim.Triangles, Has.Count.EqualTo(3));
- Assert.That(prim.Surfaces, Has.Count.EqualTo(2));
- Assert.That(triIdx, Is.EqualTo((0, 1, 2)));
- Assert.That(qadIdx, Is.EqualTo((3, 4, 5, 6)));
- Assert.That(prim.GetIndices(), Is.EqualTo(new[] { 0, 1, 2, 3, 4, 5, 3, 5, 6 }));
- }
- [Test]
- public void CreateMeshWithCustomVertexAttribute()
- {
- var dmat = Materials.MaterialBuilder.CreateDefault();
- var mesh = new MeshBuilder<VertexPosition, VertexColor1Texture1Custom1, VertexEmpty>();
- var prim = mesh.UsePrimitive(dmat);
- prim.AddTriangle
- (
- (Vector3.UnitX, (Vector4.One, Vector2.Zero, 0.1f)),
- (Vector3.UnitY, (Vector4.One, Vector2.Zero, 0.2f)),
- (Vector3.UnitZ, (Vector4.One, Vector2.Zero, 0.3f))
- );
- var dstScene = new Schema2.ModelRoot();
- var dstMesh = dstScene.CreateMesh(mesh);
- var batchId = dstMesh.Primitives[0].GetVertexAccessor(VertexColor1Texture1Custom1.CUSTOMATTRIBUTENAME).AsScalarArray();
- Assert.That(batchId, Is.EqualTo(new float[] { 0.1f, 0.2f, 0.3f }));
- }
- [Test]
- public void GenerateTangents()
- {
- var vertices = new VertexBufferColumns();
- vertices.Positions = new[] { Vector3.Zero, Vector3.UnitX, Vector3.UnitY };
- vertices.Normals = new[] { Vector3.UnitZ, Vector3.UnitZ, Vector3.UnitZ };
- vertices.TexCoords0 = new[] { Vector2.Zero, Vector2.UnitX, Vector2.UnitY };
- var indices = new[] { (0,1,2) } as IEnumerable<(int,int,int)>;
- VertexBufferColumns.CalculateTangents(new[] { (vertices, indices) });
- }
- [Test]
- public void CloneMeshBuilder()
- {
- var material1 = Materials.MaterialBuilder.CreateDefault();
- var material2 = Materials.MaterialBuilder.CreateDefault();
- var mesh = new MeshBuilder<VertexPosition>();
- mesh.AddCube(material1, Matrix4x4.Identity);
- mesh.AddSphere(material2, 5, Matrix4x4.CreateTranslation(0, 10, 0));
- var cloned1 = mesh.Clone( m => m.Clone() );
- var primitivePairs = mesh.Primitives.Zip(cloned1.Primitives, (src, dst) => (src, dst));
- foreach(var (src, dst) in primitivePairs)
- {
- Assert.That(dst.Material, Is.Not.SameAs(src.Material));
- Assert.That(dst.Triangles, Has.Count.EqualTo(src.Triangles.Count));
- Assert.That(dst.Triangles, Is.EqualTo(src.Triangles));
- }
- var material3 = Materials.MaterialBuilder.CreateDefault();
-
- // force all geometries to use a single material,
- // which should result in a mesh with with all the primitives
- // of the source mesh merged into a single primitive.
- var cloned2 = cloned1.Clone(m => material3);
- Assert.That(cloned2.Primitives.Count, Is.EqualTo(1));
- Assert.That(cloned2.Primitives.First().Material, Is.SameAs(material3));
- Assert.That(cloned2.Primitives.Sum(item => item.Triangles.Count), Is.EqualTo(cloned1.Primitives.Sum(item => item.Triangles.Count)));
- }
- }
- }
|