Browse Source

Merge pull request #213 from bertt/change_feature_id_constructor

Several changes for 3D Tiles api
Vicente Penades 1 year ago
parent
commit
6a7a68b443
100 changed files with 13558 additions and 337 deletions
  1. 195 0
      src/SharpGLTF.Ext.3DTiles/README.md
  2. 61 0
      src/SharpGLTF.Ext.3DTiles/Schema2/ComponentCount.cs
  3. 3 100
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.FeatureID.cs
  4. 90 20
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.Features.cs
  5. 173 14
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs
  6. 392 59
      src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs
  7. 63 0
      src/SharpGLTF.Ext.3DTiles/Schema2/FeatureIDBuilder.cs
  8. 38 0
      src/SharpGLTF.Ext.3DTiles/Schema2/IMeshFeatureIDInfo.cs
  9. 0 2
      src/SharpGLTF.Ext.3DTiles/SharpGLTF.Ext.3DTiles.csproj
  10. BIN
      src/SharpGLTF.Ext.3DTiles/cesium_sample_metadata_attributes.png
  11. BIN
      src/SharpGLTF.Ext.3DTiles/cesium_sample_metadata_texture.png
  12. 29 2
      tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs
  13. 73 21
      tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
  14. 0 1
      tests/SharpGLTF.Cesium.Tests/ExtPrimitiveOutlineTests.cs
  15. 406 118
      tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs
  16. 44 0
      tests/SharpGLTF.Cesium.Tests/GenericTests.cs
  17. 8 0
      tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj
  18. 141 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributeAndPropertyTableFeatureIdNotInRange.gltf
  19. 141 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableInvalidValue.gltf
  20. 131 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableWithoutPropertyTables.gltf
  21. 110 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableWithoutStructuralMetadata.gltf
  22. 117 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdTextureAndPropertyTableFeatureIdNotInRange.gltf
  23. 14 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/README.md
  24. 141 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/ValidFeatureIdAttributeAndPropertyTable.gltf
  25. 117 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/ValidFeatureIdTextureAndPropertyTable.gltf
  26. 179 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/InstanceFeaturesFeatureIdAttributeInvalidValue.gltf
  27. 171 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/InstanceFeaturesWithoutMeshGpuInstancing.gltf
  28. 179 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/ValidInstanceFeatures.gltf
  29. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAccessorNormalized.gltf
  30. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAccessorNotScalar.gltf
  31. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAttributeInvalidType.gltf
  32. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAttributeInvalidValue.gltf
  33. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf
  34. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf
  35. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMismatch.gltf
  36. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMismatchForNullFeatureId.gltf
  37. 101 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf
  38. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeLabelInvalidType.gltf
  39. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeLabelInvalidValue.gltf
  40. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeNullFeatureIdInvalidType.gltf
  41. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeNullFeatureIdInvalidValue.gltf
  42. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureFeatureCountMismatch.gltf
  43. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureSamplerInvalidFilterMode.gltf
  44. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsInvalidElementType.gltf
  45. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsInvalidType.gltf
  46. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsTooManyChannels.gltf
  47. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsTooManyElements.gltf
  48. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureImageDataInvalid.gltf
  49. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureIndexInvalidType.gltf
  50. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureIndexInvalidValue.gltf
  51. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureInvalidType.gltf
  52. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureTexCoordInvalidType.gltf
  53. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureTexCoordInvalidValue.gltf
  54. 19 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/README.md
  55. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttribute.gltf
  56. 159 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeDefault/ValidFeatureIdAttributeDefault.gltf
  57. BIN
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeDefault/ValidFeatureIdAttributeDefault_data.bin
  58. BIN
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithByteStride.glb
  59. 102 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithLargerFeatureCount.gltf
  60. 103 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithNullFeatureId.gltf
  61. BIN
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTexture.glb
  62. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTexture.gltf
  63. 65 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTextureUsingDefaultChannels.gltf
  64. 275 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf
  65. 328 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyArray.gltf
  66. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidComponentType.gltf
  67. 328 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyInvalidEnumValueType.gltf
  68. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMaxNotInRange.gltf
  69. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyMinNotInRange.gltf
  70. 326 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesClassPropertyString.gltf
  71. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementType.gltf
  72. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementValue.gltf
  73. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidLength.gltf
  74. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesMeshPrimitivePropertyAttributesInvalidType.gltf
  75. 327 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyInvalidAttribute.gltf
  76. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf
  77. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf
  78. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinMismatch.gltf
  79. 339 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf
  80. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMaxNotInRange.gltf
  81. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyMinNotInRange.gltf
  82. 118 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf
  83. 176 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureEnumsInvalidEnumValue.gltf
  84. 114 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeA.gltf
  85. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureInvalidPropertyTypeB.gltf
  86. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTextureTexCoordInvalidValue.gltf
  87. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementType.gltf
  88. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidElementValue.gltf
  89. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidLength.gltf
  90. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTextureMeshPrimitivePropertyTexturesInvalidType.gltf
  91. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyChannelsSizeMismatch.gltf
  92. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidType.gltf
  93. 115 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyIndexInvalidValue.gltf
  94. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxMismatch.gltf
  95. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf
  96. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinMismatch.gltf
  97. 116 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/PropertyTexturePropertyTexturePropertyMinNotInRange.gltf
  98. 21 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/README.md
  99. 173 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataMissingSchema.gltf
  100. 229 0
      tests/SharpGLTF.Cesium.Tests/testfixtures/structuralMetadata/StructuralMetadataSchemaAndSchemaUri.gltf

File diff suppressed because it is too large
+ 195 - 0
src/SharpGLTF.Ext.3DTiles/README.md


+ 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;
+            }
+        }
+
+    }
+}

+ 3 - 100
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.FeatureID.cs

@@ -1,95 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
 namespace SharpGLTF.Schema2.Tiles3D
 namespace SharpGLTF.Schema2.Tiles3D
 {
 {
     using Collections;
     using Collections;
 
 
-    /// <summary>
-    /// Mesh Feature Ids
-    /// </summary>
-    /// <remarks>
-    /// Implemented by <see cref="MeshExtInstanceFeatureID"/> and <see cref="MeshExtMeshFeatureID"/>
-    /// </remarks>
-    public interface IMeshFeatureIDInfo
-    {
-        /// <summary>
-        /// The number of unique features in the attribute or texture.
-        /// </summary>
-        public int FeatureCount { get; set; }
-
-        /// <summary>
-        /// A value that indicates that no feature is associated with this vertex or texel.
-        /// </summary>
-        public int? NullFeatureId { get; set; }
-
-        /// <summary>
-        /// An attribute containing feature IDs. When `attribute` and `texture` are omitted the 
-        /// feature IDs are assigned to vertices by their index.
-        /// </summary>
-        public int? Attribute { get; set; }
-
-        /// <summary>
-        /// A label assigned to this feature ID set. Labels must be alphanumeric identifiers 
-        /// matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
-        /// </summary>
-        public string Label { get; set; }
-
-        /// <summary>
-        /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
-        /// </summary>
-        public int? PropertyTableIndex { get; set; }        
-    }
-
-    public sealed class FeatureIDBuilder : IMeshFeatureIDInfo , IEquatable<IMeshFeatureIDInfo>
-    {
-        public FeatureIDBuilder(int featureCount, string label = null)
-        {
-            FeatureCount = featureCount;
-            Label = label;
-        }
-
-        public FeatureIDBuilder(int featureCount, int attribute, string label = null)
-        {
-            FeatureCount = featureCount;
-            Attribute = attribute;            
-
-            Label = label;
-        }
-
-        public FeatureIDBuilder(PropertyTable table, int? attribute = null, string label = null)
-        {
-            FeatureCount = table.Count;
-            Attribute = attribute;
-            _root = table.LogicalParent;
-            PropertyTableIndex = table.LogicalIndex;
-
-            Label = label;
-        }
-
-        private readonly EXTStructuralMetadataRoot _root;
-
-        public int FeatureCount { get; set; }
-        public int? NullFeatureId { get; set; }
-        public int? Attribute { get; set; }
-        public string Label { get; set; }
-        public int? PropertyTableIndex { get; set; }
-
-        public bool Equals(IMeshFeatureIDInfo other)
-        {
-            if (other == null) return false;
-            if (this.FeatureCount != other.FeatureCount) return false;
-            if (this.NullFeatureId != other.NullFeatureId) return false;
-            if (this.Attribute != other.Attribute) return false;
-            if (this.Label != other.Label) return false;
-            if (this.PropertyTableIndex != other.PropertyTableIndex) return false;
-
-            return true;
-        }
-    }
-
     /// <remarks>
     /// <remarks>
     /// Use <see cref="MeshExtInstanceFeatures.CreateFeatureId"/> to create an instance of this class.
     /// Use <see cref="MeshExtInstanceFeatures.CreateFeatureId"/> to create an instance of this class.
     /// </remarks>    
     /// </remarks>    
@@ -97,17 +12,6 @@ namespace SharpGLTF.Schema2.Tiles3D
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        /*
-        public MeshExtInstanceFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null)
-        {
-            FeatureCount = featureCount;
-            Attribute = attribute;
-            Label = label;
-            PropertyTableIndex = propertyTable;
-            NullFeatureId = nullFeatureId;
-        }*/
-
-
         internal MeshExtInstanceFeatureID() { }
         internal MeshExtInstanceFeatureID() { }
 
 
         #endregion
         #endregion
@@ -259,9 +163,6 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
         #region API
         #region API
 
 
-        // question: is _texture required to always exist? if that would be the case, then it should be created
-        // in the constructor an exposed as a read only property.
-
         /// <summary>
         /// <summary>
         /// Gets a texture containing feature IDs.
         /// Gets a texture containing feature IDs.
         /// </summary>
         /// </summary>
@@ -330,6 +231,8 @@ namespace SharpGLTF.Schema2.Tiles3D
             return LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent;
             return LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent;
         }
         }
 
 
+        public IReadOnlyList<int> GetChannels() => _channels;
+
         public void SetChannels(IReadOnlyList<int> channels)
         public void SetChannels(IReadOnlyList<int> channels)
         {
         {
             Guard.NotNullOrEmpty(channels, nameof(channels));
             Guard.NotNullOrEmpty(channels, nameof(channels));

+ 90 - 20
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.Features.cs

@@ -24,7 +24,7 @@ namespace SharpGLTF.Schema2
 
 
         internal static void ValidateFeatureIdContent(this IMeshFeatureIDInfo featureId)
         internal static void ValidateFeatureIdContent(this IMeshFeatureIDInfo featureId)
         {
         {
-            Guard.MustBeGreaterThanOrEqualTo((int)featureId.FeatureCount, 1, nameof(featureId.FeatureCount));
+            Guard.MustBeGreaterThanOrEqualTo(featureId.FeatureCount, 1, nameof(featureId.FeatureCount));
 
 
             if (featureId.NullFeatureId.HasValue)
             if (featureId.NullFeatureId.HasValue)
             {
             {
@@ -35,7 +35,6 @@ namespace SharpGLTF.Schema2
                 var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
                 var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
                 Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(featureId.Label, regex), nameof(featureId.Label));
                 Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(featureId.Label, regex), nameof(featureId.Label));
             }
             }
-
             if (featureId.Attribute.HasValue)
             if (featureId.Attribute.HasValue)
             {
             {
                 Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
                 Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
@@ -68,18 +67,28 @@ namespace SharpGLTF.Schema2
         /// <summary>
         /// <summary>
         /// Adds the FeatureIds to a MeshPrimitive
         /// Adds the FeatureIds to a MeshPrimitive
         /// </summary>        
         /// </summary>        
-        public static MeshExtMeshFeatureID[] AddMeshFeatureIds(this MeshPrimitive primitive, params (IMeshFeatureIDInfo fid, Texture tex, IReadOnlyList<int> channels)[] featureIds)
+        public static MeshExtMeshFeatureID[] AddMeshFeatureIds(this MeshPrimitive primitive, params IMeshFeatureIDInfo[] fids)
         {
         {
-            if (featureIds == null || featureIds.Length == 0) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return Array.Empty<MeshExtMeshFeatureID>(); }
+            if (fids == null || fids.Length == 0) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return Array.Empty<MeshExtMeshFeatureID>(); }
 
 
             var ext = primitive.UseExtension<MeshExtMeshFeatures>();
             var ext = primitive.UseExtension<MeshExtMeshFeatures>();
 
 
-            var result = new MeshExtMeshFeatureID[featureIds.Length];
+            var result = new MeshExtMeshFeatureID[fids.Length];
 
 
             for (int i = 0; i < result.Length; ++i)
             for (int i = 0; i < result.Length; ++i)
             {
             {
-                var (fid, tex, channels) = featureIds[i];
-                result[i] = ext.CreateFeatureID(fid, tex, channels);
+                var fid = fids[i];
+                result[i] = ext.CreateFeatureID(fid);
+
+                if (fid is FeatureIDBuilder builder && builder.Texture != null)
+                {
+                    var texture = result[i].UseTexture();
+                    texture.Texture = builder.Texture;
+                    if (builder.Channels != null)
+                    {
+                        texture.SetChannels(builder.Channels);
+                    }
+                }
             }
             }
 
 
             return result;
             return result;
@@ -226,7 +235,7 @@ namespace SharpGLTF.Schema2
 
 
             #region API
             #region API
 
 
-            public MeshExtMeshFeatureID CreateFeatureID(IMeshFeatureIDInfo properties, Texture texture = null, IReadOnlyList<int> texChannels = null)
+            public MeshExtMeshFeatureID CreateFeatureID(IMeshFeatureIDInfo properties)
             {
             {
                 var instance = CreateFeatureID();
                 var instance = CreateFeatureID();
 
 
@@ -236,13 +245,6 @@ namespace SharpGLTF.Schema2
                 instance.Attribute = properties.Attribute;
                 instance.Attribute = properties.Attribute;
                 instance.PropertyTableIndex = properties.PropertyTableIndex;
                 instance.PropertyTableIndex = properties.PropertyTableIndex;
 
 
-                if (texture != null)
-                {
-                    var texInfo = instance.UseTexture();
-                    texInfo.Texture = texture;
-                    if (texChannels != null) texInfo.SetChannels(texChannels);
-                }
-
                 return instance;
                 return instance;
             }
             }
 
 
@@ -264,7 +266,8 @@ namespace SharpGLTF.Schema2
                     if (featureId.Attribute.HasValue)
                     if (featureId.Attribute.HasValue)
                     {
                     {
                         var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
                         var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
-                        Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
+                        var featureIdVertex = _meshPrimitive.GetVertexAccessor(expectedVertexAttribute);
+                        Guard.NotNull(featureIdVertex, expectedVertexAttribute, $"The primitive should have custom vertex attribute {expectedVertexAttribute}.");
                     }
                     }
 
 
                     featureId.ValidateFeatureIdReferences(_meshPrimitive.LogicalParent.LogicalParent);
                     featureId.ValidateFeatureIdReferences(_meshPrimitive.LogicalParent.LogicalParent);
@@ -274,7 +277,7 @@ namespace SharpGLTF.Schema2
                     if (texture != null)
                     if (texture != null)
                     {
                     {
                         var expectedTexCoordAttribute = $"TEXCOORD_{texture.TextureCoordinate}";
                         var expectedTexCoordAttribute = $"TEXCOORD_{texture.TextureCoordinate}";
-                        Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
+                        Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute, $"The primitive should have texture {expectedTexCoordAttribute}.");
 
 
                         var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
                         var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
                         validate.IsNullOrIndex(nameof(texture), texture.TextureCoordinate, modelRoot.LogicalTextures);
                         validate.IsNullOrIndex(nameof(texture), texture.TextureCoordinate, modelRoot.LogicalTextures);
@@ -286,14 +289,81 @@ namespace SharpGLTF.Schema2
 
 
             protected override void OnValidateContent(ValidationContext validate)
             protected override void OnValidateContent(ValidationContext validate)
             {
             {
-                var extMeshFeatures = _meshPrimitive.Extensions.Where(item => item is MeshExtMeshFeatures).FirstOrDefault();
-                validate.NotNull(nameof(extMeshFeatures), extMeshFeatures);
                 validate.NotNull(nameof(FeatureIds), _featureIds);
                 validate.NotNull(nameof(FeatureIds), _featureIds);
                 validate.IsTrue(nameof(FeatureIds), _featureIds.Count > 0, "FeatureIds has items");
                 validate.IsTrue(nameof(FeatureIds), _featureIds.Count > 0, "FeatureIds has items");
 
 
                 foreach (var featureId in _featureIds)
                 foreach (var featureId in _featureIds)
                 {
                 {
                     featureId.ValidateFeatureIdContent();
                     featureId.ValidateFeatureIdContent();
+
+                    if (featureId.Attribute != null)
+                    {
+                        var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
+                        var vertex = _meshPrimitive.GetVertexAccessor(expectedVertexAttribute);
+                        Guard.IsTrue(!vertex.Normalized, expectedVertexAttribute, $"The custom vertex attribute {expectedVertexAttribute} should not be normalized.");
+
+                        var distinctFeatureIds = vertex.AsScalarArray().Distinct().ToList();
+
+                        if (featureId.NullFeatureId.HasValue)
+                        {
+                            distinctFeatureIds.Remove(featureId.NullFeatureId.Value);
+                        }
+
+                        if (distinctFeatureIds.Min() < 0)
+                        {
+                            Guard.IsTrue(distinctFeatureIds.Min() >= 0, nameof(distinctFeatureIds), $"FeatureIds must be equal or larger than 0, but contains {distinctFeatureIds.Min()}");
+                        }
+
+                        var count = distinctFeatureIds.Count();
+
+                        // FeatureCount must be greater or equal to the number of distinct feature ids
+                        Guard.IsTrue(featureId.FeatureCount >= count, $"Mismatch between FeatureCount ({featureId.FeatureCount}) and Feature Attribute ({count})");
+
+                        if (featureId.PropertyTableIndex.HasValue)
+                        {
+                            var root = _meshPrimitive.LogicalParent.LogicalParent;
+                            var metadataExtension = root.GetExtension<EXTStructuralMetadataRoot>();
+                            var propertyTable = metadataExtension.PropertyTables[featureId.PropertyTableIndex.Value];
+                            var propertyCount = propertyTable.Count;
+                            Guard.IsTrue(distinctFeatureIds.Max() <= propertyCount - 1, nameof(propertyCount),
+                                $"The feature ID refers to a property table with {propertyCount} rows, so the feature IDs must be in the range [0,{propertyCount-1}], but the feature ID attribute contains values [{distinctFeatureIds.Min()},{distinctFeatureIds.Max()}]");
+                        }
+
+
+                    }
+                    var texture = featureId.GetTexture();
+                    if (texture != null)
+                    {
+                        var expectedTexCoordAttribute = $"TEXCOORD_{texture.TextureCoordinate}";
+                        var vertex = _meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute);
+                        var distinctFeatureIds = vertex.AsVector2Array().Count();
+
+                        Guard.IsTrue(featureId.FeatureCount == distinctFeatureIds, $"Mismatch between FeatureCount ({featureId.FeatureCount}) and Feature Texture ({distinctFeatureIds})");
+
+                        var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
+
+                        var samplers = modelRoot.LogicalTextureSamplers;
+                        foreach (var sampler in samplers)
+                        {
+                            Guard.IsTrue(sampler.MagFilter == TextureInterpolationFilter.NEAREST, $"Texture magnification filter must be 9728 (NEAREST) but is set to {sampler.MagFilter}");
+                            Guard.IsTrue(sampler.MinFilter == TextureMipMapFilter.NEAREST, $"Texture minification filtering must be 9728 (NEAREST) but is set to {sampler.MinFilter}");
+                        }
+
+                        // check on channels as workaround
+                        // better solution: read the channels of the used texture using image library
+                        var logicalTexture = modelRoot.LogicalTextures[texture.TextureCoordinate];
+                        // var image = logicalTexture.PrimaryImage;
+                        var channels = texture.GetChannels();
+
+                        // chack that the length of channels list is maximum 4
+                        Guard.IsTrue(channels.Count <= 4, $"The number of channels must be maximum 4 but is {channels.Count}");
+
+                        // check that value in channels is minimum 0 and maximum 3
+                        foreach (var channel in channels)
+                        {
+                            Guard.IsTrue(channel >= 0 && channel <= 3, $"Channel value must be between 0 and 3 but has channel {channel}");
+                        }
+                    }
                 }
                 }
 
 
                 base.OnValidateContent(validate);
                 base.OnValidateContent(validate);
@@ -302,5 +372,5 @@ namespace SharpGLTF.Schema2
             #endregion
             #endregion
         }
         }
 
 
-    }    
+    }
 }
 }

