Browse Source

add reading gltf tests for ext_struture_metadata

Bert Temme 1 year ago
parent
commit
ef63cd9dcf
47 changed files with 8919 additions and 23 deletions
  1. 61 0
      src/SharpGLTF.Ext.3DTiles/Schema2/ComponentCount.cs
  2. 98 10
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs
  3. 35 11
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs
  4. 1 1
      tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs
  5. 1 1
      tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
  6. 61 0
      tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs
  7. 275 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf
  8. 328 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyArray.gltf
  9. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidComponentType.gltf
  10. 328 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidEnumValueType.gltf
  11. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMaxNotInRange.gltf
  12. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMinNotInRange.gltf
  13. 326 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyString.gltf
  14. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementType.gltf
  15. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementValue.gltf
  16. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidLength.gltf
  17. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidType.gltf
  18. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyInvalidAttribute.gltf
  19. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf
  20. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf
  21. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinMismatch.gltf
  22. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf
  23. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMaxNotInRange.gltf
  24. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMinNotInRange.gltf
  25. 118 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf
  26. 176 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureEnumsInvalidEnumValue.gltf
  27. 114 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeA.gltf
  28. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeB.gltf
  29. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTextureTexCoordInvalidValue.gltf
  30. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementType.gltf
  31. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementValue.gltf
  32. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidLength.gltf
  33. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidType.gltf
  34. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyChannelsSizeMismatch.gltf
  35. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidType.gltf
  36. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidValue.gltf
  37. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxMismatch.gltf
  38. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf
  39. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinMismatch.gltf
  40. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinNotInRange.gltf
  41. 21 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/README.md
  42. 173 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataMissingSchema.gltf
  43. 229 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataSchemaAndSchemaUri.gltf
  44. 228 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidMultipleClasses.gltf
  45. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyAttributes.gltf
  46. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyTexture.gltf
  47. 176 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyTextureEnums.gltf

+ 61 - 0
src/SharpGLTF.Ext.3DTiles/Schema2/ComponentCount.cs

@@ -0,0 +1,61 @@
+using SharpGLTF.Schema2.Tiles3D;
+
+namespace SharpGLTF.Schema2
+{
+    public static class ComponentCount
+    {
+        public static int ByteSizeForComponentType(DataType? dataType)
+        {
+            switch (dataType)
+            {
+                case DataType.INT8:
+                    return 1;
+                case DataType.UINT8:
+                    return 1;
+                case DataType.INT16:
+                    return 2;
+                case DataType.UINT16:
+                    return 2;
+                case DataType.INT32:
+                    return 4;
+                case DataType.UINT32:
+                    return 4;
+                case DataType.INT64:
+                    return 8;
+                case DataType.UINT64:
+                    return 8;
+                case DataType.FLOAT32:
+                    return 4;
+                case DataType.FLOAT64:
+                    return 8;
+                default: return 0;
+            }
+        }
+
+        public static int ElementCountForType(ElementType t)
+        {
+            switch (t)
+            {
+                case ElementType.SCALAR:
+                case ElementType.STRING:
+                case ElementType.ENUM:
+                case ElementType.BOOLEAN:
+                    return 1;
+                case ElementType.VEC2:
+                    return 2;
+                case ElementType.VEC3:
+                    return 3;
+                case ElementType.VEC4:
+                    return 4;
+                case ElementType.MAT2:
+                    return 4;
+                case ElementType.MAT3:
+                    return 9;
+                case ElementType.MAT4:
+                    return 16;
+                default: return 0;
+            }
+        }
+
+    }
+}

+ 98 - 10
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -69,11 +69,11 @@ namespace SharpGLTF.Schema2
             public void AddTexture(PropertyTexture texture)
             public void AddTexture(PropertyTexture texture)
             {
             {
                 Guard.NotNull(texture, nameof(texture));
                 Guard.NotNull(texture, nameof(texture));
-                
+
                 var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
                 var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
                 var properties = metadata.PropertyTextures;
                 var properties = metadata.PropertyTextures;
                 Guard.IsTrue(properties.Contains(texture), nameof(texture));
                 Guard.IsTrue(properties.Contains(texture), nameof(texture));
-                
+
                 _propertyTextures.Add(texture.LogicalIndex);
                 _propertyTextures.Add(texture.LogicalIndex);
             }
             }
 
 
@@ -91,11 +91,11 @@ namespace SharpGLTF.Schema2
             public void AddAttribute(PropertyAttribute attribute)
             public void AddAttribute(PropertyAttribute attribute)
             {
             {
                 Guard.NotNull(attribute, nameof(attribute));
                 Guard.NotNull(attribute, nameof(attribute));
-                
+
                 var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
                 var metadata = _GetModelRoot().UseExtension<EXTStructuralMetadataRoot>();
                 var properties = metadata.PropertyAttributes;
                 var properties = metadata.PropertyAttributes;
                 Guard.IsTrue(properties.Contains(attribute), nameof(attribute));
                 Guard.IsTrue(properties.Contains(attribute), nameof(attribute));
-                
+
                 _propertyAttributes.Add(attribute.LogicalIndex);
                 _propertyAttributes.Add(attribute.LogicalIndex);
             }
             }
 
 
