Browse Source

WIP on extensions

vpenades 2 years ago
parent
commit
3c58c0492d

+ 13 - 2
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.CESIUM_primitive_outline.cs

@@ -4,15 +4,24 @@ using System.Linq;
 
 using SharpGLTF.Validation;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
+    /// <remarks>
+    /// This extension is attached to a <see cref="Schema2.MeshPrimitive"/> using <see cref="Tiles3DExtensions.SetCesiumOutline"/>
+    /// </remarks>    
     partial class CesiumPrimitiveOutline
     {
+        #region lifecycle
+
         internal CesiumPrimitiveOutline(MeshPrimitive meshPrimitive)
         {
             this.meshPrimitive = meshPrimitive;
         }
 
+        #endregion
+
+        #region properties
+
         private MeshPrimitive meshPrimitive;
 
         public Accessor Indices
@@ -33,6 +42,8 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        #endregion
+
         #region validation
 
         protected override void OnValidateReferences(ValidationContext validate)
@@ -89,7 +100,7 @@ namespace SharpGLTF.Schema2
         #endregion
     }
 
-    partial class ThreeDTilesExtensions
+    partial class Tiles3DExtensions
     {
         /// <summary>
         /// Sets Cesium outline vertex indices

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

@@ -0,0 +1,286 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+
+namespace SharpGLTF.Schema2.Tiles3D
+{
+    using Collections;
+
+    public interface IMeshFeatureIDInfo
+    {
+        /// <summary>
+        /// The number of unique features in the attribute or texture.
+        /// </summary>
+        public int FeatureCount { get; set; }
+
+        /// <summary>
+        /// A value that indicates that no feature is associated with this vertex or texel.
+        /// </summary>
+        public int? NullFeatureId { get; set; }
+
+        /// <summary>
+        /// An attribute containing feature IDs. When `attribute` and `texture` are omitted the 
+        /// feature IDs are assigned to vertices by their index.
+        /// </summary>
+        public int? Attribute { get; set; }
+
+        /// <summary>
+        /// A label assigned to this feature ID set. Labels must be alphanumeric identifiers 
+        /// matching the regular expression `^[a-zA-Z_][a-zA-Z0-9_]*$`.
+        /// </summary>
+        public string Label { get; set; }
+
+        /// <summary>
+        /// The index of the property table containing per-feature property values. Only applicable when using the `EXT_structural_metadata` extension.
+        /// </summary>
+        public int? PropertyTable { get; set; }
+    }
+
+    /// <remarks>
+    /// Use <see cref="MeshExtInstanceFeatures.CreateFeatureId"/> to create an instance of this class.
+    /// </remarks>    
+    public partial class MeshExtInstanceFeatureID : IChildOfList<MeshExtInstanceFeatures> , IMeshFeatureIDInfo
+    {
+        #region lifecycle
+
+        public MeshExtInstanceFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null)
+        {
+            FeatureCount = featureCount;
+            Attribute = attribute;
+            Label = label;
+            PropertyTable = propertyTable;
+            NullFeatureId = nullFeatureId;
+        }
+
+
+        internal MeshExtInstanceFeatureID() { }
+
+        #endregion
+
+        #region child properties
+
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="MeshExtInstanceFeatureID"/> at <see cref="MeshExtInstanceFeatures.FeatureIds"/>.
+        /// </summary>
+        public int LogicalIndex { get; private set; } = -1;
+
+        /// <summary>
+        /// Gets the <see cref="MeshExtInstanceFeatures"/> instance that owns this <see cref="MeshExtInstanceFeatureID"/> instance.
+        /// </summary>
+        public MeshExtInstanceFeatures LogicalParent { get; private set; }
+
+        void IChildOfList<MeshExtInstanceFeatures>.SetLogicalParent(MeshExtInstanceFeatures parent, int index)
+        {
+            LogicalParent = parent;
+            LogicalIndex = index;
+        }
+
+        #endregion
+
+        #region properties        
+        public int FeatureCount
+        {
+            get => _featureCount;
+            set
+            {
+                Guard.MustBeGreaterThanOrEqualTo(value, _featureCountMinimum, nameof(value));
+                _featureCount = value;
+            }
+        }        
+        public int? NullFeatureId
+        {
+            get => _nullFeatureId;
+            set
+            {
+                if (value.HasValue) Guard.MustBeGreaterThanOrEqualTo(value.Value, _nullFeatureIdMinimum, nameof(value));
+                _nullFeatureId = value;
+            }
+        }        
+        public int? Attribute
+        {
+            get => _attribute;
+            set => _attribute = value;
+        }        
+        public string Label
+        {
+            get => _label;
+            set
+            {
+                if (value != null) Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(value, "^[a-zA-Z_][a-zA-Z0-9_]*$"), nameof(value));
+                _label = value;
+            }
+        }        
+        public int? PropertyTable
+        {
+            get => _propertyTable;
+            set
+            {
+                if (value.HasValue) Guard.MustBeGreaterThanOrEqualTo(value.Value, _propertyTableMinimum, nameof(value));
+                _propertyTable = value;
+            }
+        }
+
+        #endregion
+    }
+
+    /// <remarks>
+    /// Use <see cref="MeshExtMeshFeatures.CreateFeatureID"/> to create an instance of this class.
+    /// </remarks>
+    public partial class MeshExtMeshFeatureID : IChildOfList<MeshExtMeshFeatures>, IMeshFeatureIDInfo
+    {
+        #region lifecycle
+
+        internal MeshExtMeshFeatureID() { }
+
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
+        {
+            var items = base.GetLogicalChildren();
+
+            if (_texture != null) items = items.Append(_texture);
+
+            return items;
+        }
+
+        #endregion
+
+        #region child properties
+
+        public int LogicalIndex { get; private set; } = -1;
+
+        public MeshExtMeshFeatures LogicalParent { get; private set; }
+
+        void IChildOfList<MeshExtMeshFeatures>.SetLogicalParent(MeshExtMeshFeatures parent, int index)
+        {
+            LogicalParent = parent;
+            LogicalIndex = index;
+        }
+
+        #endregion
+
+        #region properties        
+        public int FeatureCount
+        {
+            get => _featureCount;
+            set
+            {
+                Guard.MustBeGreaterThanOrEqualTo(value, _featureCountMinimum, nameof(value));
+                _featureCount = value;
+            }
+        }        
+        public int? NullFeatureId
+        {
+            get => _nullFeatureId;
+            set
+            {
+                if (value.HasValue) Guard.MustBeGreaterThanOrEqualTo(value.Value, _nullFeatureIdMinimum, nameof(value));
+                _nullFeatureId = value;
+            }
+        }        
+        public int? Attribute
+        {
+            get => _attribute;
+            set => _attribute = value;
+        }        
+        public string Label
+        {
+            get => _label;
+            set
+            {
+                if (value != null) Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(value, "^[a-zA-Z_][a-zA-Z0-9_]*$"), nameof(value));
+                _label = value;
+            }
+        }                
+        public int? PropertyTable
+        {
+            get => _propertyTable;
+            set
+            {
+                if (value.HasValue) Guard.MustBeGreaterThanOrEqualTo(value.Value, _propertyTableMinimum, nameof(value));
+                _propertyTable = value;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Gets a texture containing feature IDs.
+        /// </summary>
+        public MeshExtMeshFeatureIDTexture GetTexture() => _texture;
+
+        /// <summary>
+        /// Gets or Creates a texture containing feature IDs.
+        /// </summary>
+        public MeshExtMeshFeatureIDTexture UseTexture()
+        {
+            if (_texture != null) return _texture;
+
+            GetChildSetter(this).SetProperty(ref _texture, new MeshExtMeshFeatureIDTexture());
+
+            return _texture;
+        }
+
+        #endregion
+    }
+
+    /// <remarks>
+    /// Use <see cref="MeshExtMeshFeatureID.UseTexture"/> to create an instance of this class.
+    /// </remarks>
+    public partial class MeshExtMeshFeatureIDTexture : IChildOf<MeshExtMeshFeatureID>
+    {
+        #region lifecycle
+        internal MeshExtMeshFeatureIDTexture()
+        {
+            _channels = new List<int>();
+        }
+
+        #endregion
+
+        #region child properties        
+
+        public MeshExtMeshFeatureID LogicalParent { get; private set; }
+
+        void IChildOf<MeshExtMeshFeatureID>.SetLogicalParent(MeshExtMeshFeatureID parent)
+        {
+            LogicalParent = parent;            
+        }
+
+        #endregion
+
+        #region properties        
+
+        public Schema2.Texture Texture
+        {
+            get => _GetModelRoot().LogicalTextures[LogicalTextureIndex];
+            set
+            {
+                Guard.NotNull(value, nameof(value));
+                Guard.MustShareLogicalParent(_GetModelRoot(), nameof(MeshExtMeshFeatureIDTexture), value, nameof(value));
+
+                LogicalTextureIndex = value.LogicalIndex;
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        private ModelRoot _GetModelRoot()
+        {
+            // traverse up to the root:
+            return LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent?.LogicalParent;
+        }
+
+        public void SetChannels(IReadOnlyList<int> channels)
+        {
+            Guard.NotNullOrEmpty(channels, nameof(channels));
+
+            _channels.Clear();
+            _channels.AddRange(channels);
+        }
+
+        #endregion
+    }
+}

+ 299 - 0
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.Features.cs

@@ -0,0 +1,299 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+using SharpGLTF.Collections;
+using SharpGLTF.Validation;
+
+namespace SharpGLTF.Schema2.Tiles3D
+{
+    /// <remarks>
+    /// This extension is attached to a <see cref="Schema2.Node"/> using <see cref="ExtraProperties.UseExtension{T}"/>
+    /// </remarks>
+    public partial class MeshExtInstanceFeatures
+    {
+        #region lifecycle
+
+        internal MeshExtInstanceFeatures() { }
+
+        internal MeshExtInstanceFeatures(Node node)
+        {
+            _node = node;
+            _featureIds = new ChildrenList<MeshExtInstanceFeatureID, MeshExtInstanceFeatures>(this);            
+        }
+
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
+        {
+            return base.GetLogicalChildren().Concat(_featureIds);
+        }
+
+        #endregion
+
+        #region data
+
+        private Node _node;
+
+        #endregion
+
+        #region properties
+
+        public Node LogicalParent => _node;
+        public IReadOnlyList<MeshExtInstanceFeatureID> FeatureIds => _featureIds;
+
+        #endregion
+
+        #region API
+
+        public MeshExtInstanceFeatureID CreateFeatureID(IMeshFeatureIDInfo properties)
+        {
+            var instance = CreateFeatureID();
+
+            instance.FeatureCount = properties.FeatureCount;
+            instance.NullFeatureId = properties.NullFeatureId;
+            instance.Label = properties.Label;
+            instance.Attribute = properties.Attribute;
+            instance.PropertyTable = properties.PropertyTable;
+
+            return instance;
+        }
+
+        public MeshExtInstanceFeatureID CreateFeatureID()
+        {
+            var featureId = new MeshExtInstanceFeatureID();
+            _featureIds.Add(featureId);
+            return featureId;
+        }
+
+        #endregion
+
+        #region validation
+
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
+            validate.NotNull(nameof(extInstanceFeatures), extInstanceFeatures);
+            var extMeshGpInstancing = _node.GetExtension<MeshGpuInstancing>();
+            validate.NotNull(nameof(extMeshGpInstancing), extMeshGpInstancing);
+
+            foreach (var instanceFeatureId in FeatureIds)
+            {
+                if (instanceFeatureId.Attribute.HasValue)
+                {
+                    var expectedVertexAttribute = $"_FEATURE_ID_{instanceFeatureId.Attribute}";
+                    var gpuInstancing = _node.GetGpuInstancing();
+                    var featureIdAccessors = gpuInstancing.GetAccessor(expectedVertexAttribute);
+                    Guard.NotNull(featureIdAccessors, expectedVertexAttribute);
+                }
+
+                instanceFeatureId.ValidateFeatureIdReferences(_node.LogicalParent);
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+        protected override void OnValidateContent(ValidationContext validate)
+        {
+            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
+            validate.NotNull(nameof(FeatureIds), extInstanceFeatures.FeatureIds);
+            validate.IsTrue(nameof(FeatureIds), extInstanceFeatures.FeatureIds.Count > 0, "Instance FeatureIds has items");
+
+            foreach (var instanceFeatureId in FeatureIds)
+            {
+                instanceFeatureId.ValidateFeatureIdContent();
+            }
+
+            base.OnValidateContent(validate);
+        }
+
+        #endregion
+    }
+
+    /// <remarks>
+    /// This extension is attached to a <see cref="Schema2.MeshPrimitive"/>
+    /// </remarks>    
+    public partial class MeshExtMeshFeatures
+    {
+        #region lifecycle
+
+        internal MeshExtMeshFeatures(MeshPrimitive meshPrimitive)
+        {
+            _meshPrimitive = meshPrimitive;
+            _featureIds = new ChildrenList<MeshExtMeshFeatureID, MeshExtMeshFeatures>(this);
+        }
+
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
+        {
+            return base.GetLogicalChildren().Concat(_featureIds);
+        }
+
+        #endregion
+
+        #region data
+
+        private MeshPrimitive _meshPrimitive;
+
+        #endregion
+
+        #region properties
+
+        public MeshPrimitive LogicalParent => _meshPrimitive;
+
+        public IReadOnlyList<MeshExtMeshFeatureID> FeatureIds => _featureIds;
+
+        #endregion
+
+        #region API
+
+        public MeshExtMeshFeatureID CreateFeatureID(IMeshFeatureIDInfo properties, Texture texture = null, IReadOnlyList<int> texChannels = null)
+        {
+            var instance = CreateFeatureID();
+
+            instance.FeatureCount = properties.FeatureCount;
+            instance.NullFeatureId = properties.NullFeatureId;
+            instance.Label = properties.Label;
+            instance.Attribute = properties.Attribute;
+            instance.PropertyTable = properties.PropertyTable;
+
+            if (texture != null)
+            {
+                var texInfo = instance.UseTexture();
+                texInfo.Texture = texture;
+                if (texChannels != null) texInfo.SetChannels(texChannels);
+            }            
+
+            return instance;
+        }
+
+        public MeshExtMeshFeatureID CreateFeatureID()
+        {
+            var featureId = new MeshExtMeshFeatureID();
+            _featureIds.Add(featureId);
+            return featureId;
+        }
+
+        #endregion
+
+        #region validation
+
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            foreach (var featureId in _featureIds)
+            {
+                if (featureId.Attribute.HasValue)
+                {
+                    var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
+                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
+                }
+
+                featureId.ValidateFeatureIdReferences(_meshPrimitive.LogicalParent.LogicalParent);
+
+                var texture = featureId.GetTexture();
+
+                if (texture != null)
+                {
+                    var expectedTexCoordAttribute = $"TEXCOORD_{texture.TextureCoordinate}";
+                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
+
+                    var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
+                    validate.IsNullOrIndex(nameof(texture), texture.TextureCoordinate, modelRoot.LogicalTextures);
+                }
+            }
+
+            base.OnValidateReferences(validate);
+        }
+
+        protected override void OnValidateContent(ValidationContext validate)
+        {
+            var extMeshFeatures = _meshPrimitive.Extensions.Where(item => item is MeshExtMeshFeatures).FirstOrDefault();
+            validate.NotNull(nameof(extMeshFeatures), extMeshFeatures);
+            validate.NotNull(nameof(FeatureIds), _featureIds);
+            validate.IsTrue(nameof(FeatureIds), _featureIds.Count > 0, "FeatureIds has items");
+
+            foreach (var featureId in _featureIds)
+            {
+                featureId.ValidateFeatureIdContent();                
+            }
+
+            base.OnValidateContent(validate);
+        }
+
+        #endregion
+    }
+
+    partial class Tiles3DExtensions
+    {
+        internal static void ValidateFeatureIdReferences(this IMeshFeatureIDInfo featureId, ModelRoot root)
+        {
+            if (featureId.PropertyTable.HasValue)
+            {
+                var metadataExtension = root.GetExtension<EXTStructuralMetadataRoot>();
+                Guard.NotNull(metadataExtension, nameof(metadataExtension), "EXT_Structural_Metadata extension is not found.");
+                Guard.NotNull(metadataExtension.PropertyTables[featureId.PropertyTable.Value], nameof(featureId.PropertyTable), $"Property table index {featureId.PropertyTable.Value} does not exist");
+            }
+        }
+        
+        internal static void ValidateFeatureIdContent(this IMeshFeatureIDInfo featureId)
+        {
+            Guard.MustBeGreaterThanOrEqualTo((int)featureId.FeatureCount, 1, nameof(featureId.FeatureCount));
+
+            if (featureId.NullFeatureId.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)featureId.NullFeatureId, 0, nameof(featureId.NullFeatureId));
+            }
+            if (featureId.Label != null)
+            {
+                var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
+                Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(featureId.Label, regex), nameof(featureId.Label));
+            }
+
+            if (featureId.Attribute.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
+            }
+            if (featureId.PropertyTable.HasValue)
+            {
+                Guard.MustBeGreaterThanOrEqualTo((int)featureId.PropertyTable, 0, nameof(featureId.PropertyTable));
+            }
+        }
+
+        /// <summary>
+        /// Set the FeatureIds for a MeshPrimitive
+        /// </summary>        
+        public static MeshExtInstanceFeatureID[] SetInstanceFeatureIds(this Node node, params IMeshFeatureIDInfo[] featureIds)
+        {
+            if (featureIds == null || featureIds.Length == 0) { node.RemoveExtensions<MeshExtInstanceFeatures>(); return Array.Empty<MeshExtInstanceFeatureID>(); }
+
+            var ext = node.UseExtension<MeshExtInstanceFeatures>();
+
+            var result = new MeshExtInstanceFeatureID[featureIds.Length];
+
+            for (int i = 0; i < result.Length; ++i)
+            {                
+                result[i] = ext.CreateFeatureID(featureIds[i]);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Set the FeatureIds for a MeshPrimitive
+        /// </summary>        
+        public static MeshExtMeshFeatureID[] SetMeshFeatureIds(this MeshPrimitive primitive, params (IMeshFeatureIDInfo fid, Texture tex, IReadOnlyList<int> channels)[] featureIds)
+        {
+            if (featureIds == null || featureIds.Length == 0) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return Array.Empty<MeshExtMeshFeatureID>(); }
+
+            var ext = primitive.UseExtension<MeshExtMeshFeatures>();
+
+            var result = new MeshExtMeshFeatureID[featureIds.Length];
+
+            for(int i=0; i < result.Length; ++i)
+            {
+                var (fid, tex, channels) = featureIds[i];
+                result[i] = ext.CreateFeatureID(fid, tex, channels);
+            }
+
+            return result;
+        }
+    }
+}