+ 173 - 14
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -10,12 +10,6 @@ namespace SharpGLTF.Schema2
 
 
     partial class Tiles3DExtensions
     partial class Tiles3DExtensions
     {
     {
-        // TODO: PropertyTexture is taken from a Schema, but it is possible the schema is an external file,
-        // in which case we could not have a PopertyTexture, just a blind ID
-
-        // Solution1: enforce loading the schema as part of the memory document
-        // Solution2: allow the API to be OneOf<int,PropertyTexture>
-
         public static void AddPropertyTexture(this MeshPrimitive primitive, PropertyTexture texture)
         public static void AddPropertyTexture(this MeshPrimitive primitive, PropertyTexture texture)
         {
         {
             var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
             var ext = primitive.UseExtension<ExtStructuralMetadataMeshPrimitive>();
@@ -75,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);
             }
             }
 
 
@@ -97,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);
             }
             }
 
 
@@ -112,22 +106,187 @@ 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");
+
+                ValidatePropertyTexturesReferences(validate, rootMetadata);
+
+                ValidatePropertyAttributesReferences(validate, rootMetadata);
+
+                base.OnValidateReferences(validate);
+            }
+
+            protected override void OnValidateContent(ValidationContext validate)
+            {
+                var rootMetadata = _GetModelRoot().GetExtension<EXTStructuralMetadataRoot>();
+                var propertyTextures = rootMetadata.PropertyTextures;
+
+                ValidatePropertyTexturesContent(rootMetadata, propertyTextures);
+
+                ValidatePropertyAttributesContent(rootMetadata);
+
+                base.OnValidateContent(validate);
+            }
+
+            private void ValidatePropertyAttributesReferences(ValidationContext validate, EXTStructuralMetadataRoot rootMetadata)
+            {
+                foreach (var propertyAttribute in _propertyAttributes)
+                {
+                    var propertyAttributes = rootMetadata.PropertyAttributes;
+                    validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, 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)
+                    {
+                        var expectedVertexAttribute = property.Value.Attribute;
+                        Guard.NotNull(meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute, $"The primitive should have custom vertex attribute {expectedVertexAttribute}.");
+
+                        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");
+
+                        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}");
+                        }
+                    }
+                }
+            }
+
+            private void ValidatePropertyTexturesReferences(ValidationContext validate, EXTStructuralMetadataRoot rootMetadata)
+            {
+                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}.");
+                    }
                 }
                 }
+            }
 
 
+            private void ValidatePropertyAttributesContent(EXTStructuralMetadataRoot rootMetadata)
+            {
                 foreach (var propertyAttribute in _propertyAttributes)
                 foreach (var propertyAttribute in _propertyAttributes)
                 {
                 {
                     var propertyAttributes = rootMetadata.PropertyAttributes;
                     var propertyAttributes = rootMetadata.PropertyAttributes;
-                    validate.IsNullOrIndex(nameof(propertyAttribute), propertyAttribute, propertyAttributes);
+                    var schema = rootMetadata.Schema;
+                    var schemaAttribute = propertyAttributes[propertyAttribute];
+
+                    var className = schemaAttribute.ClassName;
+                    schema.Classes.TryGetValue(className, out var classDefinition);
+
+                    foreach (var property in schemaAttribute.Properties)
+                    {
+                        var key = property.Key;
+
+                        var acc = property.Value.Attribute;
+                        var vertexAccessor = meshPrimitive.GetVertexAccessor(acc);
+
+                        // Todo: check min, max, scale, offset of propertyAttributeProperty
+
+                        classDefinition.Properties.TryGetValue(key, out var propertyDefinition);
+
+                        //  Todo: check min, max, scale, offset of StructuralMetadaClassProperty
+
+                        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 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;
+                            schema.Enums.TryGetValue(enumType, out var enumDefinition);
+                            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);
-            }            
+            private void ValidatePropertyTexturesContent(EXTStructuralMetadataRoot rootMetadata, IReadOnlyList<PropertyTexture> propertyTextures)
+            {
+                foreach (var propertyTexture in _propertyTextures)
+                {
+                    var schemaTexture = propertyTextures[propertyTexture];
+
+                    var className = schemaTexture.ClassName;
+                    foreach (var property in schemaTexture.Properties)
+                    {
+                        // use for validation of texture
+                        // var textureCoordinate = property.Value.TextureCoordinate;
+                        // var expectedVertexAttribute = "TEXCOORD_" + textureCoordinate;
+
+                        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");
+                        }
+
+                        // todo: check used values in texture against min, max (using scale and offset)
+                        // var min = schemaProperty.Min;
+                        // var max = schemaProperty.Max;
+                        // var scale = schemaProperty.Scale;
+                        // var offset = schemaProperty.Offset;
+
+                        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}");
+                        }
+                    }
 
 
+                }
+            }
             #endregion
             #endregion
         }
         }
     }
     }

+ 392 - 59
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -11,14 +11,16 @@ namespace SharpGLTF.Schema2
     using Collections;
     using Collections;
     using Memory;
     using Memory;
     using Validation;
     using Validation;
-    using Tiles3D;    
+    using Tiles3D;
+    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
@@ -60,11 +62,13 @@ namespace SharpGLTF.Schema2
 
 
             #region properties
             #region properties
 
 
+            /**
             internal string SchemaUri
             internal string SchemaUri
             {
             {
                 get => _schemaUri;
                 get => _schemaUri;
                 set { _schemaUri = value; }
                 set { _schemaUri = value; }
             }
             }
+            */
 
 
             internal StructuralMetadataSchema Schema
             internal StructuralMetadataSchema Schema
             {
             {
@@ -95,14 +99,23 @@ namespace SharpGLTF.Schema2
                 return schema;
                 return schema;
             }
             }
 
 
+            /**
+            // Sets the schema to use an external schema, returns an empty schema to used for adding schema properties
+            public StructuralMetadataSchema UseExternalSchema(Uri uri)
+            {
+                SchemaUri = uri.ToString();
+                return new StructuralMetadataSchema();
+            }
+            */
+
             public StructuralMetadataSchema UseEmbeddedSchema()
             public StructuralMetadataSchema UseEmbeddedSchema()
             {
             {
-                this.SchemaUri = null;
+                // SchemaUri = null;
 
 
                 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)
             {
             {
@@ -116,18 +129,18 @@ 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, string name = null)
             {
             {
                 var table = AddPropertyTable();
                 var table = AddPropertyTable();
                 table.ClassInstance = schemaClass;
                 table.ClassInstance = schemaClass;
-                if (featureCount != null) table.Count = featureCount.Value;
+                table.Count = featureCount;
                 table.Name = name;
                 table.Name = name;
                 return table;
                 return table;
             }
             }
 
 
-            public PropertyTable AddPropertyTable()
+            private PropertyTable AddPropertyTable()
             {
             {
                 var prop = new PropertyTable();
                 var prop = new PropertyTable();
                 _propertyTables.Add(prop);
                 _propertyTables.Add(prop);
@@ -137,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;
             }
             }
 
 
@@ -146,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
 
 
@@ -154,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)
@@ -249,8 +269,8 @@ namespace SharpGLTF.Schema2
                 }
                 }
 
 
                 // Check one of schema or schemaUri is defined, but not both
                 // Check one of schema or schemaUri is defined, but not both
-                Guard.IsFalse(Schema != null && SchemaUri != null, "Schema/SchemaUri", "Schema and SchemaUri cannot both be defined");
-                Guard.IsFalse(Schema == null && SchemaUri == null, "Schema/SchemaUri", "One of Schema and SchemaUri must be defined");
+                // Guard.IsFalse(Schema != null && SchemaUri != null, "Schema/SchemaUri", "Schema and SchemaUri cannot both be defined");
+                // Guard.IsFalse(Schema == null && SchemaUri == null, "Schema/SchemaUri", "One of Schema and SchemaUri must be defined");
 
 
                 base.OnValidateContent(result);
                 base.OnValidateContent(result);
             }
             }
@@ -313,7 +333,7 @@ namespace SharpGLTF.Schema2
                     {
                     {
                         return schema.Classes[ClassName];
                         return schema.Classes[ClassName];
                     }
                     }
-                    else return null;                    
+                    else return null;
                 }
                 }
                 set
                 set
                 {
                 {
@@ -338,7 +358,7 @@ namespace SharpGLTF.Schema2
                 return property;
                 return property;
             }
             }
 
 
-            public PropertyTextureProperty CreateProperty(string key)
+            private PropertyTextureProperty CreateProperty(string key)
             {
             {
                 var property = new PropertyTextureProperty();
                 var property = new PropertyTextureProperty();
                 _properties[key] = property;
                 _properties[key] = property;
@@ -387,7 +407,7 @@ namespace SharpGLTF.Schema2
                     _channels.Clear();
                     _channels.Clear();
                     _channels.AddRange(value);
                     _channels.AddRange(value);
                 }
                 }
-            }            
+            }
 
 
             public Schema2.Texture Texture
             public Schema2.Texture Texture
             {
             {
@@ -517,6 +537,31 @@ namespace SharpGLTF.Schema2
                 set => _attribute = value;
                 set => _attribute = value;
             }
             }
 
 
+            /** Commented out for now, as it is not supported
+            public JsonNode Min
+            {
+                get => _min;
+                set => _min = value;
+            }
+
+            public JsonNode Max
+            {
+                get => _max;
+                set => _max = value;
+            }
+
+            public JsonNode Scale
+            {
+                get => _scale;
+                set => _scale = value;
+            }
+
+            public JsonNode Offset
+            {
+                get => _offset;
+                set => _offset = value;
+            }
+            */
             #endregion
             #endregion
         }
         }
 
 
@@ -531,7 +576,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()
             {
             {
@@ -589,7 +634,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 get => _name;
                 get => _name;
                 set => _name = value;
                 set => _name = value;
-            }            
+            }
 
 
             public int Count
             public int Count
             {
             {
@@ -619,7 +664,7 @@ namespace SharpGLTF.Schema2
                 value = new PropertyTableProperty();
                 value = new PropertyTableProperty();
                 _properties[key] = value;
                 _properties[key] = value;
                 return value;
                 return value;
-            }            
+            }
 
 
             #endregion
             #endregion
         }
         }
@@ -672,35 +717,53 @@ namespace SharpGLTF.Schema2
 
 
             private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent.LogicalParent;
             private ModelRoot _GetModelRoot() => LogicalParent.LogicalParent.LogicalParent;
 
 
