Browse Source

Merge branch 'master' of https://github.com/vpenades/SharpGLTF

vpenades 2 years ago
parent
commit
152756a0a8

+ 13 - 1
build/SharpGLTF.CodeGen/Constants.cs

@@ -10,10 +10,16 @@ namespace SharpGLTF
 
 
         public static string RemoteSchemaRepo = "https://github.com/KhronosGroup/glTF.git";
         public static string RemoteSchemaRepo = "https://github.com/KhronosGroup/glTF.git";
 
 
+        /// <summary>
+        /// Program directory
+        /// </summary>
+        public static string ProgramDirectory => System.IO.Path.GetDirectoryName(typeof(Program).Assembly.Location);
+
         /// <summary>
         /// <summary>
         /// Directory where the schema is downloaded and used as source
         /// Directory where the schema is downloaded and used as source
         /// </summary>
         /// </summary>
-        public static string LocalRepoDirectory => System.IO.Path.Combine(System.IO.Path.GetDirectoryName(typeof(Program).Assembly.Location), "glTF");
+        public static string LocalRepoDirectory => System.IO.Path.Combine(ProgramDirectory, "glTF");
+
 
 
         #endregion
         #endregion
 
 
@@ -51,6 +57,12 @@ namespace SharpGLTF
             return System.IO.Path.Combine(VendorSchemaDir, ext, "schema", json);
             return System.IO.Path.Combine(VendorSchemaDir, ext, "schema", json);
         }
         }
 
 
+        internal static string CustomExtensionsPath(string ext, string json)
+        {
+            return System.IO.Path.Combine(ProgramDirectory, "Schemas", ext, "schema", json);
+        }
+
+
         #endregion
         #endregion
 
 
         #region code generation output paths
         #region code generation output paths

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

+ 9 - 5
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.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
@@ -56,19 +58,21 @@ namespace SharpGLTF
             // other
             // other
             processors.Add(new XmpJsonLdExtension());
             processors.Add(new XmpJsonLdExtension());
 
 
+            processors.Add(new ExtMeshFeaturesExtension());
+
             // ----------------------------------------------  process all files
             // ----------------------------------------------  process all files
 
 
             foreach (var processor in processors)
             foreach (var processor in processors)
             {
             {
-                foreach(var (targetFileName, schema) in processor.Process())
+                foreach (var (targetFileName, schema) in processor.Process())
                 {
                 {
                     System.Console.WriteLine($"Emitting {targetFileName}...");
                     System.Console.WriteLine($"Emitting {targetFileName}...");
 
 
                     SchemaProcessing.EmitCodeFromSchema(processor.GetTargetProject(), targetFileName, schema, processors);
                     SchemaProcessing.EmitCodeFromSchema(processor.GetTargetProject(), targetFileName, schema, processors);
                 }
                 }
-            }            
+            }
         }
         }
 
 
         #endregion     
         #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,4 +11,23 @@
     <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.9.0" />
     <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.9.0" />
   </ItemGroup>
   </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>
+
 </Project>
 </Project>

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

@@ -21,6 +21,8 @@ namespace SharpGLTF.Schema2
             _CesiumRegistered = true;
             _CesiumRegistered = true;
 
 
             ExtensionsFactory.RegisterExtension<MeshPrimitive, CesiumPrimitiveOutline>("CESIUM_primitive_outline");
             ExtensionsFactory.RegisterExtension<MeshPrimitive, CesiumPrimitiveOutline>("CESIUM_primitive_outline");
+            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));
+            }
+        }
+    }
+}

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

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

@@ -9,7 +9,7 @@ namespace SharpGLTF.Schema2
     /// - <see cref="MaterialOcclusionTextureInfo"/>
     /// - <see cref="MaterialOcclusionTextureInfo"/>
     /// </remarks>
     /// </remarks>
     [System.Diagnostics.DebuggerDisplay("LogicalTexture[{_LogicalTextureIndex}]")]
     [System.Diagnostics.DebuggerDisplay("LogicalTexture[{_LogicalTextureIndex}]")]
-    internal partial class TextureInfo
+    public partial class TextureInfo
     {
     {
         #region lifecycle
         #region lifecycle
 
 
@@ -39,7 +39,7 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
-        internal int _LogicalTextureIndex
+        public int _LogicalTextureIndex
         {
         {
             get => _index;
             get => _index;
             set => _index = value;
             set => _index = value;

+ 3 - 1
src/build-alpha.cmd

@@ -4,8 +4,10 @@ set VERSIONSUFFIX=alpha0031
 echo Building %VERSIONSUFFIX%
 echo Building %VERSIONSUFFIX%
 
 
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Core\SharpGLTF.Core.csproj
 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.Runtime\SharpGLTF.Runtime.csproj
 dotnet build -c:Release --version-suffix %VERSIONSUFFIX% SharpGLTF.Toolkit\SharpGLTF.Toolkit.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
 pause

+ 6 - 2
src/build-preview.cmd

@@ -8,15 +8,19 @@ set VERSIONSUFFIX=Preview-%TIMEKEY%
 echo Building 1.0.0-%VERSIONSUFFIX%
 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.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.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.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...
 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.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.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.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
 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;
+        }
+    }
+}

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