+ 0 - 150
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.InstanceFeatures.cs

@@ -1,150 +0,0 @@
-using SharpGLTF.Validation;
-using System.Collections.Generic;
-namespace SharpGLTF.Schema2
-{
-    public partial class MeshExtInstanceFeatures
-    {
-        private Node _node;
-        internal MeshExtInstanceFeatures(Node node)
-        {
-            _node = node;
-            _featureIds = new List<MeshExtInstanceFeatureID>();
-        }
-
-        public List<MeshExtInstanceFeatureID> FeatureIds
-        {
-            get
-            {
-                return _featureIds;
-            }
-            set
-            {
-                _featureIds = value;
-            }
-        }
-
-        protected override void OnValidateReferences(ValidationContext validate)
-        {
-            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
-            validate.NotNull(nameof(extInstanceFeatures), extInstanceFeatures);
-            var extMeshGpInstancing = _node.GetExtension<MeshGpuInstancing>();
-            validate.NotNull(nameof(extMeshGpInstancing), extMeshGpInstancing);
-
-            foreach (var instanceFeatureId in FeatureIds)
-            {
-                if (instanceFeatureId.Attribute.HasValue)
-                {
-                    var expectedVertexAttribute = $"_FEATURE_ID_{instanceFeatureId.Attribute}";
-                    var gpuInstancing = _node.GetGpuInstancing();
-                    var featureIdAccessors = gpuInstancing.GetAccessor(expectedVertexAttribute);
-                    Guard.NotNull(featureIdAccessors, expectedVertexAttribute);
-                }
-
-                if (instanceFeatureId.PropertyTable.HasValue)
-                {
-                    var metadataExtension = _node.LogicalParent.GetExtension<EXTStructuralMetadataRoot>();
-                    Guard.NotNull(metadataExtension, nameof(metadataExtension), "EXT_Structural_Metadata extension is not found.");
-                    Guard.NotNull(metadataExtension.PropertyTables[instanceFeatureId.PropertyTable.Value], nameof(instanceFeatureId.PropertyTable), $"Property table index {instanceFeatureId.PropertyTable.Value} does not exist");
-                }
-            }
-
-            base.OnValidateReferences(validate);
-        }
-
-        protected override void OnValidateContent(ValidationContext validate)
-        {
-            var extInstanceFeatures = _node.GetExtension<MeshExtInstanceFeatures>();
-            validate.NotNull(nameof(FeatureIds), extInstanceFeatures.FeatureIds);
-            validate.IsTrue(nameof(FeatureIds), extInstanceFeatures.FeatureIds.Count > 0, "Instance FeatureIds has items");
-
-
-            foreach (var instanceFeatureId in FeatureIds)
-            {
-                Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.FeatureCount, 1, nameof(instanceFeatureId.FeatureCount));
-
-                if (instanceFeatureId.NullFeatureId.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.NullFeatureId, 0, nameof(instanceFeatureId.NullFeatureId));
-                }
-                if (instanceFeatureId.Label != null)
-                {
-                    var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
-                    Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(instanceFeatureId.Label, regex), nameof(instanceFeatureId.Label));
-                }
-
-                if (instanceFeatureId.Attribute.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.Attribute, 0, nameof(instanceFeatureId.Attribute));
-                }
-                if (instanceFeatureId.PropertyTable.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)instanceFeatureId.PropertyTable, 0, nameof(instanceFeatureId.PropertyTable));
-                }
-            }
-
-            base.OnValidateContent(validate);
-        }
-    }
-
-    public partial class MeshExtInstanceFeatureID
-    {
-        public MeshExtInstanceFeatureID()
-        {
-        }
-
-        public MeshExtInstanceFeatureID(int featureCount, int? attribute = null, int? propertyTable = null, string label = null, int? nullFeatureId = null)
-        {
-            _featureCount = featureCount;
-            _attribute = attribute;
-            _label = label;
-            _propertyTable = propertyTable;
-            _nullFeatureId = nullFeatureId;
-        }
-
-        /// <summary>
-        /// The number of unique features in the attribute
-        /// </summary>
-        public int FeatureCount { get => _featureCount; }
-
-        /// <summary>
-        /// An attribute containing feature IDs. When this is omitted, then the feature IDs are assigned to the GPU instances by their index.
-        /// </summary>
-        public int? Attribute { get => _attribute; }
-
-        /// <summary>
-        /// A label assigned to this feature ID set
-        /// </summary>
-        public string Label { get => _label; }
-
-        /// <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; }
-
-        /// <summary>
-        /// A value that indicates that no feature is associated with this instance
-        /// </summary>
-        public int? NullFeatureId { get => _nullFeatureId; }
-    }
-
-    public static class ExtInstanceFeatures
-    {
-        /// <summary>
-        /// Set the instance feature ids for this node.
-        /// </summary>
-        /// <param name="node"></param>
-        /// <param name="instanceFeatureIds"></param>
-        public static void SetFeatureIds(this Node node, List<MeshExtInstanceFeatureID> instanceFeatureIds)
-        {
-            if (instanceFeatureIds == null) { node.RemoveExtensions<MeshExtInstanceFeatures>(); return; }
-
-            Guard.NotNullOrEmpty(instanceFeatureIds, nameof(instanceFeatureIds));
-
-            var extMeshGpInstancing = node.GetExtension<MeshGpuInstancing>();
-            Guard.NotNull(extMeshGpInstancing, nameof(extMeshGpInstancing));
-
-            var ext = node.UseExtension<MeshExtInstanceFeatures>();
-            ext.FeatureIds = instanceFeatureIds;
-        }
-    }
-}