@@ -106,30 +106,118 @@ namespace SharpGLTF.Schema2
             protected override void OnValidateReferences(ValidationContext validate)
             protected override void OnValidateReferences(ValidationContext validate)
             {
             {
                 var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
                 var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
+                Guard.NotNull(rootMetadata, nameof(rootMetadata), "EXT_Structural_Metadata extension missing in root");
+                var propertyTextures = rootMetadata.PropertyTextures;
 
 
+                // Scan textures
                 foreach (var propertyTexture in _propertyTextures)
                 foreach (var propertyTexture in _propertyTextures)
                 {
                 {
-                    var propertyTextures = rootMetadata.PropertyTextures;
                     validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
                     validate.IsNullOrIndex(nameof(propertyTexture), propertyTexture, propertyTextures);
+
+                    var schemaTexture = propertyTextures[propertyTexture];
+                    var className = schemaTexture.ClassName;
+                    foreach (var property in schemaTexture.Properties)
+                    {
+                        var textureCoordinate = property.Value.TextureCoordinate;
+                        // Guard the meshprimitive has the texture coordinate attribute
+                        var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
+                        Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), nameof(textureCoordinate), $"The primitive should have texture coordinate attribute {textureCoordinate}.");
+
+
+                        var texture = property.Value.Texture;
+                        Guard.NotNull(texture, nameof(texture), $"The primitive should have texture {texture}.");
+
+                        var schemaProperty = rootMetadata.Schema.Classes[className].Properties[property.Key];
+                        Guard.IsTrue(schemaProperty.Type != ElementType.STRING, nameof(schemaProperty.Type),
+                            $"The property '{property.Key}' has the type 'STRING', which is not supported for property textures");
+
+                        if (schemaProperty.Array)
+                        {
+                            Guard.IsTrue(schemaProperty.Count != null, nameof(schemaProperty.Array),
+                                 $"The property '{property.Key}'  is a variable-length array, which is not supported for property textures");
+                        }
+
+
+                        var channels = property.Value.Channels;
+                        var elementCount = ComponentCount.ElementCountForType(schemaProperty.Type);
+                        if (schemaProperty.ComponentType != null)
+                        {
+                            var componentByteSize = ComponentCount.ByteSizeForComponentType(schemaProperty.ComponentType);
+                            var elementByteSize = elementCount * componentByteSize;
+                            var totalByteSize = channels.Count * elementByteSize;
+                            Guard.IsTrue(totalByteSize == channels.Count, nameof(totalByteSize),
+                                $"The property '{property.Key}' has the component type {schemaProperty.ComponentType}, with a size of {componentByteSize} bytes, and the type {schemaProperty.Type} with {channels.Count} components, resulting in {totalByteSize} bytes per element, but the number of channels in the property texture property was {channels.Count}");
+                        }
+
+                    }
+
+
                 }
                 }
 
 
+                // scan attributes
                 foreach (var propertyAttribute in _propertyAttributes)
                 foreach (var propertyAttribute in _propertyAttributes)
                 {
                 {
                     var propertyAttributes = rootMetadata.PropertyAttributes;
                     var propertyAttributes = rootMetadata.PropertyAttributes;
                     validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, propertyAttributes);
                     validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, propertyAttributes);
 
 
-                    foreach(var attribute in propertyAttributes)
+                    var schema = rootMetadata.Schema;
+                    var schemaAttribute = propertyAttributes[propertyAttribute];
+
+                    var className = schemaAttribute.ClassName;
+                    Guard.NotNull(schema, nameof(schema), "EXT_Structural_Metadata extension missing schema");
+                    schema.Classes.TryGetValue(className, out var classDefinition);
+                    Guard.NotNull(classDefinition, nameof(classDefinition), $"EXT_Structural_Metadata extension missing class definition for {className}");
+
+                    foreach (var property in schemaAttribute.Properties)
                     {
                     {
-                        foreach(var property in attribute.Properties)
+                        var expectedVertexAttribute = property.Value.Attribute;
+                        Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute, $"The primitive should have custom vertex attribute {expectedVertexAttribute}.");
+
+                        // var min = property.Value.Min?.AsValue();
+                        //var max = property.Value.Max?.AsValue();
+                        // todo check against min and max of accessor
+                        // var acc = property.Value.Attribute;
+                        // var vertexAccessor = meshPrimitive.GetVertexAccessor(acc);
+                        // var a = vertexAccessor.AsScalarArray();
+
+                        var key = property.Key;
+
+                        classDefinition.Properties.TryGetValue(key, out var propertyDefinition);
+                        Guard.NotNull(propertyDefinition, nameof(propertyDefinition), $"EXT_Structural_Metadata extension missing property definition for {key}");
+
+                        Guard.IsTrue(propertyDefinition.Array == false, nameof(propertyDefinition.Array), $"The property '{property.Key}' is an array, which is not supported for property attributes");
+
+                        Guard.IsTrue(propertyDefinition.Type != ElementType.STRING, nameof(propertyDefinition.Type),
+                                                       $"The property '{property.Key}' has the type 'STRING', which is not supported for property attributes"); 
+                        if (propertyDefinition.Type == ElementType.SCALAR)
                         {
                         {
-                            var expectedVertexAttribute = property.Value.Attribute;
-                            Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute, $"The primitive should have custom vertex attribute {expectedVertexAttribute}.");
+                            var allowedComponentTypes = new List<DataType?>()
+                            {
+                                DataType.INT8,DataType.UINT8,DataType.INT16,DataType.UINT16,DataType.FLOAT32,
+                            };
+
+                            Guard.IsTrue(allowedComponentTypes.Contains(propertyDefinition.ComponentType), nameof(propertyDefinition.ComponentType),
+                                $"The property '{property.Key}' has the component type {propertyDefinition.ComponentType}, but the type must be one of INT8,UINT8,INT16,UINT16,FLOAT32 for property attribute");
+                        }
+                        else if (propertyDefinition.Type == ElementType.ENUM)
+                        {
+                            var enumType = propertyDefinition.EnumType;
+                            // Get the enum from the schema
+                            var enumDefinition = schema.Enums[enumType];
+                            Guard.NotNull(enumDefinition, nameof(enumDefinition), $"EXT_Structural_Metadata extension missing enum definition for {enumType}");
+                            var valueType = enumDefinition.ValueType;
+                            var allowedIntegerTypes = new List<IntegerType?>()
+                            {
+                                IntegerType.INT8,IntegerType.UINT8,IntegerType.INT16,IntegerType.UINT16
+                            };
+                            Guard.IsTrue(allowedIntegerTypes.Contains(valueType), nameof(valueType),
+                                                               $"The enumeration '{property.Key}' has the value type {valueType}, but the type must be one of INT8,UINT8,INT16,UINT16 for property attribute");
                         }
                         }
                     }
                     }
                 }
                 }
 
 
                 base.OnValidateReferences(validate);
                 base.OnValidateReferences(validate);
