diff --git a/build/SharpGLTF.CodeGen/Constants.cs b/build/SharpGLTF.CodeGen/Constants.cs
index 335e7510..3e21fe18 100644
--- a/build/SharpGLTF.CodeGen/Constants.cs
+++ b/build/SharpGLTF.CodeGen/Constants.cs
@@ -10,10 +10,16 @@ static class Constants
public static string RemoteSchemaRepo = "https://github.com/KhronosGroup/glTF.git";
+ ///
+ /// Program directory
+ ///
+ public static string ProgramDirectory => System.IO.Path.GetDirectoryName(typeof(Program).Assembly.Location);
+
///
/// Directory where the schema is downloaded and used as source
///
- 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
@@ -51,6 +57,12 @@ internal static string VendorExtensionPath(string ext, string 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
#region code generation output paths
diff --git a/build/SharpGLTF.CodeGen/Ext.EXT_MeshFeatures.cs b/build/SharpGLTF.CodeGen/Ext.EXT_MeshFeatures.cs
new file mode 100644
index 00000000..b0a9221c
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/build/SharpGLTF.CodeGen/Program.cs b/build/SharpGLTF.CodeGen/Program.cs
index 0722c9bb..b9278453 100644
--- a/build/SharpGLTF.CodeGen/Program.cs
+++ b/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;
@@ -56,19 +58,21 @@ static void Main(string[] args)
// other
processors.Add(new XmpJsonLdExtension());
+ processors.Add(new ExtMeshFeaturesExtension());
+
// ---------------------------------------------- process all files
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
- }
-}
+ }
+}
\ No newline at end of file
diff --git a/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/README.md b/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/README.md
new file mode 100644
index 00000000..3d9b9c95
--- /dev/null
+++ b/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
+
+
diff --git a/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureId.schema.json b/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureId.schema.json
new file mode 100644
index 00000000..c07de0b0
--- /dev/null
+++ b/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"
+ ]
+}
\ No newline at end of file
diff --git a/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdAttribute.schema.json b/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdAttribute.schema.json
new file mode 100644
index 00000000..ddb0373b
--- /dev/null
+++ b/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_` which is a reference to a key in `mesh.primitives.attributes` (e.g. a value of `0` corresponds to `_FEATURE_ID_0`)."
+}
\ No newline at end of file
diff --git a/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdTexture.schema.json b/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/featureIdTexture.schema.json
new file mode 100644
index 00000000..abe4278e
--- /dev/null
+++ b/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": {}
+ }
+}
\ No newline at end of file
diff --git a/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/mesh.primitive.EXT_mesh_features.schema.json b/build/SharpGLTF.CodeGen/Schemas/EXT_mesh_features/schema/mesh.primitive.EXT_mesh_features.schema.json
new file mode 100644
index 00000000..a8d1e052
--- /dev/null
+++ b/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"
+ ]
+}
\ No newline at end of file
diff --git a/build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj b/build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj
index 54a037ab..a75da28e 100644
--- a/build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj
+++ b/build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj
@@ -11,4 +11,23 @@
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
diff --git a/src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs b/src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs
index c013bf5f..6a22f1cc 100644
--- a/src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs
+++ b/src/SharpGLTF.Cesium/Schema2/CesiumExtensions.cs
@@ -21,6 +21,8 @@ public static void RegisterExtensions()
_CesiumRegistered = true;
ExtensionsFactory.RegisterExtension("CESIUM_primitive_outline");
+ ExtensionsFactory.RegisterExtension("EXT_mesh_features");
+
}
}
}
diff --git a/src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs b/src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs
new file mode 100644
index 00000000..7ae6772e
--- /dev/null
+++ b/src/SharpGLTF.Cesium/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs
@@ -0,0 +1,144 @@
+//
+
+//------------------------------------------------------------------------------------------------
+// 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;
+
+ ///
+ /// A texture containing feature IDs
+ ///
+#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 _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(ref reader, _channels); break;
+ default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+ }
+ }
+ }
+
+ ///
+ /// Feature IDs stored in an attribute or texture.
+ ///
+#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(ref reader); break;
+ case "featureCount": _featureCount = DeserializePropertyValue(ref reader); break;
+ case "label": _label = DeserializePropertyValue(ref reader); break;
+ case "nullFeatureId": _nullFeatureId = DeserializePropertyValue(ref reader); break;
+ case "propertyTable": _propertyTable = DeserializePropertyValue(ref reader); break;
+ case "texture": _texture = DeserializePropertyValue(ref reader); break;
+ default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+ }
+ }
+
+ }
+
+ ///
+ /// An object describing feature IDs for a mesh primitive.
+ ///
+ #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 _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(ref reader, _featureIds); break;
+ default: base.DeserializeProperty(jsonPropertyName,ref reader); break;
+ }
+ }
+
+ }
+
+}
diff --git a/src/SharpGLTF.Cesium/Schema2/MeshExtMeshFeatures.cs b/src/SharpGLTF.Cesium/Schema2/MeshExtMeshFeatures.cs
new file mode 100644
index 00000000..693b6164
--- /dev/null
+++ b/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();
+ }
+
+ public List 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 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;
+ }
+
+ ///
+ /// The number of unique features in the attribute or texture.
+ ///
+ public int FeatureCount { get => _featureCount; }
+
+ ///
+ /// A value that indicates that no feature is associated with this vertex or texel.
+ ///
+ public int? NullFeatureId { get => _nullFeatureId; }
+
+ ///
+ /// An attribute containing feature IDs. When `attribute` and `texture` are omitted the
+ /// feature IDs are assigned to vertices by their index.
+ ///
+ public int? Attribute { get => _attribute; }
+
+ ///
+ /// A label assigned to this feature ID set. Labels must be alphanumeric identifiers
+ /// matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+ ///
+ public string Label { get => _label; }
+
+ ///
+ /// A texture containing feature IDs.
+ ///
+ public MeshExtMeshFeatureIDTexture Texture { get => _texture; }
+
+ ///
+ /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
+ ///
+ public int? PropertyTable { get => _propertyTable; }
+ }
+
+ public static class ExtMeshFeatures
+ {
+ ///
+ /// Set the FeatureIds for a MeshPrimitive
+ ///
+ ///
+ ///
+ public static void SetFeatureIds(this MeshPrimitive primitive, List featureIds)
+ {
+ if (featureIds == null) { primitive.RemoveExtensions(); return; }
+
+ Guard.NotNullOrEmpty(featureIds, nameof(featureIds));
+
+ foreach (var featureId in featureIds)
+ {
+ ValidateFeature(primitive, featureId);
+ };
+
+ var ext = primitive.UseExtension();
+ 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));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SharpGLTF.Cesium/Validator.cs b/src/SharpGLTF.Cesium/Validator.cs
new file mode 100644
index 00000000..91d0cd9e
--- /dev/null
+++ b/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));
+ }
+ }
+}
diff --git a/src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs b/src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
index 330cf31a..92c542f1 100644
--- a/src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
+++ b/src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
@@ -4,11 +4,11 @@
namespace SharpGLTF.Schema2
{
[System.Diagnostics.DebuggerDisplay("LogicalTexture[{_LogicalTextureIndex}]")]
- internal partial class TextureInfo
+ public partial class TextureInfo
{
#region properties
- internal int _LogicalTextureIndex
+ public int _LogicalTextureIndex
{
get => _index;
set => _index = value;
diff --git a/tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs b/tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
new file mode 100644
index 00000000..66b8ad0b
--- /dev/null
+++ b/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;
+
+
+ [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("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() { 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 { 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() { 0 }, 0, 0);
+ var featureIdTexture = new MeshExtMeshFeatureID(4, texture: texture);
+ var featureIds = new List() { 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 GetVertexBuilderWithFeatureId(Vector3 position, Vector3 normal, int featureid)
+ {
+ var vp0 = new VertexPositionNormal(position, normal);
+ var vb0 = new VertexBuilder(vp0, featureid);
+ return vb0;
+ }
+ }
+}
diff --git a/tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs b/tests/SharpGLTF.Cesium.Tests/VertexWithFeatureId.cs
new file mode 100644
index 00000000..f0b1763b
--- /dev/null
+++ b/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 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();
+ }
+ }
+}