+ 0 - 179
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.MeshFeatures.cs

@@ -1,179 +0,0 @@
-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
-            {
-                _featureIds = value;
-            }
-        }
-
-        protected override void OnValidateReferences(ValidationContext validate)
-        {
-            foreach (var featureId in _featureIds)
-            {
-                if (featureId.Attribute.HasValue)
-                {
-                    var expectedVertexAttribute = $"_FEATURE_ID_{featureId.Attribute}";
-                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedVertexAttribute), expectedVertexAttribute);
-                }
-                if (featureId.PropertyTable.HasValue)
-                {
-                    var metadataExtension = _meshPrimitive.LogicalParent.LogicalParent.GetExtension<EXTStructuralMetadataRoot>();
-                    Guard.NotNull(metadataExtension, nameof(metadataExtension), "EXT_Structural_Metadata extension is not found.");
-                    Guard.NotNull(metadataExtension.PropertyTables[featureId.PropertyTable.Value], nameof(featureId.PropertyTable), $"Property table index {featureId.PropertyTable.Value} does not exist");
-                }
-                if (featureId.Texture != null)
-                {
-                    var expectedTexCoordAttribute = $"TEXCOORD_{featureId.Texture.TextureCoordinate}";
-                    Guard.NotNull(_meshPrimitive.GetVertexAccessor(expectedTexCoordAttribute), expectedTexCoordAttribute);
-
-                    var modelRoot = _meshPrimitive.LogicalParent.LogicalParent;
-                    validate.IsNullOrIndex(nameof(featureId.Texture), featureId.Texture.TextureCoordinate, modelRoot.LogicalTextures);
-                }
-            }
-
-            base.OnValidateReferences(validate);
-        }
-
-
-        protected override void OnValidateContent(ValidationContext validate)
-        {
-            var extMeshFeatures = _meshPrimitive.Extensions.Where(item => item is MeshExtMeshFeatures).FirstOrDefault();
-            validate.NotNull(nameof(extMeshFeatures), extMeshFeatures);
-            validate.NotNull(nameof(FeatureIds), _featureIds);
-            validate.IsTrue(nameof(FeatureIds), _featureIds.Count > 0, "FeatureIds has items");
-
-            foreach (var featureId in _featureIds)
-            {
-                if (featureId.NullFeatureId.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.NullFeatureId, 0, nameof(featureId.NullFeatureId));
-                }
-                if (featureId.Label != null)
-                {
-                    var regex = "^[a-zA-Z_][a-zA-Z0-9_]*$";
-                    Guard.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(featureId.Label, regex), nameof(featureId.Label));
-                }
-                if (featureId.Attribute.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.Attribute, 0, nameof(featureId.Attribute));
-                }
-                if (featureId.PropertyTable.HasValue)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo((int)featureId.PropertyTable, 0, nameof(featureId.PropertyTable));
-                }
-                if (featureId.Texture != null)
-                {
-                    Guard.MustBeGreaterThanOrEqualTo(featureId.Texture.TextureCoordinate, 0, nameof(featureId.Texture.TextureCoordinate));
-                }
-                base.OnValidateContent(validate);
-            }
-        }
-    }
-
-    public partial class MeshExtMeshFeatureIDTexture
-    {
-        public MeshExtMeshFeatureIDTexture()
-        {
-            _channels = new List<int>();
-        }
-
-        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()
-        {
-        }
-        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
-    {
-        public static void SetFeatureId(this MeshPrimitive primitive, MeshExtMeshFeatureID featureId)
-        {
-            primitive.SetFeatureIds(new List<MeshExtMeshFeatureID>() { featureId });
-        }
-
-        /// <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 || featureIds.Count == 0) { primitive.RemoveExtensions<MeshExtMeshFeatures>(); return; }
-
-            var ext = primitive.UseExtension<MeshExtMeshFeatures>();
-            ext.FeatureIds = featureIds;
-        }
-    }
-}