-            }            
+            }
 
 
             #endregion
             #endregion
         }
         }

+ 35 - 11
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -13,13 +13,14 @@ namespace SharpGLTF.Schema2
     using Validation;
     using Validation;
     using Tiles3D;
     using Tiles3D;
     using System.Numerics;
     using System.Numerics;
+    using System.Text.Json.Nodes;
 
 
     partial class Tiles3DExtensions
     partial class Tiles3DExtensions
     {
     {
         public static EXTStructuralMetadataRoot UseStructuralMetadata(this ModelRoot modelRoot)
         public static EXTStructuralMetadataRoot UseStructuralMetadata(this ModelRoot modelRoot)
         {
         {
             return modelRoot.UseExtension<EXTStructuralMetadataRoot>();
             return modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-        }        
+        }
     }
     }
 
 
     namespace Tiles3D
     namespace Tiles3D
@@ -114,7 +115,7 @@ namespace SharpGLTF.Schema2
                 if (_schema == null) GetChildSetter(this).SetProperty(ref _schema, new StructuralMetadataSchema());
                 if (_schema == null) GetChildSetter(this).SetProperty(ref _schema, new StructuralMetadataSchema());
 
 
                 return _schema;
                 return _schema;
-            }            
+            }
 
 
             public PropertyAttribute AddPropertyAttribute(StructuralMetadataClass schemaClass)
             public PropertyAttribute AddPropertyAttribute(StructuralMetadataClass schemaClass)
             {
             {
@@ -128,7 +129,7 @@ namespace SharpGLTF.Schema2
                 var prop = new PropertyAttribute();
                 var prop = new PropertyAttribute();
                 _propertyAttributes.Add(prop);
                 _propertyAttributes.Add(prop);
                 return prop;
                 return prop;
-            }            
+            }
 
 
             public PropertyTable AddPropertyTable(StructuralMetadataClass schemaClass, int? featureCount = null, string name = null)
             public PropertyTable AddPropertyTable(StructuralMetadataClass schemaClass, int? featureCount = null, string name = null)
             {
             {
@@ -149,7 +150,7 @@ namespace SharpGLTF.Schema2
             public PropertyTexture AddPropertyTexture(StructuralMetadataClass schemaClass)
             public PropertyTexture AddPropertyTexture(StructuralMetadataClass schemaClass)
             {
             {
                 var prop = AddPropertyTexture();
                 var prop = AddPropertyTexture();
-                prop.ClassInstance = schemaClass;                
+                prop.ClassInstance = schemaClass;
                 return prop;
                 return prop;
             }
             }
 
 
@@ -158,7 +159,7 @@ namespace SharpGLTF.Schema2
                 var prop = new PropertyTexture();
                 var prop = new PropertyTexture();
                 _propertyTextures.Add(prop);
                 _propertyTextures.Add(prop);
                 return prop;
                 return prop;
-            }            
+            }
 
 
             #endregion
             #endregion
 
 
@@ -166,6 +167,13 @@ namespace SharpGLTF.Schema2
 
 
             protected override void OnValidateReferences(ValidationContext validate)
             protected override void OnValidateReferences(ValidationContext validate)
             {
             {
+                var root = LogicalParent.GetExtension<EXTStructuralMetadataRoot>();
+                Guard.MustBeNull(root._schemaUri, nameof(root._schemaUri),  
+                    "SchemaUri must be null, use embedded achema to set the schema");
+
+                // Guard schema is null
+                Guard.NotNull(Schema, nameof(Schema), "Schema must be defined");
+
                 foreach (var propertyTexture in PropertyTextures)
                 foreach (var propertyTexture in PropertyTextures)
                 {
                 {
                     foreach (var propertyTextureProperty in propertyTexture.Properties)
                     foreach (var propertyTextureProperty in propertyTexture.Properties)
@@ -325,7 +333,7 @@ namespace SharpGLTF.Schema2
                     {
                     {
                         return schema.Classes[ClassName];
                         return schema.Classes[ClassName];
                     }
                     }
-                    else return null;                    
+                    else return null;
                 }
                 }
                 set
                 set
                 {
                 {
@@ -399,7 +407,7 @@ namespace SharpGLTF.Schema2
                     _channels.Clear();
                     _channels.Clear();
                     _channels.AddRange(value);
                     _channels.AddRange(value);
                 }
                 }
-            }            
+            }
 
 
             public Schema2.Texture Texture
             public Schema2.Texture Texture
             {
             {
@@ -529,6 +537,17 @@ namespace SharpGLTF.Schema2
                 set => _attribute = value;
                 set => _attribute = value;
             }
             }
 
 
