Преглед на файлове

WIP handling Scene build scenarios with empty meshes.

Vicente Penades преди 6 години
родител
ревизия
7ef9e57a5e

+ 6 - 9
examples/Example1/Program.cs

@@ -36,19 +36,16 @@ namespace Example1
 
             prim = mesh.UsePrimitive(material2);
             prim.AddConvexPolygon(new VERTEX(-5, 0, 3), new VERTEX(0, -5, 3), new VERTEX(5, 0, 3), new VERTEX(0, 5, 3));
-            
-            // create a new gltf model
-            var model = ModelRoot.CreateModel();
 
-            // add all meshes (just one in this case) to the model
-            model.CreateMeshes(mesh);
+            // create a scene
 
-            // create a scene, a node, and assign the first mesh
-            model.UseScene("Default")
-                .CreateNode()
-                .WithMesh(model.LogicalMeshes[0]);
+            var scene = new SharpGLTF.Scenes.SceneBuilder();
+
+            scene.AddMesh(mesh, Matrix4x4.Identity);
 
             // save the model in different formats
+
+            var model = scene.ToSchema2();
             model.SaveAsWavefront("mesh.obj");
             model.SaveGLB("mesh.glb");
             model.SaveGLTF("mesh.gltf");

+ 2 - 2
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -38,8 +38,6 @@ namespace SharpGLTF.Geometry
                 .SelectMany(item => item.Primitives)
                 .Where(item => !item.IsEmpty());
 
-            Guard.IsTrue(meshPrimitives.Any(), "No geometry found.");
-
             var vertexBlocks = VertexTypes.VertexUtils
                 .CreateVertexMemoryAccessors( meshPrimitives.Select(item => item.Vertices) )
                 .ToList();
@@ -89,6 +87,8 @@ namespace SharpGLTF.Geometry
 
         public Mesh CreateSchema2Mesh(ModelRoot root, Func<TMaterial, Material> materialEvaluator)
         {
+            if (_Primitives.Count == 0) return null;
+
             var dstMesh = root.CreateMesh(_MeshName);
 
             foreach (var p in _Primitives)

+ 5 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -213,6 +213,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             // total number of vertices
             var totalCount = vertexBlocks.Sum(item => item.Count);
 
+            if (totalCount == 0) yield break;
+
             // determine the vertex attributes from the first vertex.
             var firstVertex = vertexBlocks
                 .First(item => item.Count > 0)[0];
@@ -257,6 +259,9 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             // get attributes
             var totalCount = indexBlocks.Sum(item => item.Count);
+
+            if (totalCount == 0) yield break;
+
             var attribute = new MemoryAccessInfo("INDEX", 0, totalCount, 0, Schema2.DimensionType.SCALAR, Schema2.EncodingType.UNSIGNED_INT);
 
             // create master index buffer

+ 6 - 5
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -39,6 +39,7 @@ namespace SharpGLTF.Scenes
                 .Select(item => item.Content?.GetGeometryAsset())
                 .Where(item => item != null)
                 .SelectMany(item => item.Primitives)
+                .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
                 .Select(item => item.Material)
                 .Where(item => item != null)
                 .Distinct()
@@ -60,11 +61,11 @@ namespace SharpGLTF.Scenes
             // and group them by their vertex attribute layout.
 
             var meshGroups = srcScene.Instances
-            .Select(item => item.Content?.GetGeometryAsset())
-            .Where(item => item != null)
-            .Distinct()
-            .ToList()
-            .GroupBy(item => item.GetType());
+                .Select(item => item.Content?.GetGeometryAsset())
+                .Where(item => item != null)
+                .Distinct()
+                .ToList()
+                .GroupBy(item => item.GetType());
 
             // create Schema2.Mesh collections for every gathered group.
 

+ 13 - 1
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -28,7 +28,18 @@ namespace SharpGLTF.Schema2
 
         public static IReadOnlyList<Mesh> CreateMeshes(this ModelRoot root, params IMeshBuilder<Materials.MaterialBuilder>[] meshBuilders)
         {
-            return root.CreateMeshes(mb => root.CreateMaterial(mb), meshBuilders);
+            // MaterialBuilder instances can be grouped by their content, so we use this dictionary
+            // to reduce the number of equal materials. This is specially useful for the default material.
+
+            var materials = new Dictionary<Materials.MaterialBuilder, Material>(Materials.MaterialBuilder.ContentComparer);
+
+            Material matFactory(Materials.MaterialBuilder srcMat)
+            {
+                if (materials.TryGetValue(srcMat, out Schema2.Material dstMat)) return dstMat;
+                return materials[srcMat] = root.CreateMaterial(srcMat);
+            }
+
+            return root.CreateMeshes(matFactory, meshBuilders);
         }
 
         public static IReadOnlyList<Mesh> CreateMeshes<TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, params IMeshBuilder<TMaterial>[] meshBuilders)