-            public void SetValues2D<T>(List<List<T>> values, bool CreateArrayOffsets = true)
+            public void SetArrayValues<T>(List<List<T>> values)
             {
             {
+                Guard.IsTrue(values.Count == LogicalParent.Count, nameof(values), $"Values must have length {LogicalParent.Count}");
+
+                var className = LogicalParent.ClassName;
+                var metadataProperty = GetProperty<T>(className, LogicalKey);
+
+                metadataProperty.Array = true;
+
                 var root = _GetModelRoot();
                 var root = _GetModelRoot();
-                
+
                 int logicalIndex = GetBufferView(root, values);
                 int logicalIndex = GetBufferView(root, values);
-                this.Values = logicalIndex;
+                Values = logicalIndex;
 
 
-                if (CreateArrayOffsets)
+                var hasVariableLength = HasVariableLength<T>(values);
+
+                if (HasVariableLength<T>(values) || typeof(T) == typeof(string))
                 {
                 {
+                    // if the array has items of variable length, create arraysOffsets bufferview
                     var arrayOffsets = BinaryTable.GetArrayOffsets(values);
                     var arrayOffsets = BinaryTable.GetArrayOffsets(values);
                     int logicalIndexOffsets = GetBufferView(root, arrayOffsets);
                     int logicalIndexOffsets = GetBufferView(root, arrayOffsets);
-                    this.ArrayOffsets = logicalIndexOffsets;
+                    ArrayOffsets = logicalIndexOffsets;
 
 
                     if (typeof(T) == typeof(string))
                     if (typeof(T) == typeof(string))
                     {
                     {
                         var stringValues = values.ConvertAll(x => x.ConvertAll(y => (string)Convert.ChangeType(y, typeof(string), CultureInfo.InvariantCulture)));
                         var stringValues = values.ConvertAll(x => x.ConvertAll(y => (string)Convert.ChangeType(y, typeof(string), CultureInfo.InvariantCulture)));
                         var stringOffsets = BinaryTable.GetStringOffsets(stringValues);
                         var stringOffsets = BinaryTable.GetStringOffsets(stringValues);
                         int offsets = GetBufferView(root, stringOffsets);
                         int offsets = GetBufferView(root, stringOffsets);
-                        this.StringOffsets = offsets;
+                        StringOffsets = offsets;
                     }
                     }
-                }                
+                }
+                else
+                {
+                    metadataProperty.Count = values[0].Count;
+                }
             }
             }
 
 
-            public void SetValues1D<T>(params T[] values)
+            public void SetValues<T>(params T[] values)
             {
             {
+                Guard.IsTrue(values.Length == LogicalParent.Count, nameof(values), $"Values must have length {LogicalParent.Count}");
+                var className = LogicalParent.ClassName;
+                GetProperty<T>(className, LogicalKey);
+
                 var root = _GetModelRoot();
                 var root = _GetModelRoot();
-                
+
                 int logicalIndex = GetBufferView(root, values);
                 int logicalIndex = GetBufferView(root, values);
-                this.Values = logicalIndex;
+                Values = logicalIndex;
 
 
                 if (typeof(T) == typeof(string))
                 if (typeof(T) == typeof(string))
                 {
                 {
@@ -710,8 +773,116 @@ namespace SharpGLTF.Schema2
 
 
                     var stringOffsets = BinaryTable.GetStringOffsets(stringvalues);
                     var stringOffsets = BinaryTable.GetStringOffsets(stringvalues);
                     int offsets = GetBufferView(root, stringOffsets);
                     int offsets = GetBufferView(root, stringOffsets);
-                    this.StringOffsets = offsets;
-                }                
+                    StringOffsets = offsets;
+                }
+            }
+
+            private StructuralMetadataClassProperty GetProperty<T>(string className, string key)
+            {
+                var metadataClass = LogicalParent.LogicalParent.Schema.Classes[className];
+                Guard.IsTrue(metadataClass != null, nameof(className), $"Schema class {className} must be defined");
+                metadataClass.Properties.TryGetValue(key, out var metadataProperty);
+                Guard.IsTrue(metadataProperty != null, nameof(key), $"Property {key} in {className} must be defined");
+
+                CheckElementTypes<T>(metadataProperty);
+
+                return metadataProperty;
+            }
+
+            private bool HasVariableLength<T>(List<List<T>> values)
+            {
+                int length = values[0].Count;
+                for (int i = 1; i < values.Count; i++)
+                {
+                    if (values[i].Count != length) return true;
+                }
+                return false;
+            }
+
+
+            private void CheckElementTypes<T>(StructuralMetadataClassProperty metadataProperty)
+            {
+                var elementType = metadataProperty.Type;
+
+                if (elementType == ELEMENTTYPE.ENUM)
+                {
+                    // guard the type of t is an short in case of enum
+                    Guard.IsTrue(typeof(T) == typeof(short), nameof(T), $"Enum value type of {LogicalKey} must be short");
+                }
+                else if (elementType == ELEMENTTYPE.SCALAR)
+                {
+                    var componentType = metadataProperty.ComponentType;
+                    CheckScalarTypes<T>(componentType);
+                }
+                else if (elementType == ELEMENTTYPE.STRING)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(string), nameof(T), $"String type of property {LogicalKey} must be string");
+                }
+                else if (elementType == ELEMENTTYPE.BOOLEAN)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(bool), nameof(T), $"Boolean type of property {LogicalKey} must beboolean");
+                }
+                else if (elementType == ELEMENTTYPE.VEC2)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(Vector2), nameof(T), $"Vector2 type of property {LogicalKey} must be Vector2");
+                }
+                else if (elementType == ELEMENTTYPE.VEC3)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(Vector3), nameof(T), $"Vector3 type of property {LogicalKey} must be Vector3");
+                }
+                else if (elementType == ELEMENTTYPE.VEC3)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(Vector4), nameof(T), $"Vector4 type of property {LogicalKey} must be Vector4");
+                }
+                // todo: add MAT2, MAT3
+                else if (elementType == ELEMENTTYPE.MAT4)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(Matrix4x4), nameof(T), $"Matrix4x4 type of property {LogicalKey} must be Matrix4x4");
+                }
+            }
+
+            private void CheckScalarTypes<T>(DATATYPE? componentType)
+            {
+                if (componentType == DATATYPE.INT8)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(sbyte), nameof(T), $"Scalar value type of property {LogicalKey} must be sbyte");
+                }
+                else if (componentType == DATATYPE.UINT8)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(byte), nameof(T), $"Scalar value type of property {LogicalKey} must be byte");
+                }
+                else if (componentType == DATATYPE.INT16)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(short), nameof(T), $"Scalar value type of property {LogicalKey} must be short");
+                }
+                else if (componentType == DATATYPE.UINT16)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(ushort), nameof(T), $"Scalar value type of property {LogicalKey} must be ushort");
+                }
+                else if (componentType == DATATYPE.INT32)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(int), nameof(T), $"Scalar value type of property {LogicalKey} must be int");
+                }
+                else if (componentType == DATATYPE.UINT32)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(uint), nameof(T), $"Scalar value type of property {LogicalKey} must be uint");
+                }
+                else if (componentType == DATATYPE.INT64)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(long), nameof(T), $"Scalar value type of property {LogicalKey} must be long");
+                }
+                else if (componentType == DATATYPE.UINT64)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(ulong), nameof(T), $"Scalar value type of property {LogicalKey} must be ulong");
+                }
+                else if (componentType == DATATYPE.FLOAT32)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(Single), nameof(T), $"Scalar value type of property {LogicalKey} must be float");
+                }
+                else if (componentType == DATATYPE.FLOAT64)
+                {
+                    Guard.IsTrue(typeof(T) == typeof(double), nameof(T), $"Scalar value type of property {LogicalKey} must be double");
+                }
             }
             }
 
 
             private static int GetBufferView<T>(ModelRoot model, IReadOnlyList<T> values)
             private static int GetBufferView<T>(ModelRoot model, IReadOnlyList<T> values)
@@ -764,7 +935,7 @@ namespace SharpGLTF.Schema2
 
 
             void IChildOf<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent)
             void IChildOf<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent)
             {
             {
-                LogicalParent = parent;                
+                LogicalParent = parent;
             }
             }
 
 
             #endregion
             #endregion
@@ -814,7 +985,7 @@ namespace SharpGLTF.Schema2
             public StructuralMetadataEnum UseEnumMetadata(string key, params (string name, int value)[] enumValues)
             public StructuralMetadataEnum UseEnumMetadata(string key, params (string name, int value)[] enumValues)
             {
             {
                 var enumType = UseEnumMetadata(key);
                 var enumType = UseEnumMetadata(key);
-                foreach(var (name, value) in enumValues)
+                foreach (var (name, value) in enumValues)
                 {
                 {
                     enumType.AddEnum(name, value);
                     enumType.AddEnum(name, value);
                 }
                 }
@@ -864,6 +1035,7 @@ namespace SharpGLTF.Schema2
 
 
             public IReadOnlyList<StructuralMetadataEnumValue> Values => _values;
             public IReadOnlyList<StructuralMetadataEnumValue> Values => _values;
 
 
+
             public string Name
             public string Name
             {
             {
                 get => _name;
                 get => _name;
@@ -875,6 +1047,11 @@ namespace SharpGLTF.Schema2
                 set => _description = value;
                 set => _description = value;
             }
             }
 
 
+            public IntegerType? ValueType
+            {
+                get => _valueType;
+            }
+
             #endregion
             #endregion
 
 
             #region API
             #region API
@@ -991,10 +1168,15 @@ namespace SharpGLTF.Schema2
 
 
             #region API
             #region API
 
 
-            public StructuralMetadataClass WithNameAndDesc(string name, string desc = null)
+            public StructuralMetadataClass WithName(string name)
+            {
+                Name = name;
+                return this;
+            }
+
+            public StructuralMetadataClass WithDescription(string description)
             {
             {
-                this.Name = name;
-                this.Description = desc;
+                Description = description;
                 return this;
                 return this;
             }
             }
 
 
@@ -1017,7 +1199,7 @@ namespace SharpGLTF.Schema2
                 return LogicalParent.LogicalParent.AddPropertyAttribute(this);
                 return LogicalParent.LogicalParent.AddPropertyAttribute(this);
             }
             }
 
 
-            public PropertyTable AddPropertyTable(int? featureCount = null, string name = null)
+            public PropertyTable AddPropertyTable(int featureCount, string name = null)
             {
             {
                 return LogicalParent.LogicalParent.AddPropertyTable(this, featureCount, name);
                 return LogicalParent.LogicalParent.AddPropertyTable(this, featureCount, name);
             }
             }
@@ -1057,7 +1239,7 @@ namespace SharpGLTF.Schema2
                 set => _description = value;
                 set => _description = value;
             }
             }
 
 
-            public ELEMENTTYPE Type
+            internal ELEMENTTYPE Type
             {
             {
                 get => _type;
                 get => _type;
                 set => _type = value;
                 set => _type = value;
@@ -1066,7 +1248,7 @@ namespace SharpGLTF.Schema2
             public string EnumType
             public string EnumType
             {
             {
                 get => _enumType;
                 get => _enumType;
-                set => _enumType = value;
+                // set => _enumType = value;
             }
             }
 
 
             public DATATYPE? ComponentType
             public DATATYPE? ComponentType
@@ -1099,50 +1281,201 @@ namespace SharpGLTF.Schema2
                 set => _count = value;
                 set => _count = value;
             }
             }
 
 
+            /** Commented out for now, as it is not supported
+            public JsonNode Min
+            {
+                get => _min;
+                set => _min = value;
+            }
+
+            public JsonNode Max
+            {
+                get => _max;
+                set => _max = value;
+            }
+
+            public JsonNode Scale
+            {
+                get => _scale;
+                set => _scale = value;
+            }
+
+            public JsonNode Offset
+            {
+                get => _offset;
+                set => _offset = value;
+            }
+            */
+
+
             #endregion
             #endregion
 
 
             #region API
             #region API
 
 
