Browse Source

wrapping up scene build API

Vicente Penades 6 years ago
parent
commit
0ddd6d4f7a

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/VertexTypes/FragmentPreprocessors.cs

@@ -149,7 +149,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
             if (vertex.TryGetNormal(out Vector3 n))
             {
-                if (!n._IsReal()) return null;
+                if (!n._IsReal()) n = p;
                 if (n == Vector3.Zero) n = p;
                 if (n == Vector3.Zero) return null;
 

+ 3 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -173,6 +173,9 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public VertexJoints8x4(params (int, float)[] bindings)
         {
+            Guard.NotNull(bindings, nameof(bindings));
+            Guard.MustBeBetweenOrEqualTo(bindings.Length, 1, 4, nameof(bindings));
+
             Joints = Vector4.Zero;
             Weights = Vector4.Zero;
 

+ 3 - 2
src/SharpGLTF.Toolkit/README.md

@@ -3,8 +3,9 @@
 #### Features
 
 - Scene Evaluation.
-- Mesh Building utilities.
-- Material Building utilities
+- Mesh building utilities.
+- Material building utilities
+- Scene building utilities.
 - Animation extension helpers.
 - Skinning helpers.
 

+ 13 - 1
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -14,6 +14,8 @@ namespace SharpGLTF.Scenes
 
         public NodeBuilder() { }
 
+        public NodeBuilder(string name) { Name = name; }
+
         private NodeBuilder(NodeBuilder parent)
         {
             _Parent = parent;
@@ -133,7 +135,7 @@ namespace SharpGLTF.Scenes
 
         #region API
 
-        public NodeBuilder AddNode(string name = null)
+        public NodeBuilder CreateNode(string name = null)
         {
             var c = new NodeBuilder(this);
             _Children.Add(c);
@@ -216,5 +218,15 @@ namespace SharpGLTF.Scenes
         }
 
         #endregion
+
+        #region With* API
+
+        public NodeBuilder WithLocalTranslation(Vector3 translation)
+        {
+            this.UseTranslation().Value = translation;
+            return this;
+        }
+
+        #endregion
     }
 }

+ 3 - 2
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -28,7 +28,8 @@ namespace SharpGLTF.Scenes
 
         public void AddScene(Scene dstScene, SceneBuilder srcScene)
         {
-            // gather all meshes and group them by their attribute layout.
+            // gather all MeshBuilder unique instances
+            // and group them by their vertex attribute layout.
 
             var meshGroups = srcScene.Instances
                 .Select(item => item.GetGeometryAsset())
@@ -51,7 +52,7 @@ namespace SharpGLTF.Scenes
                 }
             }
 
-            // gather all armatures
+            // gather all NodeBuilder unique armatures
 
             var armatures = srcScene.Instances
                 .Select(item => item.GetArmatureAsset())

+ 43 - 0
src/SharpGLTF.Toolkit/Scenes/readme.md

@@ -0,0 +1,43 @@
+# Toolkit Scene API
+
+#### Overview
+
+Creating scenes with the toolkit API is slightly different than creating them
+directly with the glTF Schema2 API.
+
+In the glTF Schema2 API, you create a `Scene`, then you add some `Node` children to the
+`Scene`, and then you fill some `Node` instances them with `Mesh` and `Skin` references.
+
+```c#
+scene = model.UseScene(0);
+var n1 = scene.CreateNode();
+n1.Mesh = ...
+var n2 = scene.CreateNode();
+n2.Mesh = ...
+var n3 = scene.CreateNode();
+n3.Mesh = ...
+n3.Skin = ...
+scene.SaveGLB("scene.glb");
+
+```
+
+The Toolkit API uses a more visual approach; you just add what you want to render
+and how you want to render it, so every AddMesh method adds an mesh instance to render.
+
+```c#
+scene = new SceneBuilder();
+scene.AddMesh(...);
+scene.AddMesh(...);
+scene.AddSkinnedMesh(...);
+scene.SaveGLB("scene.glb");
+```
+
+In order to have a hierarchical tree of nodes, you use the NodeBuilder object, with
+which you can create standalone nodes, or whole skeleton armatures.
+
+In this way, NodeBuilder armatures become just another asset, like a mesh or a material,
+and a scene is just a collection of instances to be rendered.
+
+When you save the scene, all assets are gathered and matched, including meshes, materials
+and nodes, and the appropiate glTF Schema2 Scenes and Nodes are created under the hood to
+match the SceneBuilder rendering intent.

