Browse Source

update branch

Bert Temme 2 years ago
parent
commit
dedd550ce8
60 changed files with 1452 additions and 507 deletions
  1. 25 0
      .github/workflows/dotnet.yml
  2. 8 1
      SharpGLTF.sln
  3. 1 1
      build/Directory.Build.props
  4. 10 3
      build/SharpGLTF.CodeGen/Constants.cs
  5. 1 0
      build/SharpGLTF.CodeGen/Ext.AGI_Articulations.cs
  6. 1 0
      build/SharpGLTF.CodeGen/Ext.AGI_RootStkMetadata.cs
  7. 45 0
      build/SharpGLTF.CodeGen/Ext.EXT_MeshFeatures.cs
  8. 35 31
      build/SharpGLTF.CodeGen/Program.cs
  9. 10 0
      build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/README.md
  10. 47 0
      build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureId.schema.json
  11. 8 0
      build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdAttribute.schema.json
  12. 30 0
      build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdTexture.schema.json
  13. 27 0
      build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/mesh.primitive.EXT_mesh_features.schema.json
  14. 19 0
      build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj
  15. 1 1
      examples/Example1/Example1.csproj
  16. 1 1
      examples/InfiniteSkinnedTentacle/InfiniteSkinnedTentacle.csproj
  17. 1 1
      examples/PointCloudGalaxy/PointCloudGalaxy.csproj
  18. 15 15
      examples/SharpGLTF.Runtime.MonoGame/MeshPrimitiveReader.cs
  19. 1 1
      examples/SharpGLTF.Runtime.MonoGame/SharpGLTF.Runtime.MonoGame.csproj
  20. 1 1
      src/Directory.Build.props
  21. 56 18
      src/Shared/_Extensions.cs
  22. 28 0
      src/SharpGLTF.Agi/SharpGLTF.Agi.csproj
  23. 29 0
      src/SharpGLTF.Agi/schema2/AgiExtensions.cs
  24. 1 1
      src/SharpGLTF.Agi/schema2/agi.Articulations.cs
  25. 0 0
      src/SharpGLTF.Agi/schema2/agi.StkMetadata.cs
  26. 64 64
      src/SharpGLTF.Agi/schema2/generated/ext.AgiNodeArticulations.g.cs
  27. 64 64
      src/SharpGLTF.Agi/schema2/generated/ext.AgiNodeStkMetadata.g.cs
  28. 164 164
      src/SharpGLTF.Agi/schema2/generated/ext.AgiRootArticulations.g.cs
  29. 97 97
      src/SharpGLTF.Agi/schema2/generated/ext.AgiRootStkMetadata.g.cs
  30. 2 0
      src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs
  31. 145 0
      src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs
  32. 156 0
      src/SharpGLTF.Cesium/Schema2/MeshExtMeshFeatures.cs
  33. 1 1
      src/SharpGLTF.Cesium/SharpGLTF.Cesium.csproj
  34. 16 0
      src/SharpGLTF.Cesium/Validator.cs
  35. 11 3
      src/SharpGLTF.Core/Memory/FloatingArrays.cs
  36. 1 6
      src/SharpGLTF.Core/Schema2/gltf.ExtensionsFactory.cs
  37. 1 1
      src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs
  38. 68 2
      src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
  39. 2 2
      src/SharpGLTF.Core/SharpGLTF.Core.csproj
  40. 1 1
      src/SharpGLTF.Runtime/Runtime/RuntimeOptions.cs
  41. 1 1
      src/SharpGLTF.Runtime/SharpGLTF.Runtime.csproj
  42. 5 1
      src/SharpGLTF.Toolkit/BaseBuilder.cs
  43. 2 2
      src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs
  44. 2 2
      src/SharpGLTF.Toolkit/Scenes/Transformers.cs
  45. 1 1
      src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs
  46. 1 1
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  47. 3 3
      src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj
  48. 3 1
      src/build-alpha.cmd
  49. 6 2
      src/build-preview.cmd
  50. 145 0
      tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
  51. 1 1
      tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj
  52. 70 0
      tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs
  53. 1 1
      tests/SharpGLTF.Core.Tests/SharpGLTF.Core.Tests.csproj
  54. 9 3
      tests/SharpGLTF.Core.Tests/Transforms/AffineTransformMatrixTests.cs
  55. 1 1
      tests/SharpGLTF.DownloadTestFiles/SharpGLTF.DownloadTestFiles.csproj
  56. 2 2
      tests/SharpGLTF.NUnit/SharpGLTF.NUnit.csproj
  57. 1 1
      tests/SharpGLTF.Runtime.Tests/SharpGLTF.Runtime.Tests.csproj
  58. 2 2
      tests/SharpGLTF.ThirdParty.Tests/SharpGLTF.ThirdParty.Tests.csproj
  59. 1 1
      tests/SharpGLTF.Toolkit.Tests/SharpGLTF.Toolkit.Tests.csproj
  60. 1 1
      tests/SharpGLTF.Trimmed.App/SharpGLTF.Trimmed.App.csproj

+ 25 - 0
.github/workflows/dotnet.yml

@@ -0,0 +1,25 @@
+name: SharpGLTF
+
+on: [push, pull_request]
+
+jobs:
+  build:
+
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        dotnet-version: [ '6.0.x' ]
+        os: [windows-latest, ubuntu-latest, macos-latest]
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Setup dotnet ${{ matrix.dotnet-version }}
+        uses: actions/setup-dotnet@v3
+        with:
+          dotnet-version: ${{ matrix.dotnet-version }}
+      - name: Install dependencies
+        run: dotnet restore
+      - name: Build
+        run: dotnet build
+      - name: Test
+        run: dotnet test ./tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj

+ 8 - 1
SharpGLTF.sln

@@ -58,7 +58,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Runtime.Tests", "
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Cesium", "src\SharpGLTF.Cesium\SharpGLTF.Cesium.csproj", "{9FA2AFB3-B150-428D-9D6C-48F60010FFF3}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGLTF.Cesium.Tests", "tests\SharpGLTF.Cesium.Tests\SharpGLTF.Cesium.Tests.csproj", "{94011062-791E-4D34-ACA8-09AAAD49B299}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGLTF.Cesium.Tests", "tests\SharpGLTF.Cesium.Tests\SharpGLTF.Cesium.Tests.csproj", "{94011062-791E-4D34-ACA8-09AAAD49B299}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGLTF.Agi", "src\SharpGLTF.Agi\SharpGLTF.Agi.csproj", "{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -138,6 +140,10 @@ Global
 		{94011062-791E-4D34-ACA8-09AAAD49B299}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{94011062-791E-4D34-ACA8-09AAAD49B299}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{94011062-791E-4D34-ACA8-09AAAD49B299}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -161,6 +167,7 @@ Global
 		{3925E45C-E7AA-4396-9567-4D8B1D1EF1C6} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
 		{9FA2AFB3-B150-428D-9D6C-48F60010FFF3} = {072B725F-773F-4751-9616-E9778897C1D2}
 		{94011062-791E-4D34-ACA8-09AAAD49B299} = {0CBF510D-D836-40BA-95EC-E93FDBB90632}
+		{6BEBBB6A-932F-4AF2-B1D9-3B533BE2C8F3} = {072B725F-773F-4751-9616-E9778897C1D2}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {1D7BBAD9-834C-4981-AC96-0AA5226FC43F}

+ 1 - 1
build/Directory.Build.props

@@ -38,7 +38,7 @@
     
     <AdditionalFiles Include="$(MsBuildThisFileDirectory)..\stylecop.json" />
     
-    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.4">
+    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>

+ 10 - 3
build/SharpGLTF.CodeGen/Constants.cs

@@ -11,12 +11,15 @@ namespace SharpGLTF
         public static string RemoteSchemaRepo = "https://github.com/KhronosGroup/glTF.git";
 
         /// <summary>
-        /// Directory where the schema is downloaded and used as source
+        /// Program directory
         /// </summary>
-        public static string LocalRepoDirectory => System.IO.Path.Combine(System.IO.Path.GetDirectoryName(typeof(Program).Assembly.Location), "glTF");
-
         public static string ProgramDirectory => System.IO.Path.GetDirectoryName(typeof(Program).Assembly.Location);
 
+        /// <summary>
+        /// Directory where the schema is downloaded and used as source
+        /// </summary>
+        public static string LocalRepoDirectory => System.IO.Path.Combine(ProgramDirectory, "glTF");
+
         #endregion
 
         #region main schema paths
@@ -58,6 +61,7 @@ namespace SharpGLTF
             return System.IO.Path.Combine(ProgramDirectory, "Schemas", ext, "schema", json);
         }
 
+
         #endregion
 
         #region code generation output paths
@@ -69,6 +73,9 @@ namespace SharpGLTF
 
         public static string CesiumProjectDirectory => "src\\SharpGLTF.Cesium\\Schema2\\Generated";
 
+        public static string AgiProjectDirectory => "src\\SharpGLTF.Agi\\Schema2\\Generated";
+
+
         /// <summary>
         /// namespace of the emitted generated code
         /// </summary>

+ 1 - 0
build/SharpGLTF.CodeGen/Ext.AGI_Articulations.cs