-            public StructuralMetadataClassProperty WithNameAndDesc(string name, string desc = null)
+
+            public StructuralMetadataClassProperty WithName(string name)
+            {
+                Name = name;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithDescription(string description)
+            {
+                Description = description;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithStringType()
+            {
+                Type = ElementType.STRING;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithBooleanType()
+            {
+                Type = ElementType.BOOLEAN;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithUInt8Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.UINT8;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithInt8Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.INT8;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithUInt16Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.UINT16;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithInt16Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.INT16;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithUInt32Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.UINT32;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithInt32Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.INT32;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithUInt64Type()
+            {
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.UINT64;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithInt64Type()
             {
             {
-                this.Name = name;
-                this.Description = desc;
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.INT64;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null, bool normalized = false)
+            public StructuralMetadataClassProperty WithFloat32Type()
             {
             {
-                this.Type = etype;
-                this.ComponentType = ctype;
-                this.Normalized = normalized;
-                this.Array = false;
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.FLOAT32;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, bool normalized = false, int? count = null)
+            public StructuralMetadataClassProperty WithFloat64Type()
             {
             {
-                this.Type = etype;
-                this.ComponentType = ctype;
-                this.Normalized = normalized;
-                this.Array = true;
-                this.Count = count;
+                Type = ELEMENTTYPE.SCALAR;
+                ComponentType = DATATYPE.FLOAT64;
                 return this;
                 return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumType, int? count = null)
+
+            public StructuralMetadataClassProperty WithVector3Type()
+            {
+                Type = ElementType.VEC3;
+                ComponentType = DataType.FLOAT32;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithMatrix4x4Type()
             {
             {
-                return WithEnumArrayType(enumType.LogicalKey, count);
+                Type = ElementType.MAT4;
+                ComponentType = DataType.FLOAT32;
+                return this;
             }
             }
 
 
-            public StructuralMetadataClassProperty WithEnumArrayType(string enumType, int? count = null)
+            public StructuralMetadataClassProperty WithCount()
             {
             {
-                this.Type = ELEMENTTYPE.ENUM;
-                this.EnumType = enumType;                
-                this.Array = true;
-                this.Count = count;
+                Type = ElementType.MAT4;
+                ComponentType = DataType.FLOAT32;
                 return this;
                 return this;
             }
             }
 
 
+
+            //public StructuralMetadataClassProperty WithValueType(ELEMENTTYPE etype, DATATYPE? ctype = null)
+            //{
+            //    Type = etype;
+            //    ComponentType = ctype;
+            //    Array = false;
+            //    return this;
+            //}
+
+            public StructuralMetadataClassProperty WithArrayType(ELEMENTTYPE etype, DATATYPE? ctype = null, int? count = null)
+            {
+                Type = etype;
+                ComponentType = ctype;
+                Array = true;
+                Count = count;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithEnumArrayType(StructuralMetadataEnum enumeration, int? count = null)
+            {
+                Type = ELEMENTTYPE.ENUM;
+                _enumType = enumeration.LogicalKey;
+                Array = true;
+                Count = count;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithEnumeration(StructuralMetadataEnum enumeration)
+            {
+                Type = ELEMENTTYPE.ENUM;
+                _enumType = enumeration.LogicalKey;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithRequired(bool required)
+            {
+                Required = required;
+                return this;
+            }
+
+            public StructuralMetadataClassProperty WithNormalized(bool normalized)
+            {
+                Normalized = normalized;
+                return this;
+            }
+
+
             #endregion
             #endregion
         }
         }
 
 

+ 63 - 0
src/SharpGLTF.Ext.3DTiles/Schema2/FeatureIDBuilder.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using OneOf;
+
+namespace SharpGLTF.Schema2.Tiles3D
+{
+    public sealed class FeatureIDBuilder : IMeshFeatureIDInfo, IEquatable<IMeshFeatureIDInfo>
+    {
+        public FeatureIDBuilder(int featureCount, OneOf<int, Texture>? attributeOrTexture = null, PropertyTable propertyTable = null, IReadOnlyList<int> channels = null, string label = null, int? nullFeatureId = null)
+        {
+            Guard.MustBeGreaterThanOrEqualTo(featureCount, 1, nameof(featureCount));
+            FeatureCount = featureCount;
+
+            if (attributeOrTexture != null)
+            {
+                attributeOrTexture.Value.Switch(
+                    attribute => Attribute = attribute,
+                    texture =>
+                    {
+                        Texture = texture;
+                        Channels = channels ?? new[] { 0 };
+                    }
+                );
+            }
+
+            if (propertyTable != null)
+            {
+                PropertyTableIndex = propertyTable.LogicalIndex;
+            }
+
+            Label = label;
+            NullFeatureId = nullFeatureId;
+        }
+
+        public int FeatureCount { get; set; }
+        public int? NullFeatureId { get; set; }
+        public int? Attribute { get; set; }
+        public string Label { get; set; }
+
+        public int? PropertyTableIndex { get; set; }
+
+        public Texture Texture { get; set;  }
+        public IReadOnlyList<int> Channels { get; set; }
+
+        public bool Equals(IMeshFeatureIDInfo other)
+        {
+            if (other == null) return false;
+            if (FeatureCount != other.FeatureCount) return false;
+            if (NullFeatureId != other.NullFeatureId) return false;
+            if (Attribute != other.Attribute) return false;
+            if (Label != other.Label) return false;
+            if (PropertyTableIndex != other.PropertyTableIndex) return false;
+
+            if (other is FeatureIDBuilder featureIdBuilder)
+            {
+                if (Texture != featureIdBuilder.Texture) return false;
+                if (Channels != featureIdBuilder.Channels) return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 38 - 0
src/SharpGLTF.Ext.3DTiles/Schema2/IMeshFeatureIDInfo.cs

@@ -0,0 +1,38 @@
+namespace SharpGLTF.Schema2.Tiles3D
+{
+    /// <summary>
+    /// Mesh Feature Ids
+    /// </summary>
+    /// <remarks>
+    /// Implemented by <see cref="MeshExtInstanceFeatureID"/> and <see cref="MeshExtMeshFeatureID"/>
+    /// </remarks>
+    public interface IMeshFeatureIDInfo
+    {
+        /// <summary>
+        /// The number of unique features in the attribute or texture.
+        /// </summary>
+        public int FeatureCount { get; set; }
+
+        /// <summary>
+        /// A value that indicates that no feature is associated with this vertex or texel.
+        /// </summary>
+        public int? NullFeatureId { get; set; }
+
+        /// <summary>
+        /// An attribute containing feature IDs. When `attribute` and `texture` are omitted the 
+        /// feature IDs are assigned to vertices by their index.
+        /// </summary>
+        public int? Attribute { get; set; }
+
+        /// <summary>
+        /// A label assigned to this feature ID set. Labels must be alphanumeric identifiers 
+        /// matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+        /// </summary>
+        public string Label { get; set; }
+
+        /// <summary>
+        /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
+        /// </summary>
+        public int? PropertyTableIndex { get; set; }        
+    }
+}

+ 0 - 2
src/SharpGLTF.Ext.3DTiles/SharpGLTF.Ext.3DTiles.csproj

@@ -20,10 +20,8 @@
     <Folder Include="Schema2\Generated\" />
     <Folder Include="Schema2\Generated\" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <!--
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="OneOf" Version="3.0.263" />
     <PackageReference Include="OneOf" Version="3.0.263" />
   </ItemGroup>
   </ItemGroup>
-  -->
 
 
 </Project>
 </Project>

BIN
src/SharpGLTF.Ext.3DTiles/cesium_sample_metadata_attributes.png


BIN
src/SharpGLTF.Ext.3DTiles/cesium_sample_metadata_texture.png


+ 29 - 2
tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs

@@ -1,9 +1,9 @@
 using NUnit.Framework;
 using NUnit.Framework;
 using SharpGLTF.Scenes;
 using SharpGLTF.Scenes;
-using SharpGLTF.Schema2;
 using SharpGLTF.Transforms;
 using SharpGLTF.Transforms;
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
-using System.Collections.Generic;
+using System;
+using System.IO;
 using System.Numerics;
 using System.Numerics;
 using System.Text.Json.Nodes;
 using System.Text.Json.Nodes;
 
 
@@ -18,7 +18,34 @@ namespace SharpGLTF.Schema2.Tiles3D
             Tiles3DExtensions.RegisterExtensions();
             Tiles3DExtensions.RegisterExtensions();
         }
         }
 
 
+        // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/instanceFeatures
+
+        [Test(Description = "Reads glTF's with EXT_Instance_Features")]
+        [TestCase(@"InstanceFeaturesFeatureIdAttributeInvalidValue.gltf", typeof(ModelException))]
+        [TestCase(@"InstanceFeaturesWithoutMeshGpuInstancing.gltf", typeof(SchemaException))]
+        [TestCase(@"ValidInstanceFeatures.gltf", null)]
+        public void ReadExtInstanceFeatures(string file, Type exception = null)
+        {
+            var fileName = $"./testfixtures/instanceFeatures/{file}";
+
+            if (exception != null)
+            {
+                Assert.Throws(exception, delegate { ModelRoot.Load(fileName); });
+            }
+            else
+            {
+                var model = ModelRoot.Load(fileName);
+                var instanceFeaturesExtension = model.LogicalNodes[0].GetExtension<MeshExtInstanceFeatures>();
+                Assert.That(instanceFeaturesExtension.FeatureIds, Is.Not.Null);
+                Assert.That(instanceFeaturesExtension.FeatureIds, Has.Count.GreaterThanOrEqualTo(1));
+                var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+                model.ValidateContent(ctx.GetContext());
+            }
+        }
+
+
         [Test(Description = "Creates a gpu_instancing glTF from a tree with Cesium EXT_Instance_Features")]
         [Test(Description = "Creates a gpu_instancing glTF from a tree with Cesium EXT_Instance_Features")]
+        // Sample model structure is from https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_instance_features
         public void AddExtGpuInstanceFeatures()
         public void AddExtGpuInstanceFeatures()
         {
         {
             var settings = SceneBuilderSchema2Settings.WithGpuInstancing;
             var settings = SceneBuilderSchema2Settings.WithGpuInstancing;

+ 73 - 21
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -3,10 +3,10 @@ using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Materials;
 using SharpGLTF.Scenes;
 using SharpGLTF.Scenes;
-using SharpGLTF.Schema2;
 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;
 
 
@@ -23,13 +23,68 @@ namespace SharpGLTF.Schema2.Tiles3D
             Tiles3DExtensions.RegisterExtensions();
             Tiles3DExtensions.RegisterExtensions();
         }
         }
 
 
+        // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/meshFeatures
+        [Test(Description = "Reads glTF's with EXT_Mesh_Features")]
+        [TestCase(@"ValidFeatureIdAttributeDefault/ValidFeatureIdAttributeDefault.gltf", null)]
+        [TestCase("FeatureIdAttributeAccessorNormalized.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeAccessorNotScalar.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeAttributeInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdAttributeAttributeInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeFeatureCountInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdAttributeFeatureCountInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeFeatureCountMismatch.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeFeatureCountMismatchForNullFeatureId.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeFeatureCountMissing.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeLabelInvalidType.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeLabelInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributeNullFeatureIdInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdAttributeNullFeatureIdInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureFeatureCountMismatch.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureSamplerInvalidFilterMode.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureTextureChannelsInvalidElementType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdTextureTextureChannelsInvalidType.gltf", typeof(SchemaException))]
+        [TestCase("FeatureIdTextureTextureChannelsTooManyChannels.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureTextureChannelsTooManyElements.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureTextureImageDataInvalid.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureTextureIndexInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdTextureTextureIndexInvalidValue.gltf", typeof(LinkException))]
+        [TestCase("FeatureIdTextureTextureInvalidType.gltf", typeof(SchemaException))]
+        [TestCase("FeatureIdTextureTextureTexCoordInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("FeatureIdTextureTextureTexCoordInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("ValidFeatureIdAttribute.gltf", null)]
+        [TestCase("ValidFeatureIdAttributeWithByteStride.glb", null)]
+        [TestCase("ValidFeatureIdAttributeWithLargerFeatureCount.gltf", null)]
+        [TestCase("ValidFeatureIdAttributeWithNullFeatureId.gltf", null)]
+        [TestCase("ValidFeatureIdTexture.glb", null)]
+        [TestCase("ValidFeatureIdTexture.gltf", null)]
+        [TestCase("ValidFeatureIdTextureUsingDefaultChannels.gltf", null)]
+        public void ReadExtMeshFeaturesFiles(string file, Type exception = null)
+        {
+            var fileName = $"./testfixtures/meshFeatures/{file}";
+
+            if (exception != null)
+            {
+                Assert.Throws(exception, delegate { ModelRoot.Load(fileName); });
+            }
+            else
+            {
+                var model = ModelRoot.Load(fileName);
+                var meshFeaturesExtension = model.LogicalMeshes[0].Primitives[0].GetExtension<MeshExtMeshFeatures>();
+                Assert.That(meshFeaturesExtension.FeatureIds, Is.Not.Null);
+                Assert.That(meshFeaturesExtension.FeatureIds, Has.Count.GreaterThanOrEqualTo(1));
+                var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+                model.ValidateContent(ctx.GetContext());
+            }
+        }
+
         [Test(Description = "Test for settting the FeatureIds with vertex attributes. See sample https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_mesh_features/FeatureIdAttribute")]
         [Test(Description = "Test for settting the FeatureIds with vertex attributes. See sample https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_mesh_features/FeatureIdAttribute")]
+        // In the sample html code, there is a shader that uses the feature ID to color the triangle
         public void FeaturesIdAttributeTest()
         public void FeaturesIdAttributeTest()
         {
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
             // Create a triangle with feature ID custom vertex attribute
             // Create a triangle with feature ID custom vertex attribute
-            var featureId = 1;
+            var featureId = 2;
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
 
 
             var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
             var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
@@ -46,17 +101,15 @@ namespace SharpGLTF.Schema2.Tiles3D
             var model = scene.ToGltf2();
             var model = scene.ToGltf2();
 
 
             // Set the FeatureIds
             // Set the FeatureIds
-            var featureIdAttribute = new FeatureIDBuilder(1, 0);            
-            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureIdAttribute,null,null));
+            var featureIdAttribute = new FeatureIDBuilder(1, 0);
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureIdAttribute);
 
 
             // Validate the FeatureIds
             // Validate the FeatureIds
-            var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)model.LogicalMeshes[0].Primitives[0].Extensions.FirstOrDefault();
-            Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.Not.Null);
-
-            // Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds[0], Is.EqualTo(featureIdAttribute)); // this cannot be run anymore
+            var meshFeaturesExtension = (MeshExtMeshFeatures)model.LogicalMeshes[0].Primitives[0].Extensions.FirstOrDefault();
+            Assert.That(meshFeaturesExtension.FeatureIds, Is.Not.Null);
 
 
             // Check there should be a custom vertex attribute with name _FEATURE_ID_{attribute}
             // Check there should be a custom vertex attribute with name _FEATURE_ID_{attribute}
-            var attribute = cesiumExtMeshFeaturesExtension.FeatureIds[0].Attribute;
+            var attribute = meshFeaturesExtension.FeatureIds[0].Attribute;
             Assert.That(attribute == 0);
             Assert.That(attribute == 0);
             var primitive = model.LogicalMeshes[0].Primitives[0];
             var primitive = model.LogicalMeshes[0].Primitives[0];
             var featureIdVertexAccessor = primitive.GetVertexAccessor($"_FEATURE_ID_{attribute}");
             var featureIdVertexAccessor = primitive.GetVertexAccessor($"_FEATURE_ID_{attribute}");
@@ -73,6 +126,7 @@ namespace SharpGLTF.Schema2.Tiles3D
         }
         }
 
 
         [Test(Description = "Test for settting the FeatureIds with a texture. See sample https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_mesh_features/FeatureIdTexture")]
         [Test(Description = "Test for settting the FeatureIds with a texture. See sample https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_mesh_features/FeatureIdTexture")]
+        // In the sample html code, there is a shader that uses the feature'ID from the texture to color the 2 triangles
         public void FeaturesIdTextureTest()
         public void FeaturesIdTextureTest()
         {
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
@@ -107,21 +161,20 @@ namespace SharpGLTF.Schema2.Tiles3D
             var model = scene.ToGltf2();
             var model = scene.ToGltf2();
 
 
             // Set the FeatureIds, pointing to the red channel of the texture            
             // Set the FeatureIds, pointing to the red channel of the texture            
-            var featureId = new FeatureIDBuilder(4);            
+            var featureId = new FeatureIDBuilder(4, model.LogicalTextures[0]);
 
 
             var primitive = model.LogicalMeshes[0].Primitives[0];
             var primitive = model.LogicalMeshes[0].Primitives[0];
-            primitive.AddMeshFeatureIds((featureId, model.LogicalTextures[0], new int[] { 0 }));
-
-            var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)primitive.Extensions.FirstOrDefault();
-            Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.Not.Null);
+            primitive.AddMeshFeatureIds(featureId);
 
 
-            // Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.EqualTo(featureIds));
-            // var featureId = cesiumExtMeshFeaturesExtension.FeatureIds[0];
-            // var texCoord = featureId.Texture.TextureCoordinate;
+            var meshFeaturesExtension = (MeshExtMeshFeatures)primitive.Extensions.FirstOrDefault();
+            Assert.That(meshFeaturesExtension.FeatureIds, Is.Not.Null);
 
 
-            // var textureIdVertexAccessor = primitive.GetVertexAccessor($"TEXCOORD_{texCoord}");
-            // Assert.That(textureIdVertexAccessor, Is.Not.Null);
-            // Assert.That(textureIdVertexAccessor.AsVector2Array(), Has.Count.EqualTo(4));
+            var firstFeatureId = meshFeaturesExtension.FeatureIds[0];
+            var texture = firstFeatureId.GetTexture();
+            var texCoord = texture.TextureCoordinate;
+            var textureIdVertexAccessor = primitive.GetVertexAccessor($"TEXCOORD_{texCoord}");
+            Assert.That(textureIdVertexAccessor, Is.Not.Null);
+            Assert.That(textureIdVertexAccessor.AsVector2Array(), Has.Count.EqualTo(4));
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
 
 
@@ -130,6 +183,5 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.gltf");
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.gltf");
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.plotly");
             scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.plotly");
         }
         }
-
     }
     }
 }
 }

+ 0 - 1
tests/SharpGLTF.Cesium.Tests/ExtPrimitiveOutlineTests.cs

@@ -1,7 +1,6 @@
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using NUnit.Framework;
 using NUnit.Framework;
-using SharpGLTF.Schema2;
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Materials;

+ 406 - 118
tests/SharpGLTF.Cesium.Tests/ExtStructuralMetadataTests.cs

@@ -3,10 +3,10 @@ using SharpGLTF.Geometry;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Geometry.VertexTypes;
 using SharpGLTF.Materials;
 using SharpGLTF.Materials;
 using SharpGLTF.Scenes;
 using SharpGLTF.Scenes;
