Browse Source

Improved SceneBuilder GpuInstancing support with custom attributes.

vpenades 2 years ago
parent
commit
6b5e1debc5

+ 11 - 1
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -154,6 +154,14 @@ namespace SharpGLTF.Scenes
                         .Select(item => item.ChildTransform)
                         .ToList();
 
+                    var extras = _Children
+                        .Select(item => item.Extras)
+                        .ToList();
+
+                    // we require all extras to exist and be of JsonObject type
+                    // so we can retrieve a shared attribute name.
+                    if (!extras.All(item => item is System.Text.Json.Nodes.JsonObject)) extras = null;                    
+
                     if (!(dst is Node dstNode)) dstNode = dst.CreateNode();
 
                     System.Diagnostics.Debug.Assert(dstNode.Mesh == null);
@@ -162,10 +170,12 @@ namespace SharpGLTF.Scenes
 
                     srcOperator.ApplyTo(dstNode, context);
 
-                    dstNode
+                    var gpuInstExt = dstNode
                         .UseGpuInstancing()
                         .WithInstanceAccessors(xforms);
 
+                    if (extras != null) gpuInstExt.WithInstanceCustomAccessors(extras);
+
                     #if DEBUG
                     var dstInstances = dstNode.GetGpuInstancing();
                     for (int i = 0; i < _Children.Count; ++i)

+ 41 - 20
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -378,25 +378,18 @@ namespace SharpGLTF.Schema2
         {
             Guard.NotNull(instancing, nameof(instancing));
 
-            // gather attribute keys
-            var keys = extras                
+            // gather all attribute keys that begin with _ , as in _FEATURE_ID_1
+            var keys = extras
                 .OfType<System.Text.Json.Nodes.JsonObject>()
                 .SelectMany(item => item)
                 .Select(item => item.Key)
                 .Distinct()
                 .Where(item => item.StartsWith("_", StringComparison.Ordinal));
 
+            // for each attribute key found, fill the IDs
             foreach (var key in keys)
             {
-                JSONEXTRAS valueGetter(JSONEXTRAS extra)
-                {
-                    if (!(extra is System.Text.Json.Nodes.JsonObject dict)) return null;
-                    return dict.TryGetPropertyValue(key, out var val) ? val : null;
-                }
-
-                var values = extras.Select(valueGetter).ToList();
-
-                instancing.WithInstanceCustomAccessor(key, values);
+                instancing.WithInstanceCustomAccessor(key, extras);
             }
 
             return instancing;
@@ -407,22 +400,50 @@ namespace SharpGLTF.Schema2
             Guard.NotNullOrEmpty(attribute, nameof(attribute));
 
             attribute = attribute.ToUpperInvariant();
-            var firstValue = values.Where(item => item != null).FirstOrDefault() as System.Text.Json.Nodes.JsonValue;
-            if (firstValue == null) return instancing;
 
-            if (firstValue.TryGetValue<int>(out _)) // assume all integers
+            var integers = _SelectAttribute<int>(values, attribute);
+            if (integers != null)
+            {
+                return instancing.WithInstanceAccessor(attribute, integers);                
+            }
+
+            var floats = _SelectAttribute<float>(values, attribute);
+            if (floats != null)
             {
-                var xValues = values.Select(item => item.GetValue<int>()).ToList();
-                return instancing.WithInstanceAccessor(attribute, xValues);
+                return instancing.WithInstanceAccessor(attribute, floats);
             }
 
-            if (firstValue.TryGetValue<Single>(out _)) // assume all floats
+            throw new ArgumentException($"Can't retrieve {attribute} from values", nameof(attribute));
+        }
+
+        /// <summary>
+        /// Takes a list of <see cref="JSONEXTRAS"/> and selects a specific property of a specific data type.
+        /// </summary>        
+        private static IReadOnlyList<T> _SelectAttribute<T>(IReadOnlyList<JSONEXTRAS> values, string propertyName)
+        {
+            var result = new List<T>();
+
+            foreach(var item in values)
             {
-                var xValues = values.Select(item => item.GetValue<float>()).ToList();
-                return instancing.WithInstanceAccessor(attribute, xValues);
+                var value = item;
+
+                // resolve property
+
+                if (value is System.Text.Json.Nodes.JsonObject obj) 
+                {
+                    if (!obj.TryGetPropertyValue(propertyName, out value)) return null;                    
+                }
+
+                // resolve value
+
+                if (!(value is System.Text.Json.Nodes.JsonValue jval)) return null;
+
+                if (!jval.TryGetValue(out T tval)) return null;
+
+                result.Add(tval);                
             }
 
-            throw new ArgumentException(firstValue.ToString());
+            return result;
         }
 
         #endregion