@@ -8,6 +8,7 @@ namespace SharpGLTF
 {
     class AgiArticulationsExtension : SchemaProcessor
     {
+        public override string GetTargetProject() { return Constants.AgiProjectDirectory; }
         private static string RootSchemaUri => Constants.VendorExtensionPath("AGI_articulations", "glTF.AGI_articulations.schema.json");
         private static string NodeSchemaUri => Constants.VendorExtensionPath("AGI_articulations", "node.AGI_articulations.schema.json");
 

+ 1 - 0
build/SharpGLTF.CodeGen/Ext.AGI_RootStkMetadata.cs

@@ -8,6 +8,7 @@ namespace SharpGLTF
 {
     class AgiStkMetadataExtension : SchemaProcessor
     {
+        public override string GetTargetProject() { return Constants.AgiProjectDirectory; }
         private static string RootSchemaUri => Constants.VendorExtensionPath("AGI_stk_metadata", "glTF.AGI_stk_metadata.schema.json");
         private static string NodeSchemaUri => Constants.VendorExtensionPath("AGI_stk_metadata", "node.AGI_stk_metadata.schema.json");
 

+ 45 - 0
build/SharpGLTF.CodeGen/Ext.EXT_MeshFeatures.cs

@@ -0,0 +1,45 @@
+using SharpGLTF.CodeGen;
+using SharpGLTF.SchemaReflection;
+using System;
+using System.Collections.Generic;
+
+namespace SharpGLTF
+{
+    class ExtMeshFeaturesExtension : SchemaProcessor
+    {
+        public override string GetTargetProject() { return Constants.CesiumProjectDirectory; }
+    
+        private static string RootSchemaUri => Constants.CustomExtensionsPath("EXT_mesh_features", "mesh.primitive.EXT_mesh_features.schema.json");
+    
+        const string ExtensionFeatureIdTextureName = "Feature ID Texture in EXT_mesh_features";
+    
+        public override void PrepareTypes(CSharpEmitter newEmitter, SchemaType.Context ctx)
+        {
+            newEmitter.SetRuntimeName("EXT_mesh_features glTF Mesh Primitive extension", "MeshExtMeshFeatures");
+            newEmitter.SetRuntimeName("Feature ID in EXT_mesh_features", "MeshExtMeshFeatureID");
+            newEmitter.SetRuntimeName(ExtensionFeatureIdTextureName, "MeshExtMeshFeatureIDTexture");            
+        }
+    
+        public override IEnumerable<(string TargetFileName, SchemaType.Context Schema)> Process()
+        {
+            yield return ("Ext.CESIUM_ext_mesh_features.g", ProcessNode());
+        }
+    
+        private static SchemaType.Context ProcessNode()
+        {
+            var ctx = SchemaProcessing.LoadSchemaContext(RootSchemaUri);
+            ctx.IgnoredByCodeEmitter("glTF Property");
+            ctx.IgnoredByCodeEmitter("glTF Child of Root Property");
+            ctx.IgnoredByCodeEmitter("Texture Info");
+    
+            var fld = ctx.FindClass(ExtensionFeatureIdTextureName)
+                .GetField("channels");
+    
+            // for now we simply remove the default value, it can be set
+            // in the constructor or on demand when the APIs are Called.
+            fld.RemoveDefaultValue();                
+    
+            return ctx;
+        }        
+    }
+}

+ 35 - 31
build/SharpGLTF.CodeGen/Program.cs

@@ -1,4 +1,6 @@
-using System;
+using SharpGLTF.CodeGen;
+using SharpGLTF.SchemaReflection;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
@@ -23,38 +25,40 @@ namespace SharpGLTF
 
             //// ---------------------------------------------- Add extensions            
 
-            // material extensions       
-            processors.Add(new UnlitExtension());
-            processors.Add(new IorExtension());
-            processors.Add(new SheenExtension());
-            processors.Add(new VolumeExtension());
-            processors.Add(new SpecularExtension());
-            processors.Add(new ClearCoatExtension());
-            processors.Add(new IridescenceExtension());
-            processors.Add(new TransmissionExtension());
-            processors.Add(new EmissiveStrengthExtension());
-            processors.Add(new SpecularGlossinessExtension());
+            //// material extensions       
+            //processors.Add(new UnlitExtension());
+            //processors.Add(new IorExtension());
+            //processors.Add(new SheenExtension());
+            //processors.Add(new VolumeExtension());
+            //processors.Add(new SpecularExtension());
+            //processors.Add(new ClearCoatExtension());
+            //processors.Add(new IridescenceExtension());
+            //processors.Add(new TransmissionExtension());
+            //processors.Add(new EmissiveStrengthExtension());
+            //processors.Add(new SpecularGlossinessExtension());
 
-            // cesium outlines
-            processors.Add(new CesiumPrimitiveOutlineExtension());
+            //// cesium outlines
+            //processors.Add(new CesiumPrimitiveOutlineExtension());
 
-            // lights
-            processors.Add(new LightsPunctualExtension());
+            //// lights
+            //processors.Add(new LightsPunctualExtension());
 
-            // gpu mesh instancing
-            processors.Add(new MeshGpuInstancingExtension());
+            //// gpu mesh instancing
+            //processors.Add(new MeshGpuInstancingExtension());
 
-            // textures
-            processors.Add(new TextureTransformExtension());
-            processors.Add(new TextureDDSExtension());
-            processors.Add(new TextureWebpExtension());
-            processors.Add(new TextureKtx2Extension());
+            //// textures
+            //processors.Add(new TextureTransformExtension());
+            //processors.Add(new TextureDDSExtension());
+            //processors.Add(new TextureWebpExtension());
+            //processors.Add(new TextureKtx2Extension());
 
-            processors.Add(new AgiArticulationsExtension());
-            processors.Add(new AgiStkMetadataExtension());
+            //processors.Add(new AgiArticulationsExtension());
+            //processors.Add(new AgiStkMetadataExtension());
 
-            // other
-            processors.Add(new XmpJsonLdExtension());
+            //// other
+            //processors.Add(new XmpJsonLdExtension());
+
+            //processors.Add(new ExtMeshFeaturesExtension());
 
             processors.Add(new ExtInstanceFeaturesExtension());
 
@@ -62,15 +66,15 @@ namespace SharpGLTF
 
             foreach (var processor in processors)
             {
-                foreach(var (targetFileName, schema) in processor.Process())
+                foreach (var (targetFileName, schema) in processor.Process())
                 {
                     System.Console.WriteLine($"Emitting {targetFileName}...");
 
                     SchemaProcessing.EmitCodeFromSchema(processor.GetTargetProject(), targetFileName, schema, processors);
                 }
-            }            
+            }
         }
 
         #endregion     
-    }    
-}
+    }
+}

+ 10 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/README.md

@@ -0,0 +1,10 @@
+# EXT_mesh_features
+
+This directory contains schema's for Cesium extension 
+EXT_mesh_features
+
+PR at Khronos: https://github.com/KhronosGroup/glTF/pull/2082
+
+Schema's are copied from https://github.com/CesiumGS/glTF/tree/proposal-EXT_mesh_features/extensions/2.0/Vendor/EXT_mesh_features/schema
+
+

+ 47 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureId.schema.json

@@ -0,0 +1,47 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "featureId.schema.json",
+    "title": "Feature ID in EXT_mesh_features",
+    "type": "object",
+    "description": "Feature IDs stored in an attribute or texture.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "featureCount": {
+            "type": "integer",
+            "minimum": 1,
+            "description": "The number of unique features in the attribute or texture."
+        },
+        "nullFeatureId": {
+            "type": "integer",
+            "minimum": 0,
+            "description": "A value that indicates that no feature is associated with this vertex or texel."
+        },
+        "label": {
+            "type": "string",
+            "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$",
+            "description": "A label assigned to this feature ID set. Labels must be alphanumeric identifiers matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`."
+        },
+        "attribute": {
+            "description": "An attribute containing feature IDs. When `attribute` and `texture` are omitted the feature IDs are assigned to vertices by their index.",
+            "$ref": "featureIdAttribute.schema.json"
+        },
+        "texture": {
+            "description": "A texture containing feature IDs.",
+            "$ref": "featureIdTexture.schema.json"
+        },
+        "propertyTable": {
+            "type": "integer",
+            "minimum": 0,
+            "description": "The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension."
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "featureCount"
+    ]
+}

+ 8 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdAttribute.schema.json

@@ -0,0 +1,8 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "featureIdAttribute.schema.json",
+    "title": "Feature ID Attribute in EXT_mesh_features",
+    "type": "integer",
+    "minimum": 0,
+    "description": "An integer value used to construct a string in the format `_FEATURE_ID_<set index>` which is a reference to a key in `mesh.primitives.attributes` (e.g. a value of `0` corresponds to `_FEATURE_ID_0`)."
+}

+ 30 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdTexture.schema.json

@@ -0,0 +1,30 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "featureIdTexture.schema.json",
+    "title": "Feature ID Texture in EXT_mesh_features",
+    "type": "object",
+    "description": "A texture containing feature IDs",
+    "allOf": [
+        {
+            "$ref": "textureInfo.schema.json"
+        }
+    ],
+    "properties": {
+        "channels": {
+            "type": "array",
+            "items": {
+                "type": "integer",
+                "minimum": 0
+            },
+            "minItems": 1,
+            "description": "Texture channels containing feature IDs, identified by index. Feature IDs may be packed into multiple channels if a single channel does not have sufficient bit depth to represent all feature ID values. The values are packed in little-endian order.",
+            "default": [
+                0
+            ]
+        },
+        "index": {},
+        "texCoord": {},
+        "extensions": {},
+        "extras": {}
+    }
+}

+ 27 - 0
build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/mesh.primitive.EXT_mesh_features.schema.json

@@ -0,0 +1,27 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "mesh.primitive.EXT_mesh_features.schema.json",
+    "title": "EXT_mesh_features glTF Mesh Primitive extension",
+    "type": "object",
+    "description": "An object describing feature IDs for a mesh primitive.",
+    "allOf": [
+        {
+            "$ref": "glTFProperty.schema.json"
+        }
+    ],
+    "properties": {
+        "featureIds": {
+            "type": "array",
+            "description": "An array of feature ID sets.",
+            "minItems": 1,
+            "items": {
+                "$ref": "featureId.schema.json"
+            }
+        },
+        "extensions": {},
+        "extras": {}
+    },
+    "required": [
+        "featureIds"
+    ]
+}