-using SharpGLTF.Schema2;
 using SharpGLTF.Validation;
 using SharpGLTF.Validation;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
 
 
 namespace SharpGLTF.Schema2.Tiles3D
 namespace SharpGLTF.Schema2.Tiles3D
@@ -22,53 +22,307 @@ 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")]
+        [TestCase("ExtensionInMeshPrimitiveWithoutTopLevelObject.gltf", typeof(ModelException))]
+        [TestCase("PropertyAttributesClassPropertyArray.gltf", typeof(ModelException))]
+        [TestCase("PropertyAttributesClassPropertyInvalidComponentType.gltf", typeof(ModelException))]
+        [TestCase("PropertyAttributesClassPropertyInvalidEnumValueType.gltf", typeof(ModelException))]
+        // Todo: Minmax [TestCase("PropertyAttributesClassPropertyMaxNotInRange.gltf", typeof(ModelException))]
+        // Todo: Minmax [TestCase("PropertyAttributesClassPropertyMinNotInRange.gltf", typeof(ModelException))]
+        [TestCase("PropertyAttributesClassPropertyString.gltf", typeof(ModelException))]
+        [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementType.gltf", typeof(InvalidOperationException))]
+        [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidElementValue.gltf", typeof(LinkException))]
+        [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidLength.gltf", typeof(SchemaException))]
+        [TestCase("PropertyAttributesMeshPrimitivePropertyAttributesInvalidType.gltf", typeof(SchemaException))]
+        [TestCase("PropertyAttributesPropertyAttributePropertyInvalidAttribute.gltf", typeof(ModelException))]
+        // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMaxMismatch.gltf", typeof(ModelException))] 
+        // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMaxNotInRange.gltf", typeof(ModelException))]
+        // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMinMismatch.gltf", typeof(ModelException))]
+        // Todo: Minmax [TestCase("PropertyAttributesPropertyAttributePropertyMinNotInRange.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTextureClassPropertyMaxNotInRange.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTextureClassPropertyMinNotInRange.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTextureClassPropertyWithOffsetScaleMinNotInRange.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTextureEnumsInvalidEnumValue.gltf", typeof(ModelException))]
+        [TestCase("PropertyTextureInvalidPropertyTypeA.gltf", typeof(ModelException))]
+        [TestCase("PropertyTextureInvalidPropertyTypeB.gltf", typeof(ModelException))]
+        [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidElementType.gltf", typeof(InvalidOperationException))]
+        [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidElementValue.gltf", typeof(LinkException))]
+        [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidLength.gltf", typeof(SchemaException))]
+        [TestCase("PropertyTextureMeshPrimitivePropertyTexturesInvalidType.gltf", typeof(SchemaException))]
+        [TestCase("PropertyTextureMeshPrimitivePropertyTextureTexCoordInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("PropertyTexturePropertyChannelsSizeMismatch.gltf", typeof(ModelException))]
+        [TestCase("PropertyTexturePropertyIndexInvalidType.gltf", typeof(InvalidOperationException))]
+        [TestCase("PropertyTexturePropertyIndexInvalidValue.gltf", typeof(LinkException))]
+        // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMaxMismatch.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMaxNotInRange.gltf", typeof(ModelException))]
+        // todo minmax with texture [TestCase("PropertyTexturePropertyTexturePropertyMinMismatch.gltf", typeof(ModelException))]
+        // todo minmax with texture[TestCase("PropertyTexturePropertyTexturePropertyMinNotInRange.gltf", typeof(ModelException))]
+        [TestCase("StructuralMetadataMissingSchema.gltf", typeof(ModelException))]
+        [TestCase("StructuralMetadataSchemaAndSchemaUri.gltf", typeof(ModelException))]
+        [TestCase("ValidMultipleClasses.gltf", null)]
+        [TestCase("ValidPropertyAttributes.gltf", null)]
+        [TestCase("ValidPropertyTexture.gltf", null)]
+        [TestCase("ValidPropertyTextureEnums.gltf", null)]
+
+        public void ReadExtStructuralMetadata(string file, Type exception = null)
+        {
+            var fileName = $"./testfixtures/structuralMetadata/{file}";
+
+            if (exception != null)
+            {
+                Assert.Throws(exception, delegate { ModelRoot.Load(fileName); });
+            }
+            else
+            {
+                var model = ModelRoot.Load(fileName);
+                var structuralMetadataExtension = model.GetExtension<EXTStructuralMetadataRoot>();
+                var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+                model.ValidateContent(ctx.GetContext());
+            }
+        }
+
+
+        [Test(Description = "MinimalMetadataAttributeSample")]
+        public void MinimalMetadataAttributeSample()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            int featureId = 0;
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim.AddTriangle(vt0, vt1, vt2);
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("schema_001");
+
+            var schemaClass = schema.UseClassMetadata("triangles");
+
+            var nameProperty = schemaClass
+                .UseProperty("name")
+                .WithStringType();
+
+            var propertyTable = schemaClass.AddPropertyTable(1);
+
+            propertyTable
+                .UseProperty(nameProperty)
+                .SetValues("this is featureId0");
+
+            foreach (var primitive in model.LogicalMeshes[0].Primitives)
+            {
+                var featureIdAttribute = new FeatureIDBuilder(1, 0, propertyTable);
+                primitive.AddMeshFeatureIds(featureIdAttribute);
+            }
+
+            // create files
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_minimal_metadata_sample.plotly");
+        }
+
+
+        [Test(Description = "TestWith2PrimitivesAndMetadata")]
+        public void MultiplePrimitivesAndMetadata()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            int featureId = 0;
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim.AddTriangle(vt0, vt1, vt2);
+
+            // featureId = 1 and 2 (other material)
+
+            var material2 = new MaterialBuilder()
+                .WithDoubleSide(true)
+                .WithMetallicRoughnessShader()
+                .WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, new Vector4(1, 0, 1, 1));
+
+            var prim2 = mesh.UsePrimitive(material2);
+
+            featureId = 1;
+            var vt3 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt4 = VertexBuilder.GetVertexWithFeatureId(new Vector3(3, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt5 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim2.AddTriangle(vt3, vt4, vt5);
+
+            featureId = 2;
+            var vt6 = VertexBuilder.GetVertexWithFeatureId(new Vector3(4, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt7 = VertexBuilder.GetVertexWithFeatureId(new Vector3(5, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt8 = VertexBuilder.GetVertexWithFeatureId(new Vector3(4, 1, 0), new Vector3(0, 0, 1), featureId);
+
+            prim2.AddTriangle(vt6, vt7, vt8);
+
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("schema_001");
+            schema.Name = "schema 001";
+            schema.Description = "an example schema";
+            schema.Version = "3.5.1";
+
+            var trianglesClass = schema
+                .UseClassMetadata("triangles")
+                .WithName("Triangle");
+
+            var nameProperty = trianglesClass
+                .UseProperty("name")
+                .WithStringType();
+
+
+            var isTriangle = trianglesClass
+                .UseProperty("IsTriangle")
+                .WithBooleanType();
+
+            var propertyTable = trianglesClass
+                .AddPropertyTable(3, "PropertyTable");
+
+            propertyTable
+                .UseProperty(nameProperty)
+                .SetValues("this is featureId0", "this is featureId1", "this is featureId2");
+
+            propertyTable
+                .UseProperty(isTriangle)
+                .SetValues(false, true, false);
+
+
+            foreach (var primitive in model.LogicalMeshes[0].Primitives)
+            {
+                var triangles = primitive.EvaluateTriangles().Count();
+                var featureIdAttribute = new FeatureIDBuilder(triangles, 0, propertyTable);
+                primitive.AddMeshFeatureIds(featureIdAttribute);
+            }
+
+            // create files
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.glb");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.gltf");
+            model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_primitives.plotly");
+        }
+
+
         [Test(Description = "First test with ext_structural_metadata")]
         [Test(Description = "First test with ext_structural_metadata")]
+        // This test creates a simple triangle (featureId = 0) with ext_structural_metadata (4 tree attributes like
+        // species (Enumeration), age (Scalar), height (Scalar) and diameter (Scalar) and a property table.
+        // following the structure described in https://github.com/CesiumGS/glTF/tree/proposal-EXT_structural_metadata/extensions/2.0/Vendor/EXT_structural_metadata
         public void TriangleWithMetadataTest()
         public void TriangleWithMetadataTest()
         {
         {
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
             var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
-            var mesh = new MeshBuilder<VertexPosition>("mesh");
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
             var prim = mesh.UsePrimitive(material);
             var prim = mesh.UsePrimitive(material);
 
 
-            prim.AddTriangle(new VertexPosition(-10, 0, 0), new VertexPosition(10, 0, 0), new VertexPosition(0, 10, 0));
+            var features = new List<int>() { 0, 1 };
+
+            var vt0 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 0, 0), new Vector3(0, 0, 1), features[0]);
+            var vt1 = VertexBuilder.GetVertexWithFeatureId(new Vector3(1, 0, 0), new Vector3(0, 0, 1), features[0]);
+            var vt2 = VertexBuilder.GetVertexWithFeatureId(new Vector3(0, 1, 0), new Vector3(0, 0, 1), features[0]);
+
+            prim.AddTriangle(vt0, vt1, vt2);
+
+            var vt3 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 0, 0), new Vector3(0, 0, 1), features[1]);
+            var vt4 = VertexBuilder.GetVertexWithFeatureId(new Vector3(3, 0, 0), new Vector3(0, 0, 1), features[1]);
+            var vt5 = VertexBuilder.GetVertexWithFeatureId(new Vector3(2, 1, 0), new Vector3(0, 0, 1), features[1]);
+
+            prim.AddTriangle(vt3, vt4, vt5);
 
 
             var scene = new SceneBuilder();
             var scene = new SceneBuilder();
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
             var model = scene.ToGltf2();
-
-            var schema = new StructuralMetadataSchema();
-            schema.Id = "schema_001";
+           
+            var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema("schema_001");
             schema.Name = "schema 001";
             schema.Name = "schema 001";
             schema.Description = "an example schema";
             schema.Description = "an example schema";
             schema.Version = "3.5.1";
             schema.Version = "3.5.1";
-            var classes = new Dictionary<string, StructuralMetadataClass>();
-            var treeClass = new StructuralMetadataClass();
-            treeClass.Name = "Tree";
-            treeClass.Description = "Woody, perennial plant.";
-            classes["tree"] = treeClass;
-            var ageProperty = new ClassProperty();
-            ageProperty.Description = "The age of the tree, in years";
-            ageProperty.Type = ElementType.SCALAR;
-            ageProperty.ComponentType = DataType.UINT32;
-            ageProperty.Required = true;
 
 
-            treeClass.Properties.Add("age", ageProperty);
+            var speciesEnum = schema.UseEnumMetadata("speciesEnum", ("Unspecified", 0), ("Oak", 1), ("Pine", 2), ("Maple",3));
+            speciesEnum.Name = "Species";
+            speciesEnum.Description = "An example enum for tree species.";
+
+            var treeClass = schema
+                .UseClassMetadata("tree")
+                .WithName("Tree")
+                .WithDescription("Woody, perennial plant.");
+
+            // species property
+            var speciesProperty = treeClass
+                .UseProperty("species")
+                .WithDescription("Type of tree.")
+                .WithEnumeration(speciesEnum)
+                .WithRequired(true);
+
+            // age property
+            var ageProperty = treeClass
+                .UseProperty("age")
+                .WithDescription("The age of the tree, in years")
+                .WithUInt32Type()
+                .WithRequired(true);
+
+            // Height property
+            var heightProperty = treeClass
+                .UseProperty("height")
+                .WithDescription("Height of tree measured from ground level, in meters");
+            heightProperty.WithFloat32Type();
+
+            // Diameter property
+            var diameterProperty = treeClass
+                .UseProperty("diameter")
+                .WithDescription("Diameter at trunk base, in meters.");
+            diameterProperty.WithFloat32Type();
+
+            var propertyTable = treeClass
+                .AddPropertyTable(features.Count, "PropertyTable");
+
+            propertyTable
+                .UseProperty(ageProperty)
+                .SetValues((uint)100, (uint)101);
+
+            propertyTable
+                .UseProperty(speciesProperty)
+                .SetValues((short)0, (short)3);
 
 
-            schema.Classes = classes;
+            propertyTable.UseProperty(heightProperty)
+                .SetValues(10.0f, 11f);
 
 
-            var propertyTable = new PropertyTable("tree", 1, "PropertyTable");
-            var agePropertyTableProperty = model.GetPropertyTableProperty(new List<int>() { 100 });
-            propertyTable.Properties.Add("age", agePropertyTableProperty);
+            propertyTable.UseProperty(diameterProperty)
+                .SetValues(1.5f, 2f);
 
 
-            model.SetPropertyTable(propertyTable, schema);
+            // Set the FeatureIds
+            var cnt = propertyTable.Count;
+            var featureIdAttribute = new FeatureIDBuilder(2, 0, propertyTable);
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureIdAttribute);
 
 
             // create files
             // create files
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.plotly");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_basic_triangle.plotly");
-        }*/
+        }
+        
 
 
         [Test(Description = "ext_structural_metadata with FeatureId Texture and Property Table")]
         [Test(Description = "ext_structural_metadata with FeatureId Texture and Property Table")]
         // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdTextureAndPropertyTable
         // sample see https://github.com/CesiumGS/3d-tiles-samples/tree/main/glTF/EXT_structural_metadata/FeatureIdTextureAndPropertyTable
@@ -109,8 +363,6 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
             var model = scene.ToGltf2();
 
 
-            // --------------------------------------------------------------
-
             var rootMetadata = model.UseStructuralMetadata();
             var rootMetadata = model.UseStructuralMetadata();
             var schema = rootMetadata.UseEmbeddedSchema("FeatureIdTextureAndPropertyTableSchema");
             var schema = rootMetadata.UseEmbeddedSchema("FeatureIdTextureAndPropertyTableSchema");
 
 
@@ -118,38 +370,38 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var buildingComponentsClass = schema
             var buildingComponentsClass = schema
                 .UseClassMetadata("buildingComponents")
                 .UseClassMetadata("buildingComponents")
-                .WithNameAndDesc("Building components");
+                .WithName("Building components")
+                .WithDescription("The components of a building.");
 
 
             var componentProp = buildingComponentsClass
             var componentProp = buildingComponentsClass
                 .UseProperty("component")
                 .UseProperty("component")
-                .WithNameAndDesc("Component")
-                .WithValueType(ElementType.STRING);
+                .WithName("Component")
+                .WithStringType();
 
 
             var yearProp = buildingComponentsClass
             var yearProp = buildingComponentsClass
                 .UseProperty("yearBuilt")
                 .UseProperty("yearBuilt")
-                .WithNameAndDesc("Year built")
-                .WithValueType(ElementType.SCALAR, DataType.INT16);            
+                .WithName("Year built")
+                .WithInt16Type();            
 
 
             var propertyTable = buildingComponentsClass
             var propertyTable = buildingComponentsClass
                 .AddPropertyTable(4, "Example property table");
                 .AddPropertyTable(4, "Example property table");
 
 
             propertyTable
             propertyTable
                 .UseProperty(componentProp)
                 .UseProperty(componentProp)
-                .SetValues1D("Wall", "Door", "Roof", "Window");
+                .SetValues("Wall", "Door", "Roof", "Window");
 
 
             propertyTable
             propertyTable
                 .UseProperty(yearProp)
                 .UseProperty(yearProp)
-                .SetValues1D(1960, 1996, 1985, 2002);            
+                .SetValues((short)1960, (short)1996, (short)1985, (short)2002);            
 
 
             // Set the FeatureIds, pointing to the red channel of the texture
             // Set the FeatureIds, pointing to the red channel of the texture
 
 
-            var featureId = new FeatureIDBuilder(propertyTable);            
+            var featureId = new FeatureIDBuilder(4, null, propertyTable);            
 
 
             var primitive = model.LogicalMeshes[0].Primitives[0];
             var primitive = model.LogicalMeshes[0].Primitives[0];
-            primitive.AddMeshFeatureIds((featureId, model.LogicalTextures[0], new[] {0}));
+            primitive.AddMeshFeatureIds(featureId);
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
-
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.plotly");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_texture_and_property_table.plotly");
@@ -204,22 +456,23 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var exampleMetadataClass = schema
             var exampleMetadataClass = schema
                 .UseClassMetadata("buildingComponents")
                 .UseClassMetadata("buildingComponents")
-                .WithNameAndDesc("Building properties");
+                .WithName("Building properties");
 
 
             exampleMetadataClass
             exampleMetadataClass
                 .UseProperty("insideTemperature")
                 .UseProperty("insideTemperature")
-                .WithNameAndDesc("Inside temperature")
-                .WithValueType(ElementType.SCALAR, DataType.UINT8);
+                .WithName("Inside temperature")
+                .WithUInt8Type();
 
 
             exampleMetadataClass
             exampleMetadataClass
                 .UseProperty("outsideTemperature")
                 .UseProperty("outsideTemperature")
-                .WithNameAndDesc("Outside temperature")
-                .WithValueType(ElementType.SCALAR, DataType.UINT8);
+                .WithName("Outside temperature")
+                .WithUInt8Type();
 
 
             exampleMetadataClass
             exampleMetadataClass
                 .UseProperty("insulation")
                 .UseProperty("insulation")
-                .WithNameAndDesc("Insulation Thickness")
-                .WithValueType(ElementType.SCALAR, DataType.UINT8, true);
+                .WithName("Insulation Thickness")
+                .WithUInt8Type()
+                .WithNormalized(true);
 
 
             // define texture property
             // define texture property
 
 
@@ -232,7 +485,7 @@ namespace SharpGLTF.Schema2.Tiles3D
             // assign to primitive
             // assign to primitive
 
 
             var primitive = model.LogicalMeshes[0].Primitives[0];            
             var primitive = model.LogicalMeshes[0].Primitives[0];            
-            primitive.AddPropertyTexture(buildingPropertyTexture);            
+            primitive.AddPropertyTexture(buildingPropertyTexture);
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_simple_property_texture.glb");
@@ -276,17 +529,20 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var exampleMetadataClass = schema
             var exampleMetadataClass = schema
                 .UseClassMetadata("exampleMetadataClass")
                 .UseClassMetadata("exampleMetadataClass")
-                .WithNameAndDesc("Example metadata class", "An example metadata class");
+                .WithName("Example metadata class")
+                .WithDescription("An example metadata class");
 
 
             var vec3Property = exampleMetadataClass
             var vec3Property = exampleMetadataClass
                 .UseProperty("example_VEC3_FLOAT32")
                 .UseProperty("example_VEC3_FLOAT32")
-                .WithNameAndDesc("Example VEC3 FLOAT32 property", "An example property, with type VEC3, with component type FLOAT32")
-                .WithValueType(ElementType.VEC3, DataType.FLOAT32);
+                .WithName("Example VEC3 FLOAT32 property")
+                .WithDescription("An example property, with type VEC3, with component type FLOAT32")
+                .WithVector3Type();
 
 
             var stringProperty = exampleMetadataClass
             var stringProperty = exampleMetadataClass
                 .UseProperty("example_STRING")
                 .UseProperty("example_STRING")
-                .WithNameAndDesc("Example STRING property", "An example property, with type STRING")
-                .WithValueType(ElementType.STRING);
+                .WithName("Example STRING property")
+                .WithDescription("An example property, with type STRING")
+                .WithStringType();
 
 
             // define table
             // define table
 
 
@@ -294,18 +550,18 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(vec3Property)
                 .UseProperty(vec3Property)
-                .SetValues1D(new Vector3(3, 3.0999999046325684f, 3.200000047683716f), new Vector3(103, 103.0999999046325684f, 103.200000047683716f));
+                .SetValues(new Vector3(3, 3.0999999046325684f, 3.200000047683716f), new Vector3(103, 103.0999999046325684f, 103.200000047683716f));
 
 
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(stringProperty)
                 .UseProperty(stringProperty)
-                .SetValues1D("Rain 🌧", "Thunder ⛈");
+                .SetValues("Rain 🌧", "Thunder ⛈");
 
 
             // assign to primitive
             // assign to primitive
 
 
-            var featureId0 = new FeatureIDBuilder(examplePropertyTable, 0);
-            var featureId1 = new FeatureIDBuilder(examplePropertyTable, 1);
+            var featureId0 = new FeatureIDBuilder(2, 0, examplePropertyTable);
+            var featureId1 = new FeatureIDBuilder(2, 1, examplePropertyTable);
 
 
-            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds( (featureId0, null, null), (featureId1, null, null) );
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds( featureId0, featureId1 );
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_featureid_attribute_and_property_table.glb");
@@ -343,17 +599,20 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var exampleMetadataClass = schema
             var exampleMetadataClass = schema
                 .UseClassMetadata("exampleMetadataClass")
                 .UseClassMetadata("exampleMetadataClass")
-                .WithNameAndDesc("Example metadata class", "An example metadata class");
+                .WithName("Example metadata class")
+                .WithDescription("An example metadata class");
 
 
             var vector3Property = exampleMetadataClass
             var vector3Property = exampleMetadataClass
                 .UseProperty("example_VEC3_FLOAT32")
                 .UseProperty("example_VEC3_FLOAT32")
-                .WithNameAndDesc("Example VEC3 FLOAT32 property", "An example property, with type VEC3, with component type FLOAT32")
-                .WithValueType(ElementType.VEC3, DataType.FLOAT32);
+                .WithName("Example VEC3 FLOAT32 property")
+                .WithDescription("An example property, with type VEC3, with component type FLOAT32")
+                .WithVector3Type();
 
 
             var matrix4x4Property = exampleMetadataClass
             var matrix4x4Property = exampleMetadataClass
                 .UseProperty("example_MAT4_FLOAT32")
                 .UseProperty("example_MAT4_FLOAT32")
-                .WithNameAndDesc("Example MAT4 FLOAT32 property", "An example property, with type MAT4, with component type FLOAT32")
-                .WithValueType(ElementType.MAT4, DataType.FLOAT32);
+                .WithName("Example MAT4 FLOAT32 property")
+                .WithDescription("An example property, with type MAT4, with component type FLOAT32")
+                .WithMatrix4x4Type();
 
 
             // define table
             // define table
 
 
@@ -361,17 +620,17 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(vector3Property)
                 .UseProperty(vector3Property)
-                .SetValues1D(new Vector3(3, 3.0999999046325684f, 3.200000047683716f));
+                .SetValues(new Vector3(3, 3.0999999046325684f, 3.200000047683716f));
 
 
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(matrix4x4Property)
                 .UseProperty(matrix4x4Property)
-                .SetValues1D(Matrix4x4.Identity);
+                .SetValues(Matrix4x4.Identity);
 
 
             // assign to primitive
             // assign to primitive
 
 
-            var featureId = new FeatureIDBuilder(examplePropertyTable, 0);
+            var featureId = new FeatureIDBuilder(1, 0, examplePropertyTable);
 
 
-            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId, null, null));
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId);
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_featureids_and_properties.glb");
@@ -409,61 +668,72 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var exampleMetadataClass = schema
             var exampleMetadataClass = schema
                 .UseClassMetadata("exampleMetadataClass")
                 .UseClassMetadata("exampleMetadataClass")