+            public JsonNode Min
+            {
+                get => _min;
+            }
+
+            public JsonNode Max
+            {
+                get => _max;
+            }
+
+
             #endregion
             #endregion
         }
         }
 
 
@@ -543,7 +562,7 @@ namespace SharpGLTF.Schema2
                 _properties = new ChildrenDictionary<PropertyTableProperty, PropertyTable>(this);
                 _properties = new ChildrenDictionary<PropertyTableProperty, PropertyTable>(this);
 
 
                 _count = _countMinimum;
                 _count = _countMinimum;
-            }           
+            }
 
 
             protected override IEnumerable<ExtraProperties> GetLogicalChildren()
             protected override IEnumerable<ExtraProperties> GetLogicalChildren()
             {
             {
@@ -601,7 +620,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 get => _name;
                 get => _name;
                 set => _name = value;
                 set => _name = value;
-            }            
+            }
 
 
             public int Count
             public int Count
             {
             {
@@ -631,7 +650,7 @@ namespace SharpGLTF.Schema2
                 value = new PropertyTableProperty();
                 value = new PropertyTableProperty();
                 _properties[key] = value;
                 _properties[key] = value;
                 return value;
                 return value;
-            }            
+            }
 
 
             #endregion
             #endregion
         }
         }
@@ -778,7 +797,7 @@ namespace SharpGLTF.Schema2
                     var componentType = metadataProperty.ComponentType;
                     var componentType = metadataProperty.ComponentType;
                     CheckScalarTypes<T>(componentType);
                     CheckScalarTypes<T>(componentType);
                 }
                 }
-                else if(elementType == ELEMENTTYPE.STRING)
+                else if (elementType == ELEMENTTYPE.STRING)
                 {
                 {
                     Guard.IsTrue(typeof(T) == typeof(string), nameof(T), $"String type of property {LogicalKey} must be string");
                     Guard.IsTrue(typeof(T) == typeof(string), nameof(T), $"String type of property {LogicalKey} must be string");
                 }
                 }
@@ -1011,6 +1030,11 @@ namespace SharpGLTF.Schema2
                 set => _description = value;
                 set => _description = value;
             }
             }
 
 
+            public IntegerType? ValueType
+            {
+                get => _valueType;
+            }
+
             #endregion
             #endregion
 
 
             #region API
             #region API

+ 1 - 1
tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs

@@ -19,7 +19,7 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/instanceFeatures
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/instanceFeatures
 
 
-        [Test(Description = "Reads glTF's with Cesium EXT_Instance_Features")]
+        [Test(Description = "Reads glTF's with EXT_Instance_Features")]
         public void ReadExtInstanceFeatures()
         public void ReadExtInstanceFeatures()
         {
         {
             var gltffiles = Directory.GetFiles("./testfixtures/instanceFeatures", "*.gltf");
             var gltffiles = Directory.GetFiles("./testfixtures/instanceFeatures", "*.gltf");

+ 1 - 1
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -24,7 +24,7 @@ namespace SharpGLTF.Schema2.Tiles3D
         }
         }
 
 
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/meshFeatures
         // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/meshFeatures
-        [Test(Description = "Reads glTF's with Cesium EXT_Mesh_Features")]
+        [Test(Description = "Reads glTF's with EXT_Mesh_Features")]
         public void ReadExtMeshFeatures()
         public void ReadExtMeshFeatures()
         {
         {
             var gltffiles = Directory.GetFiles("./testfixtures/meshFeatures", "*.gltf", SearchOption.AllDirectories);
             var gltffiles = Directory.GetFiles("./testfixtures/meshFeatures", "*.gltf", SearchOption.AllDirectories);

+ 61 - 0
tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs

@@ -6,6 +6,7 @@ using SharpGLTF.Scenes;
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
 
 
@@ -22,6 +23,66 @@ namespace SharpGLTF.Schema2.Tiles3D
             Tiles3DExtensions.RegisterExtensions();
             Tiles3DExtensions.RegisterExtensions();
         }
         }
 
 