+ 19 - 0
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -11,6 +11,25 @@
     <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.9.0" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Schemas\" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="Schemas\EXT_mesh_features\schema\featureId.schema.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Schemas\EXT_mesh_features\schema\featureIdAttribute.schema.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Schemas\EXT_mesh_features\schema\featureIdTexture.schema.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Schemas\EXT_mesh_features\schema\mesh.primitive.EXT_mesh_features.schema.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
   <ItemGroup>
     <None Update="Schemas\EXT_instance_features\schema\featureId.schema.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 1 - 1
examples/Example1/Example1.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
examples/InfiniteSkinnedTentacle/InfiniteSkinnedTentacle.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
examples/PointCloudGalaxy/PointCloudGalaxy.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>

+ 15 - 15
examples/SharpGLTF.Runtime.MonoGame/MeshPrimitiveReader.cs

@@ -201,8 +201,8 @@ namespace SharpGLTF.Runtime
             {
                 if (element.VertexElementFormat == VertexElementFormat.Vector2)
                 {
-                    var dst = _Vertex.Slice(element.Offset, sizeof(XY));
-                    System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);
+                    var dst = _Vertex.Slice(element.Offset);
+                    System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, XY>(dst)[0] = value;
                     return;
                 }
 
@@ -213,8 +213,8 @@ namespace SharpGLTF.Runtime
             {
                 if (element.VertexElementFormat == VertexElementFormat.Vector3)
                 {
-                    var dst = _Vertex.Slice(element.Offset, sizeof(XYZ));
-                    System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);
+                    var dst = _Vertex.Slice(element.Offset);
+                    System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, XYZ>(dst)[0] = value;
                     return;
                 }
 
@@ -227,8 +227,8 @@ namespace SharpGLTF.Runtime
 
                 switch (element.VertexElementFormat)
                 {
-                    case VertexElementFormat.Vector4:                        
-                        System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);
+                    case VertexElementFormat.Vector4:
+                        System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, XYZW>(dst)[0] = value;
                         return;
 
                     case VertexElementFormat.Byte4:
@@ -259,35 +259,35 @@ namespace SharpGLTF.Runtime
             {
                 if (element.VertexElementFormat != VertexElementFormat.Byte4) throw new ArgumentException(nameof(element));
                 
-                var dst = _Vertex.Slice(element.Offset, sizeof(Microsoft.Xna.Framework.Graphics.PackedVector.Byte4));
-                System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);                
+                var dst = _Vertex.Slice(element.Offset);
+                System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, Microsoft.Xna.Framework.Graphics.PackedVector.Byte4>(dst)[0] = value;
             }
 
             public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedByte4 value)
             {
                 if (element.VertexElementFormat != VertexElementFormat.Byte4) throw new ArgumentException(nameof(element));
 
-                var dst = _Vertex.Slice(element.Offset, sizeof(Microsoft.Xna.Framework.Graphics.PackedVector.Byte4));
-                System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);
+                var dst = _Vertex.Slice(element.Offset);
+                System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedByte4>(dst)[0] = value;
             }
 
             public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.Short4 value)
             {
                 if (element.VertexElementFormat != VertexElementFormat.Short4) throw new ArgumentException(nameof(element));
 
-                var dst = _Vertex.Slice(element.Offset, sizeof(Microsoft.Xna.Framework.Graphics.PackedVector.Short4));
-                System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);
+                var dst = _Vertex.Slice(element.Offset);
+                System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, Microsoft.Xna.Framework.Graphics.PackedVector.Short4>(dst)[0] = value;
             }
 
             public unsafe void SetValue(VertexElement element, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedShort4 value)
             {
                 if (element.VertexElementFormat != VertexElementFormat.NormalizedShort4) throw new ArgumentException(nameof(element));
                 
-                var dst = _Vertex.Slice(element.Offset, sizeof(Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedShort4));
-                System.Runtime.InteropServices.MemoryMarshal.Write(dst, ref value);                
+                var dst = _Vertex.Slice(element.Offset);
+                System.Runtime.InteropServices.MemoryMarshal.Cast<Byte, Microsoft.Xna.Framework.Graphics.PackedVector.NormalizedShort4>(dst)[0] = value;
             }
 
-            #endregion
+        #endregion
         }
 
         #endregion

+ 1 - 1
examples/SharpGLTF.Runtime.MonoGame/SharpGLTF.Runtime.MonoGame.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <RootNamespace>SharpGLTF.Runtime</RootNamespace>
     <LangVersion>8</LangVersion>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

+ 1 - 1
src/Directory.Build.props

@@ -47,7 +47,7 @@
     
     <AdditionalFiles Include="$(MsBuildThisFileDirectory)..\stylecop.json" />
     
-    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.4">
+    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>

+ 56 - 18
src/Shared/_Extensions.cs

@@ -5,8 +5,6 @@ using System.Numerics;
 using System.Linq;
 
 using SharpGLTF.Schema2;
-using System.Text.Json;
-using System.Diagnostics.CodeAnalysis;
 
 namespace SharpGLTF
 {
@@ -743,27 +741,40 @@ namespace SharpGLTF
 
         #endregion
 
-        #region json
-
-        // note: these methods have been added to newer versions of json, so they might be removed eventually        
-
-#if NET6_0_OR_GREATER
-        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonValue))]
-        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonArray))]
-        [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonObject))]
-#endif
+        #region json        
+
+        #if NET6_0
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="JsonNode"/>.
+        /// All children nodes are recursively cloned.
+        /// </summary>
+        /// <remarks>
+        /// DeepClone is available in System.Text.Json v8.0.0 or higher
+        /// so we need to provide a (much slower) implementation for net6.0.
+        /// </remarks>
+        /// <param name="node">The node to clone.</param>
+        /// <returns>A clone of <paramref name="node"/>.</returns>        
+        #if NET6_0_OR_GREATER
+        [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonValue))]
+        [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonArray))]
+        [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All, typeof(System.Text.Json.Nodes.JsonObject))]
+        #endif
         public static System.Text.Json.Nodes.JsonNode DeepClone(this System.Text.Json.Nodes.JsonNode node)
         {
             // issue tracking both DeepClone and DeepEquals: https://github.com/dotnet/runtime/issues/56592            
 
-            if (node == null) return null;
+            if (node == null) throw new ArgumentNullException(nameof(node));
 
             System.Text.Json.Nodes.JsonNode clone = null;            
 
             #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
-            if (node is System.Text.Json.Nodes.JsonValue asValue) clone = asValue.Deserialize<System.Text.Json.Nodes.JsonValue>();
-            if (node is System.Text.Json.Nodes.JsonArray asArray) clone = asArray.Deserialize<System.Text.Json.Nodes.JsonArray>();
-            if (node is System.Text.Json.Nodes.JsonObject asObject) clone = asObject.Deserialize<System.Text.Json.Nodes.JsonObject>();
+            switch(node)
+            {
+                case System.Text.Json.Nodes.JsonValue asValue: clone = System.Text.Json.JsonSerializer.Deserialize<System.Text.Json.Nodes.JsonValue>(asValue); break;
+                case System.Text.Json.Nodes.JsonArray asArray: clone = System.Text.Json.JsonSerializer.Deserialize<System.Text.Json.Nodes.JsonArray>(asArray); break;
+                case System.Text.Json.Nodes.JsonObject asObject: clone = System.Text.Json.JsonSerializer.Deserialize<System.Text.Json.Nodes.JsonObject>(asObject); break;
+            }            
             #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
 
             if (clone == null) throw new NotImplementedException();
@@ -773,17 +784,42 @@ namespace SharpGLTF
             return clone;
         }
 
+        #endif        
+
         public static bool DeepEquals(this System.Text.Json.Nodes.JsonNode x, System.Text.Json.Nodes.JsonNode y, double precission)
         {
+            #if !NET6_0
+
+            return System.Text.Json.Nodes.JsonNode.DeepEquals(x, y);
+
+            #else
+
             if (x == y) return true;
             if (x == null) return false;
             if (y == null) return false;
 
-            if (x is System.Text.Json.Nodes.JsonValue xval && y is System.Text.Json.Nodes.JsonValue yval)
+            if (x is System.Text.Json.Nodes.JsonValue xval && y is System.Text.Json.Nodes.JsonValue yval)            
             {
-                if (xval.TryGetValue<double>(out var xfl) && yval.TryGetValue<double>(out var yfl))
+                // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfT.cs#L88
+
+                if (xval.TryGetValue<int>(out var xi) && yval.TryGetValue<int>(out var yi))
                 {
-                    return Math.Abs(xfl-yfl) <= precission;
+                    return xi == yi;
+                }
+
+                if (xval.TryGetValue<long>(out var xl) && yval.TryGetValue<long>(out var yl))
+                {
+                    return xl == yl;
+                }
+
+                if (xval.TryGetValue<float>(out var xs) && yval.TryGetValue<float>(out var ys))
+                {
+                    return Math.Abs(xs - ys) <= precission;
+                }
+
+                if (xval.TryGetValue<double>(out var xd) && yval.TryGetValue<double>(out var yd))
+                {
+                    return Math.Abs(xd - yd) <= precission;
                 }
 
                 return xval.ToJsonString() == yval.ToJsonString();
@@ -812,6 +848,8 @@ namespace SharpGLTF
             }
 
             return false;
+
+            #endif
         }
 
         #endregion

+ 28 - 0
src/SharpGLTF.Agi/SharpGLTF.Agi.csproj