-                .WithNameAndDesc("Example metadata class A", "First example metadata class");
+                .WithName("Example metadata class A")
+                .WithDescription("First example metadata class");
 
 
             // enums
             // enums
 
 
             var exampleEnum = schema.UseEnumMetadata("exampleEnumType", ("ExampleEnumValueA", 0), ("ExampleEnumValueB", 1), ("ExampleEnumValueC", 2));
             var exampleEnum = schema.UseEnumMetadata("exampleEnumType", ("ExampleEnumValueA", 0), ("ExampleEnumValueB", 1), ("ExampleEnumValueC", 2));
 
 
-            // class properties
+            //// class properties
 
 
             var uint8ArrayProperty = exampleMetadataClass
             var uint8ArrayProperty = exampleMetadataClass
                 .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
                 .UseProperty("example_variable_length_ARRAY_normalized_UINT8")
-                .WithNameAndDesc("Example variable-length ARRAY normalized INT8 property","An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
-                .WithArrayType(ElementType.SCALAR,DataType.UINT8,false);
+                .WithName("Example variable-length ARRAY normalized INT8 property")
+                .WithDescription("An example property, with type ARRAY, with component type UINT8, normalized, and variable length")
+                .WithArrayType(ElementType.SCALAR, DataType.UINT8)
+                .WithNormalized(false);
 
 
             var fixedLengthBooleanProperty = exampleMetadataClass
             var fixedLengthBooleanProperty = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
                 .UseProperty("example_fixed_length_ARRAY_BOOLEAN")
-                .WithNameAndDesc("Example fixed-length ARRAY BOOLEAN property", "An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
-                .WithArrayType(ElementType.BOOLEAN, null, false, 4);
+                .WithName("Example fixed-length ARRAY BOOLEAN property")
+                .WithDescription("An example property, with type ARRAY, with component type BOOLEAN, and fixed length ")
+                .WithArrayType(ElementType.BOOLEAN, null, 4)
+                .WithNormalized(false);
 
 
             var variableLengthStringArrayProperty = exampleMetadataClass
             var variableLengthStringArrayProperty = exampleMetadataClass
                 .UseProperty("example_variable_length_ARRAY_STRING")
                 .UseProperty("example_variable_length_ARRAY_STRING")
-                .WithNameAndDesc("Example variable-length ARRAY STRING property", "An example property, with type ARRAY, with component type STRING, and variable length")
+                .WithName("Example variable-length ARRAY STRING property")
+                .WithDescription("An example property, with type ARRAY, with component type STRING, and variable length")
                 .WithArrayType(ElementType.STRING);
                 .WithArrayType(ElementType.STRING);
 
 
             var fixed_length_ARRAY_ENUM = exampleMetadataClass
             var fixed_length_ARRAY_ENUM = exampleMetadataClass
                 .UseProperty("example_fixed_length_ARRAY_ENUM")
                 .UseProperty("example_fixed_length_ARRAY_ENUM")
-                .WithNameAndDesc("Example fixed-length ARRAY ENUM property", "An example property, with type ARRAY, with component type ENUM, and fixed length")
+                .WithName("Example fixed-length ARRAY ENUM property")
+                .WithDescription("An example property, with type ARRAY, with component type ENUM, and fixed length")
                 .WithEnumArrayType(exampleEnum, 2);
                 .WithEnumArrayType(exampleEnum, 2);
 
 
-            // property tables
-
             var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
             var examplePropertyTable = exampleMetadataClass.AddPropertyTable(1, "Example property table");
 
 
-            // Question: The table declares a feature count of 1, but then, these properties define different number of items
-            
+            var bytes = new List<List<byte>>();
+            bytes.Add(new List<byte>() { 0, 1, 2, 3, 4, 5, 6, 7 });
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(uint8ArrayProperty)
                 .UseProperty(uint8ArrayProperty)
-                .SetValues1D<byte>(0, 1, 2, 3, 4, 5, 6, 7);
+                .SetArrayValues(bytes);
 
 
+            var bools = new List<List<bool>>();
+            bools.Add(new List<bool>() { true, false, true, false });
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(fixedLengthBooleanProperty)
                 .UseProperty(fixedLengthBooleanProperty)
-                .SetValues1D<Boolean>(true, false, true, false);
+                .SetArrayValues(bools);
 
 
+            var strings = new List<List<string>>();
+            strings.Add(["Example string 1", "Example string 2", "Example string 3"]);
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(variableLengthStringArrayProperty)
                 .UseProperty(variableLengthStringArrayProperty)
-                .SetValues1D("Example string 1", "Example string 2", "Example string 3");
+                .SetArrayValues(strings);
 
 
+            // Fill property table with enum values
+            var shorts = new List<List<short>>();
+            shorts.Add([0, 1]);
             examplePropertyTable
             examplePropertyTable
                 .UseProperty(fixed_length_ARRAY_ENUM)
                 .UseProperty(fixed_length_ARRAY_ENUM)
-                .SetValues1D<int>(0, 1);
+                .SetArrayValues(shorts);
 
 
             // add to primitive            
             // add to primitive            
+            var featureId = new FeatureIDBuilder(1, 0, examplePropertyTable);
 
 
-            var featureId = new FeatureIDBuilder(examplePropertyTable, 0);
-
-            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId, null, null));
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId);
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_complex_types.glb");
@@ -490,8 +760,6 @@ namespace SharpGLTF.Schema2.Tiles3D
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             scene.AddRigidMesh(mesh, Matrix4x4.Identity);
             var model = scene.ToGltf2();
             var model = scene.ToGltf2();
 
 
-            // --------------------------------------------------------------
-
             var rootMetadata = model.UseStructuralMetadata();
             var rootMetadata = model.UseStructuralMetadata();
             var schema = rootMetadata.UseEmbeddedSchema("MultipleClassesSchema");            
             var schema = rootMetadata.UseEmbeddedSchema("MultipleClassesSchema");            
 
 
@@ -499,46 +767,51 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
             var classA = schema
             var classA = schema
                 .UseClassMetadata("exampleMetadataClassA")
                 .UseClassMetadata("exampleMetadataClassA")
-                .WithNameAndDesc("Example metadata class A","First example metadata class");
+                .WithName("Example metadata class A")
+                .WithDescription("First example metadata class");
 
 
             var classAp0 = classA.UseProperty("example_FLOAT32")
             var classAp0 = classA.UseProperty("example_FLOAT32")
-                .WithNameAndDesc("Example FLOAT32 property", "An example property, with component type FLOAT32")
-                .WithValueType(ElementType.SCALAR, DataType.FLOAT32);
+                .WithName("Example FLOAT32 property")
+                .WithDescription("An example property, with component type FLOAT32")
+                .WithFloat32Type();
 
 
             var classAp1 = classA.UseProperty("example_INT64")
             var classAp1 = classA.UseProperty("example_INT64")
-                .WithNameAndDesc("Example INT64 property", "An example property, with component type INT64")
-                .WithValueType(ElementType.SCALAR, DataType.INT64);
+                .WithName("Example INT64 property")
+                .WithDescription("An example property, with component type INT64")
+                .WithInt64Type();
 
 
             var classB = schema.UseClassMetadata("exampleMetadataClassB")
             var classB = schema.UseClassMetadata("exampleMetadataClassB")
-                .WithNameAndDesc("Example metadata class B", "Second example metadata class");
+                .WithName("Example metadata class B")
+                .WithDescription("Second example metadata class");
 
 
             var classBp0 = classB.UseProperty("example_UINT16")
             var classBp0 = classB.UseProperty("example_UINT16")
-                .WithNameAndDesc("Example UINT16 property", "An example property, with component type UINT16")
-                .WithValueType(ElementType.SCALAR, DataType.UINT16);
+                .WithName("Example UINT16 property")
+                .WithDescription("An example property, with component type UINT16")
+                .WithUInt16Type();
 
 
             var classBp1 = classB.UseProperty("example_FLOAT64")
             var classBp1 = classB.UseProperty("example_FLOAT64")