+ 9 - 20
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataPrimitive.cs

@@ -1,7 +1,8 @@
-using SharpGLTF.Validation;
-using System.Collections.Generic;
+using System.Collections.Generic;
 
-namespace SharpGLTF.Schema2
+using SharpGLTF.Validation;
+
+namespace SharpGLTF.Schema2.Tiles3D
 {
     partial class ExtStructuralMetadataMeshPrimitive
     {
@@ -16,26 +17,14 @@ namespace SharpGLTF.Schema2
 
         public List<int> PropertyTextures
         {
-            get
-            {
-                return _propertyTextures;
-            }
-            set
-            {
-                _propertyTextures = value;
-            }
+            get => _propertyTextures;
+            set => _propertyTextures = value;
         }
 
         public List<int> PropertyAttributes
         {
-            get
-            {
-                return _propertyAttributes;
-            }
-            set
-            {
-                _propertyAttributes = value;
-            }
+            get => _propertyAttributes;
+            set => _propertyAttributes = value;
         }
 
         protected override void OnValidateReferences(ValidationContext validate)
@@ -61,7 +50,7 @@ namespace SharpGLTF.Schema2
         }
     }
 
-    partial class ThreeDTilesExtensions
+    partial class Tiles3DExtensions
     {
         public static void SetPropertyTextures(this MeshPrimitive primitive, List<int> propertyTextures)
         {

+ 396 - 244
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.StructuralMetadataRoot.cs

@@ -1,18 +1,25 @@
-using OneOf;
-using SharpGLTF.Memory;
-using SharpGLTF.Validation;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
+using System.Reflection;
 
-namespace SharpGLTF.Schema2
+using OneOf;
+
+namespace SharpGLTF.Schema2.Tiles3D
 {
-    public static class ExtStructuralMetadataRoot
+    using Collections;
+    using Memory;
+    using Validation;
+
+    using METADATAORURI = OneOf<StructuralMetadataSchema, Uri>;
+
+    partial class Tiles3DExtensions
     {
         public static void SetPropertyAttribute(
             this ModelRoot modelRoot,
             PropertyAttribute propertyAttribute,
-            OneOf<StructuralMetadataSchema, Uri> schema)
+            METADATAORURI schema)
         {
             SetPropertyAttributes(modelRoot, new List<PropertyAttribute>() { propertyAttribute }, schema);
         }
@@ -20,12 +27,13 @@ namespace SharpGLTF.Schema2
         public static void SetPropertyAttributes(
 this ModelRoot modelRoot,
 List<PropertyAttribute> propertyAttributes,
-OneOf<StructuralMetadataSchema, Uri> schema)
+METADATAORURI schema)
         {
             if (propertyAttributes == null || propertyAttributes.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
 
             var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            ext.PropertyAttributes = propertyAttributes;
+            // ext.PropertyAttributes = propertyAttributes;
+            throw new NotImplementedException();
             ext.AddSchema(schema);
         }
 
@@ -33,7 +41,7 @@ OneOf<StructuralMetadataSchema, Uri> schema)
         public static void SetPropertyTexture(
     this ModelRoot modelRoot,
     PropertyTexture propertyTexture,
-    OneOf<StructuralMetadataSchema, Uri> schema)
+    METADATAORURI schema)
         {
             SetPropertyTextures(modelRoot, new List<PropertyTexture>() { propertyTexture }, schema);
         }
@@ -42,19 +50,20 @@ OneOf<StructuralMetadataSchema, Uri> schema)
         public static void SetPropertyTextures(
     this ModelRoot modelRoot,
     List<PropertyTexture> propertyTextures,
-    OneOf<StructuralMetadataSchema, Uri> schema)
+    METADATAORURI schema)
         {
             if (propertyTextures == null || propertyTextures.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
 
             var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            ext.PropertyTextures = propertyTextures;
+            // ext.PropertyTextures = propertyTextures;
+            throw new NotImplementedException();
             ext.AddSchema(schema);
         }
 
         public static void SetPropertyTable(
             this ModelRoot modelRoot,
             PropertyTable propertyTable,
-            OneOf<StructuralMetadataSchema, Uri> schema)
+            METADATAORURI schema)
         {
             SetPropertyTables(modelRoot, new List<PropertyTable>() { propertyTable }, schema);
         }