+ 26 - 2
tests/SharpGLTF.Tests/Geometry/MeshBuilderTests.cs

@@ -1,10 +1,9 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
-using System.Text;
 
 using NUnit.Framework;
+
 using SharpGLTF.Geometry.VertexTypes;
 
 namespace SharpGLTF.Geometry
@@ -98,5 +97,30 @@ namespace SharpGLTF.Geometry
             Assert.AreEqual(1, TriangleCounter());
         }
 
+        [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 VertexJoints8x4((0, 2), (1, 7), (2, 6), (3, 5));
+
+            var v1 = new VERTEX2(p, m, s);
+            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));
+        }
+
     }
 }

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/Authoring/SolidMeshUtils.cs → tests/SharpGLTF.Tests/Geometry/Parametric/SolidMeshUtils.cs

@@ -6,7 +6,7 @@ using System.Text;
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 
-namespace SharpGLTF.Schema2.Authoring
+namespace SharpGLTF.Geometry.Parametric
 {
     using VERTEX = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexEmpty>;
 

+ 1 - 1
tests/SharpGLTF.Tests/Geometry/VertexTypes/JointWeightPairTests.cs

@@ -11,7 +11,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     public class JointWeightPairTests
     {
         [Test]
-        public void TestSorting()
+        public void TestJointWeightSorting()
         {
             var pairs = new[]
             {

+ 118 - 19
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -1,20 +1,19 @@
 using System;
-using System.Collections.Generic;
-using System.Text;
 using System.Linq;
 using System.Numerics;
 
 using NUnit.Framework;
 
-using SharpGLTF.Schema2.Authoring;
-
-using SharpGLTF.Animations;
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Geometry.Parametric;
+using SharpGLTF.Materials;
 
 namespace SharpGLTF.Scenes
 {
-    using Geometry;
-    
-    using VPOSNRM = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexEmpty, Geometry.VertexTypes.VertexEmpty>;
+    using VPOSNRM = VertexBuilder<VertexPositionNormal, VertexEmpty, VertexEmpty>;
+
+    using SKINNEDVERTEX = VertexBuilder<VertexPosition, VertexEmpty, VertexJoints8x4>;
 
 
     [Category("Toolkit.Scenes")]
@@ -26,14 +25,14 @@ namespace SharpGLTF.Scenes
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
-            var mesh = new Cube<Materials.MaterialBuilder>(new Materials.MaterialBuilder())
+            var mesh = new Cube<MaterialBuilder>(new MaterialBuilder())
                 .ToMesh(Matrix4x4.Identity);
 
             var scene = new SceneBuilder();
 
             scene.AddMesh(mesh, Matrix4x4.Identity);
 
-            scene.AttachToCurrentTest("cubes.glb");
+            scene.AttachToCurrentTest("cube.glb");
         }
 
         [Test]
@@ -42,7 +41,8 @@ namespace SharpGLTF.Scenes
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
-            var cube = new Cube<Materials.MaterialBuilder>(Materials.MaterialBuilder.CreateDefault());
+            var mesh = new Cube<MaterialBuilder>(MaterialBuilder.CreateDefault())
+                .ToMesh(Matrix4x4.Identity);
 
             var pivot = new NodeBuilder();
 
@@ -50,9 +50,17 @@ namespace SharpGLTF.Scenes
                 .WithPoint(0, Vector3.Zero)
                 .WithPoint(1, Vector3.One);
 
+            pivot.UseRotation("track1")
+                .WithPoint(0, Quaternion.Identity)
+                .WithPoint(1, Quaternion.CreateFromAxisAngle(Vector3.UnitY, 1.5f));
+
+            pivot.UseScale("track1")
+                .WithPoint(0, Vector3.One)
+                .WithPoint(1, new Vector3(0.5f));            
+
             var scene = new SceneBuilder();            
 
-            scene.AddMesh(cube.ToMesh(Matrix4x4.Identity), pivot);
+            scene.AddMesh(mesh, pivot);
 
             scene.AttachToCurrentTest("animated.glb");
             scene.AttachToCurrentTest("animated.gltf");
@@ -80,25 +88,116 @@ namespace SharpGLTF.Scenes
             for (int i = 0; i < 100; ++i)
             {
                 // create mesh
-                var m = materials[rnd.Next(0, 10)];
-                var s = VPOSNRM.CreateCompatibleMesh("shape");
-                s.VertexPreprocessor.SetDebugPreprocessors();
+                var mat = materials[rnd.Next(0, 10)];
+                var mesh = VPOSNRM.CreateCompatibleMesh("shape");
 
-                if ((i & 1) == 0) s.AddCube(m, Matrix4x4.Identity);
-                else s.AddSphere(m, 0.5f, Matrix4x4.Identity);
+                #if DEBUG
+                mesh.VertexPreprocessor.SetDebugPreprocessors();
+                #else
+                s.VertexPreprocessor.SetSanitizerPreprocessors();
+                #endif
 
-                s.Validate();
+                if ((i & 1) == 0) mesh.AddCube(mat, Matrix4x4.Identity);
+                else mesh.AddSphere(mat, 0.5f, Matrix4x4.Identity);
+
+                mesh.Validate();
 
                 // create random transform
                 var r = rnd.NextVector3() * 5;
                 var xform = Matrix4x4.CreateFromYawPitchRoll(r.X, r.Y, r.Z) * Matrix4x4.CreateTranslation(rnd.NextVector3() * 25);
 
-                scene.AddMesh(s, xform);                
+                scene.AddMesh(mesh, xform);                
             }
 
             // save the model as GLB
 
             scene.AttachToCurrentTest("shapes.glb");
         }
+
+        [Test]
+        public void CreateSkinnedScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+            
+            // create two materials
+
+            var pink = new MaterialBuilder("material1")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 0, 1, 1))
+                .WithDoubleSide(true);
+
+            var yellow = new MaterialBuilder("material2")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1))
+                .WithDoubleSide(true);
+
+            // create the mesh            
+
+            const int jointIdx0 = 0; // index of joint node 0
+            const int jointIdx1 = 1; // index of joint node 1
+            const int jointIdx2 = 2; // index of joint node 2
+
+            var v1 = new SKINNEDVERTEX(new Vector3(-10, 0, +10), (jointIdx0, 1));
+            var v2 = new SKINNEDVERTEX(new Vector3(+10, 0, +10), (jointIdx0, 1));
+            var v3 = new SKINNEDVERTEX(new Vector3(+10, 0, -10), (jointIdx0, 1));
+            var v4 = new SKINNEDVERTEX(new Vector3(-10, 0, -10), (jointIdx0, 1));
+
+            var v5 = new SKINNEDVERTEX(new Vector3(-10, 40, +10), (jointIdx0, 0.5f), (jointIdx1, 0.5f));
+            var v6 = new SKINNEDVERTEX(new Vector3(+10, 40, +10), (jointIdx0, 0.5f), (jointIdx1, 0.5f));
+            var v7 = new SKINNEDVERTEX(new Vector3(+10, 40, -10), (jointIdx0, 0.5f), (jointIdx1, 0.5f));
+            var v8 = new SKINNEDVERTEX(new Vector3(-10, 40, -10), (jointIdx0, 0.5f), (jointIdx1, 0.5f));
+
+            var v9  = new SKINNEDVERTEX(new Vector3(-5, 80, +5), (jointIdx2, 1));
+            var v10 = new SKINNEDVERTEX(new Vector3(+5, 80, +5), (jointIdx2, 1));
+            var v11 = new SKINNEDVERTEX(new Vector3(+5, 80, -5), (jointIdx2, 1));
+            var v12 = new SKINNEDVERTEX(new Vector3(-5, 80, -5), (jointIdx2, 1));
+
+            var mesh = SKINNEDVERTEX.CreateCompatibleMesh("mesh1");
+
+            #if DEBUG
+            mesh.VertexPreprocessor.SetDebugPreprocessors();
+            #else
+            mesh.VertexPreprocessor.SetSanitizerPreprocessors();
+            #endif
+
+            mesh.UsePrimitive(pink).AddConvexPolygon(v1, v2, v6, v5);
+            mesh.UsePrimitive(pink).AddConvexPolygon(v2, v3, v7, v6);
+            mesh.UsePrimitive(pink).AddConvexPolygon(v3, v4, v8, v7);
+            mesh.UsePrimitive(pink).AddConvexPolygon(v4, v1, v5, v8);
+
+            mesh.UsePrimitive(yellow).AddConvexPolygon(v5, v6, v10, v9);
+            mesh.UsePrimitive(yellow).AddConvexPolygon(v6, v7, v11, v10);
+            mesh.UsePrimitive(yellow).AddConvexPolygon(v7, v8, v12, v11);
+            mesh.UsePrimitive(yellow).AddConvexPolygon(v8, v5, v9, v12);
+
+            mesh.Validate();
+            
+            // create the skeleton armature for the skinned mesh.
+
+            var armature = new NodeBuilder("Skeleton");
+            var joint0 = armature.CreateNode("Joint 0").WithLocalTranslation(new Vector3(0, 0, 0)); // jointIdx0
+            var joint1 = joint0.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 40, 0));  // jointIdx1
+            var joint2 = joint1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0));  // jointIdx2
+
+            joint1.UseRotation("Base Track")
+                .WithPoint(1, Quaternion.Identity)
+                .WithPoint(2, Quaternion.CreateFromYawPitchRoll(0, 1, 0))
+                .WithPoint(3, Quaternion.CreateFromYawPitchRoll(0, 0, 1))
+                .WithPoint(4, Quaternion.Identity);
+
+            // create scene
+
+            var scene = new SceneBuilder();
+
+            scene.AddSkinnedMesh
+                (
+                mesh,
+                joint0, // joint used for skinning joint index 0
+                joint1, // joint used for skinning joint index 1
+                joint2  // joint used for skinning joint index 2
+                );
+
+            scene.AttachToCurrentTest("skinned.glb");
+            scene.AttachToCurrentTest("skinned.gltf");
+        }
     }
 }