-                .WithNameAndDesc("Example FLOAT64 property", "An example property, with component type FLOAT64")
-                .WithValueType(ElementType.SCALAR, DataType.FLOAT64);
+                .WithName("Example FLOAT64 property")
+                .WithDescription("An example property, with component type FLOAT64")
+                .WithFloat64Type();
 
 
             // properties
             // properties
 
 
             var firstPropertyTable = classA.AddPropertyTable(1, "First example property table");
             var firstPropertyTable = classA.AddPropertyTable(1, "First example property table");
-            firstPropertyTable.UseProperty(classAp0).SetValues1D<float>(100);
-            firstPropertyTable.UseProperty(classAp1).SetValues1D<long>(101);
+            firstPropertyTable.UseProperty(classAp0).SetValues<float>(100);
+            firstPropertyTable.UseProperty(classAp1).SetValues<long>(101);
 
 
             var secondPropertyTable = classB.AddPropertyTable(1, "Second example property table");
             var secondPropertyTable = classB.AddPropertyTable(1, "Second example property table");
-            secondPropertyTable.UseProperty(classBp0).SetValues1D<ushort>(102);
-            secondPropertyTable.UseProperty(classBp1).SetValues1D<double>(103);
+            secondPropertyTable.UseProperty(classBp0).SetValues<ushort>(102);
+            secondPropertyTable.UseProperty(classBp1).SetValues<double>(103);
 
 
             // features
             // features
 
 
-            // FeatureID 0: featureCount=1, attribute=0, porpertyTable=0 
-            var featureId0 = new FeatureIDBuilder(firstPropertyTable, 0);
-            // FeatureID 1: featureCount=1, attribute=1, porpertyTable=1
-            var featureId1 = new FeatureIDBuilder(secondPropertyTable, 1);
+            // FeatureID 0: featureCount=1, attribute=0, propertyTable=0 
+            var featureId0 = new FeatureIDBuilder(1, 0, firstPropertyTable);
+            // FeatureID 1: featureCount=1, attribute=1, prorpertyTable=1
+            var featureId1 = new FeatureIDBuilder(1, 1, secondPropertyTable);
             
             
-            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds((featureId0, null,null), (featureId1,null,null));
-
+            model.LogicalMeshes[0].Primitives[0].AddMeshFeatureIds(featureId0, featureId1);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_multiple_classes.gltf");
@@ -547,6 +820,9 @@ namespace SharpGLTF.Schema2.Tiles3D
 
 
         
         
         // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/PropertyAttributesPointCloud/
         // Sample see https://github.com/CesiumGS/3d-tiles-samples/blob/main/glTF/EXT_structural_metadata/PropertyAttributesPointCloud/
+        // Note in the sample an external json file (MetadataSchema.json) is used to define the schema, which is not supported
+        // in this library yet.
+        // This test uses the same schema but defines it in code instead.
         [Test(Description = "ext_structural_metadata with pointcloud and custom attributes")]
         [Test(Description = "ext_structural_metadata with pointcloud and custom attributes")]
         public void CreatePointCloudWithCustomAttributesTest()
         public void CreatePointCloudWithCustomAttributesTest()
         {
         {
@@ -579,28 +855,40 @@ namespace SharpGLTF.Schema2.Tiles3D
             // --------------------------------------------------------------
             // --------------------------------------------------------------
 
 
             var rootMetadata = model.UseStructuralMetadata();
             var rootMetadata = model.UseStructuralMetadata();
+            var schema = rootMetadata.UseEmbeddedSchema();
 
 
-            // external references are problematic because the idea behind SharpGLTF is that all files are loaded into memory, so you don't
-            // need to track resources in disk while working with models. The whole mechanism is too complex to be worth the pain of implementing it.
-            // so my idea is that the UseExternalSchema returns a ISchemaProxy interface or something like that, that has pretty much the same methods
-            // of an actual schema, so the API usage remains the same for both an embedded and an external schema.
-
-            // var schemaUri = new Uri("MetadataSchema.json", UriKind.Relative);            
-            // var schemaProxy = rootMetadata.UseExternalSchema(schemaUri);
+            var classA = schema
+                .UseClassMetadata("exampleMetadataClass")
+                .WithName("Example metadata class")
+                .WithDescription("An example metadata class for property attributes");
 
 
-            var schema = rootMetadata.UseEmbeddedSchema("externalSchema");
+            classA.UseProperty("intensity")
+                .WithName("Example intensity property")
+                .WithDescription("An example property for the intensity, with component type FLOAT32")
+                .WithFloat32Type();
 
 
-            var externalClass = schema.UseClassMetadata("exampleMetadataClass");
+            var speciesEnum = schema.UseEnumMetadata("classificationEnumType", ("MediumVegetation", 0), ("Buildings", 1));
 
 
-            var propertyAttribute = rootMetadata.AddPropertyAttribute(externalClass);
+            classA
+                .UseProperty("classification")
+                .WithName("Example classification property")
+                .WithDescription("An example property for the classification, with the classificationEnumType")
+                .WithEnumeration(speciesEnum);
 
 
-            var intensityProperty = propertyAttribute.CreateProperty("intensity");             
-            intensityProperty.Attribute = "_INTENSITY";
 
 
-            var classificationProperty = propertyAttribute.CreateProperty("classification");
-            classificationProperty.Attribute = "_CLASSIFICATION";
+            var propertyAttribute = rootMetadata.AddPropertyAttribute(classA);
+            var intensityAttribute = propertyAttribute.CreateProperty("intensity");
+            intensityAttribute.Attribute = "_INTENSITY";
+            var classificationAttribute = propertyAttribute.CreateProperty("classification");
+            classificationAttribute.Attribute = "_CLASSIFICATION";
 
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+
+            foreach (var primitive in model.LogicalMeshes[0].Primitives)
+            {
+                primitive.AddPropertyAttribute(propertyAttribute);
+            }
+
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.glb");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.gltf");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.plotly");
             model.AttachToCurrentTest("cesium_ext_structural_metadata_with_pointcloud_attributes.plotly");

+ 44 - 0
tests/SharpGLTF.Cesium.Tests/GenericTests.cs

@@ -0,0 +1,44 @@
+using NUnit.Framework;
+using SharpGLTF.Schema2;
+using SharpGLTF.Validation;
+using System;
+
+namespace SharpGLTF
+{
+
+    [Category("Toolkit.Scenes")]
+    public class ExtStructuralMetadataTests
+    {
+        [SetUp]
+        public void SetUp()
+        {
+            Tiles3DExtensions.RegisterExtensions();
+        }
+
+        // Test files are from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions/
+
+        [Test(Description = "Reads generic 3D Tiles glTF's")]
+        [TestCase("FeatureIdAttributeAndPropertyTableFeatureIdNotInRange.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributePropertyTableInvalidValue.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributePropertyTableWithoutPropertyTables.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdAttributePropertyTableWithoutStructuralMetadata.gltf", typeof(ModelException))]
+        [TestCase("FeatureIdTextureAndPropertyTableFeatureIdNotInRange.gltf", typeof(ModelException))]
+        [TestCase("ValidFeatureIdAttributeAndPropertyTable.gltf", null)]
+        [TestCase("ValidFeatureIdTextureAndPropertyTable.gltf", null)]
+        public void ReadGenericFiles(string file, Type exception = null)
+        {
+            var fileName = $"./testfixtures/{file}";
+
+            if (exception != null)
+            {
+                Assert.Throws(exception, delegate { ModelRoot.Load(fileName); });
+            }
+            else
+            {
+                var model = ModelRoot.Load(fileName);
+                var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+                model.ValidateContent(ctx.GetContext());
+            }
+        }
+    }
+}

+ 8 - 0
tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj

@@ -26,6 +26,14 @@
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+	<Folder Include="testfixtures\meshFeatures\" />
+	<Folder Include="testfixtures\instanceFeatures\" />
+    <Folder Include="testfixtures\structuralMetadata\" />
+	<None Update="testfixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
+
+  </ItemGroup>
+
   <ItemGroup>
   <ItemGroup>
     <None Update="tree.glb">
     <None Update="tree.glb">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 141 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributeAndPropertyTableFeatureIdNotInRange.gltf

@@ -0,0 +1,141 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "FeatureIdAttributeAndPropertyTableSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_VEC3_FLOAT32" : {
+                "name" : "Example VEC3 FLOAT32 property",
+                "description" : "An example property, with type VEC3, with component type FLOAT32",
+                "type" : "VEC3",
+                "componentType" : "FLOAT32"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 4,
+        "properties" : {
+          "example_VEC3_FLOAT32" : {
+            "values" : 4
+          }
+        }
+      } ]
+    }
+  },
+  "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" : 5120,
+    "count" : 16,
+    "type" : "SCALAR",
+    "max" : [ 123 ],
+    "min" : [ -123 ]
+  } ],
+  "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/hQAAAIUAAACFAAAAhQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAewAAAHsAAAB7AAAAewAAAA==",
+    "byteLength" : 496
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAM3MzD3NzEw+AACAP83MjD+amZk/AAAAQGZmBkDNzAxAAABAQGZmRkDNzExA",
+    "byteLength" : 48
+  } ],
+  "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" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 48
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 141 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableInvalidValue.gltf

@@ -0,0 +1,141 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "FeatureIdAttributeAndPropertyTableSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_VEC3_FLOAT32" : {
+                "name" : "Example VEC3 FLOAT32 property",
+                "description" : "An example property, with type VEC3, with component type FLOAT32",
+                "type" : "VEC3",
+                "componentType" : "FLOAT32"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 4,
+        "properties" : {
+          "example_VEC3_FLOAT32" : {
+            "values" : 4
+          }
+        }
+      } ]
+    }
+  },
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAM3MzD3NzEw+AACAP83MjD+amZk/AAAAQGZmBkDNzAxAAABAQGZmRkDNzExA",
+    "byteLength" : 48
+  } ],
+  "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" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 48
+  } ],
+  "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" : 12345
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 131 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableWithoutPropertyTables.gltf

@@ -0,0 +1,131 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "FeatureIdAttributeAndPropertyTableSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_VEC3_FLOAT32" : {
+                "name" : "Example VEC3 FLOAT32 property",
+                "description" : "An example property, with type VEC3, with component type FLOAT32",
+                "type" : "VEC3",
+                "componentType" : "FLOAT32"
+              }
+            }
+          }
+        }
+      } 
+    }
+  },
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAM3MzD3NzEw+AACAP83MjD+amZk/AAAAQGZmBkDNzAxAAABAQGZmRkDNzExA",
+    "byteLength" : 48
+  } ],
+  "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" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 48
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 110 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdAttributePropertyTableWithoutStructuralMetadata.gltf

@@ -0,0 +1,110 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAM3MzD3NzEw+AACAP83MjD+amZk/AAAAQGZmBkDNzAxAAABAQGZmRkDNzExA",
+    "byteLength" : 48
+  } ],
+  "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" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 48
+  } ],
+  "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" : 12345
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

File diff suppressed because it is too large
+ 117 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/FeatureIdTextureAndPropertyTableFeatureIdNotInRange.gltf


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

@@ -0,0 +1,14 @@
+# Test fixtures
+
+This directory contains glTF files used for testing 3D Tiles functionality.
+
+3D Tiles Test fixtures are obtained from https://github.com/CesiumGS/3d-tiles-validator/tree/main/specs/data/gltfExtensions
+
+## Validating
+
+The files can be validated using the 3D Tiles Validator:
+
+```
+$ git clone https://github.com/CesiumGS/3d-tiles-validator
+$ npx ts-node  ./3d-tiles-validator/src/main.ts --tileContentFile test.gltf
+```

+ 141 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/ValidFeatureIdAttributeAndPropertyTable.gltf

@@ -0,0 +1,141 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "FeatureIdAttributeAndPropertyTableSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_VEC3_FLOAT32" : {
+                "name" : "Example VEC3 FLOAT32 property",
+                "description" : "An example property, with type VEC3, with component type FLOAT32",
+                "type" : "VEC3",
+                "componentType" : "FLOAT32"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 4,
+        "properties" : {
+          "example_VEC3_FLOAT32" : {
+            "values" : 4
+          }
+        }
+      } ]
+    }
+  },
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAM3MzD3NzEw+AACAP83MjD+amZk/AAAAQGZmBkDNzAxAAABAQGZmRkDNzExA",
+    "byteLength" : 48
+  } ],
+  "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" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 48
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

File diff suppressed because it is too large
+ 117 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/ValidFeatureIdTextureAndPropertyTable.gltf


+ 179 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/InstanceFeaturesFeatureIdAttributeInvalidValue.gltf