@@ -62,12 +71,13 @@ OneOf<StructuralMetadataSchema, Uri> schema)
         public static void SetPropertyTables(
             this ModelRoot modelRoot,
             List<PropertyTable> propertyTables,
-            OneOf<StructuralMetadataSchema, Uri> schema)
+            METADATAORURI schema)
         {
             if (propertyTables == null || propertyTables.Count == 0) { modelRoot.RemoveExtensions<EXTStructuralMetadataRoot>(); return; }
 
             var ext = modelRoot.UseExtension<EXTStructuralMetadataRoot>();
-            ext.PropertyTables = propertyTables;
+            // ext.PropertyTables = propertyTables;
+            throw new NotImplementedException();
             ext.AddSchema(schema);
         }
 
@@ -130,61 +140,77 @@ OneOf<StructuralMetadataSchema, Uri> schema)
 
     public partial class EXTStructuralMetadataRoot
     {
-        private ModelRoot modelRoot;
+        #region lifecycle
 
         internal EXTStructuralMetadataRoot(ModelRoot modelRoot)
         {
             this.modelRoot = modelRoot;
-            _propertyTables = new List<PropertyTable>();
-            _propertyAttributes = new List<PropertyAttribute>();
-            _propertyTextures = new List<PropertyTexture>();
+            _propertyTables = new ChildrenList<PropertyTable, EXTStructuralMetadataRoot>(this);
+            _propertyAttributes = new ChildrenList<PropertyAttribute, EXTStructuralMetadataRoot>(this);
+            _propertyTextures = new ChildrenList<PropertyTexture, EXTStructuralMetadataRoot>(this);
         }
 
-        internal void AddSchema(OneOf<StructuralMetadataSchema, Uri> schema)
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            schema.Switch(
-                StructuralMetadataSchema => _schema = StructuralMetadataSchema,
-                Uri => this.SchemaUri = Uri.ToString()
-                );
-        }
+            var items = base.GetLogicalChildren()
+                .Concat(_propertyTables)
+                .Concat(_propertyAttributes)
+                .Concat(_propertyTextures);
 
-        internal List<PropertyTable> PropertyTables
-        {
-            get { return _propertyTables; }
-            set { _propertyTables = value; }
+            if (Schema != null) items = items.Append(Schema);
+
+            return items;
         }
 
+        #endregion
+
+        #region data
+
+        private ModelRoot modelRoot;
+
+        #endregion
+
+        #region properties
+
         internal string SchemaUri
         {
-            get { return _schemaUri; }
+            get => _schemaUri;
             set { _schemaUri = value; }
         }
 
-        internal List<PropertyAttribute> PropertyAttributes
-        {
-            get { return _propertyAttributes; }
-            set { _propertyAttributes = value; }
-        }
-
         internal StructuralMetadataSchema Schema
         {
-            get { return _schema; }
-            set { _schema = value; }
+            get => _schema;
+            set { GetChildSetter(this).SetListProperty(ref _schema, value); }
         }
 
-        internal List<PropertyTexture> PropertyTextures
+        internal IReadOnlyList<PropertyTable> PropertyTables => _propertyTables;
+        internal IReadOnlyList<PropertyAttribute> PropertyAttributes => _propertyAttributes;
+        internal IReadOnlyList<PropertyTexture> PropertyTextures => _propertyTextures;
+
+        #endregion
+
+        #region API
+
+        internal void AddSchema(METADATAORURI schema)
         {
-            get { return _propertyTextures; }
-            set { _propertyTextures = value; }
+            schema.Switch(
+                StructuralMetadataSchema => _schema = StructuralMetadataSchema,
+                Uri => this.SchemaUri = Uri.ToString()
+                );
         }
 