@@ -42,6 +53,7 @@ namespace SharpGLTF.Schema2
             // create a new material for every unique material in the mesh builders.
             var mapMaterials = meshBuilders
                 .SelectMany(item => item.Primitives)
+                .Where(item => !item.IsEmpty())
                 .Select(item => item.Material)
                 .Distinct()
                 .ToDictionary(m => m, m => materialEvaluator(m));

+ 72 - 28
tests/SharpGLTF.Tests/Geometry/MeshBuilderTests.cs

@@ -5,6 +5,7 @@ using System.Numerics;
 using NUnit.Framework;
 
 using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Schema2;
 
 namespace SharpGLTF.Geometry
 {
@@ -122,38 +123,81 @@ namespace SharpGLTF.Geometry
             NumericsAssert.AreEqual(v1Bis.Skinning.Weights, new Vector4(7, 6, 5, 2) / (7f + 6f + 5f + 2f));
         }
 
+        [Test]
+        public void CreateEmptyMesh()
+        {
+            var mesh1 = VERTEX1.CreateCompatibleMesh();
+
+            var model = Schema2.ModelRoot.CreateModel();
+
+            var mmmmm = model.CreateMeshes(mesh1);
+
+            Assert.AreEqual(null, mmmmm[0]);
+        }
+
         [Test]
         public void CreatePartiallyEmptyMesh()
         {
-            var p0 = new Vector3(4403.12831325084f, 5497.3228336684406f, -451.62756590108586f);
-            var p1 = new Vector3(4403.1283132596873f, 5497.3228336591274f, -451.62756593199413f);
-            var p2 = new Vector3(4392.54991199635f, 5483.549242743291f, -450.72132376581396f);
-
-            Assert.AreEqual(p0, p1);
-
-
-
-            /*
-            var triangle = new Triangle(p0, p1, p2);
-            var normal = triangle.GetNormal();
-            var material1 = new MaterialBuilder().WithDoubleSide(true).WithMetallicRoughnessShader().WithChannelParam("BaseColor", new Vector4(1, 1, 1, 1));
-            var mesh = new MeshBuilder<VertexPositionNormal>("mesh");
-            var prim = mesh.UsePrimitive(material1);
-            prim.AddTriangle(
-            new VertexPositionNormal((float)triangle.GetP0().X, (float)triangle.GetP0().Y, (float)triangle.GetP0().Z, normal.X, normal.Y, normal.Z),
-            new VertexPositionNormal((float)triangle.GetP1().X, (float)triangle.GetP1().Y, (float)triangle.GetP1().Z, normal.X, normal.Y, normal.Z),
-            new VertexPositionNormal((float)triangle.GetP2().X, (float)triangle.GetP2().Y, (float)triangle.GetP2().Z, normal.X, normal.Y, normal.Z));
-
-            var model = ModelRoot.CreateModel();
-            try
-            {
-                model.CreateMeshes(mesh);
-            }
-            catch (ArgumentOutOfRangeException e)
-            {
-                Console.WriteLine(e);
-            }*/
+            
         }
 
