ExtStructuralMetadataTests.cs 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. using NUnit.Framework;
  2. using SharpGLTF.Geometry;
  3. using SharpGLTF.Geometry.VertexTypes;
  4. using SharpGLTF.Materials;
  5. using SharpGLTF.Scenes;
  6. using SharpGLTF.Validation;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Numerics;
  11. namespace SharpGLTF.Schema2.Tiles3D
  12. {
  13. using VBTexture1 = VertexBuilder<VertexPosition, VertexTexture1, VertexEmpty>;
  14. [Category("Toolkit.Scenes")]
  15. public class ExtStructuralMetadataTests
  16. {
  17. [SetUp]
  18. public void SetUp()
  19. {
  20. Tiles3DExtensions.RegisterExtensions();
  21. }
  22. // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/structuralMetadata
  23. [Test(Description = "Reads glTF's with EXT_Structural_Metadata")]
  24. [TestCase("ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf", typeof(ModelException))]
  25. [TestCase("PropertyAttributesClassPropertyArray.gltf", typeof(ModelException))]
  26. [TestCase("PropertyAttributesClassPropertyInvalidComponentType.gltf", typeof(ModelException))]
  27. [TestCase("PropertyAttributesClassPropertyInvalidEnumValueType.gltf", typeof(ModelException))]
  28. // Todo: Minmax [TestCase("PropertyAttributesClassPropertyMaxNotInRange.gltf", typeof(ModelException))]
  29. // Todo: Minmax [TestCase("PropertyAttributesClassPropertyMinNotInRange.gltf", typeof(ModelException))]
  30. [TestCase("PropertyAttributesClassPropertyString.gltf", typeof(ModelException))]
  31. [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementType.gltf", typeof(InvalidOperationException))]
  32. [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementValue.gltf", typeof(LinkException))]
  33. [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidLength.gltf", typeof(SchemaException))]
  34. [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidType.gltf", typeof(SchemaException))]
  35. [TestCase("PropertyAttributesPropertyAttributePropertyInvalidAttribute.gltf", typeof(ModelException))]
  36. // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf", typeof(ModelException))]
  37. // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf", typeof(ModelException))]
  38. // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMinMismatch.gltf", typeof(ModelException))]
  39. // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf", typeof(ModelException))]
  40. // todo minmax with texture [TestCase("PropertyTextureClassPropertyMaxNotInRange.gltf", typeof(ModelException))]
  41. // todo minmax with texture [TestCase("PropertyTextureClassPropertyMinNotInRange.gltf", typeof(ModelException))]
  42. // todo minmax with texture [TestCase("PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf", typeof(ModelException))]
  43. // todo minmax with texture [TestCase("PropertyTextureEnumsInvalidEnumValue.gltf", typeof(ModelException))]
  44. [TestCase("PropertyTextureInvalidPropertyTypeA.gltf", typeof(ModelException))]
  45. [TestCase("PropertyTextureInvalidPropertyTypeB.gltf", typeof(ModelException))]
  46. [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidElementType.gltf", typeof(InvalidOperationException))]
  47. [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidElementValue.gltf", typeof(LinkException))]
  48. [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidLength.gltf", typeof(SchemaException))]
  49. [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidType.gltf", typeof(SchemaException))]
  50. [TestCase("PropertyTextureMeshPrimitivePropertyTextureTexCoordInvalidValue.gltf", typeof(ModelException))]
  51. [TestCase("PropertyTexturePropertyChannelsSizeMismatch.gltf", typeof(ModelException))]
  52. [TestCase("PropertyTexturePropertyIndexInvalidType.gltf", typeof(InvalidOperationException))]
  53. [TestCase("PropertyTexturePropertyIndexInvalidValue.gltf", typeof(LinkException))]
  54. // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMaxMismatch.gltf", typeof(ModelException))]
  55. // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf", typeof(ModelException))]
  56. // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMinMismatch.gltf", typeof(ModelException))]
  57. // todo minmax with texture[TestCase("PropertyTexturePropertyTexturePropertyMinNotInRange.gltf", typeof(ModelException))]
  58. [TestCase("StructuralMetadataMissingSchema.gltf", typeof(ModelException))]
  59. [TestCase("StructuralMetadataSchemaAndSchemaUri.gltf", typeof(ModelException))]
  60. [TestCase("ValidMultipleClasses.gltf", null)]
  61. [TestCase("ValidPropertyAttributes.gltf", null)]
  62. [TestCase("ValidPropertyTexture.gltf", null)]
  63. [TestCase("ValidPropertyTextureEnums.gltf", null)]
  64. public void ReadExtStructuralMetadata(string file, Type exception = null)
  65. {
  66. var fileName = ResourceInfo.From($"structuralMetadata/{file}");
  67. if (exception != null)
  68. {
  69. Assert.Throws(exception, delegate { ModelRoot.Load(fileName); });
  70. }
  71. else
  72. {
  73. var model = ModelRoot.Load(fileName);
  74. var structuralMetadataExtension = model.GetExtension<EXTStructuralMetadataRoot>();
  75. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  76. model.ValidateContent(ctx.GetContext());
  77. }
  78. }
  79. [Test(Description = "MinimalMetadataAttributeSample")]
  80. public void MinimalMetadataAttributeSample()
  81. {
  82. TestContext.CurrentContext.AttachGltfValidatorLinks();
  83. int featureId = 0;
  84. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  85. var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
  86. var prim = mesh.UsePrimitive(material);
  87. var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
  88. var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
  89. var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
  90. prim.AddTriangle(vt0, vt1, vt2);
  91. var scene = new SceneBuilder();
  92. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  93. var model = scene.ToGltf2();
  94. var rootMetadata = model.UseStructuralMetadata();
  95. var schema = rootMetadata.UseEmbeddedSchema("schema_001");
  96. var schemaClass = schema.UseClassMetadata("triangles");
  97. var nameProperty = schemaClass
  98. .UseProperty("name")
  99. .WithStringType();
  100. var propertyTable = schemaClass.AddPropertyTable(1);
  101. propertyTable
  102. .UseProperty(nameProperty)
  103. .SetValues("this is featureId0");
  104. foreach (var primitive in model.LogicalMeshes[0].Primitives)
  105. {
  106. var featureIdAttribute = new FeatureIDBuilder(1, 0, propertyTable);
  107. primitive.AddMeshFeatureIds(featureIdAttribute);
  108. }
  109. // create files
  110. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  111. model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.glb");
  112. model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.gltf");
  113. model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.plotly");
  114. }
  115. [Test(Description = "TestWith2PrimitivesAndMetadata")]
  116. public void MultiplePrimitivesAndMetadata()
  117. {
  118. TestContext.CurrentContext.AttachGltfValidatorLinks();
  119. int featureId = 0;
  120. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  121. var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
  122. var prim = mesh.UsePrimitive(material);
  123. var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
  124. var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
  125. var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
  126. prim.AddTriangle(vt0, vt1, vt2);
  127. // featureId = 1 and 2 (other material)
  128. var material2 = new MaterialBuilder()
  129. .WithDoubleSide(true)
  130. .WithMetallicRoughnessShader()
  131. .WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, new Vector4(1, 0, 1, 1));
  132. var prim2 = mesh.UsePrimitive(material2);
  133. featureId = 1;
  134. var vt3 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 0, 0), new Vector3(0, 0, 1), featureId);
  135. var vt4 = VertexBuilder.GetVertexWithFeatureId(new Vector3(3, 0, 0), new Vector3(0, 0, 1), featureId);
  136. var vt5 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 1, 0), new Vector3(0, 0, 1), featureId);
  137. prim2.AddTriangle(vt3, vt4, vt5);
  138. featureId = 2;
  139. var vt6 = VertexBuilder.GetVertexWithFeatureId(new Vector3(4, 0, 0), new Vector3(0, 0, 1), featureId);
  140. var vt7 = VertexBuilder.GetVertexWithFeatureId(new Vector3(5, 0, 0), new Vector3(0, 0, 1), featureId);
  141. var vt8 = VertexBuilder.GetVertexWithFeatureId(new Vector3(4, 1, 0), new Vector3(0, 0, 1), featureId);
  142. prim2.AddTriangle(vt6, vt7, vt8);
  143. var scene = new SceneBuilder();
  144. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  145. var model = scene.ToGltf2();
  146. var rootMetadata = model.UseStructuralMetadata();
  147. var schema = rootMetadata.UseEmbeddedSchema("schema_001");
  148. schema.Name = "schema 001";
  149. schema.Description = "an example schema";
  150. schema.Version = "3.5.1";
  151. var trianglesClass = schema
  152. .UseClassMetadata("triangles")
  153. .WithName("Triangle");
  154. var nameProperty = trianglesClass
  155. .UseProperty("name")
  156. .WithStringType();
  157. var isTriangle = trianglesClass
  158. .UseProperty("IsTriangle")
  159. .WithBooleanType();
  160. var propertyTable = trianglesClass
  161. .AddPropertyTable(3, "PropertyTable");
  162. propertyTable
  163. .UseProperty(nameProperty)
  164. .SetValues("this is featureId0", "this is featureId1", "this is featureId2");
  165. propertyTable
  166. .UseProperty(isTriangle)
  167. .SetValues(false, true, false);
  168. foreach (var primitive in model.LogicalMeshes[0].Primitives)
  169. {
  170. var triangles = primitive.EvaluateTriangles().Count();
  171. var featureIdAttribute = new FeatureIDBuilder(triangles, 0, propertyTable);
  172. primitive.AddMeshFeatureIds(featureIdAttribute);
  173. }
  174. // create files
  175. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  176. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.glb");
  177. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.gltf");
  178. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.plotly");
  179. }
  180. [Test(Description = "First test with ext_structural_metadata")]
  181. // This test creates a simple triangle (featureId = 0) with ext_structural_metadata (4 tree attributes like
  182. // species (Enumeration), age (Scalar), height (Scalar) and diameter (Scalar) and a property table.
  183. // following the structure described in https://github.com/CesiumGS/glTF/tree/proposal-EXT_structural_metadata/extensions/2.0/Vendor/EXT_structural_metadata
  184. public void TriangleWithMetadataTest()
  185. {
  186. TestContext.CurrentContext.AttachGltfValidatorLinks();
  187. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  188. var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
  189. var prim = mesh.UsePrimitive(material);
  190. var features = new List<int>() { 0, 1 };
  191. var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), features[0]);
  192. var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), features[0]);
  193. var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), features[0]);
  194. prim.AddTriangle(vt0, vt1, vt2);
  195. var vt3 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 0, 0), new Vector3(0, 0, 1), features[1]);
  196. var vt4 = VertexBuilder.GetVertexWithFeatureId(new Vector3(3, 0, 0), new Vector3(0, 0, 1), features[1]);
  197. var vt5 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 1, 0), new Vector3(0, 0, 1), features[1]);
  198. prim.AddTriangle(vt3, vt4, vt5);
  199. var scene = new SceneBuilder();
  200. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  201. var model = scene.ToGltf2();
  202. var rootMetadata = model.UseStructuralMetadata();
  203. var schema = rootMetadata.UseEmbeddedSchema("schema_001");
  204. schema.Name = "schema 001";
  205. schema.Description = "an example schema";
  206. schema.Version = "3.5.1";
  207. var speciesEnum = schema.UseEnumMetadata("speciesEnum", ("Unspecified", 0), ("Oak", 1), ("Pine", 2), ("Maple",3));
  208. speciesEnum.Name = "Species";
  209. speciesEnum.Description = "An example enum for tree species.";
  210. var treeClass = schema
  211. .UseClassMetadata("tree")
  212. .WithName("Tree")
  213. .WithDescription("Woody, perennial plant.");
  214. // species property
  215. var speciesProperty = treeClass
  216. .UseProperty("species")
  217. .WithDescription("Type of tree.")
  218. .WithEnumeration(speciesEnum)
  219. .WithRequired(true);
  220. // age property
  221. var ageProperty = treeClass
  222. .UseProperty("age")
  223. .WithDescription("The age of the tree, in years")
  224. .WithUInt32Type()
  225. .WithRequired(true);
  226. // Height property
  227. var heightProperty = treeClass
  228. .UseProperty("height")
  229. .WithDescription("Height of tree measured from ground level, in meters");
  230. heightProperty.WithFloat32Type();
  231. // Diameter property
  232. var diameterProperty = treeClass
  233. .UseProperty("diameter")
  234. .WithDescription("Diameter at trunk base, in meters.");
  235. diameterProperty.WithFloat32Type();
  236. var propertyTable = treeClass
  237. .AddPropertyTable(features.Count, "PropertyTable");
  238. propertyTable
  239. .UseProperty(ageProperty)
  240. .SetValues((uint)100, (uint)101);
  241. propertyTable
  242. .UseProperty(speciesProperty)
  243. .SetValues((short)0, (short)3);
  244. propertyTable.UseProperty(heightProperty)
  245. .SetValues(10.0f, 11f);
  246. propertyTable.UseProperty(diameterProperty)
  247. .SetValues(1.5f, 2f);
  248. // Set the FeatureIds
  249. var cnt = propertyTable.Count;
  250. var featureIdAttribute = new FeatureIDBuilder(2, 0, propertyTable);
  251. model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureIdAttribute);
  252. // create files
  253. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  254. model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.glb");
  255. model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.gltf");
  256. model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.plotly");
  257. }
  258. [Test(Description = "ext_structural_metadata with FeatureId Texture and Property Table")]
  259. // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdTextureAndPropertyTable
  260. public void FeatureIdTextureAndPropertytableTest()
  261. {
  262. TestContext.CurrentContext.AttachGltfValidatorLinks();
  263. var img0 = "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAIvklEQVR42u3csW5URxsG4BHBRRoklxROEQlSRCJCKShoXFJZiDSpQEqX2pYii8ZVlDZF7oNcAAURDREdpCEXQKoIlAKFEE3O4s0KoV17zxm8Z+Z8j6zvj6Nfj7Q663k968y8aXd3NxtjYk6a/U9OafDwPN+uFwA8LwA8QJ4XAO/Mw26+6Garm6vd/NbzBfA8X79fGQCXuvll/v1P3XzZ8wXwPF+/X+sjwL/zJBm6BeF5vk6/VgC8nG8nhr4Anufr9GsFwA/d/FzwAnier9OfGgC/d/NdwV8heZ6v158YAH908203/wx8ATzP1+1XBsDsL4hfdfNq4H+H5Hm+fr8yAD6Z/Z/vTZ8XwPN8/d5JQJ53EtAD5HkB4AHyfLwAMMboA5CgPO8jgAfI8wLAA+R5fQDuU/O8PgD3qXleH4D71DyvD8B9ap7XB+A+Nc/rA+B5Xh8Az/P6AHie1wfA87w+AJ7nHQXmeV4A8DyvD8AYow+A53kfAXieFwA8z+sD4HleHwDP8/oAeJ7XB8DzvD4Anuf1AfA8rw+A53l9ADzP6wPgeV4fAM/zjgLzPC8AeJ7XB2CMOaEPIBV88TzfrhcAPC8APECeFwDvfj3p5lI3W91c7eZhzxfA83z1fnUA3O7mx/n333fzdc8XwPN89X51AHzazd/z7//s5vOeL4Dn+er96gD4+JR/P+0F8DxfvV8dAOm9f9/q+QJ4nq/e2wHwvB3Akq/Punk5//6v+V8U+7wAnuer96sD4Jv5Xw///yvi7Z4vgOf56v3qAPh1/pfEj+bp8aTnC+B5vnrvJCDPOwnoAfK8APAAeT5eABhj9AFIUJ73EcAD5HkB4AHyfOAAcJ+a5/UBLE4SuU/N85Pz+gB4PrB3G5DnA3t9ADwf2NsB8LwdwJIv96l5fvJeHwDPB/b6AHg+sHcSkOedBPQAeV4AeIA8Hy8AjDH6AMZLsJQHD+83IN/6RwABIAB4ASAABABfSwBs8j7zkh/sK1dyfvw459evc370KOfLl/stoFB+7PePb9bX0Qew5Af76dOcb906/v7OnePF0GcBhfJjv398s76OPoA1trqz34QlW+hJ+7HfP75ZX8dtwBN+8M+dy/nu3Zzv3Ru2gEL4sd8/vllfRx/Aih/+8+dzfvEi5zdvcr55s/8CCuPHfv/4Zn31O4DZ3LiR8/Pnw7fQk/d+A/IffAewyfvM/gbw4f8G4D4830wfwJIf7GfPjv9T2Oz769dzvn+/3wIK5cd+//hmfR19AEt+sK9dO/5PYbPffA8e5HzxYr8FFMqP/f7xzXonAZ0E5J0EFAACgBcAAkAA8PECwBijD8AOwA6A9xFAAAgAXgAIAAHABw4AfQD6AHh9AGkT95n1AegD4Efx+gD0AfCBvT4AfQC824Bp3PvM+gD0AfCjeH0A+gB4O4A07n1mfwPQB8CP4vUB6APgA3t9APoA+MDeSUAnAXknAQWAAOAFgAAQAHy8ADDG6AOwA7AD4H0EEAACgBcAAkAA8IEDQB+APgBeH0DaxH1mfQD6APhRvD4AfQB8YK8PQB8A7zZgGvc+sz4AfQD8KF4fgD4A3g4gjXuf2d8A9AHwo3h9APoA+MBeH4A+AD6wdxLQSUDeSUABIAB4ASAABAAfLwCMMfoAJCjP+wjgAfK8APAAeT5wALhPzfP6ABYnidyn5vnJ+eQ+Nc/H9cltKp6P65P71Dwf19sB8LwdwJIv96l5fvI+uU/N83F9cp+a5+N6JwF53klAD5DnBYAHyPPxAsAYow9AgvK8jwAeIM8LAA+Q5wMHgPvUPK8PYHGSyH1qnp+c1wfA84G924A8H9jrA+D5wN4OgOftAJZ8uU/N85P3+gB4PrDXB8Dzgb2TgDzvJKAHyPMCwAPk+XgBYIzRByBB+UH+6Oho8NTgfQSwAHgBIAAsAF4ACIDjL/ep+TX9qsV1eHiYt7e3By/gTfnI758+AL7YL1tYBwcHeWdn5+2llCELeJM+8vunD4Av9ssW1oULF/Le3t7gBbxJH/n9cxuQL/bLFtb+/v7bfw5dwJv0kd8/fQB8sT9pgQ1dwJv0kd8/OwD+THYAzQeAPoDkPjW/lp9kAOgDSO5T82v5SQaAPoDkPjW/lp9kAOgDcBKOdxLQUWALgBcAAsAC4AXARAPAGKMPwG9A3g7ARwALgBcAAsAC4AVA4ABwH57XB6APYHGSyH14vkcA6ANI+gD4GF4fQLvebUC+2OsDaNfrA+CLvT6Adr0dAH8mOwB9AK3vANyH5/UBTP790wfAF3t9AO16fQB8sdcH0K53EpB3EtBJQAuAFwACwALgBUC8ADDG6APwG5C3A/ARwALgBYAAsAB4ARA4ANyH5/UB6ANYnCRyH57vEQD6AJI+AD6G1wfQrncbkC/2+gDa9foA+GKvD6BdbwfAn8kOQB9A6zsA9+F5fQCTf//0AfDFXh9Au14fAF/s9QG0650E5J0EdBLQAuAFgACwAHgBEC8AjDH6APwG5O0AfASwAHgBIAAsAF4ABA4A9+F5fQD6ABYnidyH53sEgD6ApA+Aj+H1AbTr3Qbki70+gHa9PgC+2OsDaNfbAfBnsgPQB9D6DsB9eF4fwOTfP30AfLHXB9Cu1wfAF3t9AO16JwF5JwGdBLQAeAEgACwAXgDECwBjjD4AvwF5OwAfASwAXgAIAAuAFwCBA8B9eF4fgD6AxUki9+H5HgGgDyDpA+BjeH0A7Xq3Aflirw+gXa8PgC/2+gDa9XYA/JnsAPQBtL4DcB+e1wcw+fdPHwBf7PUBtOv1AfDFXh9Au95JQN5JQCcBLQBeAAgAC4AXAPECwBijD8BvQN4OwEcAC4AXAALAAuAFQOAAcB+e1wegD2Bxksh9eL5HAOgDSPoA+BheH0C73m1AvtjrA2jX6wPgi70+gHa9HQB/JjsAfQCt7wDch+f1AUz+/dMHwBd7fQDten0AfLHXB9CudxKQdxLQSUALgBcAAsAC4AVAqPfvPyVxz6xUBN7bAAAAAElFTkSuQmCC";
  264. var imageBytes0 = Convert.FromBase64String(img0);
  265. var imageBuilder0 = ImageBuilder.From(imageBytes0);
  266. var img1 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAJElEQVR42mNgYmBgoAQzDLwBgwcwY8FDzIDBDRiR8KgBNDAAAOKBAKByX2jMAAAAAElFTkSuQmCC";
  267. var imageBytes1 = Convert.FromBase64String(img1);
  268. var imageBuilder1 = ImageBuilder.From(imageBytes1);
  269. var material = MaterialBuilder
  270. .CreateDefault()
  271. .WithMetallicRoughnessShader()
  272. .WithBaseColor(imageBuilder0, new Vector4(1, 1, 1, 1))
  273. .WithDoubleSide(true)
  274. .WithAlpha(Materials.AlphaMode.OPAQUE)
  275. .WithMetallicRoughness(0, 1)
  276. .WithMetallicRoughness(imageBuilder1);
  277. var mesh = VBTexture1.CreateCompatibleMesh("mesh");
  278. var prim = mesh.UsePrimitive(material);
  279. prim.AddTriangle(
  280. new VBTexture1(new VertexPosition(0, 0, 0), new Vector2(0, 1)),
  281. new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
  282. new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
  283. prim.AddTriangle(
  284. new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
  285. new VBTexture1(new VertexPosition(1, 1, 0), new Vector2(1, 0)),
  286. new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
  287. var scene = new SceneBuilder();
  288. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  289. var model = scene.ToGltf2();
  290. var rootMetadata = model.UseStructuralMetadata();
  291. var schema = rootMetadata.UseEmbeddedSchema("FeatureIdTextureAndPropertyTableSchema");
  292. // define schema
  293. var buildingComponentsClass = schema
  294. .UseClassMetadata("buildingComponents")
  295. .WithName("Building components")
  296. .WithDescription("The components of a building.");
  297. var componentProp = buildingComponentsClass
  298. .UseProperty("component")
  299. .WithName("Component")
  300. .WithStringType();
  301. var yearProp = buildingComponentsClass
  302. .UseProperty("yearBuilt")
  303. .WithName("Year built")
  304. .WithInt16Type();
  305. var propertyTable = buildingComponentsClass
  306. .AddPropertyTable(4, "Example property table");
  307. propertyTable
  308. .UseProperty(componentProp)
  309. .SetValues("Wall", "Door", "Roof", "Window");
  310. propertyTable
  311. .UseProperty(yearProp)
  312. .SetValues((short)1960, (short)1996, (short)1985, (short)2002);
  313. // Set the FeatureIds, pointing to the red channel of the texture
  314. var featureId = new FeatureIDBuilder(4, null, propertyTable);
  315. var primitive = model.LogicalMeshes[0].Primitives[0];
  316. primitive.AddMeshFeatureIds(featureId);
  317. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  318. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.glb");
  319. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.gltf");
  320. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.plotly");
  321. }
  322. [Test(Description = "ext_structural_metadata with simple property texture")]
  323. // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/SimplePropertyTexture
  324. public void SimplePropertyTextureTest()
  325. {
  326. TestContext.CurrentContext.AttachGltfValidatorLinks();
  327. var img0 = "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";
  328. var imageBytes0 = Convert.FromBase64String(img0);
  329. var imageBuilder0 = ImageBuilder.From(imageBytes0);
  330. var img1 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABLUlEQVR42mVSSxbDIAh0GzUxKZrmCF3n/oerIx9pupgHIswAGtblE7bIKN0vqSOyXSOjPLAtktv9sCFxmcXj7EgsFj8zN00yYxrBZZJBRYk2LdC4WCDUfAdab7bpDm1lCyBW+7lpDnyNS34gcTQRltTPbAeEdFjcSQ0X9EOhGPYjhgLA7xh3kjxEEpMj1qQj7iAzAYoPELzYtuwK02M06WywAFDfX1MdJEoOtSZ7Allz1mYmWZDNL0pNF6ezu9jsQJUcNK7qzbWvMdSYQ8Jo7KKK8/uo4dxreHe0/HgF2/IqBen/za+Di69Sf8cZz5jmk+hcuhdd2tWLz8IE5MbFnRWT+yyU5vZJRtAOqlvq6MDeOrstu0UidsoO0Ak9xGwE+67+34salNEBSCxX7Bexg0rbq6TFvwAAAABJRU5ErkJggg==";
  331. var imageBytes1 = Convert.FromBase64String(img1);
  332. var imageBuilder1 = ImageBuilder.From(imageBytes1);
  333. var material = MaterialBuilder
  334. .CreateDefault()
  335. .WithMetallicRoughnessShader()
  336. .WithBaseColor(imageBuilder0, new Vector4(1, 1, 1, 1))
  337. .WithDoubleSide(true)
  338. .WithAlpha(Materials.AlphaMode.OPAQUE)
  339. .WithMetallicRoughness(0, 1)
  340. .WithMetallicRoughness(imageBuilder1);
  341. var mesh = VBTexture1.CreateCompatibleMesh("mesh");
  342. var prim = mesh.UsePrimitive(material);
  343. prim.AddTriangle(
  344. new VBTexture1(new VertexPosition(0, 0, 0), new Vector2(0, 1)),
  345. new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
  346. new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
  347. prim.AddTriangle(
  348. new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
  349. new VBTexture1(new VertexPosition(1, 1, 0), new Vector2(1, 0)),
  350. new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
  351. var scene = new SceneBuilder();
  352. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  353. var model = scene.ToGltf2();
  354. // --------------------------------------------------------------
  355. var rootMetadata = model.UseStructuralMetadata();
  356. var schema = rootMetadata.UseEmbeddedSchema("SimplePropertyTextureSchema");
  357. // define schema
  358. var exampleMetadataClass = schema
  359. .UseClassMetadata("buildingComponents")
  360. .WithName("Building properties");
  361. exampleMetadataClass
  362. .UseProperty("insideTemperature")
  363. .WithName("Inside temperature")
  364. .WithUInt8Type();
  365. exampleMetadataClass
  366. .UseProperty("outsideTemperature")
  367. .WithName("Outside temperature")
  368. .WithUInt8Type();
  369. exampleMetadataClass
  370. .UseProperty("insulation")
  371. .WithName("Insulation Thickness")
  372. .WithUInt8Type()
  373. .WithNormalized(true);
  374. // define texture property
  375. var buildingPropertyTexture = exampleMetadataClass.AddPropertyTexture();
  376. buildingPropertyTexture.CreateProperty("insideTemperature", model.LogicalTextures[1], new int[] {0});
  377. buildingPropertyTexture.CreateProperty("outsideTemperature", model.LogicalTextures[1], new int[] {1});
  378. buildingPropertyTexture.CreateProperty("insulation", model.LogicalTextures[1], new int[] {2});
  379. // assign to primitive
  380. var primitive = model.LogicalMeshes[0].Primitives[0];
  381. primitive.AddPropertyTexture(buildingPropertyTexture);
  382. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  383. model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.glb");
  384. model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.gltf");
  385. model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.plotly");
  386. }
  387. [Test(Description = "ext_structural_metadata with Multiple Feature IDs and Properties")]
  388. // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/MultipleFeatureIdsAndProperties
  389. public void MultipleFeatureIdsAndPropertiesTest()
  390. {
  391. TestContext.CurrentContext.AttachGltfValidatorLinks();
  392. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  393. var mesh = new MeshBuilder<VertexPosition, VertexWithFeatureIds, VertexEmpty>("mesh");
  394. var prim = mesh.UsePrimitive(material);
  395. // first triangle has _feature_id_0 = 0 and _feature_id_1 = 1
  396. var vt0 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(0, 0, 0), new Vector3(0, 0, 1), 0, 1);
  397. var vt1 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(1, 0, 0), new Vector3(0, 0, 1), 0, 1);
  398. var vt2 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(0, 1, 0), new Vector3(0, 0, 1), 0, 1);
  399. // second triangle has _feature_id_0 = 1 and _feature_id_1 = 0
  400. var vt3 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(1, 1, 0), new Vector3(0, 0, 1), 1, 0);
  401. var vt4 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(0, 0, 0), new Vector3(0, 0, 1), 1, 0);
  402. var vt5 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(1, 0, 0), new Vector3(0, 0, 1), 1, 0);
  403. prim.AddTriangle(vt0, vt1, vt2);
  404. prim.AddTriangle(vt3, vt4, vt5);
  405. var scene = new SceneBuilder();
  406. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  407. var model = scene.ToGltf2();
  408. // --------------------------------------------------------------
  409. var rootMetadata = model.UseStructuralMetadata();
  410. var schema = rootMetadata.UseEmbeddedSchema("MultipleFeatureIdsAndPropertiesSchema");
  411. // define schema
  412. var exampleMetadataClass = schema
  413. .UseClassMetadata("exampleMetadataClass")
  414. .WithName("Example metadata class")
  415. .WithDescription("An example metadata class");
  416. var vec3Property = exampleMetadataClass
  417. .UseProperty("example_VEC3_FLOAT32")
  418. .WithName("Example VEC3 FLOAT32 property")
  419. .WithDescription("An example property, with type VEC3, with component type FLOAT32")
  420. .WithVector3Type();
  421. var stringProperty = exampleMetadataClass
  422. .UseProperty("example_STRING")
  423. .WithName("Example STRING property")
  424. .WithDescription("An example property, with type STRING")
  425. .WithStringType();
  426. // define table
  427. var examplePropertyTable = exampleMetadataClass.AddPropertyTable(2, "Example property table");
  428. examplePropertyTable
  429. .UseProperty(vec3Property)
  430. .SetValues(new Vector3(3, 3.0999999046325684f, 3.200000047683716f), new Vector3(103, 103.0999999046325684f, 103.200000047683716f));
  431. examplePropertyTable
  432. .UseProperty(stringProperty)
  433. .SetValues("Rain 🌧", "Thunder ⛈");
  434. // assign to primitive
  435. var featureId0 = new FeatureIDBuilder(2, 0, examplePropertyTable);
  436. var featureId1 = new FeatureIDBuilder(2, 1, examplePropertyTable);
  437. model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds( featureId0, featureId1 );
  438. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  439. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.glb");
  440. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.gltf");
  441. model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.plotly");
  442. }
  443. // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdAttributeAndPropertyTable
  444. [Test(Description = "ext_structural_metadata with FeatureIdAttributeAndPropertyTable")]
  445. public void FeatureIdAndPropertyTableTest()
  446. {
  447. TestContext.CurrentContext.AttachGltfValidatorLinks();
  448. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  449. var mesh = new MeshBuilder<VertexPosition, VertexWithFeatureId, VertexEmpty>("mesh");
  450. var prim = mesh.UsePrimitive(material);
  451. // All the vertices in the triangle have the same feature ID
  452. var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(-1, 0, 0), new Vector3(0, 0, 1), 0);
  453. var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), 0);
  454. var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), 0);
  455. prim.AddTriangle(vt0, vt1, vt2);
  456. var scene = new SceneBuilder();
  457. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  458. var model = scene.ToGltf2();
  459. // --------------------------------------------------------------
  460. var rootMetadata = model.UseStructuralMetadata();
  461. var schema = rootMetadata.UseEmbeddedSchema("FeatureIdAttributeAndPropertyTableSchema");
  462. // define schema
  463. var exampleMetadataClass = schema
  464. .UseClassMetadata("exampleMetadataClass")
  465. .WithName("Example metadata class")
  466. .WithDescription("An example metadata class");
  467. var vector3Property = exampleMetadataClass
  468. .UseProperty("example_VEC3_FLOAT32")
  469. .WithName("Example VEC3 FLOAT32 property")
  470. .WithDescription("An example property, with type VEC3, with component type FLOAT32")
  471. .WithVector3Type();
  472. var matrix4x4Property = exampleMetadataClass
  473. .UseProperty("example_MAT4_FLOAT32")
  474. .WithName("Example MAT4 FLOAT32 property")
  475. .WithDescription("An example property, with type MAT4, with component type FLOAT32")
  476. .WithMatrix4x4Type();
  477. // define table
  478. var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
  479. examplePropertyTable
  480. .UseProperty(vector3Property)
  481. .SetValues(new Vector3(3, 3.0999999046325684f, 3.200000047683716f));
  482. examplePropertyTable
  483. .UseProperty(matrix4x4Property)
  484. .SetValues(Matrix4x4.Identity);
  485. // assign to primitive
  486. var featureId = new FeatureIDBuilder(1, 0, examplePropertyTable);
  487. model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId);
  488. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  489. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.glb");
  490. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.gltf");
  491. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.plotly");
  492. }
  493. // sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/ComplexTypes/
  494. [Test(Description = "ext_structural_metadata with complex types")]
  495. public void ComplexTypesTest()
  496. {
  497. TestContext.CurrentContext.AttachGltfValidatorLinks();
  498. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  499. var mesh = new MeshBuilder<VertexPosition, VertexWithFeatureId, VertexEmpty>("mesh");
  500. var prim = mesh.UsePrimitive(material);
  501. // All the vertices in the triangle have the same feature ID
  502. var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(-1, 0, 0), new Vector3(0, 0, 1), 0);
  503. var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), 0);
  504. var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), 0);
  505. prim.AddTriangle(vt0, vt1, vt2);
  506. var scene = new SceneBuilder();
  507. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  508. var model = scene.ToGltf2();
  509. // --------------------------------------------------------------
  510. var rootMetadata = model.UseStructuralMetadata();
  511. var schema = rootMetadata.UseEmbeddedSchema("FeatureIdAttributeAndPropertyTableSchema");
  512. // define schema
  513. var exampleMetadataClass = schema
  514. .UseClassMetadata("exampleMetadataClass")
  515. .WithName("Example metadata class A")
  516. .WithDescription("First example metadata class");
  517. // enums
  518. var exampleEnum = schema.UseEnumMetadata("exampleEnumType", ("ExampleEnumValueA", 0), ("ExampleEnumValueB", 1), ("ExampleEnumValueC", 2));
  519. //// class properties
  520. var uint8ArrayProperty = exampleMetadataClass
  521. .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
  522. .WithName("Example variable-length ARRAY normalized INT8 property")
  523. .WithDescription("An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
  524. .WithArrayType(ElementType.SCALAR, DataType.UINT8)
  525. .WithNormalized(false);
  526. var fixedLengthBooleanProperty = exampleMetadataClass
  527. .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
  528. .WithName("Example fixed-length ARRAY BOOLEAN property")
  529. .WithDescription("An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
  530. .WithArrayType(ElementType.BOOLEAN, null, 4)
  531. .WithNormalized(false);
  532. var variableLengthStringArrayProperty = exampleMetadataClass
  533. .UseProperty("example_variable_length_ARRAY_STRING")
  534. .WithName("Example variable-length ARRAY STRING property")
  535. .WithDescription("An example property, with type ARRAY, with component type STRING, and variable length")
  536. .WithArrayType(ElementType.STRING);
  537. var fixed_length_ARRAY_ENUM = exampleMetadataClass
  538. .UseProperty("example_fixed_length_ARRAY_ENUM")
  539. .WithName("Example fixed-length ARRAY ENUM property")
  540. .WithDescription("An example property, with type ARRAY, with component type ENUM, and fixed length")
  541. .WithEnumArrayType(exampleEnum, 2);
  542. var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
  543. var bytes = new List<List<byte>>();
  544. bytes.Add(new List<byte>() { 0, 1, 2, 3, 4, 5, 6, 7 });
  545. examplePropertyTable
  546. .UseProperty(uint8ArrayProperty)
  547. .SetArrayValues(bytes);
  548. var bools = new List<List<bool>>();
  549. bools.Add(new List<bool>() { true, false, true, false });
  550. examplePropertyTable
  551. .UseProperty(fixedLengthBooleanProperty)
  552. .SetArrayValues(bools);
  553. var strings = new List<List<string>>();
  554. strings.Add(["Example string 1", "Example string 2", "Example string 3"]);
  555. examplePropertyTable
  556. .UseProperty(variableLengthStringArrayProperty)
  557. .SetArrayValues(strings);
  558. // Fill property table with enum values
  559. var shorts = new List<List<short>>();
  560. shorts.Add([0, 1]);
  561. examplePropertyTable
  562. .UseProperty(fixed_length_ARRAY_ENUM)
  563. .SetArrayValues(shorts);
  564. // add to primitive
  565. var featureId = new FeatureIDBuilder(1, 0, examplePropertyTable);
  566. model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId);
  567. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  568. model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.glb");
  569. model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.gltf");
  570. model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.plotly");
  571. }
  572. // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/MultipleClasses/
  573. [Test(Description = "ext_structural_metadata with multiple classes")]
  574. public void MultipleClassesTest()
  575. {
  576. var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
  577. var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureIds, VertexEmpty>("mesh");
  578. var prim = mesh.UsePrimitive(material);
  579. // All the vertices in the triangle have the same feature ID
  580. var vt0 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(-10, 0, 0), new Vector3(0, 0, 1), 0, 0);
  581. var vt1 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(10, 0, 0), new Vector3(0, 0, 1), 0, 0);
  582. var vt2 = VertexBuilder.GetVertexWithFeatureIds(new Vector3(0, 10, 0), new Vector3(0, 0, 1), 0, 0);
  583. prim.AddTriangle(vt0, vt1, vt2);
  584. var scene = new SceneBuilder();
  585. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  586. var model = scene.ToGltf2();
  587. var rootMetadata = model.UseStructuralMetadata();
  588. var schema = rootMetadata.UseEmbeddedSchema("MultipleClassesSchema");
  589. // classes
  590. var classA = schema
  591. .UseClassMetadata("exampleMetadataClassA")
  592. .WithName("Example metadata class A")
  593. .WithDescription("First example metadata class");
  594. var classAp0 = classA.UseProperty("example_FLOAT32")
  595. .WithName("Example FLOAT32 property")
  596. .WithDescription("An example property, with component type FLOAT32")
  597. .WithFloat32Type();
  598. var classAp1 = classA.UseProperty("example_INT64")
  599. .WithName("Example INT64 property")
  600. .WithDescription("An example property, with component type INT64")
  601. .WithInt64Type();
  602. var classB = schema.UseClassMetadata("exampleMetadataClassB")
  603. .WithName("Example metadata class B")
  604. .WithDescription("Second example metadata class");
  605. var classBp0 = classB.UseProperty("example_UINT16")
  606. .WithName("Example UINT16 property")
  607. .WithDescription("An example property, with component type UINT16")
  608. .WithUInt16Type();
  609. var classBp1 = classB.UseProperty("example_FLOAT64")
  610. .WithName("Example FLOAT64 property")
  611. .WithDescription("An example property, with component type FLOAT64")
  612. .WithFloat64Type();
  613. // properties
  614. var firstPropertyTable = classA.AddPropertyTable(1, "First example property table");
  615. firstPropertyTable.UseProperty(classAp0).SetValues<float>(100);
  616. firstPropertyTable.UseProperty(classAp1).SetValues<long>(101);
  617. var secondPropertyTable = classB.AddPropertyTable(1, "Second example property table");
  618. secondPropertyTable.UseProperty(classBp0).SetValues<ushort>(102);
  619. secondPropertyTable.UseProperty(classBp1).SetValues<double>(103);
  620. // features
  621. // FeatureID 0: featureCount=1, attribute=0, propertyTable=0
  622. var featureId0 = new FeatureIDBuilder(1, 0, firstPropertyTable);
  623. // FeatureID 1: featureCount=1, attribute=1, prorpertyTable=1
  624. var featureId1 = new FeatureIDBuilder(1, 1, secondPropertyTable);
  625. model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId0, featureId1);
  626. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  627. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.glb");
  628. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.gltf");
  629. model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.plotly");
  630. }
  631. // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/PropertyAttributesPointCloud/
  632. // Note in the sample an external json file (MetadataSchema.json) is used to define the schema, which is not supported
  633. // in this library yet.
  634. // This test uses the same schema but defines it in code instead.
  635. [Test(Description = "ext_structural_metadata with pointcloud and custom attributes")]
  636. public void CreatePointCloudWithCustomAttributesTest()
  637. {
  638. var material = new MaterialBuilder("material1").WithUnlitShader();
  639. var mesh = new MeshBuilder<VertexPosition, VertexPointcloud, VertexEmpty>("mesh");
  640. var pointCloud = mesh.UsePrimitive(material, 1);
  641. var redColor = new Vector4(1f, 0f, 0f, 1f);
  642. var rand = new Random();
  643. for (var x = -10; x < 10; x++)
  644. {
  645. for (var y = -10; y < 10; y++)
  646. {
  647. for (var z = -10; z < 10; z++)
  648. {
  649. // intensity values is based on x-axis values
  650. // classification of points is 0 or 1 (random)
  651. var vt0 = VertexBuilder.GetVertexPointcloud(new Vector3(x, y, z), redColor, x, rand.Next(0, 2));
  652. pointCloud.AddPoint(vt0);
  653. }
  654. }
  655. }
  656. var model = ModelRoot.CreateModel();
  657. model.CreateMeshes(mesh);
  658. // create a scene, a node, and assign the first mesh (the terrain)
  659. model.UseScene("Default")
  660. .CreateNode().WithMesh(model.LogicalMeshes[0]);
  661. // --------------------------------------------------------------
  662. var rootMetadata = model.UseStructuralMetadata();
  663. var schema = rootMetadata.UseEmbeddedSchema();
  664. var classA = schema
  665. .UseClassMetadata("exampleMetadataClass")
  666. .WithName("Example metadata class")
  667. .WithDescription("An example metadata class for property attributes");
  668. classA.UseProperty("intensity")
  669. .WithName("Example intensity property")
  670. .WithDescription("An example property for the intensity, with component type FLOAT32")
  671. .WithFloat32Type();
  672. var speciesEnum = schema.UseEnumMetadata("classificationEnumType", ("MediumVegetation", 0), ("Buildings", 1));
  673. classA
  674. .UseProperty("classification")
  675. .WithName("Example classification property")
  676. .WithDescription("An example property for the classification, with the classificationEnumType")
  677. .WithEnumeration(speciesEnum);
  678. var propertyAttribute = rootMetadata.AddPropertyAttribute(classA);
  679. var intensityAttribute = propertyAttribute.CreateProperty("intensity");
  680. intensityAttribute.Attribute = "_INTENSITY";
  681. var classificationAttribute = propertyAttribute.CreateProperty("classification");
  682. classificationAttribute.Attribute = "_CLASSIFICATION";
  683. var ctx = new ValidationResult(model, ValidationMode.Strict, true);
  684. foreach (var primitive in model.LogicalMeshes[0].Primitives)
  685. {
  686. primitive.AddPropertyAttribute(propertyAttribute);
  687. }
  688. model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.glb");
  689. model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.gltf");
  690. model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.plotly");
  691. }
  692. }
  693. }