+        #endregion
+
+        #region validation
+
         protected override void OnValidateReferences(ValidationContext validate)
         {
             foreach (var propertyTexture in PropertyTextures)
             {
                 foreach (var propertyTextureProperty in propertyTexture.Properties)
                 {
-                    var textureId = propertyTextureProperty.Value._LogicalTextureIndex;
+                    var textureId = propertyTextureProperty.Value.LogicalTextureIndex;
                     validate.IsNullOrIndex(nameof(propertyTexture), textureId, modelRoot.LogicalTextures);
                 }
             }
@@ -260,7 +286,7 @@ OneOf<StructuralMetadataSchema, Uri> schema)
                 {
                     var texCoord = propertyTextureProperty.Value.TextureCoordinate;
                     var channels = propertyTextureProperty.Value.Channels;
-                    var index = propertyTextureProperty.Value._LogicalTextureIndex;
+                    var index = propertyTextureProperty.Value.LogicalTextureIndex;
                     Guard.MustBeGreaterThanOrEqualTo(texCoord, 0, nameof(texCoord));
                     Guard.IsTrue(channels.Count > 0, nameof(channels), "Channels must be defined");
                     Guard.IsTrue(index >= 0, nameof(index), "Index must be defined");
@@ -280,322 +306,422 @@ OneOf<StructuralMetadataSchema, Uri> schema)
 
             base.OnValidateContent(result);
         }
+
+        #endregion
     }
 
-    public partial class PropertyTexture
+    public partial class PropertyTexture : IChildOfList<EXTStructuralMetadataRoot>
     {
+        #region lifecycle
         public PropertyTexture()
         {
-            _properties = new Dictionary<string, PropertyTextureProperty>();
+            _properties = new ChildrenDictionary<PropertyTextureProperty, PropertyTexture>(this);
         }
 
-        public string Class
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            get { return _class; }
-            set { _class = value; }
+            return base.GetLogicalChildren()
+                .Concat(_properties.Values);
         }
 
-        public Dictionary<string, PropertyTextureProperty> Properties
+        #endregion
+
+        #region child properties
+
+        public int LogicalIndex { get; private set; } = -1;
+        
+        public EXTStructuralMetadataRoot LogicalParent { get; private set; }
+
+        void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
+        {
+            LogicalParent = parent;
+            LogicalIndex = index;
+        }
+
+        #endregion
+
+        #region properties
+
+        public string ClassName
         {
-            get { return _properties; }
-            set { _properties = value; }
+            get => _class;
+            set => _class = value;
         }
+
+        public IReadOnlyDictionary<string, PropertyTextureProperty> Properties => _properties;
+
+        #endregion
     }
 
-    public partial class PropertyTextureProperty
+    public partial class PropertyTextureProperty : IChildOfDictionary<PropertyTexture>
     {
+        #region lifecycle
         public PropertyTextureProperty()
         {
             _channels = new List<int>();
         }
 
-        public List<int> Channels
+        #endregion
+
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public PropertyTexture LogicalParent { get; private set; }
+
+        void IChildOfDictionary<PropertyTexture>.SetLogicalParent(PropertyTexture parent, string key)
         {
-            get { return _channels; }
-            set { _channels = value; }
+            LogicalParent = parent;
+            LogicalKey = key;
         }
+
+        #endregion
+
+        #region data
+
+        public List<int> Channels => _channels;
+
+        #endregion
     }
 
-    public partial class PropertyAttribute
+    public partial class PropertyAttribute : IChildOfList<EXTStructuralMetadataRoot>
     {
+        #region lifecycle
         public PropertyAttribute()
         {
-            _properties = new Dictionary<string, PropertyAttributeProperty>();
+            _properties = new ChildrenDictionary<PropertyAttributeProperty, PropertyAttribute>(this);
         }
-        public string Class
+
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            get { return _class; }
-            set
-            {
-                _class = value;
-            }
+            return base.GetLogicalChildren()
+                .Concat(_properties.Values);
         }
 
-        public Dictionary<string, PropertyAttributeProperty> Properties
+        #endregion
+
+        #region child properties
+
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="MeshExtInstanceFeatureID"/> at <see cref="MeshExtInstanceFeatures.FeatureIds"/>.
+        /// </summary>
+        public int LogicalIndex { get; private set; } = -1;
+
+        /// <summary>
+        /// Gets the <see cref="MeshExtInstanceFeatures"/> instance that owns this <see cref="MeshExtInstanceFeatureID"/> instance.
+        /// </summary>
+        public EXTStructuralMetadataRoot LogicalParent { get; private set; }
+
+        void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
         {
-            get { return _properties; }
-            set
-            {
-                _properties = value;
-            }
+            LogicalParent = parent;
+            LogicalIndex = index;
         }
 
+        #endregion
+
+        #region properties
+        public string Class
+        {
+            get => _class;
+            set => _class = value;
+        }
+
+        public IReadOnlyDictionary<string, PropertyAttributeProperty> Properties => _properties;
+
+        #endregion
+
     }
 
-    public partial class PropertyAttributeProperty
+    public partial class PropertyAttributeProperty : IChildOfDictionary<PropertyAttribute>
     {
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public PropertyAttribute LogicalParent { get; private set; }
+
+        void IChildOfDictionary<PropertyAttribute>.SetLogicalParent(PropertyAttribute parent, string key)
+        {
+            LogicalParent = parent;
+            LogicalKey = key;
+        }
+
+        #endregion
+
+        #region properties
+
         public string Attribute
         {
-            get { return _attribute; }
-            set
-            {
-                _attribute = value;
-            }
+            get => _attribute;
+            set => _attribute = value;
         }
+
+        #endregion
     }
 
-    public partial class StructuralMetadataSchema
+    public partial class StructuralMetadataSchema : IChildOfList<EXTStructuralMetadataRoot>
     {
+        #region lifecycle
         public StructuralMetadataSchema()
         {
-            _classes = new Dictionary<string, StructuralMetadataClass>();
-            _enums = new Dictionary<string, StructuralMetadataEnum>();
+            _classes = new ChildrenDictionary<StructuralMetadataClass, StructuralMetadataSchema>(this);
+            _enums = new ChildrenDictionary<StructuralMetadataEnum, StructuralMetadataSchema>(this);
         }
 
-        public Dictionary<string, StructuralMetadataClass> Classes
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            get { return _classes; }
-            set
-            {
-                _classes = value;
-            }
+            return base.GetLogicalChildren()
+                .Concat(_classes.Values)
+                .Concat(_enums.Values);
         }
 