@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
+    <AssemblyName>SharpGLTF.Agi</AssemblyName>
+    <RootNamespace>SharpGLTF</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <Description>
+      Provides additional extensions support for SharpGLTF:
+      - AGI_articulations
+      - AGI_stk_metadata
+    </Description>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="..\Shared\Guard.cs" Link="Diagnostics\Guard.cs" />
+    <Compile Include="..\Shared\_Extensions.cs" Link="_Extensions.cs" />
+    <Compile Include="..\SharpGLTF.Core\Schema2\_Extensions.cs" Link="Schema2\_Extensions.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SharpGLTF.Core\SharpGLTF.Core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 29 - 0
src/SharpGLTF.Agi/schema2/AgiExtensions.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Schema2
+{
+    /// <summary>
+    /// Extension methods for AGI glTF Extensions
+    /// </summary>
+    public static partial class AGIExtensions
+    {
+        private static bool _AgiRegistered;
+
+        /// <summary>
+        /// This method most be called once at application's startup to register the extensions.
+        /// </summary>
+        public static void RegisterExtensions()
+        {
+            if (_AgiRegistered) return;
+
+            _AgiRegistered = true;
+
+            ExtensionsFactory.RegisterExtension<ModelRoot, AgiRootArticulations>("AGI_articulations");
+            ExtensionsFactory.RegisterExtension<ModelRoot, AgiRootStkMetadata>("AGI_stk_metadata");
+            ExtensionsFactory.RegisterExtension<Node, AgiNodeArticulations>("AGI_articulations");
+            ExtensionsFactory.RegisterExtension<Node, AgiNodeStkMetadata>("AGI_stk_metadata");
+        }
+    }
+}

+ 1 - 1
src/SharpGLTF.Core/Schema2/agi.Articulations.cs → src/SharpGLTF.Agi/schema2/agi.Articulations.cs

@@ -6,7 +6,7 @@ using System.Linq;
 using SharpGLTF.Collections;
 
 namespace SharpGLTF.Schema2