+ 2 - 0
tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs

@@ -2,6 +2,8 @@
 
 using NUnit.Framework;
 
+using SharpGLTF.Geometry.Parametric;
+
 namespace SharpGLTF.Schema2.Authoring
 {
     using VPOSNRM = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal,Geometry.VertexTypes.VertexEmpty,Geometry.VertexTypes.VertexEmpty>;

+ 41 - 23
tests/SharpGLTF.Tests/Schema2/Authoring/MeshBuilderCreationTests.cs

@@ -5,11 +5,12 @@ using System.Linq;
 
 using NUnit.Framework;
 
+using SharpGLTF.Geometry;
+using SharpGLTF.Materials;
+using SharpGLTF.Geometry.Parametric;
+
 namespace SharpGLTF.Schema2.Authoring
 {
-    using Geometry;
-    using Materials;
-
     using VEMPTY = Geometry.VertexTypes.VertexEmpty;
     using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
     using VPOS = Geometry.VertexTypes.VertexPosition;
@@ -59,8 +60,11 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
             // create materials
-            var material1 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1));
-            var material2 = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 0, 1, 1));            
+            var material1 = new MaterialBuilder("material1")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1));
+
+            var material2 = new MaterialBuilder("material1")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 0, 1, 1));            
 
             // create several meshes
             var meshBuilder1 = new MeshBuilder<VPOSNRM>("mesh1");