+        #endregion
+
+        #region child properties
+
+        public int LogicalIndex { get; private set; } = -1;
+
+        public EXTStructuralMetadataRoot LogicalParent { get; private set; }
+
+        void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
+        {
+            LogicalParent = parent;
+            LogicalIndex = index;
+        }
+
+        #endregion
+
+        #region properties        
+
+        public IReadOnlyDictionary<string, StructuralMetadataClass> Classes => _classes;
+        public IReadOnlyDictionary<string, StructuralMetadataEnum> Enums => _enums;
+
         public string Id
         {
-            get { return _id; }
-            set
-            {
-                _id = value;
-            }
+            get => _id;
+            set => _id = value;
         }
 
         public string Version
         {
-            get { return _version; }
-            set
-            {
-                _version = value;
-            }
+            get => _version;
+            set => _version = value;
         }
 
         public string Name
         {
-            get { return _name; }
-            set
-            {
-                _name = value;
-            }
+            get => _name;
+            set => _name = value;
         }
 
         public string Description
         {
-            get { return _description; }
-            set
-            {
-                _description = value;
-            }
-        }
+            get => _description;
+            set => _description = value;
+        }        
 
-        public Dictionary<string, StructuralMetadataEnum> Enums
-        {
-            get { return _enums; }
-            set
-            {
-                _enums = value;
-            }
-        }
+        #endregion
     }
 
-    public partial class StructuralMetadataEnum
+    public partial class StructuralMetadataEnum : IChildOfDictionary<StructuralMetadataSchema>
     {
+        #region lifecycle
         public StructuralMetadataEnum()
         {
             _values = new List<EnumValue>();
         }
+
+        #endregion
+
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public StructuralMetadataSchema LogicalParent { get; private set; }
+
+        void IChildOfDictionary<StructuralMetadataSchema>.SetLogicalParent(StructuralMetadataSchema parent, string key)
+        {
+            LogicalParent = parent;
+            LogicalKey = key;
+        }
+
+        #endregion
+
+        #region properties
+
         public string Name
         {
-            get { return _name; }
-            set
-            {
-                _name = value;
-            }
+            get => _name;
+            set => _name = value;
         }
         public string Description
         {
-            get { return _description; }
-            set
-            {
-                _description = value;
-            }
+            get => _description;
+            set => _description = value;
         }
         public List<EnumValue> Values
         {
-            get { return _values; }
-            set
-            {
-                _values = value;
-            }
+            get => _values;
+            set => _values = value;
         }
+
+        #endregion
     }
 
     public partial class EnumValue
     {
+        public string Description
+        {
+            get => _description;
+            set => _description = value;
+        }
         public string Name
         {
-            get { return _name; }
-            set
-            {
-                _name = value;
-            }
+            get => _name;
+            set => _name = value;
         }
         public int Value
         {
-            get { return _value; }
-            set
-            {
-                _value = value;
-            }
+            get => _value;
+            set => _value = value;
         }
     }
 
-    public partial class StructuralMetadataClass
+    public partial class StructuralMetadataClass : IChildOfDictionary<StructuralMetadataSchema>
     {
+        #region lifecycle
+
         public StructuralMetadataClass()
         {
-            _properties = new Dictionary<string, ClassProperty>();
+            _properties = new ChildrenDictionary<ClassProperty, StructuralMetadataClass>(this);
         }
 
-        public Dictionary<string, ClassProperty> Properties
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            get { return _properties; }
-            set
-            {
-                _properties = value;
-            }
+            return base.GetLogicalChildren()
+                .Concat(_properties.Values);
         }
 
+        #endregion
+
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public StructuralMetadataSchema LogicalParent { get; private set; }
+
+        void IChildOfDictionary<StructuralMetadataSchema>.SetLogicalParent(StructuralMetadataSchema parent, string key)
+        {
+            LogicalParent = parent;
+            LogicalKey = key;
+        }
+
+        #endregion
+
+        #region properties
+
+        public IReadOnlyDictionary<string, ClassProperty> Properties => _properties;
+
         public string Name
         {
-            get { return _name; }
-            set
-            {
-                if (value == null) { _name = null; return; }
-                _name = value;
-            }
+            get => _name;
+            set => _name = value;
         }
 
         public string Description
         {
-            get { return _description; }
-            set
-            {
-                _description = value;
-            }
+            get => _description;
+            set => _description = value;
         }
 
+        #endregion
+
     }
 
-    public partial class ClassProperty
+    public partial class ClassProperty : IChildOfDictionary<StructuralMetadataClass>
     {
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public StructuralMetadataClass LogicalParent { get; private set; }
+
+        void IChildOfDictionary<StructuralMetadataClass>.SetLogicalParent(StructuralMetadataClass parent, string key)
+        {
+            LogicalParent = parent;
+            LogicalKey = key;
+        }
+
+        #endregion
+
+        #region properties
         public string Name
         {
-            get { return _name; }
-            set
-            {
-                _name = value;
-            }
+            get => _name;
+            set => _name = value;
         }
 
         public string Description
         {
-            get { return _description; }
-            set
-            {
-                _description = value;
-            }
+            get => _description;
+            set => _description = value;
         }
 
         public ElementType Type
         {
-            get { return _type; }
-            set
-            {
-                _type = value;
-            }
+            get => _type;
+            set => _type = value;
         }
 
         public string EnumType
         {
-            get { return _enumType; }
-            set
-            {
-                _enumType = value;
-            }
+            get => _enumType;
+            set => _enumType = value;
         }
 
         public DataType? ComponentType
         {
-            get { return _componentType; }
-            set
-            {
-                _componentType = value;
-            }
+            get => _componentType;
+            set => _componentType = value;
         }
 
         public bool? Required
         {
-            get { return _required; }
-            set
-            {
-                _required = value;
-            }
+            get => _required;
+            set => _required = value;
         }
 
         public bool? Normalized
         {
-            get { return _normalized; }
-            set
-            {
-                _normalized = value;
-            }
+            get => _normalized;
+            set => _normalized = value;
         }
 
         public bool? Array
         {
-            get { return _array; }
-            set
-            {
-                _array = value;
-            }
-
+            get => _array;
+            set => _array = value;
         }
 
         public int? Count
         {
-            get { return _count; }
-            set
-            {
-                _count = value;
-            }
+            get => _count;
+            set => _count = value;
         }
+
+        #endregion
     }
 
-    public partial class PropertyTable
+    /// <remarks>
+    /// Represents a Propery table of <see cref="EXTStructuralMetadataRoot"/>
+    /// </remarks> 
+    public partial class PropertyTable : IChildOfList<EXTStructuralMetadataRoot>
     {
+        #region lifecycle
         public PropertyTable()
         {
-            _properties = new Dictionary<string, PropertyTableProperty>();
+            _properties = new ChildrenDictionary<PropertyTableProperty, PropertyTable>(this);
         }
         public PropertyTable(string Class, int Count, string Name = "") : this()
         {
@@ -604,68 +730,94 @@ OneOf<StructuralMetadataSchema, Uri> schema)
             _name = Name;
         }
 
-        public string Name
+        protected override IEnumerable<ExtraProperties> GetLogicalChildren()
         {
-            get { return _name; }
-            set
-            {
-                _name = value;
-            }
+            return base.GetLogicalChildren()
+                .Concat(_properties.Values);
         }
 
-        public string Class
+        #endregion
+
+        #region child properties
+
+        public int LogicalIndex { get; private set; } = -1;        
+        public EXTStructuralMetadataRoot LogicalParent { get; private set; }
+
+        void IChildOfList<EXTStructuralMetadataRoot>.SetLogicalParent(EXTStructuralMetadataRoot parent, int index)
         {
-            get { return _class; }
-            set
-            {
-                _class = value;
-            }
+            LogicalParent = parent;
+            LogicalIndex = index;
         }
 
