diff --git a/README.md b/README.md index f4464cb..99c0ab2 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,11 @@ There is an experimental option to create 3D Tiles 1.1 using GPU instancing: --u This option is currently in development. -The following features should work: Positioning, Rotation (roll, pitch, yaw) and Scaling of instances. +The following features should work: + +- Attribute information from tags using EXT_structural_metadata, EXT_mesh_gpu_instancing and EXT_instance_features. + +- Positioning, Rotation (roll, pitch, yaw) and Scaling of instances. To use this option, the input table should contain columns 'roll', 'pitch' and 'yaw' (column 'rotation' is not used). @@ -217,9 +221,9 @@ alter table instances drop column rotation The columns should be filled with radian angles (0 - 2PI). -The following features are not yet supported when using use_gpu_instancing: +Known limits: -- batch information (EXT_Mesh_Features/EXT_Structural_Metadata) +- When using GPU instancing, the column type 'string' is used (so no support for other types yet). - composite tiles (formerly known as cmpt). When there are multiple models in the input table only the first one is used. @@ -253,6 +257,8 @@ To Visualize in CesiumJS, add references to: ## History +2023-03-28: release 2.7.0: add support for EXT_structural_metadata + 2023-11-08: release 2.6.0: Add support for GPU instancing (experimental), removed option -r RTC_CENTER 2023-10-18: release 2.5.0: Improved root bounding volume calculation, improved batch table handling diff --git a/src/Program.cs b/src/Program.cs index db35e7f..6f79b53 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -3,6 +3,7 @@ using i3dm.export.Cesium; using i3dm.export.extensions; using Npgsql; +using SharpGLTF.Schema2; using ShellProgressBar; using subtree; using System; @@ -97,6 +98,7 @@ static void Main(string[] args) if ((bool)o.UseGpuInstancing) { translate = new Vector3((float)center_wgs84.X, (float)center_wgs84.Y, (float)center_wgs84.Z); + Tiles3DExtensions.RegisterExtensions(); } var options = new ProgressBarOptions diff --git a/src/TileHandler.cs b/src/TileHandler.cs index 8367c9c..d6bb635 100644 --- a/src/TileHandler.cs +++ b/src/TileHandler.cs @@ -1,18 +1,16 @@ using Cmpt.Tile; -using i3dm.export.Cesium; using I3dm.Tile; using Newtonsoft.Json.Linq; -using SharpGLTF.Geometry.VertexTypes; -using SharpGLTF.Geometry; using SharpGLTF.Scenes; using SharpGLTF.Schema2; using SharpGLTF.Transforms; -using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; using Wkx; +using System.Text.Json.Nodes; +using SharpGLTF.Schema2.Tiles3D; namespace i3dm.export; @@ -46,7 +44,12 @@ public static byte[] GetTile(List instances, Format format, Vector3 tr if (useGpuInstancing) { - var bytesGlb = GetGpuGlb(model, modelInstances, translate, UseScaleNonUniform); + foreach (var instance in instances) + { + tags.Add(instance.Tags); + } + + var bytesGlb = GetGpuGlb(model, modelInstances, translate, UseScaleNonUniform, tags); tiles.Add(bytesGlb); } else @@ -94,13 +97,15 @@ private static Vector3 GetPosition(Point p, Vector3 translate) return vec; } - private static byte[] GetGpuGlb(object model, List positions, Vector3 translate, bool UseScaleNonUniform) + private static byte[] GetGpuGlb(object model, List positions, Vector3 translate, bool UseScaleNonUniform, List tags) { var modelRoot = ModelRoot.Load((string)model); var meshBuilder = modelRoot.LogicalMeshes.First().ToMeshBuilder(); var sceneBuilder = new SceneBuilder(); + var pointId = 0; + foreach (var p in positions) { var point = (Point)p.Position; @@ -124,12 +129,50 @@ private static byte[] GetGpuGlb(object model, List positions, Vector3 scale, quaternion, translation); - sceneBuilder.AddRigidMesh(meshBuilder, transformation); + var json = "{\"_FEATURE_ID_0\":" + pointId + "}"; + sceneBuilder.AddRigidMesh(meshBuilder, transformation).WithExtras(JsonNode.Parse(json)); + pointId++; } var settings = SceneBuilderSchema2Settings.WithGpuInstancing; settings.GpuMeshInstancingMinCount = 0; var gltf = sceneBuilder.ToGltf2(settings); + + var rootMetadata = gltf.UseStructuralMetadata(); + var schema = rootMetadata.UseEmbeddedSchema("schema"); + var schemaClass = schema.UseClassMetadata("propertyTable"); + + if(tags.Count > 0) + { + var propertyTable = schemaClass.AddPropertyTable(positions.Count); + + if (tags[0] != null) + { + var properties = TinyJson.GetProperties(tags[0]); + foreach(var property in properties) + { + var values = TinyJson.GetValues(tags, property); + + var nameProperty = schemaClass + .UseProperty(property) + .WithStringType(); + + // todo: use other types than string + var strings = values.Select(s => s.ToString()).ToArray(); + + propertyTable + .UseProperty(nameProperty) + .SetValues(strings); + } + } + + + var featureId0 = new FeatureIDBuilder(positions.Count, 0, propertyTable); + gltf.LogicalNodes[0].AddInstanceFeatureIds(featureId0); + + } + + var bytes = gltf.WriteGLB().Array; return bytes; } diff --git a/src/TinyJson.cs b/src/TinyJson.cs index 342492c..fea3902 100644 --- a/src/TinyJson.cs +++ b/src/TinyJson.cs @@ -42,7 +42,7 @@ public static string ToJson(List tags, List properties) return resres; } - private static List GetValues(List tags, string prop) + public static List GetValues(List tags, string prop) { var res = new List(); foreach (var tag in tags) diff --git a/tests/TileHandlerTests.cs b/tests/TileHandlerTests.cs index 8e3178b..60df70d 100644 --- a/tests/TileHandlerTests.cs +++ b/tests/TileHandlerTests.cs @@ -2,6 +2,8 @@ using I3dm.Tile; using Newtonsoft.Json.Linq; using NUnit.Framework; +using SharpGLTF.Schema2; +using SharpGLTF.Schema2.Tiles3D; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,29 +16,38 @@ public class TileHandlerTests [Test] public void GetGpuTileTest() { + Tiles3DExtensions.RegisterExtensions(); + // arrange var instances = new List(); var instance = new Instance(); instance.Position = new Wkx.Point(1, 2, 0); instance.Scale = 1; instance.Model = "Box.glb"; + instance.Tags = JArray.Parse("[{'id':123},{'name': 'test'}]"); instances.Add(instance); // act - var tile = TileHandler.GetTile(instances, Format.Cesium, Vector3.Zero,useGpuInstancing:true); + var tile = TileHandler.GetTile(instances, Format.Cesium, Vector3.Zero, useGpuInstancing: true); var fileName = Path.Combine(TestContext.CurrentContext.WorkDirectory, "ams_building_multiple_colors.glb"); File.WriteAllBytes(fileName, tile); - var model = SharpGLTF.Schema2.ModelRoot.Load(fileName); - + var model = ModelRoot.Load(fileName); + var extInstanceFeaturesExtension = model.LogicalNodes[0].GetExtension(); + + var extStructuralMetadataExtension = model.GetExtension(); + Assert.That(extStructuralMetadataExtension != null); + + var fid0 = extInstanceFeaturesExtension.FeatureIds[0]; + Assert.That(fid0.FeatureCount == 1); + + // assert // todo: can we read the instance positions from the glb? Assert.That(tile.Length > 0); } - - [Test] public void GetTileTest() { @@ -164,7 +175,7 @@ public void GetTileWithRtcCenterTest() instances.Add(instance1); // act - var tile = TileHandler.GetTile(instances, Format.Mapbox,translate: new Vector3(5, 5, 0)); + var tile = TileHandler.GetTile(instances, Format.Mapbox, translate: new Vector3(5, 5, 0)); var cmpt = CmptReader.Read(new MemoryStream(tile)); var i3dm = I3dmReader.Read(new MemoryStream(cmpt.Tiles.First()));