Browse Source

add 3d tiles metadata noData support

Bert Temme 1 year ago
parent
commit
0318608eff

+ 134 - 26
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -233,7 +233,6 @@ namespace SharpGLTF.Schema2
                     var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
                     var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
                     Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(Schema.Id, regex), nameof(Schema.Id));
                     Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(Schema.Id, regex), nameof(Schema.Id));
 
 
-
                     foreach (var _class in Schema.Classes)
                     foreach (var _class in Schema.Classes)
                     {
                     {
                         Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(_class.Key, regex), nameof(_class.Key));
                         Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(_class.Key, regex), nameof(_class.Key));
@@ -244,6 +243,17 @@ namespace SharpGLTF.Schema2
                             {
                             {
                                 Guard.MustBeGreaterThanOrEqualTo(property.Value.Count.Value, 2, nameof(property.Value.Count));
                                 Guard.MustBeGreaterThanOrEqualTo(property.Value.Count.Value, 2, nameof(property.Value.Count));
                             }
                             }
+
+                            if (property.Value.Required)
+                            {
+                                Guard.IsTrue(property.Value.NoData == null, nameof(property.Value.NoData), $"The property '{property.Key}' defines a 'noData' value, but is 'required'");
+                            }
+
+                            if(property.Value.Type == ELEMENTTYPE.SCALAR)
+                            {
+                                // check The 'componentType' must be defined for a property with type 'SCALAR'
+                                Guard.IsTrue(property.Value.ComponentType.HasValue, nameof(property.Value.ComponentType), $"The 'componentType' must be defined for a property '{property.Key}' with type 'SCALAR'");
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
@@ -843,13 +853,13 @@ namespace SharpGLTF.Schema2
 
 
             private void CheckScalarTypes<T>(DATATYPE? componentType)
             private void CheckScalarTypes<T>(DATATYPE? componentType)
             {
             {
-                if (componentType == DATATYPE.INT8)
+                if (componentType == DATATYPE.UINT8)
                 {
                 {
-                    Guard.IsTrue(typeof(T) == typeof(sbyte), nameof(T), $"Scalar value type of property {LogicalKey} must be sbyte");
+                    Guard.IsTrue(typeof(T) == typeof(byte), nameof(T), $"Scalar value type of property {LogicalKey} must be byte");
                 }
                 }
-                else if (componentType == DATATYPE.UINT8)
+                else if (componentType == DATATYPE.INT8)
                 {
                 {
-                    Guard.IsTrue(typeof(T) == typeof(byte), nameof(T), $"Scalar value type of property {LogicalKey} must be byte");
+                    Guard.IsTrue(typeof(T) == typeof(sbyte), nameof(T), $"Scalar value type of property {LogicalKey} must be sbyte");
                 }
                 }
                 else if (componentType == DATATYPE.INT16)
                 else if (componentType == DATATYPE.INT16)
                 {
                 {
@@ -1227,6 +1237,7 @@ namespace SharpGLTF.Schema2
             #endregion
             #endregion
 
 
             #region properties
             #region properties
+
             public string Name
             public string Name
             {
             {
                 get => _name;
                 get => _name;
@@ -1263,6 +1274,11 @@ namespace SharpGLTF.Schema2
                 set => _required = value.AsNullable(_requiredDefault);
                 set => _required = value.AsNullable(_requiredDefault);
             }
             }
 
 
+            public JsonNode NoData
+            {
+                get => _noData;
+            }
+
             public bool Normalized
             public bool Normalized
             {
             {
                 get => _normalized ?? _normalizedDefault;
                 get => _normalized ?? _normalizedDefault;
@@ -1325,9 +1341,10 @@ namespace SharpGLTF.Schema2
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithStringType()
+            public StructuralMetadataClassProperty WithStringType(string noData = null)
             {
             {
                 Type = ElementType.STRING;
                 Type = ElementType.STRING;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
@@ -1337,73 +1354,83 @@ namespace SharpGLTF.Schema2
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithUInt8Type()
+            public StructuralMetadataClassProperty WithUInt8Type(byte? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT8;
                 ComponentType = DATATYPE.UINT8;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithInt8Type()
+            public StructuralMetadataClassProperty WithInt8Type(sbyte? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT8;
                 ComponentType = DATATYPE.INT8;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithUInt16Type()
+            public StructuralMetadataClassProperty WithUInt16Type(ushort? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT16;
                 ComponentType = DATATYPE.UINT16;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithInt16Type()
+            public StructuralMetadataClassProperty WithInt16Type(short? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT16;
                 ComponentType = DATATYPE.INT16;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithUInt32Type()
+            public StructuralMetadataClassProperty WithUInt32Type(uint? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT32;
                 ComponentType = DATATYPE.UINT32;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithInt32Type()
+            public StructuralMetadataClassProperty WithInt32Type(int? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT32;
                 ComponentType = DATATYPE.INT32;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithUInt64Type()
+            public StructuralMetadataClassProperty WithUInt64Type(ulong? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT64;
                 ComponentType = DATATYPE.UINT64;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithInt64Type()
+            public StructuralMetadataClassProperty WithInt64Type(long? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT64;
                 ComponentType = DATATYPE.INT64;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithFloat32Type()
+            public StructuralMetadataClassProperty WithFloat32Type(float? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.FLOAT32;
                 ComponentType = DATATYPE.FLOAT32;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithFloat64Type()
+            public StructuralMetadataClassProperty WithFloat64Type(double? noData = null)
             {
             {
                 Type = ELEMENTTYPE.SCALAR;
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.FLOAT64;
                 ComponentType = DATATYPE.FLOAT64;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
@@ -1429,16 +1456,95 @@ namespace SharpGLTF.Schema2
                 return this;
                 return this;
             }
             }
 
 
+            public StructuralMetadataClassProperty WithBooleanArrayType(int? count = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.BOOLEAN, null, count);
+                return property;
+            }
+
+            public StructuralMetadataClassProperty WithUInt8ArrayType(int? count = null, byte? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.UINT8, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
 
 
-            //public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null)
-            //{
-            //    Type = etype;
-            //    ComponentType = ctype;
-            //    Array = false;
-            //    return this;
-            //}
+            public StructuralMetadataClassProperty WithInt8ArrayType(int? count = null, sbyte? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.INT8, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+
+            public StructuralMetadataClassProperty WithInt16ArrayType(int? count = null, short? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.INT16, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+
+            public StructuralMetadataClassProperty WithUInt16ArrayType(int? count = null, ushort? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.UINT16, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithInt32ArrayType(int? count = null, int? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.INT32, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithUInt32ArrayType(int? count = null, uint? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.UINT32, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithInt64ArrayType(int? count = null, long? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.INT64, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithUInt64ArrayType(int? count = null, ulong? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.UINT64, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithFloat32ArrayType(int? count = null, float? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.FLOAT32, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+            public StructuralMetadataClassProperty WithFloat64ArrayType(int? count = null, double? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.SCALAR, DATATYPE.FLOAT64, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
+
+            public StructuralMetadataClassProperty WithVector3ArrayType(int? count = null, Vector3? noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.VEC3, DATATYPE.FLOAT32, count);
+                // todo: how to set noData for vector3?
+                return property;
+            }
+            public StructuralMetadataClassProperty WithMatrix4x4ArrayType(int? count = null)
+            {
+                return WithArrayType(ELEMENTTYPE.MAT4, DATATYPE.FLOAT32, count);
+            }
+
+            public StructuralMetadataClassProperty WithStringArrayType(int? count = null, string noData = null)
+            {
+                var property = WithArrayType(ELEMENTTYPE.STRING, null, count);
+                if (noData != null) property._noData = noData;
+                return property;
+            }
 
 
-            public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, int? count = null)
+            private StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, int? count = null)
             {
             {
                 Type = etype;
                 Type = etype;
                 ComponentType = ctype;
                 ComponentType = ctype;
@@ -1447,19 +1553,21 @@ namespace SharpGLTF.Schema2
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumeration, int? count = null)
+            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumeration, int? count = null, string noData = null)
             {
             {
                 Type = ELEMENTTYPE.ENUM;
                 Type = ELEMENTTYPE.ENUM;
                 _enumType = enumeration.LogicalKey;
                 _enumType = enumeration.LogicalKey;
                 Array = true;
                 Array = true;
                 Count = count;
                 Count = count;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithEnumeration(StructuralMetadataEnum enumeration)
+            public StructuralMetadataClassProperty WithEnumeration(StructuralMetadataEnum enumeration, string noData = null)
             {
             {
                 Type = ELEMENTTYPE.ENUM;
                 Type = ELEMENTTYPE.ENUM;
                 _enumType = enumeration.LogicalKey;
                 _enumType = enumeration.LogicalKey;
+                if (noData != null) _noData = noData;
                 return this;
                 return this;
             }
             }
 
 

+ 173 - 3
tests/SharpGLTF.Ext.3DTiles.Tests/ExtStructuralMetadataTests.cs

@@ -82,6 +82,176 @@ namespace SharpGLTF.Schema2.Tiles3D
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// In this test a single triangle is defined, it has attributes defined for all types with a noData value, 
+        /// but the values are set to the noData value. In CesiumJS the triangle is rendered but the 
+        /// attritutes are not shown (because noData).
+        /// </summary>
+        [Test(Description = "MetadataAndNullValuesAttributeSample")]
+        public void MetadataNullValuesAttributeSample()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            int featureId = 0;
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim.AddTriangle(vt0, vt1, vt2);
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("schema_001");
+
+            var schemaClass = schema.UseClassMetadata("triangles");
+
+            var speciesEnum = schema.UseEnumMetadata("speciesEnum", ("Unspecified", 0), ("Oak", 1), ("Pine", 2), ("Maple",3));
+            speciesEnum.Name = "Species";
+            speciesEnum.Description = "An example enum for tree species.";
+
+            var descriptionProperty = schemaClass
+                    .UseProperty("description")
+                    .WithStringType();
+
+            var uint8Property = schemaClass
+                .UseProperty("uint8")
+                .WithUInt8Type(byte.MinValue);
+
+            var int8Property = schemaClass
+                .UseProperty("int8")
+                .WithInt8Type(sbyte.MinValue);
+
+            var int16Property = schemaClass
+                .UseProperty("int16")
+                .WithInt16Type(short.MinValue);
+
+            var uint16Property = schemaClass
+                .UseProperty("uint16")
+                .WithUInt16Type(ushort.MinValue);
+
+            var int32Property = schemaClass
+                .UseProperty("int32")
+                .WithInt32Type(int.MinValue);
+
+            var uint32Property = schemaClass
+                .UseProperty("uint32")
+                .WithUInt32Type(uint.MinValue);
+
+            var int64Property = schemaClass
+                .UseProperty("int64")
+                .WithInt64Type(long.MinValue);
+
+            var uint64Property = schemaClass
+                .UseProperty("uint64")
+                .WithUInt64Type(ulong.MinValue);
+
+            // when using float.MinValue there is an error in the validator: ""The value has type FLOAT32 and must be in [-3.4028234663852886e+38,3.4028234663852886e+38], but is -3.4028235e+38"
+            // And the noData value is shown in CesiumJS. Therefore we use -10.0f here.
+            var float32Property = schemaClass
+                .UseProperty("float32")
+                .WithFloat32Type(-10.0f);
+
+            var float64Property = schemaClass
+                .UseProperty("float64")
+                .WithFloat64Type(double.MinValue);
+
+            var vector3Property = schemaClass
+                .UseProperty("vector3")
+                .WithVector3Type();
+
+            var stringProperty = schemaClass
+                .UseProperty("string")
+                .WithStringType("noData");
+
+            var speciesProperty = schemaClass
+                .UseProperty("species")
+                .WithDescription("Type of tree.")
+                .WithEnumeration(speciesEnum, "Unspecified")
+                .WithRequired(false);
+
+            // todo add array types
+
+            var propertyTable = schemaClass.AddPropertyTable(1);
+
+            propertyTable
+                .UseProperty(descriptionProperty)
+                .SetValues("Description of the triangle");
+
+            propertyTable
+                .UseProperty(uint8Property)
+                .SetValues(byte.MinValue);
+
+            propertyTable
+                .UseProperty(int8Property)
+                .SetValues(sbyte.MinValue);
+
+            propertyTable
+                .UseProperty(int16Property)
+                .SetValues(short.MinValue);
+
+            propertyTable
+                .UseProperty(uint16Property)
+                .SetValues(ushort.MinValue);
+
+            propertyTable
+                .UseProperty(int32Property)
+                .SetValues(int.MinValue);
+
+            propertyTable
+                .UseProperty(uint32Property)
+                .SetValues(uint.MinValue);
+
+            propertyTable
+                .UseProperty(int64Property)
+                .SetValues(long.MinValue);
+
+            propertyTable
+                .UseProperty(uint64Property)
+                .SetValues(ulong.MinValue);
+
+            propertyTable
+                .UseProperty(float32Property)
+                .SetValues(-10f);
+
+            propertyTable
+                .UseProperty(float64Property)
+                .SetValues(double.MinValue);
+
+            // todo: how to set a vector3 to null?
+            propertyTable
+                .UseProperty(vector3Property)
+                .SetValues(new Vector3(0,0,0));
+
+            propertyTable
+                .UseProperty(stringProperty)
+                .SetValues("noData");
+
+            propertyTable
+                .UseProperty(speciesProperty)
+                .SetValues((short)0);
+
+            foreach (var primitive in model.LogicalMeshes[0].Primitives)
+            {
+                var featureIdAttribute = new FeatureIDBuilder(1, 0, propertyTable);
+                primitive.AddMeshFeatureIds(featureIdAttribute);
+            }
+
+            // create files
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.plotly");
+        }
+
+
+
 
 
         [Test(Description = "MinimalMetadataAttributeSample")]
         [Test(Description = "MinimalMetadataAttributeSample")]
         public void MinimalMetadataAttributeSample()
         public void MinimalMetadataAttributeSample()
@@ -681,21 +851,21 @@ namespace SharpGLTF.Schema2.Tiles3D
                 .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
                 .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
                 .WithName("Example variable-length ARRAY normalized INT8 property")
                 .WithName("Example variable-length ARRAY normalized INT8 property")
                 .WithDescription("An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
                 .WithDescription("An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
-                .WithArrayType(ElementType.SCALAR, DataType.UINT8)
+                .WithUInt8ArrayType()
                 .WithNormalized(false);
                 .WithNormalized(false);
 
 
             var fixedLengthBooleanProperty = exampleMetadataClass
             var fixedLengthBooleanProperty = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
                 .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
                 .WithName("Example fixed-length ARRAY BOOLEAN property")
                 .WithName("Example fixed-length ARRAY BOOLEAN property")
                 .WithDescription("An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
                 .WithDescription("An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
-                .WithArrayType(ElementType.BOOLEAN, null, 4)
+                .WithBooleanArrayType(4)
                 .WithNormalized(false);
                 .WithNormalized(false);
 
 
             var variableLengthStringArrayProperty = exampleMetadataClass
             var variableLengthStringArrayProperty = exampleMetadataClass
                 .UseProperty("example_variable_length_ARRAY_STRING")
                 .UseProperty("example_variable_length_ARRAY_STRING")
                 .WithName("Example variable-length ARRAY STRING property")
                 .WithName("Example variable-length ARRAY STRING property")
                 .WithDescription("An example property, with type ARRAY, with component type STRING, and variable length")
                 .WithDescription("An example property, with type ARRAY, with component type STRING, and variable length")
-                .WithArrayType(ElementType.STRING);
+                .WithStringArrayType();
 
 
             var fixed_length_ARRAY_ENUM = exampleMetadataClass
             var fixed_length_ARRAY_ENUM = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_ENUM")
                 .UseProperty("example_fixed_length_ARRAY_ENUM")