+ 23 - 9
tests/SharpGLTF.ThirdParty.Tests/CesiumInstancingTests.cs

@@ -15,24 +15,38 @@ namespace SharpGLTF.ThirdParty
         [ResourcePathFormat("*\\Assets")]
         public void WriteInstancedGlbWithFeatureIds()
         {
-            var settings = SceneBuilderSchema2Settings.WithGpuInstancing;
-            settings.GpuMeshInstancingMinCount = 0;
-
             var modelRoot = ModelRoot.Load(ResourceInfo.From("tree.glb"));
             var meshBuilder = modelRoot.LogicalMeshes.First().ToMeshBuilder();
             var sceneBuilder = new SceneBuilder();
             var quaternion = Quaternion.CreateFromYawPitchRoll(0, 0, 0);
             var scale = Vector3.One;
 
-            sceneBuilder.
-                    AddRigidMesh(meshBuilder, new AffineTransform(scale, quaternion, new Vector3(-10, 0, 10))).
-                    WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":0}"));
-            sceneBuilder.
-                    AddRigidMesh(meshBuilder, new AffineTransform(scale, quaternion, new Vector3(0, 0, 0))).
-                    WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":1}"));
+            sceneBuilder
+                .AddRigidMesh(meshBuilder, new AffineTransform(scale, quaternion, new Vector3(-10, 0, 10)))
+                .WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":0}"));
+            sceneBuilder
+                .AddRigidMesh(meshBuilder, new AffineTransform(scale, quaternion, new Vector3(0, 0, 0)))
+                .WithExtras(JsonNode.Parse("{\"_FEATURE_ID_0\":1}"));
 
+
+            var settings = SceneBuilderSchema2Settings.WithGpuInstancing;
+            settings.GpuMeshInstancingMinCount = 0;
             var instancedModel = sceneBuilder.ToGltf2(settings);
 
+            Assert.AreEqual(1,instancedModel.LogicalNodes.Count);
+
+            var node = instancedModel.LogicalNodes[0];
+            var instances = node.GetExtension<MeshGpuInstancing>();
+            Assert.NotNull(instances);
+
+            Assert.AreEqual(2, instances.Accessors.Count);
+            CollectionAssert.Contains(instances.Accessors.Keys, "TRANSLATION");
+            CollectionAssert.Contains(instances.Accessors.Keys, "_FEATURE_ID_0");
+
+            var ids = instances.Accessors["_FEATURE_ID_0"].AsIndicesArray();
+
+            CollectionAssert.AreEqual(new int[] { 0, 1 }, ids);
+
             var dstPath = AttachmentInfo
                 .From("instanced_model_with_feature_id.glb")
                 .WriteObject(f => instancedModel.SaveGLB(f));

+ 7 - 2
tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs

@@ -542,6 +542,8 @@ namespace SharpGLTF.Scenes
         [Test]
         public void CreateSharedNodeInstanceScene()
         {
+            // Note, this is NOT GPU instancing, it's about SceneBuilder's instance concept.
+
             // SceneBuilder API supports reusing a NodeBuilder in multiple instances with different content.
             // but glTF nodes can only hold one mesh per node, so if we find this case we need to internally
             // add an additional child node to give room to the the extra mesh.
@@ -562,8 +564,11 @@ namespace SharpGLTF.Scenes
             scene.AddRigidMesh(cube, joint0);
             scene.AddRigidMesh(sphere, joint0);
 
-            scene.AttachToCurrentTest("instanced.glb");
-            scene.AttachToCurrentTest("instanced.gltf");
+            var gltf = scene.ToGltf2();
+            Assert.AreEqual(3, gltf.LogicalNodes.Count);
+
+            gltf.AttachToCurrentTest("instanced.glb");
+            gltf.AttachToCurrentTest("instanced.gltf");
         }