-{
+{    
     public sealed partial class AgiRootArticulations
     {
         internal AgiRootArticulations(ModelRoot root)

+ 0 - 0
src/SharpGLTF.Core/Schema2/agi.StkMetadata.cs → src/SharpGLTF.Agi/schema2/agi.StkMetadata.cs


+ 64 - 64
src/SharpGLTF.Core/Schema2/Generated/ext.AgiNodeArticulations.g.cs → src/SharpGLTF.Agi/schema2/generated/ext.AgiNodeArticulations.g.cs

@@ -1,64 +1,64 @@
-// <auto-generated/>
-
-//------------------------------------------------------------------------------------------------
-//      This file has been programatically generated; DON´T EDIT!
-//------------------------------------------------------------------------------------------------
-
-#pragma warning disable SA1001
-#pragma warning disable SA1027
-#pragma warning disable SA1028
-#pragma warning disable SA1121
-#pragma warning disable SA1205
-#pragma warning disable SA1309
-#pragma warning disable SA1402
-#pragma warning disable SA1505
-#pragma warning disable SA1507
-#pragma warning disable SA1508
-#pragma warning disable SA1652
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Numerics;
-using System.Text.Json;
-
-namespace SharpGLTF.Schema2
-{
-	using Collections;
-
-	/// <summary>
-	/// glTF Extension for an individual node in a glTF model, to associate it with the model's root AGI_articulations object.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiNodeArticulations : ExtraProperties
-	{
-	
-		private String _articulationName;
-		
-		private Boolean? _isAttachPoint;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "articulationName", _articulationName);
-			SerializeProperty(writer, "isAttachPoint", _isAttachPoint);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "articulationName": _articulationName = DeserializePropertyValue<String>(ref reader); break;
-				case "isAttachPoint": _isAttachPoint = DeserializePropertyValue<Boolean?>(ref reader); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-}
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// glTF Extension for an individual node in a glTF model, to associate it with the model's root AGI_articulations object.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiNodeArticulations : ExtraProperties
+	{
+	
+		private String _articulationName;
+		
+		private Boolean? _isAttachPoint;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "articulationName", _articulationName);
+			SerializeProperty(writer, "isAttachPoint", _isAttachPoint);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "articulationName": _articulationName = DeserializePropertyValue<String>(ref reader); break;
+				case "isAttachPoint": _isAttachPoint = DeserializePropertyValue<Boolean?>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 64 - 64
src/SharpGLTF.Core/Schema2/Generated/ext.AgiNodeStkMetadata.g.cs → src/SharpGLTF.Agi/schema2/generated/ext.AgiNodeStkMetadata.g.cs

@@ -1,64 +1,64 @@
-// <auto-generated/>
-
-//------------------------------------------------------------------------------------------------
-//      This file has been programatically generated; DON´T EDIT!
-//------------------------------------------------------------------------------------------------
-
-#pragma warning disable SA1001
-#pragma warning disable SA1027
-#pragma warning disable SA1028
-#pragma warning disable SA1121
-#pragma warning disable SA1205
-#pragma warning disable SA1309
-#pragma warning disable SA1402
-#pragma warning disable SA1505
-#pragma warning disable SA1507
-#pragma warning disable SA1508
-#pragma warning disable SA1652
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Numerics;
-using System.Text.Json;
-
-namespace SharpGLTF.Schema2
-{
-	using Collections;
-
-	/// <summary>
-	/// glTF Extension for an individual node in a glTF model, to associate it with the model's root AGI_stk_metadata object.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiNodeStkMetadata : ExtraProperties
-	{
-	
-		private Boolean? _noObscuration;
-		
-		private String _solarPanelGroupName;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "noObscuration", _noObscuration);
-			SerializeProperty(writer, "solarPanelGroupName", _solarPanelGroupName);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "noObscuration": _noObscuration = DeserializePropertyValue<Boolean?>(ref reader); break;
-				case "solarPanelGroupName": _solarPanelGroupName = DeserializePropertyValue<String>(ref reader); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-}
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// glTF Extension for an individual node in a glTF model, to associate it with the model's root AGI_stk_metadata object.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiNodeStkMetadata : ExtraProperties
+	{
+	
+		private Boolean? _noObscuration;
+		
+		private String _solarPanelGroupName;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "noObscuration", _noObscuration);
+			SerializeProperty(writer, "solarPanelGroupName", _solarPanelGroupName);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "noObscuration": _noObscuration = DeserializePropertyValue<Boolean?>(ref reader); break;
+				case "solarPanelGroupName": _solarPanelGroupName = DeserializePropertyValue<String>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 164 - 164
src/SharpGLTF.Core/Schema2/Generated/ext.AgiRootArticulations.g.cs → src/SharpGLTF.Agi/schema2/generated/ext.AgiRootArticulations.g.cs

@@ -1,164 +1,164 @@
-// <auto-generated/>
-
-//------------------------------------------------------------------------------------------------
-//      This file has been programatically generated; DON´T EDIT!
-//------------------------------------------------------------------------------------------------
-
-#pragma warning disable SA1001
-#pragma warning disable SA1027
-#pragma warning disable SA1028
-#pragma warning disable SA1121
-#pragma warning disable SA1205
-#pragma warning disable SA1309
-#pragma warning disable SA1402
-#pragma warning disable SA1505
-#pragma warning disable SA1507
-#pragma warning disable SA1508
-#pragma warning disable SA1652
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Numerics;
-using System.Text.Json;
-
-namespace SharpGLTF.Schema2
-{
-	using Collections;
-
-	/// <summary>
-	/// The type of motion applied by this articulation stage.
-	/// </summary>
-	public enum AgiArticulationTransformType
-	{
-		xTranslate,
-		yTranslate,
-		zTranslate,
-		xRotate,
-		yRotate,
-		zRotate,
-		xScale,
-		yScale,
-		zScale,
-		uniformScale,
-	}
-
-
-	/// <summary>
-	/// One stage of a model articulation definition.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiArticulationStage : ExtraProperties
-	{
-	
-		private Double _initialValue;
-		
-		private Double _maximumValue;
-		
-		private Double _minimumValue;
-		
-		private String _name;
-		
-		private AgiArticulationTransformType _type;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "initialValue", _initialValue);
-			SerializeProperty(writer, "maximumValue", _maximumValue);
-			SerializeProperty(writer, "minimumValue", _minimumValue);
-			SerializeProperty(writer, "name", _name);
-			SerializePropertyEnumSymbol<AgiArticulationTransformType>(writer, "type", _type);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "initialValue": _initialValue = DeserializePropertyValue<Double>(ref reader); break;
-				case "maximumValue": _maximumValue = DeserializePropertyValue<Double>(ref reader); break;
-				case "minimumValue": _minimumValue = DeserializePropertyValue<Double>(ref reader); break;
-				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
-				case "type": _type = DeserializePropertyValue<AgiArticulationTransformType>(ref reader); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-	/// <summary>
-	/// A model articulation definition.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiArticulation : ExtraProperties
-	{
-	
-		private String _name;
-		
-		private Vector3? _pointingVector;
-		
-		private const int _stagesMinItems = 1;
-		private ChildrenList<AgiArticulationStage,AgiArticulation> _stages;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "name", _name);
-			SerializeProperty(writer, "pointingVector", _pointingVector);
-			SerializeProperty(writer, "stages", _stages, _stagesMinItems);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
-				case "pointingVector": _pointingVector = DeserializePropertyValue<Vector3?>(ref reader); break;
-				case "stages": DeserializePropertyList<AgiArticulationStage>(ref reader, _stages); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-	/// <summary>
-	/// glTF Extension that defines metadata for applying external analysis or effects to a model.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiRootArticulations : ExtraProperties
-	{
-	
-		private const int _articulationsMinItems = 1;
-		private ChildrenList<AgiArticulation,AgiRootArticulations> _articulations;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "articulations", _articulations, _articulationsMinItems);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "articulations": DeserializePropertyList<AgiArticulation>(ref reader, _articulations); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-}
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// The type of motion applied by this articulation stage.
+	/// </summary>
+	public enum AgiArticulationTransformType
+	{
+		xTranslate,
+		yTranslate,
+		zTranslate,
+		xRotate,
+		yRotate,
+		zRotate,
+		xScale,
+		yScale,
+		zScale,
+		uniformScale,
+	}
+
+
+	/// <summary>
+	/// One stage of a model articulation definition.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiArticulationStage : ExtraProperties
+	{
+	
+		private Double _initialValue;
+		
+		private Double _maximumValue;
+		
+		private Double _minimumValue;
+		
+		private String _name;
+		
+		private AgiArticulationTransformType _type;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "initialValue", _initialValue);
+			SerializeProperty(writer, "maximumValue", _maximumValue);
+			SerializeProperty(writer, "minimumValue", _minimumValue);
+			SerializeProperty(writer, "name", _name);
+			SerializePropertyEnumSymbol<AgiArticulationTransformType>(writer, "type", _type);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "initialValue": _initialValue = DeserializePropertyValue<Double>(ref reader); break;
+				case "maximumValue": _maximumValue = DeserializePropertyValue<Double>(ref reader); break;
+				case "minimumValue": _minimumValue = DeserializePropertyValue<Double>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "type": _type = DeserializePropertyValue<AgiArticulationTransformType>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// A model articulation definition.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiArticulation : ExtraProperties
+	{
+	
+		private String _name;
+		
+		private Vector3? _pointingVector;
+		
+		private const int _stagesMinItems = 1;
+		private ChildrenList<AgiArticulationStage,AgiArticulation> _stages;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "name", _name);
+			SerializeProperty(writer, "pointingVector", _pointingVector);
+			SerializeProperty(writer, "stages", _stages, _stagesMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				case "pointingVector": _pointingVector = DeserializePropertyValue<Vector3?>(ref reader); break;
+				case "stages": DeserializePropertyList<AgiArticulationStage>(ref reader, _stages); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// glTF Extension that defines metadata for applying external analysis or effects to a model.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiRootArticulations : ExtraProperties
+	{
+	
+		private const int _articulationsMinItems = 1;
+		private ChildrenList<AgiArticulation,AgiRootArticulations> _articulations;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "articulations", _articulations, _articulationsMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "articulations": DeserializePropertyList<AgiArticulation>(ref reader, _articulations); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 97 - 97
src/SharpGLTF.Core/Schema2/Generated/ext.AgiRootStkMetadata.g.cs → src/SharpGLTF.Agi/schema2/generated/ext.AgiRootStkMetadata.g.cs

@@ -1,97 +1,97 @@
-// <auto-generated/>
-
-//------------------------------------------------------------------------------------------------
-//      This file has been programatically generated; DON´T EDIT!
-//------------------------------------------------------------------------------------------------
-
-#pragma warning disable SA1001
-#pragma warning disable SA1027
-#pragma warning disable SA1028
-#pragma warning disable SA1121
-#pragma warning disable SA1205
-#pragma warning disable SA1309
-#pragma warning disable SA1402
-#pragma warning disable SA1505
-#pragma warning disable SA1507
-#pragma warning disable SA1508
-#pragma warning disable SA1652
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Numerics;
-using System.Text.Json;
-
-namespace SharpGLTF.Schema2
-{
-	using Collections;
-
-	/// <summary>
-	/// A solar panel group definition.
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiStkSolarPanelGroup : ExtraProperties
-	{
-	
-		private const Double _efficiencyMinimum = 0;
-		private const Double _efficiencyMaximum = 100;
-		private Double _efficiency;
-		
-		private String _name;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "efficiency", _efficiency);
-			SerializeProperty(writer, "name", _name);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "efficiency": _efficiency = DeserializePropertyValue<Double>(ref reader); break;
-				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-	/// <summary>
-	/// glTF Extension that defines metadata for use with STK (Systems Tool Kit).
-	/// </summary>
-	#if NET6_0_OR_GREATER
-	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
-	#endif
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
-	partial class AgiRootStkMetadata : ExtraProperties
-	{
-	
-		private const int _solarPanelGroupsMinItems = 1;
-		private ChildrenList<AgiStkSolarPanelGroup,AgiRootStkMetadata> _solarPanelGroups;
-		
-	
-		protected override void SerializeProperties(Utf8JsonWriter writer)
-		{
-			base.SerializeProperties(writer);
-			SerializeProperty(writer, "solarPanelGroups", _solarPanelGroups, _solarPanelGroupsMinItems);
-		}
-	
-		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
-		{
-			switch (jsonPropertyName)
-			{
-				case "solarPanelGroups": DeserializePropertyList<AgiStkSolarPanelGroup>(ref reader, _solarPanelGroups); break;
-				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
-			}
-		}
-	
-	}
-
-}
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// A solar panel group definition.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiStkSolarPanelGroup : ExtraProperties
+	{
+	
+		private const Double _efficiencyMinimum = 0;
+		private const Double _efficiencyMaximum = 100;
+		private Double _efficiency;
+		
+		private String _name;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "efficiency", _efficiency);
+			SerializeProperty(writer, "name", _name);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "efficiency": _efficiency = DeserializePropertyValue<Double>(ref reader); break;
+				case "name": _name = DeserializePropertyValue<String>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// glTF Extension that defines metadata for use with STK (Systems Tool Kit).
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class AgiRootStkMetadata : ExtraProperties
+	{
+	
+		private const int _solarPanelGroupsMinItems = 1;
+		private ChildrenList<AgiStkSolarPanelGroup,AgiRootStkMetadata> _solarPanelGroups;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "solarPanelGroups", _solarPanelGroups, _solarPanelGroupsMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "solarPanelGroups": DeserializePropertyList<AgiStkSolarPanelGroup>(ref reader, _solarPanelGroups); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 2 - 0
src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs

@@ -22,6 +22,8 @@ namespace SharpGLTF.Schema2
 
             ExtensionsFactory.RegisterExtension<MeshPrimitive, CesiumPrimitiveOutline>("CESIUM_primitive_outline");
             ExtensionsFactory.RegisterExtension<Node, MeshExtInstanceFeatures>("EXT_instance_features");
+            ExtensionsFactory.RegisterExtension<MeshPrimitive, MeshExtMeshFeatures>("EXT_mesh_features");
+
         }
     }
 }

+ 145 - 0
src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs

@@ -0,0 +1,145 @@
+// <auto-generated/>
+
+//------------------------------------------------------------------------------------------------
+//      This file has been programatically generated; DON´T EDIT!
+//------------------------------------------------------------------------------------------------
+
+#pragma warning disable SA1001
+#pragma warning disable SA1027
+#pragma warning disable SA1028
+#pragma warning disable SA1121
+#pragma warning disable SA1205
+#pragma warning disable SA1309
+#pragma warning disable SA1402
+#pragma warning disable SA1505
+#pragma warning disable SA1507
+#pragma warning disable SA1508
+#pragma warning disable SA1652
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Numerics;
+using System.Text.Json;
+
+namespace SharpGLTF.Schema2
+{
+	using Collections;
+
+	/// <summary>
+	/// A texture containing feature IDs
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class MeshExtMeshFeatureIDTexture : TextureInfo
+	{
+	
+		private const int _channelsMinItems = 1;
+		private List<Int32> _channels;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "channels", _channels, _channelsMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "channels": DeserializePropertyList<Int32>(ref reader, _channels); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// Feature IDs stored in an attribute or texture.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class MeshExtMeshFeatureID : ExtraProperties
+	{
+	
+		private Int32? _attribute;
+		
+		private const Int32 _featureCountMinimum = 1;
+		private Int32 _featureCount;
+		
+		private String _label;
+		
+		private const Int32 _nullFeatureIdMinimum = 0;
+		private Int32? _nullFeatureId;
+		
+		private const Int32 _propertyTableMinimum = 0;
+		private Int32? _propertyTable;
+		
+		private MeshExtMeshFeatureIDTexture _texture;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "attribute", _attribute);
+			SerializeProperty(writer, "featureCount", _featureCount);
+			SerializeProperty(writer, "label", _label);
+			SerializeProperty(writer, "nullFeatureId", _nullFeatureId);
+			SerializeProperty(writer, "propertyTable", _propertyTable);
+			SerializePropertyObject(writer, "texture", _texture);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "attribute": _attribute = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "featureCount": _featureCount = DeserializePropertyValue<Int32>(ref reader); break;
+				case "label": _label = DeserializePropertyValue<String>(ref reader); break;
+				case "nullFeatureId": _nullFeatureId = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "propertyTable": _propertyTable = DeserializePropertyValue<Int32?>(ref reader); break;
+				case "texture": _texture = DeserializePropertyValue<MeshExtMeshFeatureIDTexture>(ref reader); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+	/// <summary>
+	/// An object describing feature IDs for a mesh primitive.
+	/// </summary>
+	#if NET6_0_OR_GREATER
+	[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+	#endif
+	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("SharpGLTF.CodeGen", "1.0.0.0")]
+	partial class MeshExtMeshFeatures : ExtraProperties
+	{
+	
+		private const int _featureIdsMinItems = 1;
+		private List<MeshExtMeshFeatureID> _featureIds;
+		
+	
+		protected override void SerializeProperties(Utf8JsonWriter writer)
+		{
+			base.SerializeProperties(writer);
+			SerializeProperty(writer, "featureIds", _featureIds, _featureIdsMinItems);
+		}
+	
+		protected override void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader)
+		{
+			switch (jsonPropertyName)
+			{
+				case "featureIds": DeserializePropertyList<MeshExtMeshFeatureID>(ref reader, _featureIds); break;
+				default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+			}
+		}
+	
+	}
+
+}

+ 156 - 0
src/SharpGLTF.Cesium/Schema2/MeshExtMeshFeatures.cs

@@ -0,0 +1,156 @@
+using SharpGLTF.Validation;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SharpGLTF.Schema2
+{
+    partial class MeshExtMeshFeatures
+    {
+        private MeshPrimitive _meshPrimitive;
+
+        internal MeshExtMeshFeatures(MeshPrimitive meshPrimitive)
+        {
+            _meshPrimitive = meshPrimitive;
+            _featureIds = new List<MeshExtMeshFeatureID>();
+        }
+
+        public List<MeshExtMeshFeatureID> FeatureIds
+        {
+            get => _featureIds;
+            set
+            {
+                if (value == null) { _featureIds = null; return; }
+                _featureIds = value;
+            }
+        }
+
+        protected override void OnValidateContent(ValidationContext validate)
+        {
+            var extMeshFeatures = (MeshExtMeshFeatures)_meshPrimitive.Extensions.FirstOrDefault();
+
+            validate.NotNull(nameof(FeatureIds), extMeshFeatures.FeatureIds);
+            validate.IsTrue(nameof(FeatureIds), extMeshFeatures.FeatureIds.Count > 0, "FeatureIds has items");
+
+            base.OnValidateContent(validate);
+        }
+    }
+
+    public partial class MeshExtMeshFeatureIDTexture
+    {
+        public MeshExtMeshFeatureIDTexture(List<int> channels, int? index = null, int? texCoord = null)
+        {
+            Guard.NotNullOrEmpty(channels, nameof(channels));
+            Guard.MustBeGreaterThanOrEqualTo((int)index, 0, nameof(index));
+            Guard.MustBeGreaterThanOrEqualTo((int)texCoord, 0, nameof(index));
+
+            _channels = channels;
+            if (index.HasValue) _LogicalTextureIndex = (int)index;
+            if (texCoord.HasValue) TextureCoordinate = (int)texCoord;
+        }
+
+        public int Index { get => _LogicalTextureIndex; }
+    }
+
+    public partial class MeshExtMeshFeatureID
+    {
+        public MeshExtMeshFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null, MeshExtMeshFeatureIDTexture texture = null)
+        {
+            _featureCount = featureCount;
+            _attribute = attribute;
+            _label = label;
+            _propertyTable = propertyTable;
+            _nullFeatureId = nullFeatureId;
+            _texture = texture;
+        }
+
+        /// <summary>
+        /// The number of unique features in the attribute or texture.
+        /// </summary>
+        public int FeatureCount { get => _featureCount; }
+
+        /// <summary>
+        /// A value that indicates that no feature is associated with this vertex or texel.
+        /// </summary>
+        public int? NullFeatureId { get => _nullFeatureId; }
+
+        /// <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 => _attribute; }
+
+        /// <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 => _label; }
+
+        /// <summary>
+        /// A texture containing feature IDs.
+        /// </summary>
+        public MeshExtMeshFeatureIDTexture Texture { get => _texture; }
+
+        /// <summary>
+        /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
+        /// </summary>
+        public int? PropertyTable { get => _propertyTable; }
+    }
+
+    public static class ExtMeshFeatures
+    {
+        /// <summary>
+        /// Set the FeatureIds for a MeshPrimitive
+        /// </summary>
+        /// <param name="primitive"></param>
+        /// <param name="featureIds"></param>
+        public static void SetFeatureIds(this MeshPrimitive primitive, List<MeshExtMeshFeatureID> featureIds)
+        {
+            if (featureIds == null) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return; }
+
+            Guard.NotNullOrEmpty(featureIds, nameof(featureIds));
+
+            foreach (var featureId in featureIds)
+            {
+                ValidateFeature(primitive, featureId);
+            };
+
+            var ext = primitive.UseExtension<MeshExtMeshFeatures>();
+            ext.FeatureIds = featureIds;
+        }
+
+        private static void ValidateFeature(MeshPrimitive primitive, MeshExtMeshFeatureID item)
+        {
+            Guard.MustBeGreaterThanOrEqualTo((int)item.FeatureCount, 1, nameof(item.FeatureCount));
+
+            if (item.NullFeatureId.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)item.NullFeatureId, 0, nameof(item.NullFeatureId));
+            }
+            if (item.Label != null)
+            {
+                var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
+                Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(item.Label, regex), nameof(item.Label));
+            }
+            if (item.Attribute.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)item.Attribute, 0, nameof(item.Attribute));
+                // Guard that the custom vertex attribute (_FEATURE_ID_{attribute}) exists when FeatureID has attribute set
+                var expectedVertexAttribute = $"_FEATURE_ID_{item.Attribute}";
+                Guard.NotNull(primitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
+            }
+            if (item.PropertyTable.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)item.PropertyTable, 0, nameof(item.PropertyTable));
+            }
+            if (item.Texture != null)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)item.Texture.TextureCoordinate, 0, nameof(item.Texture.TextureCoordinate));
+                var expectedTexCoordAttribute = $"TEXCOORD_{item.Texture.TextureCoordinate}";
+                Guard.NotNull(primitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
+
+                var image = primitive.LogicalParent.LogicalParent.LogicalImages[item.Texture.Index];
+                Guard.NotNull(image, "Texture " + nameof(item.Texture.Index));
+            }
+        }
+    }
+}