@@ -161,27 +165,41 @@ namespace SharpGLTF.Schema2.Authoring
             };
 
             // create two materials
-            var pink = new MaterialBuilder("material1").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 0, 1, 1)).WithDoubleSide(true);
-            var yellow = new MaterialBuilder("material2").WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1)).WithDoubleSide(true);
+            var pink = new MaterialBuilder("material1")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 0, 1, 1))
+                .WithDoubleSide(true);
+
+            var yellow = new MaterialBuilder("material2")
+                .WithChannelParam(KnownChannels.BaseColor, new Vector4(1, 1, 0, 1))
+                .WithDoubleSide(true);
 
             // create the mesh
             var meshBuilder = new MeshBuilder<VPOS, VEMPTY, VSKIN4>("mesh1");
+
+            #if DEBUG
             meshBuilder.VertexPreprocessor.SetDebugPreprocessors();
+            #else
+            meshBuilder.VertexPreprocessor.SetSanitizerPreprocessors();
+            #endif
+
+            const int jointIdx0 = 0;
+            const int jointIdx1 = 1;
+            const int jointIdx2 = 2;
 
-            var v1 = (new VPOS(-10, 0, +10), new VSKIN4(0));
-            var v2 = (new VPOS(+10, 0, +10), new VSKIN4(0));
-            var v3 = (new VPOS(+10, 0, -10), new VSKIN4(0));
-            var v4 = (new VPOS(-10, 0, -10), new VSKIN4(0));
+            var v1 = (new VPOS(-10, 0, +10), new VSKIN4(jointIdx0));
+            var v2 = (new VPOS(+10, 0, +10), new VSKIN4(jointIdx0));
+            var v3 = (new VPOS(+10, 0, -10), new VSKIN4(jointIdx0));
+            var v4 = (new VPOS(-10, 0, -10), new VSKIN4(jointIdx0));
 