@@ -0,0 +1,179 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "GpuInstancesMetadataSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_STRING" : {
+                "name" : "Example STRING property",
+                "description" : "An example property, with component type STRING",
+                "type" : "STRING"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 10,
+        "properties" : {
+          "example_STRING" : {
+            "values" : 7,
+            "stringOffsets" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata", "EXT_mesh_gpu_instancing", "EXT_instance_features" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5125,
+    "count" : 36,
+    "type" : "SCALAR",
+    "max" : [ 23 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ -1.0, -1.0, -1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 10.0, 10.0, 10.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC4",
+    "max" : [ 0.733, 0.462, 0.191, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0, 0.462 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 2.0, 2.0, 2.0 ],
+    "min" : [ 1.0, 1.0, 1.0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 10,
+    "type" : "SCALAR",
+    "max" : [ 9 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAIAAAABAAAAAAAAAAMAAAACAAAABAAAAAYAAAAFAAAABAAAAAcAAAAGAAAACAAAAAoAAAAJAAAACAAAAAsAAAAKAAAADAAAAA4AAAANAAAADAAAAA8AAAAOAAAAEAAAABIAAAARAAAAEAAAABMAAAASAAAAFAAAABYAAAAVAAAAFAAAABcAAAAWAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAA",
+    "byteLength" : 720
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAA5DiOP+Q4jj/kOI4/5DgOQOQ4DkDkOA5AVlVVQFZVVUBWVVVA5DiOQOQ4jkDkOI5AHcexQB3HsUAdx7FAVlXVQFZV1UBWVdVAjuP4QI7j+ECO4/hA5DgOQeQ4DkHkOA5BAAAgQQAAIEEAACBBAAAAAAAAAAAAAAAAAACAP92ZzD0Q9YA9B0HVPHcffj/QGUs+AAMAPrywUz3rhHg/HXaWPuWqPT4f05w9Y0VvP3UqxT6+ing+IIHNPZiDYj+d+vA+w+KXPqEr+z1ub1I/DaEMP/RFsT6bkxI+REU/P9m0Hj+hD8g+H2slPgpNKT/WdC4/P+rbPqHVNT462RA/46U7P0SL7D6BlUM+RIvsPgAAgD8AAIA/AACAP+Q4jj/kOI4/5DiOP8dxnD/HcZw/x3GcP6uqqj+rqqo/q6qqP47juD+O47g/juO4P3Icxz9yHMc/chzHP1ZV1T9WVdU/VlXVPzmO4z85juM/OY7jPxzH8T8cx/E/HMfxPwAAAEAAAABAAAAAQAkACAAHAAYABQAEAAMAAgABAAAA",
+    "byteLength" : 420
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,emVyb29uZXR3b3RocmVlZm91cmZpdmVzaXhzZXZlbmVpZ2h0bmluZQAAAAAEAAAABwAAAAoAAAAPAAAAEwAAABcAAAAaAAAAHwAAACQAAAAoAAAA",
+    "byteLength" : 84
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 144,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 144,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 120,
+    "byteLength" : 160
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 280,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 400,
+    "byteLength" : 20
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 40
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 40,
+    "byteLength" : 44
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2
+      },
+      "indices" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "extensions" : {
+      "EXT_mesh_gpu_instancing" : {
+        "attributes" : {
+          "TRANSLATION" : 3,
+          "ROTATION" : 4,
+          "SCALE" : 5,
+          "_FEATURE_ID_0" : 6
+        }
+      },
+      "EXT_instance_features" : {
+        "featureIds" : [ {
+          "featureCount" : 10,
+          "attribute" : 12345,
+          "propertyTable" : 0
+        } ]
+      }
+    },
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 171 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/InstanceFeaturesWithoutMeshGpuInstancing.gltf

@@ -0,0 +1,171 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "GpuInstancesMetadataSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_STRING" : {
+                "name" : "Example STRING property",
+                "description" : "An example property, with component type STRING",
+                "type" : "STRING"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 10,
+        "properties" : {
+          "example_STRING" : {
+            "values" : 7,
+            "stringOffsets" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata", "EXT_mesh_gpu_instancing", "EXT_instance_features" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5125,
+    "count" : 36,
+    "type" : "SCALAR",
+    "max" : [ 23 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ -1.0, -1.0, -1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 10.0, 10.0, 10.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC4",
+    "max" : [ 0.733, 0.462, 0.191, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0, 0.462 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 2.0, 2.0, 2.0 ],
+    "min" : [ 1.0, 1.0, 1.0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 10,
+    "type" : "SCALAR",
+    "max" : [ 9 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAIAAAABAAAAAAAAAAMAAAACAAAABAAAAAYAAAAFAAAABAAAAAcAAAAGAAAACAAAAAoAAAAJAAAACAAAAAsAAAAKAAAADAAAAA4AAAANAAAADAAAAA8AAAAOAAAAEAAAABIAAAARAAAAEAAAABMAAAASAAAAFAAAABYAAAAVAAAAFAAAABcAAAAWAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAA",
+    "byteLength" : 720
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAA5DiOP+Q4jj/kOI4/5DgOQOQ4DkDkOA5AVlVVQFZVVUBWVVVA5DiOQOQ4jkDkOI5AHcexQB3HsUAdx7FAVlXVQFZV1UBWVdVAjuP4QI7j+ECO4/hA5DgOQeQ4DkHkOA5BAAAgQQAAIEEAACBBAAAAAAAAAAAAAAAAAACAP92ZzD0Q9YA9B0HVPHcffj/QGUs+AAMAPrywUz3rhHg/HXaWPuWqPT4f05w9Y0VvP3UqxT6+ing+IIHNPZiDYj+d+vA+w+KXPqEr+z1ub1I/DaEMP/RFsT6bkxI+REU/P9m0Hj+hD8g+H2slPgpNKT/WdC4/P+rbPqHVNT462RA/46U7P0SL7D6BlUM+RIvsPgAAgD8AAIA/AACAP+Q4jj/kOI4/5DiOP8dxnD/HcZw/x3GcP6uqqj+rqqo/q6qqP47juD+O47g/juO4P3Icxz9yHMc/chzHP1ZV1T9WVdU/VlXVPzmO4z85juM/OY7jPxzH8T8cx/E/HMfxPwAAAEAAAABAAAAAQAkACAAHAAYABQAEAAMAAgABAAAA",
+    "byteLength" : 420
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,emVyb29uZXR3b3RocmVlZm91cmZpdmVzaXhzZXZlbmVpZ2h0bmluZQAAAAAEAAAABwAAAAoAAAAPAAAAEwAAABcAAAAaAAAAHwAAACQAAAAoAAAA",
+    "byteLength" : 84
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 144,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 144,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 120,
+    "byteLength" : 160
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 280,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 400,
+    "byteLength" : 20
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 40
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 40,
+    "byteLength" : 44
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2
+      },
+      "indices" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "extensions" : {
+      "EXT_instance_features" : {
+        "featureIds" : [ {
+          "featureCount" : 10,
+          "attribute" : 0,
+          "propertyTable" : 0
+        } ]
+      }
+    },
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 179 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/instanceFeatures/ValidInstanceFeatures.gltf

@@ -0,0 +1,179 @@
+{
+  "extensions" : {
+    "EXT_structural_metadata" : {
+      "schema" : {
+        "id": "GpuInstancesMetadataSchema",
+        "classes" : {
+          "exampleMetadataClass" : {
+            "name" : "Example metadata class",
+            "description" : "An example metadata class",
+            "properties" : {
+              "example_STRING" : {
+                "name" : "Example STRING property",
+                "description" : "An example property, with component type STRING",
+                "type" : "STRING"
+              }
+            }
+          }
+        }
+      },
+      "propertyTables" : [ {
+        "name" : "Example property table",
+        "class" : "exampleMetadataClass",
+        "count" : 10,
+        "properties" : {
+          "example_STRING" : {
+            "values" : 7,
+            "stringOffsets" : 8
+          }
+        }
+      } ]
+    }
+  },
+  "extensionsUsed" : [ "EXT_structural_metadata", "EXT_mesh_gpu_instancing", "EXT_instance_features" ],
+  "accessors" : [ {
+    "bufferView" : 0,
+    "byteOffset" : 0,
+    "componentType" : 5125,
+    "count" : 36,
+    "type" : "SCALAR",
+    "max" : [ 23 ],
+    "min" : [ 0 ]
+  }, {
+    "bufferView" : 1,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 2,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 24,
+    "type" : "VEC3",
+    "max" : [ 1.0, 1.0, 1.0 ],
+    "min" : [ -1.0, -1.0, -1.0 ]
+  }, {
+    "bufferView" : 3,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 10.0, 10.0, 10.0 ],
+    "min" : [ 0.0, 0.0, 0.0 ]
+  }, {
+    "bufferView" : 4,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC4",
+    "max" : [ 0.733, 0.462, 0.191, 1.0 ],
+    "min" : [ 0.0, 0.0, 0.0, 0.462 ]
+  }, {
+    "bufferView" : 5,
+    "byteOffset" : 0,
+    "componentType" : 5126,
+    "count" : 10,
+    "type" : "VEC3",
+    "max" : [ 2.0, 2.0, 2.0 ],
+    "min" : [ 1.0, 1.0, 1.0 ]
+  }, {
+    "bufferView" : 6,
+    "byteOffset" : 0,
+    "componentType" : 5123,
+    "count" : 10,
+    "type" : "SCALAR",
+    "max" : [ 9 ],
+    "min" : [ 0 ]
+  } ],
+  "asset" : {
+    "generator" : "JglTF from https://github.com/javagl/JglTF",
+    "version" : "2.0"
+  },
+  "buffers" : [ {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAIAAAABAAAAAAAAAAMAAAACAAAABAAAAAYAAAAFAAAABAAAAAcAAAAGAAAACAAAAAoAAAAJAAAACAAAAAsAAAAKAAAADAAAAA4AAAANAAAADAAAAA8AAAAOAAAAEAAAABIAAAARAAAAEAAAABMAAAASAAAAFAAAABYAAAAVAAAAFAAAABcAAAAWAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAA",
+    "byteLength" : 720
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAA5DiOP+Q4jj/kOI4/5DgOQOQ4DkDkOA5AVlVVQFZVVUBWVVVA5DiOQOQ4jkDkOI5AHcexQB3HsUAdx7FAVlXVQFZV1UBWVdVAjuP4QI7j+ECO4/hA5DgOQeQ4DkHkOA5BAAAgQQAAIEEAACBBAAAAAAAAAAAAAAAAAACAP92ZzD0Q9YA9B0HVPHcffj/QGUs+AAMAPrywUz3rhHg/HXaWPuWqPT4f05w9Y0VvP3UqxT6+ing+IIHNPZiDYj+d+vA+w+KXPqEr+z1ub1I/DaEMP/RFsT6bkxI+REU/P9m0Hj+hD8g+H2slPgpNKT/WdC4/P+rbPqHVNT462RA/46U7P0SL7D6BlUM+RIvsPgAAgD8AAIA/AACAP+Q4jj/kOI4/5DiOP8dxnD/HcZw/x3GcP6uqqj+rqqo/q6qqP47juD+O47g/juO4P3Icxz9yHMc/chzHP1ZV1T9WVdU/VlXVPzmO4z85juM/OY7jPxzH8T8cx/E/HMfxPwAAAEAAAABAAAAAQAkACAAHAAYABQAEAAMAAgABAAAA",
+    "byteLength" : 420
+  }, {
+    "uri" : "data:application/gltf-buffer;base64,emVyb29uZXR3b3RocmVlZm91cmZpdmVzaXhzZXZlbmVpZ2h0bmluZQAAAAAEAAAABwAAAAoAAAAPAAAAEwAAABcAAAAaAAAAHwAAACQAAAAoAAAA",
+    "byteLength" : 84
+  } ],
+  "bufferViews" : [ {
+    "buffer" : 0,
+    "byteOffset" : 0,
+    "byteLength" : 144,
+    "target" : 34963
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 144,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 0,
+    "byteOffset" : 432,
+    "byteLength" : 288,
+    "target" : 34962
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 0,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 120,
+    "byteLength" : 160
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 280,
+    "byteLength" : 120
+  }, {
+    "buffer" : 1,
+    "byteOffset" : 400,
+    "byteLength" : 20
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 0,
+    "byteLength" : 40
+  }, {
+    "buffer" : 2,
+    "byteOffset" : 40,
+    "byteLength" : 44
+  } ],
+  "meshes" : [ {
+    "primitives" : [ {
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2
+      },
+      "indices" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "extensions" : {
+      "EXT_mesh_gpu_instancing" : {
+        "attributes" : {
+          "TRANSLATION" : 3,
+          "ROTATION" : 4,
+          "SCALE" : 5,
+          "_FEATURE_ID_0" : 6
+        }
+      },
+      "EXT_instance_features" : {
+        "featureIds" : [ {
+          "featureCount" : 10,
+          "attribute" : 0,
+          "propertyTable" : 0
+        } ]
+      }
+    },
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAccessorNormalized.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ],
+    "normalized": true
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAccessorNotScalar.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 2
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAttributeInvalidType.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : "NOT_A_NUMBER"
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeAttributeInvalidValue.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : 12345
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : "NOT_AN_INTEGER",
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : -12345,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMismatch.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : 1,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMismatchForNullFeatureId.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : 2,
+            "attribute" : 0,
+            "nullFeatureId": 1
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 101 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf

@@ -0,0 +1,101 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : [ {
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeLabelInvalidType.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : [ {
+            "label": 12345,
+            "featureCount" : 4,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeLabelInvalidValue.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : [ {
+            "label": "NOT:A:VALID/IDENTIFIER",
+            "featureCount" : 4,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeNullFeatureIdInvalidType.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : [ {
+            "nullFeatureId": "NOT_A_NUMBER",
+            "featureCount" : 4,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdAttributeNullFeatureIdInvalidValue.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : [ {
+            "nullFeatureId": -12345,
+            "featureCount" : 4,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureFeatureCountMismatch.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureSamplerInvalidFilterMode.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsInvalidElementType.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsInvalidType.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsTooManyChannels.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureChannelsTooManyElements.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureImageDataInvalid.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureIndexInvalidType.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureIndexInvalidValue.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureInvalidType.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureTexCoordInvalidType.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/FeatureIdTextureTextureTexCoordInvalidValue.gltf


+ 19 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/README.md

@@ -0,0 +1,19 @@
+
+
+The files in this directory are used for the specs for the `EXT_mesh_features`
+validation.
+
+The valid files have been taken from
+https://github.com/CesiumGS/3d-tiles-samples/tree/a256d9f68df15bbfc75ea3891f52c72a36d04202/glTF/EXT_mesh_features
+
+The `ValidFeatureIdTexture.glb` and `ValidFeatureIdAttributeDefault/` are 
+intended for basic tests of binary- and default (non-embedded) glTF assets. 
+
+The `ValidFeatureIdAttributeWithByteStride.glb` was created from the original
+`ValidFeatureIdTexture.gltf` by passing it through https://gltf.report/ , which 
+happens to write all attributes in an interleaved way, causing a byte stride 
+to be inserted. 
+
+The other files (starting with `FeatureIdTexture*` or `FeatureIdAttribute*`)
+have been edited to cause validation errors (with the error indicated by 
+their file name, as far as reasonably possible). 

+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttribute.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 159 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeDefault/ValidFeatureIdAttributeDefault.gltf

@@ -0,0 +1,159 @@
+{
+  "extensionsUsed": [
+    "EXT_mesh_features"
+  ],
+  "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,
+        1,
+        0
+      ],
+      "min": [
+        0,
+        0,
+        0
+      ]
+    },
+    {
+      "bufferView": 2,
+      "byteOffset": 0,
+      "componentType": 5126,
+      "count": 16,
+      "type": "VEC3",
+      "max": [
+        0,
+        0,
+        1
+      ],
+      "min": [
+        0,
+        0,
+        1
+      ]
+    },
+    {
+      "bufferView": 3,
+      "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": "ValidFeatureIdAttributeDefault_data.bin",
+      "byteLength": 496
+    }
+  ],
+  "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
+    }
+  ],
+  "materials": [
+    {
+      "pbrMetallicRoughness": {
+        "baseColorFactor": [
+          0.5,
+          1,
+          0.5,
+          1
+        ],
+        "metallicFactor": 0,
+        "roughnessFactor": 1
+      },
+      "alphaMode": "OPAQUE",
+      "doubleSided": true
+    }
+  ],
+  "meshes": [
+    {
+      "primitives": [
+        {
+          "extensions": {
+            "EXT_mesh_features": {
+              "featureIds": [
+                {
+                  "featureCount": 4,
+                  "attribute": 0
+                }
+              ]
+            }
+          },
+          "attributes": {
+            "POSITION": 1,
+            "NORMAL": 2,
+            "_FEATURE_ID_0": 3
+          },
+          "indices": 0,
+          "material": 0,
+          "mode": 4
+        }
+      ]
+    }
+  ],
+  "nodes": [
+    {
+      "mesh": 0
+    }
+  ],
+  "scene": 0,
+  "scenes": [
+    {
+      "nodes": [
+        0
+      ]
+    }
+  ]
+}

BIN
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeDefault/ValidFeatureIdAttributeDefault_data.bin


BIN
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithByteStride.glb


+ 102 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithLargerFeatureCount.gltf

@@ -0,0 +1,102 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : 123,
+            "attribute" : 0
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

+ 103 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdAttributeWithNullFeatureId.gltf

@@ -0,0 +1,103 @@
+{
+  "extensionsUsed" : [ "EXT_mesh_features" ],
+  "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 ]
+  } ],
+  "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/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==",
+    "byteLength" : 496
+  } ],
+  "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
+  } ],
+  "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" : 3,
+            "attribute" : 0,
+            "nullFeatureId": 2
+          } ]
+        }
+      },
+      "attributes" : {
+        "POSITION" : 1,
+        "NORMAL" : 2,
+        "_FEATURE_ID_0" : 3
+      },
+      "indices" : 0,
+      "material" : 0,
+      "mode" : 4
+    } ]
+  } ],
+  "nodes" : [ {
+    "mesh" : 0
+  } ],
+  "scene" : 0,
+  "scenes" : [ {
+    "nodes" : [ 0 ]
+  } ]
+}

BIN
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTexture.glb


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTexture.gltf


File diff suppressed because it is too large
+ 65 - 0
tests/SharpGLTF.Cesium.Tests/testfixtures/meshFeatures/ValidFeatureIdTextureUsingDefaultChannels.gltf


+ 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 ]
+  } ]
+}

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