+        // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/structuralMetadata
+
+        [Test(Description = "Reads glTF's with EXT_Structural_Metadata")]
+        public void ReadExtStructuralMetadata()
+        {
+            var gltffiles = Directory.GetFiles("./testfixtures/structuralMetadata", "*.gltf");
+
+            var excludedFilesWithStats = new List<string>()
+            {
+                "PropertyAttributesClassPropertyMaxNotInRange.gltf",
+                "PropertyAttributesClassPropertyMinNotInRange.gltf",
+                "PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf",
+                "PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf",
+                "PropertyAttributesPropertyAttributePropertyMinMismatch.gltf",
+                "PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf",
+            };
+
+            var excludedFilesWithTextureInspection = new List<string>()
+            {
+                "PropertyTextureClassPropertyMaxNotInRange.gltf",
+                "PropertyTextureClassPropertyMinNotInRange.gltf",
+                "PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf",
+                "PropertyTextureEnumsInvalidEnumValue.gltf",
+                "PropertyTexturePropertyTexturePropertyMaxMismatch.gltf",
+                "PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf",
+                "PropertyTexturePropertyTexturePropertyMinMismatch.gltf",
+                "PropertyTexturePropertyTexturePropertyMinNotInRange.gltf",
+            };
+
+            // combine both lists excludedFilesWithStats and excludedFilesWithTextureInspection into a new list
+
+            var excludedFiles = excludedFilesWithStats.Concat(excludedFilesWithTextureInspection);
+
+
+
+            // var excludedTests = excludedFilesWithStats.AddRange(excludedFilesWithTextureInspection);
+
+            foreach (var file in gltffiles)
+            {
+                var fileName = Path.GetFileName(file);
+
+                if (!excludedFiles.Contains(fileName) )
+                {
+
+                    if (!fileName.StartsWith("Valid"))
+                    {
+                        Assert.That(() => ModelRoot.Load(file), Throws.Exception);
+                    }
+                    else
+                    {
+                        var model = ModelRoot.Load(file);
+                        var structuralMetadataExtension = model.GetExtension<EXTStructuralMetadataRoot>();
+                        var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+                        model.ValidateContent(ctx.GetContext());
+                    }
+                }
+            }
+        }
+
+
         [Test(Description = "TestWith2PrimitivesAndMetadata")]
         [Test(Description = "TestWith2PrimitivesAndMetadata")]
         public void MultiplePrimitivesAndMetadata()
         public void MultiplePrimitivesAndMetadata()
         {
         {

+ 275 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf

@@ -0,0 +1,275 @@
+{
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 328 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyArray.gltf

@@ -0,0 +1,328 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32",
+                "array": true
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidComponentType.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType": "FLOAT64"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 328 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidEnumValueType.gltf

@@ -0,0 +1,328 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType": "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "valueType": "INT64",
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMaxNotInRange.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32",
+                "max": 0.7
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMinNotInRange.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32",
+                "min": 0.3
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 326 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyString.gltf

@@ -0,0 +1,326 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "STRING"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementType.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0, "NOT_A_NUMBER" ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementValue.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0, 12345 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidLength.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : []
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidType.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : "NOT_A_NUMBER"
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyInvalidAttribute.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "THIS_ATTRIBUTE_DOES_NOT_EXIST"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY",
+            "max": 0.3
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY",
+            "max": 0.2
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinMismatch.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY",
+            "min": 0.2
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 339 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf

@@ -0,0 +1,339 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY",
+            "min": 0.3
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }, {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      }
+      ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 1 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMaxNotInRange.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMinNotInRange.gltf


File diff suppressed because it is too large
+ 118 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf


+ 176 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureEnumsInvalidEnumValue.gltf

@@ -0,0 +1,176 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "PropertyTextureEnumsSchemaId",
+        "classes" : {
+          "propertyTextureEnumsClass" : {
+            "name" : "Property texture example enum properties",
+            "properties" : {
+              "enumProperty" : {
+                "name" : "Example enum property",
+                "type" : "ENUM",
+                "enumType" : "exampleEnum",
+                "noData" : "NO_DATA_ENUM_VALUE",
+                "default" : "DEFAULT_ENUM_VALUE"
+              },
+              "enumArrayProperty" : {
+                "name" : "Example enum array property",
+                "type" : "ENUM",
+                "enumType" : "exampleEnum",
+                "array" : true,
+                "count" : 2,
+                "noData" : [ "NO_DATA_ENUM_VALUE", "NO_DATA_ENUM_VALUE" ],
+                "default" : [ "DEFAULT_ENUM_VALUE", "DEFAULT_ENUM_VALUE" ]
+              }
+            }
+          }
+        },
+        "enums" : {
+          "exampleEnum" : {
+            "valueType": "UINT8",
+            "values" : [ {
+              "name" : "NO_DATA_ENUM_VALUE",
+              "value" : 255
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_A",
+              "value" : 0
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_B",
+              "value" : 1
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_C",
+              "value" : 2
+            }, {
+              "name" : "DEFAULT_ENUM_VALUE",
+              "value" : 3
+            } ]
+          }
+        }
+      },
+      "propertyTextures" : [ {
+        "class" : "propertyTextureEnumsClass",
+        "properties" : {
+          "enumProperty" : {
+            "index" : 0,
+            "texCoord" : 0,
+            "channels" : [ 0 ]
+          },
+          "enumArrayProperty" : {
+            "index" : 0,
+            "texCoord" : 0,
+            "channels" : [ 1, 2 ]
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC2",
+    "max" : [ 1.0, 1.0 ],
+    "min" : [ 0.0, 0.0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAAAAAAAAAAACAPwAAAAA=",
+    "byteLength" : 140
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 32,
+    "target" : 34962
+  } ],
+  "images" : [ {
+    "uri" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGUlEQVR42mP4/5/hPwMTAwNDSgpDCpBmAgA21QQw4gn1GgAAAABJRU5ErkJggg==",
+    "mimeType" : "image/png"
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 0.5, 0.5, 0.5, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyTextures" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "TEXCOORD_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "samplers" : [ {
+    "magFilter" : 9728,
+    "minFilter" : 9728
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ],
+  "textures" : [ {
+    "sampler" : 0,
+    "source" : 0
+  } ]
+}

File diff suppressed because it is too large
+ 114 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeA.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeB.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTextureTexCoordInvalidValue.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementType.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementValue.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidLength.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidType.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyChannelsSizeMismatch.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidType.gltf


File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidValue.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxMismatch.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinMismatch.gltf


File diff suppressed because it is too large
+ 116 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinNotInRange.gltf


+ 21 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/README.md

@@ -0,0 +1,21 @@
+The files in this directory are used for the specs for the 
+`EXT_structural_metadata` validation.
+
+The valid files have been taken from
+https://github.com/CesiumGS/3d-tiles-samples/tree/a256d9f68df15bbfc75ea3891f52c72a36d04202/glTF/EXT_structural_metadata
+except for the following ones, which have been created dedicatedly for these tests:
+
+- `ValidPropertyAttributes.gltf`
+- `ValidPropertyTextureEnums.gltf`
+
+The files that cause issues have been created by modifying these files 
+This mostly happened manually, with the exception of certain invalid values in 
+property textures, that have been written out in this form by the generator.
+
+Note that some parts of the validation specs are not covered here, because
+they are covered by the main 3D Tiles Validator specs. Particularly:
+
+- Validation of the metadata schema is covered with `./specs/data/schemas/`
+- Validation of the property tables is covered with `./specs/data/subtrees/subtreePropertyTables*.json`
+
+

+ 173 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataMissingSchema.gltf

@@ -0,0 +1,173 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "propertyTables" : [ {
+        "name" : "First example property table",
+        "class" : "exampleMetadataClassA",
+        "count" : 4,
+        "properties" : {
+          "example_FLOAT32" : {
+            "values" : 5
+          },
+          "example_INT64" : {
+            "values" : 6
+          }
+        }
+      }, {
+        "name" : "Second example property table",
+        "class" : "exampleMetadataClassB",
+        "count" : 4,
+        "properties" : {
+          "example_UINT16" : {
+            "values" : 7
+          },
+          "example_FLOAT64" : {
+            "values" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_mesh_features", "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 24,
+    "type" : "SCALAR",
+    "max" : [ 15 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AwAAAAMAAAADAAAAAwAAAAIAAAACAAAAAgAAAAIAAAABAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAACAAAAAgAAAAIAAAACAAAAAQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=",
+    "byteLength" : 560
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,zcyMP83MDEAzM1NAzcyMQIfWEgAAAAAAzsojAAAAAAAVvzQAAAAAAFKzRQAAAAAA",
+    "byteLength" : 48
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,OTCgWweHbrKHiJtTycDzP9GSx9PywwJAX2HB/YCnC0Da5sb0hEUSQA==",
+    "byteLength" : 40
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 48,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 48,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 240,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 496,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 16
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 16,
+    "byteLength" : 32
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 8
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 8,
+    "byteLength" : 32
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_mesh_features" : {
+          "featureIds" : [ {
+            "featureCount" : 4,
+            "attribute" : 0,
+            "propertyTable" : 0
+          }, {
+            "featureCount" : 4,
+            "attribute" : 1,
+            "propertyTable" : 1
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3,
+        "_FEATURE_ID_1" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 229 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataSchemaAndSchemaUri.gltf

@@ -0,0 +1,229 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schemaUri": "uriToCreateValidationError.json",
+      "schema" : {
+        "id": "MultipleClassesSchema",
+        "classes" : {
+          "exampleMetadataClassA" : {
+            "name" : "Example metadata class A",
+            "description" : "First example metadata class",
+            "properties" : {
+              "example_FLOAT32" : {
+                "name" : "Example FLOAT32 property",
+                "description" : "An example property, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "example_INT64" : {
+                "name" : "Example INT64 property",
+                "description" : "An example property, with component type INT64",
+                "type" : "SCALAR",
+                "componentType" : "INT64"
+              }
+            }
+          },
+          "exampleMetadataClassB" : {
+            "name" : "Example metadata class B",
+            "description" : "Second example metadata class",
+            "properties" : {
+              "example_UINT16" : {
+                "name" : "Example UINT16 property",
+                "description" : "An example property, with component type UINT16",
+                "type" : "SCALAR",
+                "componentType" : "UINT16"
+              },
+              "example_FLOAT64" : {
+                "name" : "Example FLOAT64 property",
+                "description" : "An example property, with component type FLOAT64",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT64"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "exampleEnumType" : {
+            "values" : [ {
+              "name" : "ExampleEnumValueA",
+              "value" : 0
+            }, {
+              "name" : "ExampleEnumValueB",
+              "value" : 1
+            }, {
+              "name" : "ExampleEnumValueC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "First example property table",
+        "class" : "exampleMetadataClassA",
+        "count" : 4,
+        "properties" : {
+          "example_FLOAT32" : {
+            "values" : 5
+          },
+          "example_INT64" : {
+            "values" : 6
+          }
+        }
+      }, {
+        "name" : "Second example property table",
+        "class" : "exampleMetadataClassB",
+        "count" : 4,
+        "properties" : {
+          "example_UINT16" : {
+            "values" : 7
+          },
+          "example_FLOAT64" : {
+            "values" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_mesh_features", "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 24,
+    "type" : "SCALAR",
+    "max" : [ 15 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AwAAAAMAAAADAAAAAwAAAAIAAAACAAAAAgAAAAIAAAABAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAACAAAAAgAAAAIAAAACAAAAAQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=",
+    "byteLength" : 560
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,zcyMP83MDEAzM1NAzcyMQIfWEgAAAAAAzsojAAAAAAAVvzQAAAAAAFKzRQAAAAAA",
+    "byteLength" : 48
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,OTCgWweHbrKHiJtTycDzP9GSx9PywwJAX2HB/YCnC0Da5sb0hEUSQA==",
+    "byteLength" : 40
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 48,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 48,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 240,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 496,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 16
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 16,
+    "byteLength" : 32
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 8
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 8,
+    "byteLength" : 32
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_mesh_features" : {
+          "featureIds" : [ {
+            "featureCount" : 4,
+            "attribute" : 0,
+            "propertyTable" : 0
+          }, {
+            "featureCount" : 4,
+            "attribute" : 1,
+            "propertyTable" : 1
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3,
+        "_FEATURE_ID_1" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 228 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidMultipleClasses.gltf

@@ -0,0 +1,228 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "MultipleClassesSchema",
+        "classes" : {
+          "exampleMetadataClassA" : {
+            "name" : "Example metadata class A",
+            "description" : "First example metadata class",
+            "properties" : {
+              "example_FLOAT32" : {
+                "name" : "Example FLOAT32 property",
+                "description" : "An example property, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "example_INT64" : {
+                "name" : "Example INT64 property",
+                "description" : "An example property, with component type INT64",
+                "type" : "SCALAR",
+                "componentType" : "INT64"
+              }
+            }
+          },
+          "exampleMetadataClassB" : {
+            "name" : "Example metadata class B",
+            "description" : "Second example metadata class",
+            "properties" : {
+              "example_UINT16" : {
+                "name" : "Example UINT16 property",
+                "description" : "An example property, with component type UINT16",
+                "type" : "SCALAR",
+                "componentType" : "UINT16"
+              },
+              "example_FLOAT64" : {
+                "name" : "Example FLOAT64 property",
+                "description" : "An example property, with component type FLOAT64",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT64"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "exampleEnumType" : {
+            "values" : [ {
+              "name" : "ExampleEnumValueA",
+              "value" : 0
+            }, {
+              "name" : "ExampleEnumValueB",
+              "value" : 1
+            }, {
+              "name" : "ExampleEnumValueC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "First example property table",
+        "class" : "exampleMetadataClassA",
+        "count" : 4,
+        "properties" : {
+          "example_FLOAT32" : {
+            "values" : 5
+          },
+          "example_INT64" : {
+            "values" : 6
+          }
+        }
+      }, {
+        "name" : "Second example property table",
+        "class" : "exampleMetadataClassB",
+        "count" : 4,
+        "properties" : {
+          "example_UINT16" : {
+            "values" : 7
+          },
+          "example_FLOAT64" : {
+            "values" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_mesh_features", "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 24,
+    "type" : "SCALAR",
+    "max" : [ 15 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 16,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5121,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AwAAAAMAAAADAAAAAwAAAAIAAAACAAAAAgAAAAIAAAABAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAACAAAAAgAAAAIAAAACAAAAAQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=",
+    "byteLength" : 560
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,zcyMP83MDEAzM1NAzcyMQIfWEgAAAAAAzsojAAAAAAAVvzQAAAAAAFKzRQAAAAAA",
+    "byteLength" : 48
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,OTCgWweHbrKHiJtTycDzP9GSx9PywwJAX2HB/YCnC0Da5sb0hEUSQA==",
+    "byteLength" : 40
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 48,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 48,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 240,
+    "byteLength" : 192,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 496,
+    "byteLength" : 64,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 16
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 16,
+    "byteLength" : 32
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 8
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 8,
+    "byteLength" : 32
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_mesh_features" : {
+          "featureIds" : [ {
+            "featureCount" : 4,
+            "attribute" : 0,
+            "propertyTable" : 0
+          }, {
+            "featureCount" : 4,
+            "attribute" : 1,
+            "propertyTable" : 1
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3,
+        "_FEATURE_ID_1" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 327 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyAttributes.gltf

@@ -0,0 +1,327 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "SimplePropertyAttributeSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class for property attributes",
+            "properties" : {
+              "intensity" : {
+                "name" : "Example intensity property",
+                "description" : "An example property for the intensity, with component type FLOAT32",
+                "type" : "SCALAR",
+                "componentType" : "FLOAT32"
+              },
+              "classification" : {
+                "name" : "Example classification property",
+                "description" : "An example property for the classification, with the classificationEnumType",
+                "type" : "ENUM",
+                "enumType" : "classificationEnumType"
+              }
+            }
+          }
+        },
+        "enums" : {
+          "classificationEnumType" : {
+            "values" : [ {
+              "name" : "ExampleClassificationA",
+              "value" : 0
+            }, {
+              "name" : "ExampleClassificationB",
+              "value" : 1
+            }, {
+              "name" : "ExampleClassificationC",
+              "value" : 2
+            } ]
+          }
+        }
+      },
+      "propertyAttributes" : [ {
+        "class" : "exampleMetadataClass",
+        "properties" : {
+          "intensity" : {
+            "attribute" : "_INTENSITY"
+          },
+          "classification" : {
+            "attribute" : "_CLASSIFICATION"
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 1.0, 0.0 ],
+    "min" : [ -1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.25 ],
+    "min" : [ 0.25 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 7,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 8,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.5 ],
+    "min" : [ 0.5 ]
+  }, {
+    "bufferView" : 9,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 1 ],
+    "min" : [ 1 ]
+  }, {
+    "bufferView" : 10,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 11,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 2.0, 1.0, 0.0 ],
+    "min" : [ 1.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 12,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 13,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 0.75 ],
+    "min" : [ 0.75 ]
+  }, {
+    "bufferView" : 14,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 4,
+    "type" : "SCALAR",
+    "max" : [ 2 ],
+    "min" : [ 2 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAACAvwAAAAAAAAAAAAAAAAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPgAAgD4AAIA+AACAPgAAAAAAAAAAAAAAAAAAAAAAAAEAAgABAAMAAgAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAA/AAAAPwAAAD8AAAA/AQAAAAEAAAABAAAAAQAAAAAAAQACAAEAAwACAAAAgD8AAAAAAAAAAAAAAEAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAEAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAQD8AAEA/AABAPwAAQD8CAAAAAgAAAAIAAAACAAAA",
+    "byteLength" : 420
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 124,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 140,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 152,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 200,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 248,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 264,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 280,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 292,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 340,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 388,
+    "byteLength" : 16,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 404,
+    "byteLength" : 16,
+    "byteStride" : 4,
+    "target" : 34962
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 1.0, 1.0, 1.0, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_INTENSITY" : 3,
+        "_CLASSIFICATION" : 4
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 6,
+        "NORMAL" : 7,
+        "_INTENSITY" : 8,
+        "_CLASSIFICATION" : 9
+      },
+      "indices" : 5,
+      "material" : 0,
+      "mode" : 4
+    }, {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyAttributes" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 11,
+        "NORMAL" : 12,
+        "_INTENSITY" : 13,
+        "_CLASSIFICATION" : 14
+      },
+      "indices" : 10,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

File diff suppressed because it is too large
+ 115 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyTexture.gltf


+ 176 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ValidPropertyTextureEnums.gltf

@@ -0,0 +1,176 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "PropertyTextureEnumsSchemaId",
+        "classes" : {
+          "propertyTextureEnumsClass" : {
+            "name" : "Property texture example enum properties",
+            "properties" : {
+              "enumProperty" : {
+                "name" : "Example enum property",
+                "type" : "ENUM",
+                "enumType" : "exampleEnum",
+                "noData" : "NO_DATA_ENUM_VALUE",
+                "default" : "DEFAULT_ENUM_VALUE"
+              },
+              "enumArrayProperty" : {
+                "name" : "Example enum array property",
+                "type" : "ENUM",
+                "enumType" : "exampleEnum",
+                "array" : true,
+                "count" : 2,
+                "noData" : [ "NO_DATA_ENUM_VALUE", "NO_DATA_ENUM_VALUE" ],
+                "default" : [ "DEFAULT_ENUM_VALUE", "DEFAULT_ENUM_VALUE" ]
+              }
+            }
+          }
+        },
+        "enums" : {
+          "exampleEnum" : {
+            "valueType": "UINT8",
+            "values" : [ {
+              "name" : "NO_DATA_ENUM_VALUE",
+              "value" : 255
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_A",
+              "value" : 0
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_B",
+              "value" : 1
+            }, {
+              "name" : "EXAMPLE_ENUM_VALUE_C",
+              "value" : 2
+            }, {
+              "name" : "DEFAULT_ENUM_VALUE",
+              "value" : 3
+            } ]
+          }
+        }
+      },
+      "propertyTextures" : [ {
+        "class" : "propertyTextureEnumsClass",
+        "properties" : {
+          "enumProperty" : {
+            "index" : 0,
+            "texCoord" : 0,
+            "channels" : [ 0 ]
+          },
+          "enumArrayProperty" : {
+            "index" : 0,
+            "texCoord" : 0,
+            "channels" : [ 1, 2 ]
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 6,
+    "type" : "SCALAR",
+    "max" : [ 3 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 0.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC3",
+    "max" : [ 0.0, 0.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 4,
+    "type" : "VEC2",
+    "max" : [ 1.0, 1.0 ],
+    "min" : [ 0.0, 0.0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAAAAAAAAAAACAPwAAAAA=",
+    "byteLength" : 140
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 12,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 12,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 60,
+    "byteLength" : 48,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 108,
+    "byteLength" : 32,
+    "target" : 34962
+  } ],
+  "images" : [ {
+    "uri" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGUlEQVR42mP4/5/hPwMTAwMDIyMDI5BmAgAvGQMH5R702wAAAABJRU5ErkJggg==",
+    "mimeType" : "image/png"
+  } ],
+  "materials" : [ {
+    "pbrMetallicRoughness" : {
+      "baseColorFactor" : [ 0.5, 0.5, 0.5, 1.0 ],
+      "metallicFactor" : 0.0,
+      "roughnessFactor" : 1.0
+    },
+    "alphaMode" : "OPAQUE",
+    "doubleSided" : true
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "extensions" : {
+        "EXT_structural_metadata" : {
+          "propertyTextures" : [ 0 ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "TEXCOORD_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "samplers" : [ {
+    "magFilter" : 9728,
+    "minFilter" : 9728
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ],
+  "textures" : [ {
+    "sampler" : 0,
+    "source" : 0
+  } ]
+}

Some files were not shown because too many files changed in this diff