+        [Test]
+        public static void CreateWithDegeneratedTriangle()
+        {
+            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 material1 = new Materials.MaterialBuilder()
+                .WithMetallicRoughnessShader()
+                .WithChannelParam("BaseColor", Vector4.One * 0.5f);
+
+            var material2 = new Materials.MaterialBuilder()
+                .WithMetallicRoughnessShader()
+                .WithChannelParam("BaseColor", Vector4.One * 0.7f);
+
+            var mesh = new MeshBuilder<VertexPosition>("mesh");
+            mesh.VertexPreprocessor.SetDebugPreprocessors();
+
+            var validIndices = mesh.UsePrimitive(material1)
+                .AddTriangle
+                    (
+                    new VertexPosition(validTriangle.Item1),
+                    new VertexPosition(validTriangle.Item2),
+                    new VertexPosition(validTriangle.Item3)
+                    );
+            Assert.GreaterOrEqual(validIndices.Item1, 0);
+            Assert.GreaterOrEqual(validIndices.Item2, 0);
+            Assert.GreaterOrEqual(validIndices.Item3, 0);
+
+            var degenIndices = mesh.UsePrimitive(material2)
+                .AddTriangle
+                    (
+                    new VertexPosition(degeneratedTriangle.Item1),
+                    new VertexPosition(degeneratedTriangle.Item2),
+                    new VertexPosition(degeneratedTriangle.Item3)
+                    );
+            Assert.Less(degenIndices.Item1, 0);
+            Assert.Less(degenIndices.Item2, 0);
+            Assert.Less(degenIndices.Item3, 0);
+
+            // create meshes:
+
+            var model = ModelRoot.CreateModel();            
+
+            var dstMeshes = model.CreateMeshes(mesh);
+
+            Assert.AreEqual(1, dstMeshes[0].Primitives.Count);
+        }
     }
 }

+ 57 - 1
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -72,7 +72,7 @@ namespace SharpGLTF.Scenes
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
-            var rnd = new Random();
+            var rnd = new Random(177);
 
             // create materials
             var materials = Enumerable
@@ -114,6 +114,62 @@ namespace SharpGLTF.Scenes
             scene.AttachToCurrentTest("shapes.glb");
         }
 
+        [Test]
+        public void CreateSceneWithEmptyMeshes()
+        {
+            // Schema2 does NOT allow meshes to be empty, or meshes with empty MeshPrimitives.
+            // but MeshBuilder and SceneBuilder should be able to handle them.
+
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            var rnd = new Random(177);
+
+            // create materials
+            var materials = Enumerable
+                .Range(0, 10)
+                .Select(idx => new Materials.MaterialBuilder($"material{idx}")
+                .WithChannelParam("BaseColor", new Vector4(rnd.NextVector3(), 1)))
+                .ToList();
+
+            // create scene            
+
+            var mesh1 = VPOSNRM.CreateCompatibleMesh("mesh1");
+            mesh1.VertexPreprocessor.SetSanitizerPreprocessors();
+            mesh1.AddCube(materials[0], Matrix4x4.Identity);
+            mesh1.UsePrimitive(materials[1]).AddTriangle(default, default, default); // add degenerated triangle to produce an empty primitive
+            mesh1.AddCube(materials[2], Matrix4x4.CreateTranslation(10,0,0));
+
+            var mesh2 = VPOSNRM.CreateCompatibleMesh("mesh2"); // empty mesh
+
+            var mesh3 = VPOSNRM.CreateCompatibleMesh("mesh3");
+            mesh3.VertexPreprocessor.SetSanitizerPreprocessors();
+            mesh3.AddCube(materials[3], Matrix4x4.Identity);
+
+            var scene = new SceneBuilder();
+
+            scene.AddMesh(mesh1, Matrix4x4.Identity);
+            scene.AddMesh(mesh2, Matrix4x4.Identity);
+            scene.AddMesh(mesh3, Matrix4x4.CreateTranslation(0,10,0));
+
+            var model = scene.ToSchema2();
+
+            Assert.AreEqual(3, model.LogicalMaterials.Count);
+            CollectionAssert.AreEquivalent(new[] { "material0", "material2", "material3" }, model.LogicalMaterials.Select(item => item.Name));
+
+            Assert.AreEqual(2, model.LogicalMeshes.Count);
+
+            Assert.AreEqual("mesh1", model.LogicalMeshes[0].Name);
+            Assert.AreEqual(2, model.LogicalMeshes[0].Primitives.Count);
+
+            Assert.AreEqual("mesh3", model.LogicalMeshes[1].Name);
+            Assert.AreEqual(1, model.LogicalMeshes[1].Primitives.Count);
+
+            // save the model as GLB
+
+            scene.AttachToCurrentTest("scene.glb");
+        }
+
         [Test]
         public void CreateSkinnedScene()
         {