+ 1 - 1
src/SharpGLTF.Cesium/SharpGLTF.Cesium.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
     <AssemblyName>SharpGLTF.Cesium</AssemblyName>
     <RootNamespace>SharpGLTF</RootNamespace>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

+ 16 - 0
src/SharpGLTF.Cesium/Validator.cs

@@ -0,0 +1,16 @@
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF
+{
+    internal static class Validator
+    {
+        internal static void ValidateAccessor(ModelRoot model, Accessor accessor)
+        {
+            Guard.NotNull(accessor, nameof(accessor));
+            Guard.MustShareLogicalParent(model, "this", accessor, nameof(accessor));
+            Guard.IsTrue(accessor.Encoding == EncodingType.UNSIGNED_INT, nameof(accessor));
+            Guard.IsTrue(accessor.Dimensions == DimensionType.SCALAR, nameof(accessor));
+            Guard.IsFalse(accessor.Normalized, nameof(accessor));
+        }
+    }
+}

+ 11 - 3
src/SharpGLTF.Core/Memory/FloatingArrays.cs

@@ -155,16 +155,24 @@ namespace SharpGLTF.Memory
         private Single _GetNormalizedS16(int byteOffset) { return Math.Max(_GetValueS16(byteOffset) / 32767.0f, -1); }
         private void _SetNormalizedS16(int byteOffset, Single value) { _SetValueS16(byteOffset, (Single)Math.Round(value * 32767.0f)); }
 
+        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
         private T _GetValue<T>(int byteOffset)
             where T : unmanaged
         {
             return System.Runtime.InteropServices.MemoryMarshal.Read<T>(_Data.Span.Slice(byteOffset));
         }
 
+        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
         private void _SetValue<T>(int byteOffset, T value)
             where T : unmanaged
         {
-            System.Runtime.InteropServices.MemoryMarshal.Write<T>(_Data.Span.Slice(byteOffset), ref value);
+            var dst = _Data.Span.Slice(byteOffset);
+
+            #if NET8_0_OR_GREATER
+            System.Runtime.InteropServices.MemoryMarshal.Write<T>(dst, value);
+            #else
+            System.Runtime.InteropServices.MemoryMarshal.Write<T>(dst, ref value);
+            #endif
         }
 
         #endregion
@@ -1236,9 +1244,9 @@ namespace SharpGLTF.Memory
 
         bool ICollection<Single[]>.IsReadOnly => false;
 
-        #pragma warning disable CA1819 // Properties should not return arrays
+#pragma warning disable CA1819 // Properties should not return arrays
         public Single[] this[int index]