-        public int Count
+        #endregion
+
+        #region properties
+
+        public IReadOnlyDictionary<string, PropertyTableProperty> Properties => _properties;
+
+        public string Name
         {
-            get { return _count; }
-            set
-            {
-                _count = value;
-            }
+            get => _name;
+            set => _name = value;
         }
 
-        public Dictionary<string, PropertyTableProperty> Properties
+        public string Class
         {
-            get { return _properties; }
-            set
-            {
-                _properties = value;
-            }
+            get => _class;
+            set => _class = value;
         }
+
+        public int Count
+        {
+            get => _count;
+            set => _count = value;
+        }        
+
+        #endregion
     }
 
-    public partial class PropertyTableProperty
+    /// <remarks>
+    /// Represents a Property of <see cref="PropertyTable"/>
+    /// </remarks>    
+    public partial class PropertyTableProperty : IChildOfDictionary<PropertyTable>
     {
+        #region child properties
+
+        public string LogicalKey { get; private set; }
+
+        public PropertyTable LogicalParent { get; private set; }
+
+        void IChildOfDictionary<PropertyTable>.SetLogicalParent(PropertyTable parent, string key)
+        {
+            LogicalParent = parent;
+            LogicalKey = key;
+        }
+
+        #endregion
+
+        #region properties
+        /// <summary>
+        /// this is an index to a BufferView
+        /// </summary>
         public int Values
         {
-            get { return _values; }
-            set { _values = value; }
+            get => _values;
+            set => _values = value;
         }
 
         public int? ArrayOffsets
         {
-            get { return _arrayOffsets; }
-            set
-            {
-                _arrayOffsets = value;
-            }
+            get => _arrayOffsets;
+            set => _arrayOffsets = value;
         }
 
         public int? StringOffsets
         {
-            get { return _stringOffsets; }
-            set
-            {
-                _stringOffsets = value;
-            }
+            get => _stringOffsets;
+            set => _stringOffsets = value;
         }
+
+        #endregion
     }
 }
 

+ 2 - 2
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_ext_instance_features.g.cs

@@ -23,7 +23,7 @@ using System.Text;
 using System.Numerics;
 using System.Text.Json;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
 	using Collections;
 
@@ -87,7 +87,7 @@ namespace SharpGLTF.Schema2
 	{
 	
 		private const int _featureIdsMinItems = 1;
-		private List<MeshExtInstanceFeatureID> _featureIds;
+		private ChildrenList<MeshExtInstanceFeatureID,MeshExtInstanceFeatures> _featureIds;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)

+ 2 - 2
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_ext_mesh_features.g.cs

@@ -23,7 +23,7 @@ using System.Text;
 using System.Numerics;
 using System.Text.Json;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
 	using Collections;
 
@@ -122,7 +122,7 @@ namespace SharpGLTF.Schema2
 	{
 	
 		private const int _featureIdsMinItems = 1;
-		private List<MeshExtMeshFeatureID> _featureIds;
+		private ChildrenList<MeshExtMeshFeatureID,MeshExtMeshFeatures> _featureIds;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)

+ 1 - 1
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_primitive.g.cs

@@ -23,7 +23,7 @@ using System.Text;
 using System.Numerics;
 using System.Text.Json;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
 	using Collections;
 

+ 10 - 10
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_ext_structural_metadata_root.g.cs

@@ -23,7 +23,7 @@ using System.Text;
 using System.Numerics;
 using System.Text.Json;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
 	using Collections;
 
@@ -199,7 +199,7 @@ namespace SharpGLTF.Schema2
 		
 		private String _name;
 		
-		private Dictionary<String,ClassProperty> _properties;
+		private ChildrenDictionary<ClassProperty,StructuralMetadataClass> _properties;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -315,11 +315,11 @@ namespace SharpGLTF.Schema2
 	partial class StructuralMetadataSchema : ExtraProperties
 	{
 	
-		private Dictionary<String,StructuralMetadataClass> _classes;
+		private ChildrenDictionary<StructuralMetadataClass,StructuralMetadataSchema> _classes;
 		
 		private String _description;
 		
-		private Dictionary<String,StructuralMetadataEnum> _enums;
+		private ChildrenDictionary<StructuralMetadataEnum,StructuralMetadataSchema> _enums;
 		
 		private String _id;
 		
@@ -436,7 +436,7 @@ namespace SharpGLTF.Schema2
 		
 		private String _name;
 		
-		private Dictionary<String,PropertyTableProperty> _properties;
+		private ChildrenDictionary<PropertyTableProperty,PropertyTable> _properties;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -523,7 +523,7 @@ namespace SharpGLTF.Schema2
 		
 		private String _name;
 		
-		private Dictionary<String,PropertyTextureProperty> _properties;
+		private ChildrenDictionary<PropertyTextureProperty,PropertyTexture> _properties;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -607,7 +607,7 @@ namespace SharpGLTF.Schema2
 		
 		private String _name;
 		
-		private Dictionary<String,PropertyAttributeProperty> _properties;
+		private ChildrenDictionary<PropertyAttributeProperty,PropertyAttribute> _properties;
 		
 	
 		protected override void SerializeProperties(Utf8JsonWriter writer)
@@ -642,13 +642,13 @@ namespace SharpGLTF.Schema2
 	{
 	
 		private const int _propertyAttributesMinItems = 1;
-		private List<PropertyAttribute> _propertyAttributes;
+		private ChildrenList<PropertyAttribute,EXTStructuralMetadataRoot> _propertyAttributes;
 		
 		private const int _propertyTablesMinItems = 1;
-		private List<PropertyTable> _propertyTables;
+		private ChildrenList<PropertyTable,EXTStructuralMetadataRoot> _propertyTables;
 		
 		private const int _propertyTexturesMinItems = 1;
-		private List<PropertyTexture> _propertyTextures;
+		private ChildrenList<PropertyTexture,EXTStructuralMetadataRoot> _propertyTextures;
 		
 		private StructuralMetadataSchema _schema;
 		

+ 1 - 1
src/SharpGLTF.Ext.3DTiles/Schema2/Generated/Ext.CESIUM_primitive_outline.g.cs

@@ -23,7 +23,7 @@ using System.Text;
 using System.Numerics;
 using System.Text.Json;
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
 	using Collections;
 

+ 2 - 2
src/SharpGLTF.Ext.3DTiles/Schema2/3DTilesExtensions.cs → src/SharpGLTF.Ext.3DTiles/Schema2/Tiles3DExtensions.cs

@@ -1,9 +1,9 @@
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.Tiles3D
 {
     /// <summary>
     /// Extension methods for 3DTiles glTF Extensions
     /// </summary>
-    public static partial class ThreeDTilesExtensions
+    public static partial class Tiles3DExtensions
     {
         private static bool _3DTilesRegistered;
 

+ 3 - 4
src/SharpGLTF.Ext.3DTiles/SharpGLTF.Ext.3DTiles.csproj

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

+ 1 - 2
src/SharpGLTF.Ext.Agi/SharpGLTF.Ext.Agi.csproj

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