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_]*$";
                     Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(Schema.Id, regex), nameof(Schema.Id));
 
-
                     foreach (var _class in Schema.Classes)
                     {
                         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));
                             }
+
+                            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)
             {
-                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)
                 {
@@ -1227,6 +1237,7 @@ namespace SharpGLTF.Schema2
             #endregion
 
             #region properties
+
             public string Name
             {
                 get => _name;
@@ -1263,6 +1274,11 @@ namespace SharpGLTF.Schema2
                 set => _required = value.AsNullable(_requiredDefault);
             }
 
+            public JsonNode NoData
+            {
+                get => _noData;
+            }
+
             public bool Normalized
             {
                 get => _normalized ?? _normalizedDefault;
@@ -1325,9 +1341,10 @@ namespace SharpGLTF.Schema2
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithStringType()
+            public StructuralMetadataClassProperty WithStringType(string noData = null)
             {
                 Type = ElementType.STRING;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
@@ -1337,73 +1354,83 @@ namespace SharpGLTF.Schema2
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithUInt8Type()
+            public StructuralMetadataClassProperty WithUInt8Type(byte? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT8;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithInt8Type()
+            public StructuralMetadataClassProperty WithInt8Type(sbyte? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT8;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithUInt16Type()
+            public StructuralMetadataClassProperty WithUInt16Type(ushort? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT16;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithInt16Type()
+            public StructuralMetadataClassProperty WithInt16Type(short? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT16;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithUInt32Type()
+            public StructuralMetadataClassProperty WithUInt32Type(uint? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT32;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithInt32Type()
+            public StructuralMetadataClassProperty WithInt32Type(int? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT32;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithUInt64Type()
+            public StructuralMetadataClassProperty WithUInt64Type(ulong? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.UINT64;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithInt64Type()
+            public StructuralMetadataClassProperty WithInt64Type(long? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.INT64;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithFloat32Type()
+            public StructuralMetadataClassProperty WithFloat32Type(float? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.FLOAT32;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithFloat64Type()
+            public StructuralMetadataClassProperty WithFloat64Type(double? noData = null)
             {
                 Type = ELEMENTTYPE.SCALAR;
                 ComponentType = DATATYPE.FLOAT64;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
@@ -1429,16 +1456,95 @@ namespace SharpGLTF.Schema2
                 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;
                 ComponentType = ctype;
@@ -1447,19 +1553,21 @@ namespace SharpGLTF.Schema2
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumeration, int? count = null)
+            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumeration, int? count = null, string noData = null)
             {
                 Type = ELEMENTTYPE.ENUM;
                 _enumType = enumeration.LogicalKey;
                 Array = true;
                 Count = count;
+                if (noData != null) _noData = noData;
                 return this;
             }
 
-            public StructuralMetadataClassProperty WithEnumeration(StructuralMetadataEnum enumeration)
+            public StructuralMetadataClassProperty WithEnumeration(StructuralMetadataEnum enumeration, string noData = null)
             {
                 Type = ELEMENTTYPE.ENUM;
                 _enumType = enumeration.LogicalKey;
+                if (noData != null) _noData = noData;
                 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")]
         public void MinimalMetadataAttributeSample()
@@ -681,21 +851,21 @@ namespace SharpGLTF.Schema2.Tiles3D
                 .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
                 .WithName("Example variable-length ARRAY normalized INT8 property")
                 .WithDescription("An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
-                .WithArrayType(ElementType.SCALAR, DataType.UINT8)
+                .WithUInt8ArrayType()
                 .WithNormalized(false);
 
             var fixedLengthBooleanProperty = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
                 .WithName("Example fixed-length ARRAY BOOLEAN property")
                 .WithDescription("An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
-                .WithArrayType(ElementType.BOOLEAN, null, 4)
+                .WithBooleanArrayType(4)
                 .WithNormalized(false);
 
             var variableLengthStringArrayProperty = exampleMetadataClass
                 .UseProperty("example_variable_length_ARRAY_STRING")
                 .WithName("Example variable-length ARRAY STRING property")
                 .WithDescription("An example property, with type ARRAY, with component type STRING, and variable length")
-                .WithArrayType(ElementType.STRING);
+                .WithStringArrayType();
 
             var fixed_length_ARRAY_ENUM = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_ENUM")