-        #pragma warning restore CA1819 // Properties should not return arrays
+#pragma warning restore CA1819 // Properties should not return arrays
         {
             get
             {

+ 1 - 6
src/SharpGLTF.Core/Schema2/gltf.ExtensionsFactory.cs

@@ -46,12 +46,7 @@ namespace SharpGLTF.Schema2
             RegisterExtension<Texture, TextureKTX2>("KHR_texture_basisu");
 
             RegisterExtension<ModelRoot, XmpPackets>("KHR_xmp_json_ld");
-            RegisterExtension<ExtraProperties, XmpPacketReference>("KHR_xmp_json_ld");            
-
-            RegisterExtension<ModelRoot, AgiRootArticulations>("AGI_articulations");
-            RegisterExtension<ModelRoot, AgiRootStkMetadata>("AGI_stk_metadata");
-            RegisterExtension<Node, AgiNodeArticulations>("AGI_articulations");
-            RegisterExtension<Node, AgiNodeStkMetadata>("AGI_stk_metadata");
+            RegisterExtension<ExtraProperties, XmpPacketReference>("KHR_xmp_json_ld");                        
         }
 
         #endregion

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs

@@ -50,7 +50,7 @@ namespace SharpGLTF.Schema2
         public JSONEXTRAS Extras
         {
             get => _extras;
-            set => _extras = value.DeepClone();
+            set => _extras = value?.DeepClone();
         }
 
         #endregion

+ 68 - 2
src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs

@@ -3,12 +3,43 @@ using System.Numerics;
 
 namespace SharpGLTF.Schema2
 {
+    /// <remarks>
+    /// Derived classes:<br/>
+    /// - <see cref="MaterialNormalTextureInfo"/><br/>
+    /// - <see cref="MaterialOcclusionTextureInfo"/>
+    /// </remarks>
     [System.Diagnostics.DebuggerDisplay("LogicalTexture[{_LogicalTextureIndex}]")]
-    internal partial class TextureInfo
+    public partial class TextureInfo
     {
+        #region lifecycle
+
+        public TextureInfo() { }
+
+        public TextureInfo(TextureInfo other)
+        {
+            if (other == null) throw new ArgumentNullException(nameof(other));
+
+            _index = other._index;
+            _texCoord = other._texCoord;
+
+            this.Extras = other.Extras;
+
+            // TODO: should copy all extensions, not only TextureTransform.
+
+            var otherXform = other.GetExtension<TextureTransform>();
+
+            if (otherXform != null && !otherXform.IsDefault)
+            {
+                var thisXform = other.UseExtension<TextureTransform>();
+                otherXform.CopyTo(thisXform);
+            }
+        }
+
+        #endregion
+
         #region properties
 
-        internal int _LogicalTextureIndex
+        public int _LogicalTextureIndex
         {
             get => _index;
             set => _index = value;
@@ -122,11 +153,35 @@ namespace SharpGLTF.Schema2
         }
 
         #endregion
+
+        #region API
+
+        internal void CopyTo(TextureTransform other)
+        {
+            if (other == null) throw new ArgumentNullException(nameof(other));
+            other.TextureCoordinateOverride = this.TextureCoordinateOverride;
+            other.Rotation = this.Rotation;
+            other.Offset = this.Offset;
+            other.Scale = this.Scale;            
+        }
+
+        #endregion
     }
 
     [System.Diagnostics.DebuggerDisplay("Normal LogicalTexture[{_LogicalTextureIndex}] x {Scale}")]
     internal sealed partial class MaterialNormalTextureInfo
     {
+        #region lifecycle
+
+        public MaterialNormalTextureInfo() { }
+
+        public MaterialNormalTextureInfo(MaterialNormalTextureInfo other) :base(other)
+        {
+            _scale = other._scale;
+        }
+
+        #endregion
+
         #region properties
 
         public static Single ScaleDefault => (float)_scaleDefault;
@@ -143,6 +198,17 @@ namespace SharpGLTF.Schema2
     [System.Diagnostics.DebuggerDisplay("Occlusion LogicalTexture[{_LogicalTextureIndex}] x {Strength}")]
     internal sealed partial class MaterialOcclusionTextureInfo
     {
+        #region lifecycle
+
+        public MaterialOcclusionTextureInfo() { }
+
+        public MaterialOcclusionTextureInfo(MaterialOcclusionTextureInfo other) : base(other)
+        {
+            _strength = other._strength;
+        }
+
+        #endregion
+
         #region properties
 
         public static Single StrengthDefault => (float)_strengthDefault;

+ 2 - 2
src/SharpGLTF.Core/SharpGLTF.Core.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>    
-    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
     <AssemblyName>SharpGLTF.Core</AssemblyName>
     <RootNamespace>SharpGLTF</RootNamespace>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -19,7 +19,7 @@
   </ItemGroup>  
   
   <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == 'netstandard2.1'">    
-    <PackageReference Include="System.Text.Json" Version="7.0.3" />
+    <PackageReference Include="System.Text.Json" Version="8.0.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
src/SharpGLTF.Runtime/Runtime/RuntimeOptions.cs

@@ -47,7 +47,7 @@ namespace SharpGLTF.Runtime
 
             return callback != null
                 ? callback(source)
-                : (options.IsolateMemory ? source.Extras.DeepClone() : source.Extras);
+                : (options.IsolateMemory ? source.Extras?.DeepClone() : source.Extras);
         }
     }
 }

+ 1 - 1
src/SharpGLTF.Runtime/SharpGLTF.Runtime.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
     <AssemblyName>SharpGLTF.Runtime</AssemblyName>
     <RootNamespace>SharpGLTF.Runtime</RootNamespace>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

+ 5 - 1
src/SharpGLTF.Toolkit/BaseBuilder.cs

@@ -28,7 +28,7 @@ namespace SharpGLTF
             Guard.NotNull(other, nameof(other));
 
             this.Name = other.Name;
-            this.Extras = other.Extras.DeepClone();            
+            this.Extras = other.Extras?.DeepClone();            
         }
 
         #endregion
@@ -63,6 +63,10 @@ namespace SharpGLTF
 
             if (x.Name != y.Name) return false;
 
+            if (x.Extras == null && y.Extras == null) return true;
+            if (x.Extras == null) return false;
+            if (y.Extras == null) return false;
+
             return x.Extras.DeepEquals(y.Extras, 0.0001f);
 
             // return IO.JsonContent.AreEqualByContent(x.Extras, y.Extras, 0.0001f);

+ 2 - 2
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -135,7 +135,7 @@ namespace SharpGLTF.Scenes
                     {
                         var dstNode = dst.CreateNode();
                         dstNode.Name = srcChild.Name;
-                        dstNode.Extras = srcChild.Extras.DeepClone();
+                        dstNode.Extras = srcChild.Extras?.DeepClone();
                         dstNode.LocalTransform = srcChild.ChildTransform;
 
                         srcOperator.ApplyTo(dstNode, context);
@@ -232,7 +232,7 @@ namespace SharpGLTF.Scenes
 
             var dstNode = dstScene.CreateNode();
             dstNode.Name = _srcChild.Name;
-            dstNode.Extras = _srcChild.Extras.DeepClone();
+            dstNode.Extras = _srcChild.Extras?.DeepClone();
             dstNode.LocalTransform = _srcChild.ChildTransform;
 
             schema2Target.ApplyTo(dstNode, context);

+ 2 - 2
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -215,7 +215,7 @@ namespace SharpGLTF.Scenes
 
             this._ParentNode = args.GetNode(other._ParentNode);
             this._NodeName = other._NodeName;
-            this._NodeExtras = other._NodeExtras.DeepClone();
+            this._NodeExtras = other._NodeExtras?.DeepClone();
             this._ChildTransform = other._ChildTransform;
         }
 
@@ -380,7 +380,7 @@ namespace SharpGLTF.Scenes
             Guard.NotNull(other, nameof(other));
 
             this._NodeName = other._NodeName;
-            this._NodeExtras = other._NodeExtras.DeepClone();
+            this._NodeExtras = other._NodeExtras?.DeepClone();
             this._MeshPoseWorldTransform = other._MeshPoseWorldTransform;
 
             foreach (var (joint, inverseBindMatrix) in other._Joints)

+ 1 - 1
src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

@@ -366,7 +366,7 @@ namespace SharpGLTF.Schema2
             ImageBuilder _convert(Image src)
             {
                 if (src == null) return null;
-                var dst = ImageBuilder.From(src.Content, src.Name, src.Extras.DeepClone());
+                var dst = ImageBuilder.From(src.Content, src.Name, src.Extras?.DeepClone());
                 dst.AlternateWriteFileName = src.AlternateWriteFileName;
                 return dst;
             }

+ 1 - 1
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -706,7 +706,7 @@ namespace SharpGLTF.Schema2
 
             var dstMesh = MeshBuilderToolkit.CreateMeshBuilderFromVertexAttributes<Materials.MaterialBuilder>(vertexAttributes);
             dstMesh.Name = srcMesh.Name;
-            dstMesh.Extras = srcMesh.Extras.DeepClone();
+            dstMesh.Extras = srcMesh.Extras?.DeepClone();
 
             Materials.MaterialBuilder defMat = null;
 

+ 3 - 3
src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+    <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
     <AssemblyName>SharpGLTF.Toolkit</AssemblyName>
     <RootNamespace>SharpGLTF</RootNamespace>    
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>    
@@ -16,6 +16,6 @@
 
   <ItemGroup>
     <ProjectReference Include="..\SharpGLTF.Runtime\SharpGLTF.Runtime.csproj" />
-  </ItemGroup>
-  
+  </ItemGroup> 
+
 </Project>

+ 3 - 1
src/build-alpha.cmd

@@ -4,8 +4,10 @@ set VERSIONSUFFIX=alpha0031
 echo Building %VERSIONSUFFIX%
 
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Core\SharpGLTF.Core.csproj
-dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Cesium\SharpGLTF.Cesium.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Runtime\SharpGLTF.Runtime.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Agi\SharpGLTF.Agi.csproj
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Cesium\SharpGLTF.Cesium.csproj
+
 pause

+ 6 - 2
src/build-preview.cmd

@@ -8,15 +8,19 @@ set VERSIONSUFFIX=Preview-%TIMEKEY%
 echo Building 1.0.0-%VERSIONSUFFIX%
 
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Core\SharpGLTF.Core.csproj
-dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Cesium\SharpGLTF.Cesium.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Runtime\SharpGLTF.Runtime.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Toolkit\SharpGLTF.Toolkit.csproj
 
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Agi\SharpGLTF.Agi.csproj
+dotnet build -c:Release --version-suffix %VERSIONSUFFIX% /p:Authors=vpenades SharpGLTF.Cesium\SharpGLTF.Cesium.csproj
+
 set /p DUMMY=Hit ENTER to publish nuget packages on Github...
 
 dotnet nuget push "SharpGLTF.Core/bin/Release/SharpGLTF.Core.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
-dotnet nuget push "SharpGLTF.Cesium/bin/Release/SharpGLTF.Cesium.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 dotnet nuget push "SharpGLTF.Runtime/bin/Release/SharpGLTF.Runtime.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 dotnet nuget push "SharpGLTF.Toolkit/bin/Release/SharpGLTF.Toolkit.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
 
+dotnet nuget push "SharpGLTF.Agi/bin/Release/SharpGLTF.Agi.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
+dotnet nuget push "SharpGLTF.Cesium/bin/Release/SharpGLTF.Cesium.1.0.0-%VERSIONSUFFIX%.nupkg" -s "github" --force-english-output
+
 pause