-            var v5 = (new VPOS(-10, 40, +10), new VSKIN4((0,0.5f), (1, 0.5f)));
-            var v6 = (new VPOS(+10, 40, +10), new VSKIN4((0, 0.5f), (1, 0.5f)));
-            var v7 = (new VPOS(+10, 40, -10), new VSKIN4((0, 0.5f), (1, 0.5f)));
-            var v8 = (new VPOS(-10, 40, -10), new VSKIN4((0, 0.5f), (1, 0.5f)));
+            var v5 = (new VPOS(-10, 40, +10), new VSKIN4((jointIdx0, 0.5f), (jointIdx1, 0.5f)));
+            var v6 = (new VPOS(+10, 40, +10), new VSKIN4((jointIdx0, 0.5f), (jointIdx1, 0.5f)));
+            var v7 = (new VPOS(+10, 40, -10), new VSKIN4((jointIdx0, 0.5f), (jointIdx1, 0.5f)));
+            var v8 = (new VPOS(-10, 40, -10), new VSKIN4((jointIdx0, 0.5f), (jointIdx1, 0.5f)));
 
-            var v9  = (new VPOS(-5, 80, +5), new VSKIN4(2));
-            var v10 = (new VPOS(+5, 80, +5), new VSKIN4(2));
-            var v11 = (new VPOS(+5, 80, -5), new VSKIN4(2));
-            var v12 = (new VPOS(-5, 80, -5), new VSKIN4(2));
+            var v9  = (new VPOS(-5, 80, +5), new VSKIN4(jointIdx2));
+            var v10 = (new VPOS(+5, 80, +5), new VSKIN4(jointIdx2));
+            var v11 = (new VPOS(+5, 80, -5), new VSKIN4(jointIdx2));
+            var v12 = (new VPOS(-5, 80, -5), new VSKIN4(jointIdx2));
 
             meshBuilder.UsePrimitive(pink).AddConvexPolygon(v1, v2, v6, v5);
             meshBuilder.UsePrimitive(pink).AddConvexPolygon(v2, v3, v7, v6);
@@ -201,14 +219,14 @@ namespace SharpGLTF.Schema2.Authoring
 
             // create the three joints that will affect the mesh
             var skelet = scene.CreateNode("Skeleton");
-            var joint1 = skelet.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 0, 0));
-            var joint2 = joint1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0)).WithRotationAnimation("Base Track", keyframes);
-            var joint3 = joint2.CreateNode("Joint 3").WithLocalTranslation(new Vector3(0, 40, 0));
+            var joint0 = skelet.CreateNode("Joint 0").WithLocalTranslation(new Vector3(0, 0, 0));
+            var joint1 = joint0.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 40, 0)).WithRotationAnimation("Base Track", keyframes);
+            var joint2 = joint1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0));
 
             // setup skin
             var snode = scene.CreateNode("Skeleton Node");
             snode.Skin = model.CreateSkin();            
-            snode.Skin.BindJoints(joint1, joint2, joint3);
+            snode.Skin.BindJoints(joint0, joint1, joint2);
 
             snode.WithMesh( model.CreateMesh(meshBuilder) );