+ 145 - 0
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -0,0 +1,145 @@
+using NUnit.Framework;
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Materials;
+using SharpGLTF.Scenes;
+using SharpGLTF.Schema2;
+using SharpGLTF.Validation;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace SharpGLTF.Cesium
+{
+    using VBTexture1 = VertexBuilder<VertexPosition, VertexTexture1, VertexEmpty>;
+
+
+    [Category("Toolkit.Scenes")]
+    public class ExtMeshFeaturesTests
+    {
+        [SetUp]
+        public void SetUp()
+        {
+            CesiumExtensions.RegisterExtensions();
+        }
+
+        [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")]
+        public void FeaturesIdAttributeTest()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            // Create a triangle with feature ID custom vertex attribute
+            var featureId = 1;
+            var material = MaterialBuilder.CreateDefault().WithDoubleSide(true);
+
+            var mesh = new MeshBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>("mesh");
+            var prim = mesh.UsePrimitive(material);
+
+            // All the vertices in the triangle have the same feature ID
+            var vt0 = GetVertexBuilderWithFeatureId(new Vector3(-10, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt1 = GetVertexBuilderWithFeatureId(new Vector3(10, 0, 0), new Vector3(0, 0, 1), featureId);
+            var vt2 = GetVertexBuilderWithFeatureId(new Vector3(0, 10, 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 featureIdAttribute = new MeshExtMeshFeatureID(1, 0);
+
+            // Set the FeatureIds
+            var featureIds = new List<MeshExtMeshFeatureID>() { featureIdAttribute };
+            model.LogicalMeshes[0].Primitives[0].SetFeatureIds(featureIds);
+
+            // Validate the FeatureIds
+            var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)model.LogicalMeshes[0].Primitives[0].Extensions.FirstOrDefault();
+            Assert.NotNull(cesiumExtMeshFeaturesExtension.FeatureIds);
+
+            Assert.IsTrue(cesiumExtMeshFeaturesExtension.FeatureIds.Equals(featureIds));
+
+            // Check there should be a custom vertex attribute with name _FEATURE_ID_{attribute}
+            var attribute = cesiumExtMeshFeaturesExtension.FeatureIds[0].Attribute;
+            Assert.IsTrue(attribute == 0);
+            var primitive = model.LogicalMeshes[0].Primitives[0];
+            var featureIdVertexAccessor = primitive.GetVertexAccessor($"_FEATURE_ID_{attribute}");
+            Assert.NotNull(featureIdVertexAccessor);
+            var items = featureIdVertexAccessor.AsScalarArray();
+            Assert.AreEqual(items, new List<int> { featureId, featureId, featureId });
+
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+
+            model.ValidateContent(ctx.GetContext());
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_attribute.glb");
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_attribute.gltf");
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_attribute.plotly");
+        }
+
+        [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")]
+        public void FeaturesIdTextureTest()
+        {
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            // Bitmap of 16*16 pixels, containing FeatureID's (0, 1, 2, 3) in the red channel
+            var img0 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAJElEQVR42mNgYmBgoAQzDLwBgwcwY8FDzIDBDRiR8KgBNDAAAOKBAKByX2jMAAAAAElFTkSuQmCC";
+            var imageBytes = Convert.FromBase64String(img0);
+            var imageBuilder = ImageBuilder.From(imageBytes);
+
+            var material = MaterialBuilder
+                .CreateDefault()
+                .WithMetallicRoughnessShader()
+                .WithBaseColor(imageBuilder, new Vector4(1, 1, 1, 1))
+                .WithDoubleSide(true)
+                .WithAlpha(Materials.AlphaMode.OPAQUE)
+                .WithMetallicRoughness(0, 1);
+
+            var mesh = VBTexture1.CreateCompatibleMesh("mesh");
+            var prim = mesh.UsePrimitive(material);
+            prim.AddTriangle(
+                new VBTexture1(new VertexPosition(0, 0, 0), new Vector2(0, 1)),
+                new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
+                new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
+
+            prim.AddTriangle(
+                new VBTexture1(new VertexPosition(1, 0, 0), new Vector2(1, 1)),
+                new VBTexture1(new VertexPosition(1, 1, 0), new Vector2(1, 0)),
+                new VBTexture1(new VertexPosition(0, 1, 0), new Vector2(0, 0)));
+
+            var scene = new SceneBuilder();
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            var model = scene.ToGltf2();
+
+            // Set the FeatureIds, pointing to the red channel of the texture
+            var texture = new MeshExtMeshFeatureIDTexture(new List<int>() { 0 }, 0, 0);
+            var featureIdTexture = new MeshExtMeshFeatureID(4, texture: texture);
+            var featureIds = new List<MeshExtMeshFeatureID>() { featureIdTexture };
+            var primitive = model.LogicalMeshes[0].Primitives[0];
+            primitive.SetFeatureIds(featureIds);
+
+            var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)primitive.Extensions.FirstOrDefault();
+            Assert.NotNull(cesiumExtMeshFeaturesExtension.FeatureIds);
+
+            Assert.IsTrue(cesiumExtMeshFeaturesExtension.FeatureIds.Equals(featureIds));
+            var featureId = cesiumExtMeshFeaturesExtension.FeatureIds[0];
+            var texCoord = featureId.Texture.TextureCoordinate;
+
+            var textureIdVertexAccessor = primitive.GetVertexAccessor($"TEXCOORD_{texCoord}");
+            Assert.NotNull(textureIdVertexAccessor);
+            Assert.IsTrue(textureIdVertexAccessor.AsVector2Array().Count == 4);
+
+            var ctx = new ValidationResult(model, ValidationMode.Strict, true);
+
+            model.ValidateContent(ctx.GetContext());
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.glb");
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.gltf");
+            scene.AttachToCurrentTest("cesium_ext_mesh_features_feature_id_texture.plotly");
+        }
+
+        private static VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty> GetVertexBuilderWithFeatureId(Vector3 position, Vector3 normal, int featureid)
+        {
+            var vp0 = new VertexPositionNormal(position, normal);
+            var vb0 = new VertexBuilder<VertexPositionNormal, VertexWithFeatureId, VertexEmpty>(vp0, featureid);
+            return vb0;
+        }
+    }
+}

+ 1 - 1
tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net8.0-windows</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>

+ 70 - 0
tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+using SharpGLTF.Geometry.VertexTypes;
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF
+{
+    [System.Diagnostics.DebuggerDisplay("𝐂:{Color} 𝐔𝐕:{TexCoord}")]
+    public struct VertexWithFeatureId : IVertexCustom
+    {
+        public static implicit operator VertexWithFeatureId(float batchId)
+        {
+            return new VertexWithFeatureId(batchId);
+        }
+
+        public VertexWithFeatureId(float batchId)
+        {
+            BatchId = batchId;
+        }
+
+        public const string CUSTOMATTRIBUTENAME = "_FEATURE_ID_0";
+
+        [VertexAttribute(CUSTOMATTRIBUTENAME, EncodingType.FLOAT, false)]
+        public float BatchId;
+
+        public int MaxColors => 0;
+
+        public int MaxTextCoords => 0;
+
+        public IEnumerable<string> CustomAttributes => throw new NotImplementedException();
+
+        public void SetColor(int setIndex, Vector4 color) { }
+
+        public void SetTexCoord(int setIndex, Vector2 coord) { }
+
+        public Vector4 GetColor(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
+
+        public Vector2 GetTexCoord(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
+
+        public void Validate() { }
+
+        public object GetCustomAttribute(string attributeName)
+        {
+            return attributeName == CUSTOMATTRIBUTENAME ? (Object)BatchId : null;
+        }
+
+        public bool TryGetCustomAttribute(string attribute, out object value)
+        {
+            if (attribute != CUSTOMATTRIBUTENAME) { value = null; return false; }
+            value = BatchId; return true;
+        }
+
+        public void SetCustomAttribute(string attributeName, object value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public VertexMaterialDelta Subtract(IVertexMaterial baseValue)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Add(in VertexMaterialDelta delta)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 1 - 1
tests/SharpGLTF.Core.Tests/SharpGLTF.Core.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net6.0-windows;net8.0-windows</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>

+ 9 - 3
tests/SharpGLTF.Core.Tests/Transforms/AffineTransformMatrixTests.cs

@@ -169,10 +169,16 @@ namespace SharpGLTF.Transforms
 
             var xmi = xi.Matrix;
 
-            var tolerance = NumericsAssert.AreGeometryicallyEquivalent(mi, xmi, 0.00001f);
-            TestContext.WriteLine(tolerance);
+            var tolerance = 0.00001f;
 
-            Assert.IsTrue(AffineTransform.AreGeometricallyEquivalent(mi, xi, 0.00001f));
+            #if NET8_0_OR_GREATER
+            tolerance = 0.0001f; // something has changed on net8 that has lowered the precission
+            #endif
+
+            var diff = NumericsAssert.AreGeometryicallyEquivalent(mi, xmi, tolerance);
+            TestContext.WriteLine(diff);
+
+            Assert.IsTrue(AffineTransform.AreGeometricallyEquivalent(mi, xi, tolerance));
         }
     }    
 }

+ 1 - 1
tests/SharpGLTF.DownloadTestFiles/SharpGLTF.DownloadTestFiles.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <RootNamespace>SharpGLTF</RootNamespace>
   </PropertyGroup>
 

+ 2 - 2
tests/SharpGLTF.NUnit/SharpGLTF.NUnit.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0</TargetFrameworks>
+    <TargetFrameworks>net471;net6.0;net8.0</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>
@@ -10,7 +10,7 @@
   <ItemGroup>
     <PackageReference Include="nunit" Version="3.14.0" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
-    <PackageReference Include="GltfValidator" Version="2.0.0-dev.3.8.202306260919" />    
+    <PackageReference Include="GltfValidator" Version="2.0.0-dev.3.8.202311262112" />    
     <PackageReference Include="TestAttachments.NUnit" Version="3.0.0-Preview-20220721-1621" />    
   </ItemGroup>
 

+ 1 - 1
tests/SharpGLTF.Runtime.Tests/SharpGLTF.Runtime.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net6.0-windows;net8.0-windows</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>

+ 2 - 2
tests/SharpGLTF.ThirdParty.Tests/SharpGLTF.ThirdParty.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net8.0-windows</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF.ThirdParty</RootNamespace>
     <LangVersion>latest</LangVersion>
@@ -19,7 +19,7 @@
     <PackageReference Include="NUnit" Version="3.14.0" />
     <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
-    <PackageReference Include="System.Drawing.Common" Version="7.0.0" />    
+    <PackageReference Include="System.Drawing.Common" Version="8.0.0" />    
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
tests/SharpGLTF.Toolkit.Tests/SharpGLTF.Toolkit.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net6.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net6.0-windows;net8.0-windows</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>

+ 1 - 1
tests/SharpGLTF.Trimmed.App/SharpGLTF.Trimmed.App.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>    
+    <TargetFramework>net8.0</TargetFramework>    
   </PropertyGroup>  
 
   <PropertyGroup>