Browse Source

WIP: refactoring model validation...

Vicente Penades 6 years ago
parent
commit
9bf4fde8c4

+ 13 - 5
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -12,16 +12,24 @@ namespace SharpGLTF.IO
     {
     {
         #region validation
         #region validation
 
 
-        public IEnumerable<Exception> Validate()
+        internal void ValidateReferences(Validation.ValidationContext context)
         {
         {
-            var result = new Validation.ValidationContext();
+            context = context.Result.GetContext(this);
+            OnValidateReferences(context);
+        }
 
 
-            Validate(result);
+        protected virtual void OnValidateReferences(Validation.ValidationContext result)
+        {
 
 
-            return result.Errors;
         }
         }
 
 
-        internal virtual void Validate(Validation.ValidationContext result)
+        internal void Validate(Validation.ValidationContext context)
+        {
+            context = context.Result.GetContext(this);
+            OnValidate(context);
+        }
+
+        protected virtual void OnValidate(Validation.ValidationContext result)
         {
         {
         }
         }
 
 

+ 80 - 53
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -333,37 +333,44 @@ namespace SharpGLTF.Schema2
 
 
         #endregion
         #endregion
 
 
-        #region validation
+        #region Validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
 
 
-            result.CheckIsDefined(this, "BufferView", _bufferView);
-            result.CheckIndex(this, "BufferView", _bufferView, this.LogicalParent.LogicalBufferViews.Count);
+            result.CheckIsDefined("BufferView", _bufferView);
+            result.CheckReferenceIndex("BufferView", _bufferView, this.LogicalParent.LogicalBufferViews);
+        }
+
+        /// <see href="https://github.com/KhronosGroup/glTF-Validator/blob/master/lib/src/base/accessor.dart"/>
+        /// <seealso href="https://github.com/KhronosGroup/glTF-Validator/blob/master/lib/src/data_access/validate_accessors.dart"/>
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
 
 
-            if (_count < _countMinimum) result.AddError(this, $"Count is out of range");
-            if (_byteOffset < 0) result.AddError(this, $"ByteOffset is out of range");
+            // if (_count < _countMinimum) result.AddError(this, $"Count is out of range");
+            // if (_byteOffset < 0) result.AddError(this, $"ByteOffset is out of range");
 
 
             if (this.Normalized)
             if (this.Normalized)
             {
             {
                 if (this._componentType != EncodingType.UNSIGNED_BYTE && this._componentType != EncodingType.UNSIGNED_SHORT)
                 if (this._componentType != EncodingType.UNSIGNED_BYTE && this._componentType != EncodingType.UNSIGNED_SHORT)
                 {
                 {
-                    result.AddDataError(this, Validation.ErrorCodes.ACCESSOR_NORMALIZED_INVALID);
+                    result.AddDataError(Validation.ErrorCodes.ACCESSOR_NORMALIZED_INVALID);
                 }
                 }
             }
             }
 
 
             if (SourceBufferView.DeviceBufferTarget == BufferMode.ARRAY_BUFFER)
             if (SourceBufferView.DeviceBufferTarget == BufferMode.ARRAY_BUFFER)
             {
             {
                 var len = Encoding.ByteLength() * Dimensions.DimCount();
                 var len = Encoding.ByteLength() * Dimensions.DimCount();
-                result.CheckMultipleOf(this, "Encoding", len, 4);
+                result.CheckMultipleOf("Encoding", len, 4);
             }
             }
 
 
             if (SourceBufferView.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER)
             if (SourceBufferView.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER)
             {
             {
-                if (this.Normalized) result.AddDataError(this, "Normalized", Validation.ErrorCodes.ACCESSOR_NORMALIZED_INVALID);
-                if (this.Dimensions != DimensionType.SCALAR) result.AddDataError(this, "Dimensions", Validation.ErrorCodes.VALUE_MULTIPLE_OF, Encoding.ByteLength() * Dimensions.DimCount(), Encoding.ByteLength());
-                if (this.Encoding != EncodingType.UNSIGNED_BYTE && this.Encoding != EncodingType.UNSIGNED_INT && this.Encoding != EncodingType.UNSIGNED_SHORT) result.AddDataError(this, "Encoding", Validation.ErrorCodes.VALUE_MULTIPLE_OF, Encoding.ByteLength() * Dimensions.DimCount(), Encoding.ByteLength());
+                if (this.Normalized) result.AddDataError("Normalized", Validation.ErrorCodes.ACCESSOR_NORMALIZED_INVALID);
+                if (this.Dimensions != DimensionType.SCALAR) result.AddDataError("Dimensions", Validation.ErrorCodes.VALUE_MULTIPLE_OF, Encoding.ByteLength() * Dimensions.DimCount(), Encoding.ByteLength());
+                if (this.Encoding != EncodingType.UNSIGNED_BYTE && this.Encoding != EncodingType.UNSIGNED_INT && this.Encoding != EncodingType.UNSIGNED_SHORT) result.AddDataError("Encoding", Validation.ErrorCodes.VALUE_MULTIPLE_OF, Encoding.ByteLength() * Dimensions.DimCount(), Encoding.ByteLength());
 
 
                 // if this.Encoding != EncodingType.UNSIGNED_BYTE > warning, no longer valid.
                 // if this.Encoding != EncodingType.UNSIGNED_BYTE > warning, no longer valid.
             }
             }
@@ -371,16 +378,18 @@ namespace SharpGLTF.Schema2
 
 
         internal void ValidateBounds(Validation.ValidationContext result)
         internal void ValidateBounds(Validation.ValidationContext result)
         {
         {
+            result = result.GetContext(this);
+
             if (_min.Count == 0 && _max.Count == 0) return;
             if (_min.Count == 0 && _max.Count == 0) return;
 
 
             var dimensions = this.Dimensions.DimCount();
             var dimensions = this.Dimensions.DimCount();
 
 
-            if (_min.Count != dimensions) { result.AddError(this, $"min bounds length mismatch; expected {dimensions} but found {_min.Count}"); return; }
-            if (_max.Count != dimensions) { result.AddError(this, $"max bounds length mismatch; expected {dimensions} but found {_max.Count}"); return; }
+            // if (_min.Count != dimensions) { result.AddError($"min bounds length mismatch; expected {dimensions} but found {_min.Count}"); return; }
+            // if (_max.Count != dimensions) { result.AddError($"max bounds length mismatch; expected {dimensions} but found {_max.Count}"); return; }
 
 
             for (int i = 0; i < _min.Count; ++i)
             for (int i = 0; i < _min.Count; ++i)
             {
             {
-                if (_min[i] > _max[i]) result.AddError(this, $"min[{i}] is larger than max[{i}]");
+                // if (_min[i] > _max[i]) result.AddError(this, $"min[{i}] is larger than max[{i}]");
             }
             }
 
 
             if (this.Encoding != EncodingType.FLOAT) return;
             if (this.Encoding != EncodingType.FLOAT) return;
@@ -399,48 +408,29 @@ namespace SharpGLTF.Schema2
                 {
                 {
                     var v = current[j];
                     var v = current[j];
 
 
-                    if (!v._IsFinite()) result.AddError(this, $"Item[{j}][{i}] is not a finite number: {v}");
+                    // if (!v._IsFinite()) result.AddError(this, $"Item[{j}][{i}] is not a finite number: {v}");
 
 
                     var min = minimum[j];
                     var min = minimum[j];
                     var max = maximum[j];
                     var max = maximum[j];
 
 
-                    if (v < min || v > max) result.AddError(this, $"Item[{j}][{i}] is out of bounds. {min} <= {v} <= {max}");
+                    // if (v < min || v > max) result.AddError(this, $"Item[{j}][{i}] is out of bounds. {min} <= {v} <= {max}");
                 }
                 }
             }
             }
         }
         }
 
 
         internal void ValidateIndices(Validation.ValidationContext result, uint vertexCount, PrimitiveType drawingType)
         internal void ValidateIndices(Validation.ValidationContext result, uint vertexCount, PrimitiveType drawingType)
         {
         {
-            // if (SourceBufferView.DeviceBufferTarget == BufferMode.ARRAY_BUFFER) // error, this must be a ELEMENT_ARRAY_BUFFER
+            result = result.GetContext(this);
 
 
-            if (this.Normalized) result.AddDataError(this, Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Normalized, false);
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ELEMENT_ARRAY_BUFFER);
+
+            if (this.Normalized) result.AddDataError(Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Normalized, false);
 
 
             if (Encoding != EncodingType.UNSIGNED_BYTE &&
             if (Encoding != EncodingType.UNSIGNED_BYTE &&
                 Encoding != EncodingType.UNSIGNED_SHORT &&
                 Encoding != EncodingType.UNSIGNED_SHORT &&
-                Encoding != EncodingType.UNSIGNED_INT) result.AddDataError(this, Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Encoding, EncodingType.UNSIGNED_BYTE, EncodingType.UNSIGNED_SHORT, EncodingType.UNSIGNED_INT);
-
-            if (Dimensions != DimensionType.SCALAR) result.AddDataError(this, Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Dimensions, DimensionType.SCALAR);
+                Encoding != EncodingType.UNSIGNED_INT) result.AddDataError(Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Encoding, EncodingType.UNSIGNED_BYTE, EncodingType.UNSIGNED_SHORT, EncodingType.UNSIGNED_INT);
 
 
-            switch (drawingType)
-            {
-                case PrimitiveType.LINE_LOOP:
-                case PrimitiveType.LINE_STRIP:
-                    if (this.Count < 2) result.AddError(this, $"Indices count {this.Count} is less than 2");
-                    break;
-
-                case PrimitiveType.TRIANGLE_FAN:
-                case PrimitiveType.TRIANGLE_STRIP:
-                    if (this.Count < 3) result.AddError(this, $"Indices count {this.Count} is less than 3");
-                    break;
-
-                case PrimitiveType.LINES:
-                    if (!this.Count.IsMultipleOf(2)) result.AddError(this, $"Indices count {this.Count} incompatible with Primitive.{drawingType}");
-                    break;
-
-                case PrimitiveType.TRIANGLES:
-                    if (!this.Count.IsMultipleOf(3)) result.AddError(this, $"Indices count {this.Count} incompatible with Primitive.{drawingType}");
-                    break;
-            }
+            if (Dimensions != DimensionType.SCALAR) result.AddDataError(Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT, this.Dimensions, DimensionType.SCALAR);
 
 
             uint restart_value = 0xff;
             uint restart_value = 0xff;
             if (this.Encoding == EncodingType.UNSIGNED_SHORT) restart_value = 0xffff;
             if (this.Encoding == EncodingType.UNSIGNED_SHORT) restart_value = 0xffff;
@@ -450,74 +440,111 @@ namespace SharpGLTF.Schema2
 
 
             for (int i = 0; i < indices.Count; ++i)
             for (int i = 0; i < indices.Count; ++i)
             {
             {
-                result.CheckVertexIndex(this, i, indices[i], vertexCount, restart_value);
+                result.CheckVertexIndex(i, indices[i], vertexCount, restart_value);
             }
             }
         }
         }
 
 
         internal void ValidatePositions(Validation.ValidationContext result)
         internal void ValidatePositions(Validation.ValidationContext result)
         {
         {
+            result = result.GetContext(this);
+
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ARRAY_BUFFER);
+
             var positions = this.AsVector3Array();
             var positions = this.AsVector3Array();
 
 
             for (int i = 0; i < positions.Count; ++i)
             for (int i = 0; i < positions.Count; ++i)
             {
             {
                 var pos = positions[i];
                 var pos = positions[i];
-                result.CheckDataIsFinite(this, i, pos);
+                result.CheckDataIsFinite(i, pos);
             }
             }
         }
         }
 
 
         internal void ValidateNormals(Validation.ValidationContext result)
         internal void ValidateNormals(Validation.ValidationContext result)
         {
         {
+            result = result.GetContext(this);
+
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ARRAY_BUFFER);
+
             var normals = this.AsVector3Array();
             var normals = this.AsVector3Array();
 
 
             for (int i = 0; i < normals.Count; ++i)
             for (int i = 0; i < normals.Count; ++i)
             {
             {
                 var nrm = normals[i];
                 var nrm = normals[i];
-                result.CheckDataIsFinite(this, i, nrm);
-                result.CheckDataIsUnitLength(this, i, nrm);
+                result.CheckDataIsFinite(i, nrm);
+                result.CheckDataIsUnitLength(i, nrm);
             }
             }
         }
         }
 
 
         internal void ValidateTangents(Validation.ValidationContext result)
         internal void ValidateTangents(Validation.ValidationContext result)
         {
         {
+            result = result.GetContext(this);
+
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ARRAY_BUFFER);
+
             var tangents = this.AsVector4Array();
             var tangents = this.AsVector4Array();
 
 
             for (int i = 0; i < tangents.Count; ++i)
             for (int i = 0; i < tangents.Count; ++i)
             {
             {
                 var tgt = tangents[i];
                 var tgt = tangents[i];
 
 
-                result.CheckDataIsFinite(this, i, tgt);
-                result.CheckDataIsUnitLength(this, i, new Vector3(tgt.X, tgt.Y, tgt.Z));
-                result.CheckDataIsValidSign(this, i, tgt.W);
+                result.CheckDataIsFinite(i, tgt);
+                result.CheckDataIsUnitLength(i, new Vector3(tgt.X, tgt.Y, tgt.Z));
+                result.CheckDataIsValidSign(i, tgt.W);
             }
             }
         }
         }
 
 
         internal void ValidateJoints(Validation.ValidationContext result, int jwset, int jointsCount)
         internal void ValidateJoints(Validation.ValidationContext result, int jwset, int jointsCount)
         {
         {
+            result = result.GetContext(this);
+
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ARRAY_BUFFER);
+
             var joints = this.AsVector4Array();
             var joints = this.AsVector4Array();
 
 
             for (int i = 0; i < joints.Count; ++i)
             for (int i = 0; i < joints.Count; ++i)
             {
             {
                 var jjjj = joints[i];
                 var jjjj = joints[i];
-                result.CheckDataIsFinite(this, i, jjjj);
-                result.CheckDataIsInRange(this, i, jjjj, 0, jointsCount-1);
+                result.CheckDataIsFinite(i, jjjj);
+                result.CheckDataIsInRange(i, jjjj, 0, jointsCount-1);
             }
             }
         }
         }
 
 
         internal void ValidateWeights(Validation.ValidationContext result, int jwset)
         internal void ValidateWeights(Validation.ValidationContext result, int jwset)
         {
         {
+            result = result.GetContext(this);
+
+            SourceBufferView.ValidateBufferUsage(result, BufferMode.ARRAY_BUFFER);
+
             var weights = this.AsVector4Array();
             var weights = this.AsVector4Array();
 
 
             for (int i = 0; i < weights.Count; ++i)
             for (int i = 0; i < weights.Count; ++i)
             {
             {
                 var wwww = weights[i];
                 var wwww = weights[i];
-                result.CheckDataIsFinite(this, i, wwww);
-                result.CheckDataIsInRange(this, i, wwww, 0, 1);
+                result.CheckDataIsFinite(i, wwww);
+                result.CheckDataIsInRange(i, wwww, 0, 1);
 
 
                 // theoretically, the sum of all the weights should give 1, ASSUMING there's only one weight set.
                 // theoretically, the sum of all the weights should give 1, ASSUMING there's only one weight set.
                 // but in practice, that seems not to be true.
                 // but in practice, that seems not to be true.
             }
             }
         }
         }
 
 
+        internal void ValidateMatrices(Validation.ValidationContext result)
+        {
+            result = result.GetContext(this);
+
+            // if (SourceBufferView.DeviceBufferTarget != null)  
+
+            var matrices = this.AsMatrix4x4Array();
+
+            for (int i = 0; i < matrices.Count; ++i)
+            {
+                var m = matrices[i];
+
+                if (Matrix4x4.Invert(m, out Matrix4x4 r)) continue;
+                result.AddDataError(Validation.ErrorCodes.ACCESSOR_INDECOMPOSABLE_MATRIX, i);
+            }
+        }
+
         #endregion
         #endregion
     }
     }
 
 

+ 20 - 12
src/SharpGLTF.Core/Schema2/gltf.Asset.cs

@@ -67,18 +67,6 @@ namespace SharpGLTF.Schema2
 
 
         #region API
         #region API
 
 
-        internal override void Validate(Validation.ValidationContext result)
-        {
-            base.Validate(result);
-
-            if (string.IsNullOrWhiteSpace(_version)) result.AddError(this, "version number is missing");
-
-            if (Version < MINVERSION) result.AddError(this, $"Minimum supported version is {MINVERSION} but found:{MinVersion}");
-            if (MinVersion > MAXVERSION) result.AddError(this, $"Maximum supported version is {MAXVERSION} but found:{MinVersion}");
-
-            if (MinVersion > Version) result.AddSemanticWarning(this, Validation.ErrorCodes.ASSET_MIN_VERSION_GREATER_THAN_VERSION, MinVersion, Version);
-        }
-
         private string _GetExtraInfo(string key)
         private string _GetExtraInfo(string key)
         {
         {
             if (this.Extras is IReadOnlyDictionary<string, Object> dict)
             if (this.Extras is IReadOnlyDictionary<string, Object> dict)
@@ -98,5 +86,25 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         #endregion
         #endregion
+
+        #region Validation
+
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            if (!Version.TryParse(_version, out Version ver))
+            {
+                result.AddSemanticError(Validation.ErrorCodes.UNKNOWN_ASSET_MAJOR_VERSION, _version);
+                return;
+            }
+
+            // if (Version < MINVERSION) result.AddError($"Minimum supported version is {MINVERSION} but found:{MinVersion}");
+            // if (MinVersion > MAXVERSION) result.AddError( $"Maximum supported version is {MAXVERSION} but found:{MinVersion}");
+
+            if (MinVersion > Version) result.AddSemanticWarning(Validation.WarnCodes.ASSET_MIN_VERSION_GREATER_THAN_VERSION, MinVersion, Version);
+        }
+
+        #endregion
     }
     }
 }
 }

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

@@ -172,7 +172,7 @@ namespace SharpGLTF.Schema2
 
 
             var sbbuilder = new _StaticBufferBuilder(0);
             var sbbuilder = new _StaticBufferBuilder(0);
 
 
-            foreach (var bv in views) bv._ConvertToStaticBuffer(sbbuilder);
+            foreach (var bv in views) bv._IsolateBufferMemory(sbbuilder);
 
 
             this._buffers.Clear();
             this._buffers.Clear();
 
 

+ 24 - 8
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -96,7 +96,7 @@ namespace SharpGLTF.Schema2
                 .Where(accessor => accessor._LogicalBufferViewIndex == idx);
                 .Where(accessor => accessor._LogicalBufferViewIndex == idx);
         }
         }
 
 
-        internal void _ConvertToStaticBuffer(_StaticBufferBuilder targetBuffer)
+        internal void _IsolateBufferMemory(_StaticBufferBuilder targetBuffer)
         {
         {
             // retrieve old buffer
             // retrieve old buffer
             var srcBuf = this.LogicalParent.LogicalBuffers[this._buffer].Content;
             var srcBuf = this.LogicalParent.LogicalBuffers[this._buffer].Content;
@@ -126,16 +126,32 @@ namespace SharpGLTF.Schema2
 
 
         #endregion
         #endregion
 
 
-        #region validation
+        #region Validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
 
 
-            if (this.FindAccessors().Skip(1).Any())
-            {
-                if (!_byteStride.HasValue) result.AddLinkError(this, Validation.ErrorCodes.MESH_PRIMITIVE_ACCESSOR_WITHOUT_BYTESTRIDE);
-            }
+            result.CheckReferenceIndex(nameof(Buffer), _buffer, this.LogicalParent.LogicalBuffers);
+        }
+
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            result.CheckMultipleOf(nameof(ByteStride), ByteStride, 4);
+
+            // if (this.DeviceBufferTarget.HasValue && this.FindAccessors().Any(item => item.IsSparse)) result.AddError()
+        }
+
+        internal void ValidateBufferUsage(Validation.ValidationContext result, BufferMode usingMode)
+        {
+            result = result.GetContext(this);
+
+            if (!this.DeviceBufferTarget.HasValue) return;
+            if (usingMode == this.DeviceBufferTarget.Value) return;
+
+            result.AddSchemaError(nameof(DeviceBufferTarget), Validation.ErrorCodes.BUFFER_VIEW_TARGET_OVERRIDE, this.DeviceBufferTarget.Value, usingMode);
         }
         }
 
 
         #endregion
         #endregion

+ 16 - 3
src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs

@@ -119,13 +119,26 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
+
+            foreach (var ext in this.Extensions) ext.ValidateReferences(result);
+
+            if (this._extras is JsonSerializable js) js.ValidateReferences(result);
+        }
+
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            foreach (var ext in this.Extensions) ext.Validate(result);
+
+            if (this._extras is JsonSerializable js) js.Validate(result);
 
 
             if (this._extras != null)
             if (this._extras != null)
             {
             {
-                result.CheckJsonSerializable(this, "Extras", this._extras);
+                result.CheckJsonSerializable("Extras", this._extras);
             }
             }
         }
         }
 
 

+ 16 - 0
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -320,6 +320,22 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         #endregion
         #endregion
+
+        #region Validation
+
+        protected override void OnValidateReferences(Validation.ValidationContext result)
+        {
+            base.OnValidateReferences(result);
+
+            if (!String.IsNullOrWhiteSpace(_uri))
+            {
+                if (!Uri.TryCreate(_uri, UriKind.Relative, out Uri uri)) result.AddLinkError(Validation.ErrorCodes.INVALID_URI);
+            }
+
+            result.CheckReferenceIndex("BufferView", _bufferView, this.LogicalParent.LogicalBufferViews);
+        }
+
+        #endregion
     }
     }
 
 
     public partial class ModelRoot
     public partial class ModelRoot

+ 1 - 8
src/SharpGLTF.Core/Schema2/gltf.LogicalChildOfRoot.cs

@@ -38,14 +38,7 @@ namespace SharpGLTF.Schema2
         {
         {
             return items.All(item => Object.ReferenceEquals(this.LogicalParent, item.LogicalParent));
             return items.All(item => Object.ReferenceEquals(this.LogicalParent, item.LogicalParent));
         }
         }
-
-        internal override void Validate(Validation.ValidationContext result)
-        {
-            base.Validate(result);
-
-            // TODO: verify the name does not have invalid characters
-        }
-
+        
         #endregion
         #endregion
 
 
         #region API
         #region API

+ 6 - 8
src/SharpGLTF.Core/Schema2/gltf.Mesh.cs

@@ -92,21 +92,19 @@ namespace SharpGLTF.Schema2
 
 
         #region Validation
         #region Validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidate(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidate(result);
 
 
-            foreach (var p in this.Primitives)
-            {
-                p.Validate(result);
-            }
+            foreach (var p in this.Primitives) { p.Validate(result); }
 
 
             var morphTargetsCount = this.Primitives
             var morphTargetsCount = this.Primitives
                 .Select(item => item.MorphTargetsCount)
                 .Select(item => item.MorphTargetsCount)
                 .Distinct();
                 .Distinct();
 
 
-            if (morphTargetsCount.Count() != 1) result.AddSemanticError(this, Validation.ErrorCodes.MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT);
-            if (_weights.Count != 0 && morphTargetsCount.First() != _weights.Count) result.AddSemanticError(this, Validation.ErrorCodes.MESH_INVALID_WEIGHTS_COUNT, _weights.Count, morphTargetsCount.First());
+            if (morphTargetsCount.Count() != 1) result.AddSemanticError(Validation.ErrorCodes.MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT);
+
+            if (_weights.Count != 0 && morphTargetsCount.First() != _weights.Count) result.AddSemanticError(Validation.ErrorCodes.MESH_INVALID_WEIGHTS_COUNT, _weights.Count, morphTargetsCount.First());
         }
         }
 
 
         internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)
         internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)

+ 94 - 8
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -194,21 +194,107 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
+
+            var root = this.LogicalParent.LogicalParent;
+
+            result.CheckReferenceIndex("Material", _material, root.LogicalMaterials);
+            result.CheckReferenceIndex("Indices", _indices, root.LogicalAccessors);
+
+            foreach (var idx in _attributes.Values)
+            {
+                result.CheckReferenceIndex("Attributes", idx, root.LogicalAccessors);
+            }
+
+            foreach (var idx in _targets.SelectMany(item => item.Values))
+            {
+                result.CheckReferenceIndex("Targets", idx, root.LogicalAccessors);
+            }
+        }
+
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            // check vertex count
 
 
             var vertexCounts = VertexAccessors
             var vertexCounts = VertexAccessors
                 .Select(item => item.Value.Count)
                 .Select(item => item.Value.Count)
                 .Distinct();
                 .Distinct();
 
 
-            if (vertexCounts.Count() != 1) result.AddError(this, $"Vertex Accessors have mismatching vertices count.");
+            if (vertexCounts.Count() != 1)
+            {
+                result.AddLinkError(Validation.ErrorCodes.MESH_PRIMITIVE_UNEQUAL_ACCESSOR_COUNT);
+                return;
+            }
+
+            var vertexCount = vertexCounts.First();
 
 
-            if (IndexAccessor != null) IndexAccessor.ValidateIndices(result, (uint)vertexCounts.First(), DrawPrimitiveType);
+            // check indices
+
+            if (IndexAccessor != null)
+            {
+                if (IndexAccessor.SourceBufferView.ByteStride != 0) result.AddLinkError(Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_WITH_BYTESTRIDE);
+                IndexAccessor.ValidateIndices(result, (uint)vertexCount, DrawPrimitiveType);
+
+                var incompatibleMode = false;
+
+                switch (this.DrawPrimitiveType)
+                {
+                    case PrimitiveType.LINE_LOOP:
+                    case PrimitiveType.LINE_STRIP:
+                        if (IndexAccessor.Count < 2) incompatibleMode = true;
+                        break;
+
+                    case PrimitiveType.TRIANGLE_FAN:
+                    case PrimitiveType.TRIANGLE_STRIP:
+                        if (IndexAccessor.Count < 3) incompatibleMode = true;
+                        break;
+
+                    case PrimitiveType.LINES:
+                        if (!IndexAccessor.Count.IsMultipleOf(2)) incompatibleMode = true;
+                        break;
+
+                    case PrimitiveType.TRIANGLES:
+                        if (!IndexAccessor.Count.IsMultipleOf(3)) incompatibleMode = true;
+                        break;
+                }
+
+                if (incompatibleMode) result.AddLinkWarning(Validation.WarnCodes.MESH_PRIMITIVE_INCOMPATIBLE_MODE, IndexAccessor.Count, this.DrawPrimitiveType);
+            }
+
+            // check attributes
+
+            foreach (var group in this.VertexAccessors.Values.GroupBy(item => item.SourceBufferView))
+            {
+                if (group.Skip(1).Any())
+                {
+                    if (group.Key.ByteStride == 0) result.AddLinkError(Validation.ErrorCodes.MESH_PRIMITIVE_ACCESSOR_WITHOUT_BYTESTRIDE);
+                }
+            }
+
+            var positions = GetVertexAccessor("POSITION");
+            if (positions != null)
+            {
+                positions.ValidatePositions(result);
+            }
+            else
+            {
+                result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_NO_POSITION);
+            }
 
 
-            GetVertexAccessor("POSITION")?.ValidatePositions(result);
             GetVertexAccessor("NORMAL")?.ValidateNormals(result);
             GetVertexAccessor("NORMAL")?.ValidateNormals(result);
-            GetVertexAccessor("TANGENT")?.ValidateTangents(result);
+
+            var tangents = GetVertexAccessor("TANGENT");
+            if (tangents != null)
+            {
+                if (GetVertexAccessor("NORMAL") == null) result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL);
+                if (DrawPrimitiveType == PrimitiveType.POINTS) result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_TANGENT_POINTS);
+                tangents.ValidateTangents(result);
+            }
+
         }
         }
 
 
         internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)
         internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)
@@ -224,8 +310,8 @@ namespace SharpGLTF.Schema2
 
 
         private void ValidateSkinning(Validation.ValidationContext result, Accessor j, Accessor w, int jwset, int jointsCount)
         private void ValidateSkinning(Validation.ValidationContext result, Accessor j, Accessor w, int jwset, int jointsCount)
         {
         {
-            if (j == null) result.AddError(this, $"Missing JOINTS_{jwset} vertex attribute");
-            if (w == null) result.AddError(this, $"Missing WEIGHTS_{jwset} vertex attribute");
+            // if (j == null) result.AddError(this, $"Missing JOINTS_{jwset} vertex attribute");
+            // if (w == null) result.AddError(this, $"Missing WEIGHTS_{jwset} vertex attribute");
             if (j == null || w == null) return;
             if (j == null || w == null) return;
 
 
             j.ValidateJoints(result, jwset, jointsCount);
             j.ValidateJoints(result, jwset, jointsCount);

+ 27 - 14
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -364,21 +364,32 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
 
 
             // check out of range indices
             // check out of range indices
             foreach (var idx in this._children)
             foreach (var idx in this._children)
             {
             {
-                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.AddError(this, $"references invalid Node[{idx}]");
+                result.CheckReferenceIndex(nameof(VisualChildren), idx, this.LogicalParent.LogicalNodes);
             }
             }
 
 
+            result.CheckReferenceIndex(nameof(Mesh), _mesh, this.LogicalParent.LogicalMeshes);
+            result.CheckReferenceIndex(nameof(Skin), _skin, this.LogicalParent.LogicalSkins);
+            result.CheckReferenceIndex(nameof(Camera), _camera, this.LogicalParent.LogicalCameras);
+        }
+
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            var thisIndex = this.LogicalIndex;
+
             // check duplicated indices
             // check duplicated indices
-            if (this._children.Distinct().Count() != this._children.Count) result.AddError(this, "has duplicated node references");
+            if (this._children.Distinct().Count() != this._children.Count) result.AddSchemaError(nameof(VisualChildren), Validation.ErrorCodes.DUPLICATE_ELEMENTS);
 
 
             // check self references
             // check self references
-            if (this._children.Contains(this.LogicalIndex)) result.AddError(this, "has self references");
+            if (this._children.Contains(thisIndex)) result.AddLinkError(Validation.ErrorCodes.NODE_LOOP);
 
 
             // check circular references
             // check circular references
             var p = this;
             var p = this;
@@ -386,28 +397,30 @@ namespace SharpGLTF.Schema2
             {
             {
                 p = p.VisualParent;
                 p = p.VisualParent;
                 if (p == null) break;
                 if (p == null) break;
-                if (p.LogicalIndex == this.LogicalIndex)
+                if (p.LogicalIndex == thisIndex)
                 {
                 {
-                    result.AddError(this, "has a circular reference");
+                    result.AddLinkError(Validation.ErrorCodes.NODE_LOOP);
                     break;
                     break;
                 }
                 }
             }
             }
 
 
-            // check Transforms (out or range, NaN, etc)
-
-            // check morph weights
-
-            if (this._skin != null)
+            if (_skin.HasValue)
             {
             {
-                if (this._mesh == null)
+                if (!_mesh.HasValue)
                 {
                 {
-                    result.AddError(this, "Found a Skin, but Mesh is missing");
+                    result.AddLinkError(Validation.ErrorCodes.NODE_SKIN_WITH_NON_SKINNED_MESH);
                 }
                 }
                 else
                 else
                 {
                 {
                     this.Mesh.ValidateSkinning(result, this.Skin.JointsCount);
                     this.Mesh.ValidateSkinning(result, this.Skin.JointsCount);
                 }
                 }
             }
             }
+
+            // TODO:
+
+            // check Transforms (out or range, NaN, etc)
+
+            // check morph weights
         }
         }
 
 
         #endregion
         #endregion

+ 27 - 6
src/SharpGLTF.Core/Schema2/gltf.Root.cs

@@ -144,29 +144,50 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
+        {
+            base.OnValidateReferences(result);
+
+            result.CheckReferenceIndex(nameof(DefaultScene), _scene, this.LogicalScenes);
+
+            foreach (var b in _bufferViews) b.ValidateReferences(result);
+
+            foreach (var t in _textures) t.ValidateReferences(result);
+            foreach (var m in _materials) m.ValidateReferences(result);
+
+            foreach (var a in _accessors) a.ValidateReferences(result);
+            foreach (var m in _meshes) m.ValidateReferences(result);
+            foreach (var s in _skins) s.ValidateReferences(result);
+
+            foreach (var s in _scenes) s.ValidateReferences(result);
+            foreach (var n in _nodes) n.ValidateReferences(result);
+            foreach (var a in _animations) a.ValidateReferences(result);
+        }
+
+        protected override void OnValidate(Validation.ValidationContext result)
         {
         {
             // 1st check version number
             // 1st check version number
 
 
-            if (Asset == null) result.AddError(this, "missing Asset object, can't check glTF version"); // fix: create a default Asset
+            if (Asset == null) result.AddSchemaError("Asset","Missing");
             else Asset.Validate(result);
             else Asset.Validate(result);
 
 
-            if (result.HasErrors) return;
+            if (result.Result.HasErrors) return;
 
 
             // 2nd check incompatible extensions
             // 2nd check incompatible extensions
 
 
             foreach (var iex in this.IncompatibleExtensions)
             foreach (var iex in this.IncompatibleExtensions)
             {
             {
-                result.UnsupportedExtensionError(this, iex);
+                result.UnsupportedExtensionError(iex);
             }
             }
 
 
-            if (result.HasErrors) return;
+            if (result.Result.HasErrors) return;
 
 
             // 3rd check base class
             // 3rd check base class
 
 
-            base.Validate(result);
+            base.OnValidate(result);
 
 
             // 4th check contents
             // 4th check contents
+
             foreach (var s in _scenes) s.Validate(result);
             foreach (var s in _scenes) s.Validate(result);
             foreach (var n in _nodes) n.Validate(result);
             foreach (var n in _nodes) n.Validate(result);
 
 

+ 5 - 5
src/SharpGLTF.Core/Schema2/gltf.Scene.cs

@@ -59,20 +59,20 @@ namespace SharpGLTF.Schema2
 
 
         #endregion
         #endregion
 
 
-        #region validation
+        #region Validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
 
 
             // check out of range indices
             // check out of range indices
             foreach (var idx in this._nodes)
             foreach (var idx in this._nodes)
             {
             {
-                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.AddError(this, $"references invalid Node[{idx}]");
+                result.CheckReferenceIndex( nameof(VisualChildren), idx, this.LogicalParent.LogicalNodes);
             }
             }
 
 
             // check duplicated indices
             // check duplicated indices
-            if (this._nodes.Distinct().Count() != this._nodes.Count) result.AddError(this, "has duplicated node references");
+            // if (this._nodes.Distinct().Count() != this._nodes.Count) result.AddError(this, "has duplicated node references");
         }
         }
 
 
         #endregion
         #endregion

+ 9 - 1
src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

@@ -210,6 +210,12 @@ namespace SharpGLTF.Schema2
                     throw new Validation.SchemaException(root, rex);
                     throw new Validation.SchemaException(root, rex);
                 }
                 }
 
 
+                // reference checking is mandatory and cannot be skipped
+                var result = new Validation.ValidationResult();
+                root.ValidateReferences(result.GetContext(root));
+                var ex = result.Errors.FirstOrDefault();
+                if (ex != null) throw ex;
+
                 foreach (var buffer in root._buffers)
                 foreach (var buffer in root._buffers)
                 {
                 {
                     buffer._ResolveUri(settings.FileReader);
                     buffer._ResolveUri(settings.FileReader);
@@ -222,7 +228,9 @@ namespace SharpGLTF.Schema2
 
 
                 if (!settings.SkipValidation)
                 if (!settings.SkipValidation)
                 {
                 {
-                    var ex = root.Validate().FirstOrDefault();
+                    result = new Validation.ValidationResult();
+                    root.Validate(result.GetContext(root));
+                    ex = result.Errors.FirstOrDefault();
                     if (ex != null) throw ex;
                     if (ex != null) throw ex;
                 }
                 }
 
 

+ 25 - 16
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -238,37 +238,46 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
-        internal override void Validate(Validation.ValidationContext result)
+        protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
-            base.Validate(result);
+            base.OnValidateReferences(result);
 
 
-            var ibxAccessor = GetInverseBindMatricesAccessor();
+            result.CheckReferenceIndex("Skeleton", _skeleton, this.LogicalParent.LogicalNodes);
 
 
-            var logicalNodeCount = this.LogicalParent.LogicalNodes.Count;
+            result.CheckIsDefined("InverseBindMatrices", _inverseBindMatrices);
+            result.CheckReferenceIndex("InverseBindMatrices", _inverseBindMatrices, this.LogicalParent.LogicalAccessors);
 
 
-            if (_joints == null || _joints.Count < _jointsMinItems)
+            if (_joints.Count < _jointsMinItems)
             {
             {
-                result.AddError(this, $"Expected at least {_jointsMinItems} Joints");
+
+                // result.AddError(this, $"Expected at least {_jointsMinItems} Joints");
                 return;
                 return;
             }
             }
 
 
             for (int i = 0; i < _joints.Count; ++i)
             for (int i = 0; i < _joints.Count; ++i)
             {
             {
                 var jidx = _joints[i];
                 var jidx = _joints[i];
-                if (jidx < 0 || jidx >= logicalNodeCount) result.AddError(this, $"Joint {i} Node index reference is out of bounds.");
 
 
-                if (ibxAccessor != null)
-                {
-                    var ibxform = ibxAccessor.AsMatrix4x4Array()[i];
-                    try { ibxform.Inverse(); }
-                    catch (ArgumentException) { result.AddError(this, $"Joint {i} has invalid bind matrix"); }
-                }
+                result.CheckReferenceIndex("Joints", _joints[i], this.LogicalParent.LogicalNodes);
             }
             }
+        }
 
 
-            if (_skeleton.HasValue)
+        protected override void OnValidate(Validation.ValidationContext result)
+        {
+            base.OnValidate(result);
+
+            var ibxAccessor = GetInverseBindMatricesAccessor();
+
+            if (ibxAccessor != null)
             {
             {
-                if (_skeleton.Value < 0 || _skeleton.Value >= logicalNodeCount) result.AddError(this, $"Skeleton Node index reference is out of bounds.");
+                if (_joints.Count != ibxAccessor.Count) result.AddLinkError(Validation.ErrorCodes.INVALID_IBM_ACCESSOR_COUNT, _joints.Count, ibxAccessor.Count);
 
 
+                if (ibxAccessor.Dimensions != DimensionType.MAT4) result.AddLinkError(Validation.ErrorCodes.SKIN_IBM_INVALID_FORMAT, ibxAccessor.Dimensions);
+                else ibxAccessor.ValidateMatrices(result);
+            }
+
+            if (_skeleton.HasValue)
+            {
                 var skeletonNode = this.Skeleton;
                 var skeletonNode = this.Skeleton;
 
 
                 for (int i = 0; i < this.JointsCount; ++i)
                 for (int i = 0; i < this.JointsCount; ++i)
@@ -278,7 +287,7 @@ namespace SharpGLTF.Schema2
                     if (skeletonNode == jointNode) continue;
                     if (skeletonNode == jointNode) continue;
                     if (skeletonNode._ContainsVisualNode(jointNode, true)) continue;
                     if (skeletonNode._ContainsVisualNode(jointNode, true)) continue;
 
 
-                    result.AddError(this, $"Skeleton node is not a common ancestor of Joint[{i}]");
+                    // result.AddError(this, $"Skeleton node is not a common ancestor of Joint[{i}]");
                 }
                 }
             }
             }
         }
         }

+ 20 - 7
src/SharpGLTF.Core/Schema2/gltf.Textures.cs

@@ -6,6 +6,7 @@ using System.Text;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
+    using SharpGLTF.Validation;
     using TEXLERP = TextureInterpolationFilter;
     using TEXLERP = TextureInterpolationFilter;
     using TEXMIPMAP = TextureMipMapFilter;
     using TEXMIPMAP = TextureMipMapFilter;
     using TEXWRAP = TextureWrapMode;
     using TEXWRAP = TextureWrapMode;
@@ -57,21 +58,21 @@ namespace SharpGLTF.Schema2
 
 
         private Image _GetPrimaryImage()
         private Image _GetPrimaryImage()
         {
         {
-            var ddstex = this.GetExtension<TextureDDS>();
-            if (ddstex != null && ddstex.Image != null) return ddstex.Image;
+            var ddsimg = this.GetExtension<TextureDDS>()?.Image;
+            if (ddsimg != null) return ddsimg;
 
 
-            var wbptex = this.GetExtension<TextureWEBP>();
-            if (wbptex != null && wbptex.Image != null) return wbptex.Image;
+            var wbpimg = this.GetExtension<TextureWEBP>()?.Image;
+            if (wbpimg != null) return wbpimg;
 
 
             return _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
             return _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
         }
         }
 
 
         private Image _GetFallbackImage()
         private Image _GetFallbackImage()
         {
         {
-            var ddstex = this.GetExtension<TextureDDS>();
-            if (ddstex == null) return null;
+            var img = _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
 
 
-            return _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
+            // if the default image is provided by _GetPrimaryImage() we don't need to return anything here.
+            return _GetPrimaryImage() == img ? null : img;
         }
         }
 
 
         public void ClearImages()
         public void ClearImages()
@@ -148,6 +149,18 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         #endregion
         #endregion
+
+        #region Validation
+
+        protected override void OnValidateReferences(ValidationContext result)
+        {
+            base.OnValidateReferences(result);
+
+            result.CheckReferenceIndex("Source", _source, this.LogicalParent.LogicalImages);
+            result.CheckReferenceIndex("Sampler", _sampler, this.LogicalParent.LogicalTextureSamplers);
+        }
+
+        #endregion
     }
     }
 
 
     partial class TextureDDS
     partial class TextureDDS

+ 0 - 42
src/SharpGLTF.Core/Validation/ErrorCodes.Data.cs

@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static partial class ErrorCodes
-    {
-        // INFORMATION
-
-        public const string ACCESSOR_INDEX_TRIANGLE_DEGENERATE = "Indices accessor contains {0} degenerate triangles.";
-        public const string DATA_URI_GLB = "Data URI is used in GLB container.";
-        public const string IMAGE_NPOT_DIMENSIONS = "Image has non-power-of-two dimensions: {0}x{1}.";
-
-        // WARNINGS
-
-        public const string BUFFER_GLB_CHUNK_TOO_BIG = "GLB-stored BIN chunk contains {0} extra padding byte (s).";
-        public const string IMAGE_UNRECOGNIZED_FORMAT = "Image format not recognized.";
-
-        // ERRORS
-
-        public const string ACCESSOR_ANIMATION_INPUT_NEGATIVE = "Animation input accessor element at index {0} is negative: {1}.";
-        public const string ACCESSOR_ANIMATION_INPUT_NON_INCREASING = "Animation input accessor element at index {0} is less than or equal to previous: {1} <= {2}.";
-        public const string ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND = "Accessor contains {0} element(s) greater than declared maximum value {1}.";
-        public const string ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND = "Accessor contains {0} element(s) less than declared minimum value {1}.";
-        public const string ACCESSOR_INDECOMPOSABLE_MATRIX = "Matrix element at index {0} is not decomposable to TRS.";
-        public const string ACCESSOR_INDEX_OOB = "Indices accessor element at index {0} has vertex index {1} that exceeds number of available vertices {2}.";
-        public const string ACCESSOR_INDEX_PRIMITIVE_RESTART = "Indices accessor contains primitive restart value ({0}) at index {1}.";
-        public const string ACCESSOR_INVALID_FLOAT = "Accessor element at index {0} is NaN or Infinity.";
-        public const string ACCESSOR_INVALID_SIGN = "Accessor element at index {0} has invalid w component: {1}. Must be 1.0 or -1.0.";
-        public const string ACCESSOR_MAX_MISMATCH = "Declared maximum value for this component ({0}) does not match actual maximum({1}).";
-        public const string ACCESSOR_MIN_MISMATCH = "Declared minimum value for this component({0}) does not match actual minimum({1}).";
-        public const string ACCESSOR_NON_UNIT = "Accessor element at index {0} is not of unit length: {1}.";
-        public const string ACCESSOR_SPARSE_INDEX_OOB = "Accessor sparse indices element at index {0} is greater than or equal to the number of accessor elements: {1} >= {2}.";
-        public const string ACCESSOR_SPARSE_INDICES_NON_INCREASING = "Accessor sparse indices element at index {0} is less than or equal to previous: {1} <= {2}.";
-        public const string BUFFER_EMBEDDED_BYTELENGTH_MISMATCH = "Actual data length {0} is not equal to the declared buffer byteLength {1}.";
-        public const string BUFFER_EXTERNAL_BYTELENGTH_MISMATCH = "Actual data length {0} is less than the declared buffer byteLength {1}.";
-        public const string IMAGE_DATA_INVALID = "Image data is invalid. {0}";
-        public const string IMAGE_MIME_TYPE_INVALID = "Recognized image format '{0}' does not match declared image format '{1}'.";
-        public const string IMAGE_UNEXPECTED_EOS = "Unexpected end of image stream.";
-    }
-}

+ 0 - 60
src/SharpGLTF.Core/Validation/ErrorCodes.Link.cs

@@ -1,60 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static partial class ErrorCodes
-    {
-        // INFORMATION
-
-        public const string MESH_PRIMITIVE_UNUSED_TEXCOORD = "Material does not use texture coordinates sets with indices ('%a', '%b', '%c').";
-        public const string UNUSED_OBJECT = "This object may be unused.";
-
-        // WARNINGS
-
-        public const string MESH_PRIMITIVE_INCOMPATIBLE_MODE = "Number of vertices or indices({0}) is not compatible with used drawing mode('{1}').";
-        public const string NODE_SKINNED_MESH_WITHOUT_SKIN = "Node uses skinned mesh, but has no skin defined.";
-        public const string UNSUPPORTED_EXTENSION = "Cannot validate an extension as it is not supported by the validator: '{0}'.";
-
-        // ERRORS
-
-        public const string ACCESSOR_SMALL_BYTESTRIDE = "Referenced bufferView's byteStride value {0} is less than accessor element's length {1}.";
-        public const string ACCESSOR_TOO_LONG = "Accessor(offset: {0}, length: {1}) does not fit referenced bufferView[% 3] length %4.";
-        public const string ACCESSOR_TOTAL_OFFSET_ALIGNMENT = "Accessor's total byteOffset {0} isn't a multiple of componentType length {1}.";
-        public const string ACCESSOR_USAGE_OVERRIDE = "Override of previously set accessor usage.Initial: '{0}', new: '{1}'.";
-        public const string ANIMATION_CHANNEL_TARGET_NODE_MATRIX = "Animation channel cannot target TRS properties of node with defined matrix.";
-        public const string ANIMATION_CHANNEL_TARGET_NODE_WEIGHTS_NO_MORPHS = "Animation channel cannot target WEIGHTS when mesh does not have morph targets.";
-        public const string ANIMATION_DUPLICATE_TARGETS = "Animation channel has the same target as channel {0}. 	Error";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_INVALID_FORMAT = "Invalid Animation sampler input accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_TOO_FEW_ELEMENTS = "Animation sampler output accessor with '{0}' interpolation must have at least {1} elements.Got {2}.";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for animation input accessor.";
-        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_COUNT = "Animation sampler output accessor of count {0} expected.Found {1}.";
-        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_FORMAT = "Invalid animation sampler output accessor format '{0}' for path '{2}'. Must be one of('%a', '%b', '%c').";
-        public const string ANIMATION_SAMPLER_OUTPUT_INTERPOLATION = "The same output accessor cannot be used both for spline and linear data.";
-        public const string BUFFER_MISSING_GLB_DATA = "Buffer refers to an unresolved GLB binary chunk.";
-        public const string BUFFER_NON_FIRST_GLB = "Buffer referring to GLB binary chunk must be the first.";
-        public const string BUFFER_VIEW_TARGET_OVERRIDE = "Override of previously set bufferView target or usage. Initial: '{0}', new: '{1}'.";
-        public const string BUFFER_VIEW_TOO_LONG = "BufferView does not fit buffer({0}) byteLength({1}).";
-        public const string INVALID_IBM_ACCESSOR_COUNT = "Accessor of count {0} expected.Found {1}.";
-        public const string MESH_PRIMITIVE_ACCESSOR_UNALIGNED = "Vertex attribute data must be aligned to 4-byte boundaries.";
-        public const string MESH_PRIMITIVE_ACCESSOR_WITHOUT_BYTESTRIDE = "bufferView.byteStride must be defined when two or more accessors use the same buffer view.";
-        public const string MESH_PRIMITIVE_ATTRIBUTES_ACCESSOR_INVALID_FORMAT = "Invalid accessor format '{0}' for this attribute semantic. Must be one of ('%a', '%b', '%c').";
-        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT = "Invalid indices accessor format '{0}'. Must be one of('{1}', '{2}', '{3}').";
-        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_WITH_BYTESTRIDE = "bufferView.byteStride must not be defined for indices accessor.";
-        public const string MESH_PRIMITIVE_MORPH_TARGET_INVALID_ATTRIBUTE_COUNT = "Base accessor has different count.";
-        public const string MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR = "No base accessor for this attribute semantic.";
-        public const string MESH_PRIMITIVE_POSITION_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for POSITION attribute accessor.";
-        public const string MESH_PRIMITIVE_TOO_FEW_TEXCOORDS = "Material is incompatible with mesh primitive: Texture binding '{0}' needs 'TEXCOORD_{1}' attribute.";
-        public const string MESH_PRIMITIVE_UNEQUAL_ACCESSOR_COUNT = "All accessors of the same primitive must have the same count.";
-        public const string NODE_LOOP = "Node is a part of a node loop.";
-        public const string NODE_PARENT_OVERRIDE = "Value overrides parent of node {0}.";
-        public const string NODE_SKIN_WITH_NON_SKINNED_MESH = "Node has skin defined, but mesh has no joints data.";
-        public const string NODE_WEIGHTS_INVALID = "The length of weights array ({0}) does not match the number of morph targets({1}).";
-        public const string SCENE_NON_ROOT_NODE = "Node {0} is not a root node.";
-        public const string SKIN_IBM_INVALID_FORMAT = "Invalid IBM accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
-        public const string UNDECLARED_EXTENSION = "Extension was not declared in extensionsUsed.";
-        public const string UNEXPECTED_EXTENSION_OBJECT = "Unexpected location for this extension.";
-        public const string UNRESOLVED_REFERENCE = "Unresolved reference: {0}.";
-    }
-}

+ 0 - 31
src/SharpGLTF.Core/Validation/ErrorCodes.Schema.cs

@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static partial class ErrorCodes
-    {
-        // WARNINGS
-
-        public const string UNEXPECTED_PROPERTY = "Unexpected property.";
-        public const string VALUE_NOT_IN_LIST = "Invalid value '{0}'. Valid values are ('%a', '%b', '%c').";
-
-        // ERRRORS
-
-        public const string ARRAY_LENGTH_NOT_IN_LIST = "Invalid array length {0}. Valid lengths are: ('%a', '%b', '%c').";
-        public const string ARRAY_TYPE_MISMATCH = "Type mismatch. Array element '{0}' is not a '{1}'.";
-        public const string DUPLICATE_ELEMENTS = "Duplicate element.";
-        public const string EMPTY_ENTITY = "Entity cannot be empty.";
-        public const string INVALID_INDEX = "Index must be a non-negative integer.";
-        public const string INVALID_JSON = "Invalid JSON data.Parser output: {0}";
-        public const string INVALID_URI = "Invalid URI '{0}'. Parser output: {1}";
-        public const string ONE_OF_MISMATCH = "Exactly one of ('{0}', '{1}', '{2}', '{3}') properties must be defined.";
-        public const string PATTERN_MISMATCH = "Value '{0}' does not match regexp pattern '{1}'.";
-        public const string TYPE_MISMATCH = "Type mismatch. Property value '{0}' is not a '{1}'.";
-        public const string UNDEFINED_PROPERTY = "Property '{0}' must be defined.";
-        public const string UNSATISFIED_DEPENDENCY = "Dependency failed. '{0}' must be defined.";
-        public const string VALUE_MULTIPLE_OF = "Value {0} is not a multiple of {1}.";
-        public const string VALUE_NOT_IN_RANGE = "Value {0} is out of range.";
-    }
-}

+ 0 - 58
src/SharpGLTF.Core/Validation/ErrorCodes.Semantic.cs

@@ -1,58 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static partial class ErrorCodes
-    {
-        #pragma warning disable SA1310 // Field names should not contain underscore
-
-        // INFORMATION
-
-        public const string EXTRA_PROPERTY = "This property should not be defined as it will not be used.";
-        public const string NODE_EMPTY = "Empty node encountered.";
-        public const string NODE_MATRIX_DEFAULT = "Do not specify default transform matrix.";
-        public const string NON_OBJECT_EXTRAS = "Prefer JSON Objects for extras.";
-
-        // WARNINGS
-
-        public const string ASSET_MIN_VERSION_GREATER_THAN_VERSION = "Asset minVersion '{0}' is greater than version '{1}'.";
-        public const string CAMERA_XMAG_YMAG_ZERO = "xmag and ymag must not be zero.";
-        public const string INTEGER_WRITTEN_AS_FLOAT = "Integer value is written with fractional part: {0}.";
-        public const string MATERIAL_ALPHA_CUTOFF_INVALID_MODE = "Alpha cutoff is supported only for 'MASK' alpha mode.";
-        public const string MESH_PRIMITIVES_UNEQUAL_JOINTS_COUNT = "All primitives should contain the same number of 'JOINTS' and 'WEIGHTS' attribute sets.";
-        public const string MESH_PRIMITIVE_TANGENT_POINTS = "TANGENT attribute defined for POINTS rendering mode.";
-        public const string MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL = "TANGENT attribute without NORMAL found.";
-        public const string MULTIPLE_EXTENSIONS = "Multiple extensions are defined for this object: ('%a', '%b', '%c').";
-        public const string MESH_PRIMITIVE_NO_POSITION = "No POSITION attribute found.";
-        public const string NON_RELATIVE_URI = "Non-relative URI found: {0}.";
-        public const string UNKNOWN_ASSET_MINOR_VERSION = "Unknown glTF minor asset version: {0}.";
-        public const string UNRESERVED_EXTENSION_PREFIX = "Extension uses unreserved extension prefix '{0}'.";
-
-        // ERRORS
-
-        public const string ACCESSOR_MATRIX_ALIGNMENT = "Matrix accessors must be aligned to 4-byte boundaries.";
-        public const string ACCESSOR_NORMALIZED_INVALID = "Only (u)byte and (u)short accessors can be normalized.";
-        public const string ACCESSOR_OFFSET_ALIGNMENT = "Offset {0} is not a multiple of componentType length {1}.";
-        public const string ACCESSOR_SPARSE_COUNT_OUT_OF_RANGE = "Sparse accessor overrides more elements ({0}) than the base accessor contains({1}).";
-        public const string BUFFER_DATA_URI_MIME_TYPE_INVALID = "Buffer's Data URI MIME-Type must be 'application/octet-stream' or 'application/gltf-buffer'. Found '{0}' instead.";
-        public const string BUFFER_VIEW_INVALID_BYTE_STRIDE = "Only buffer views with raw vertex data can have byteStride.";
-        public const string BUFFER_VIEW_TOO_BIG_BYTE_STRIDE = "Buffer view's byteStride ({0}) is smaller than byteLength ({1}).";
-        public const string CAMERA_ZFAR_LEQUAL_ZNEAR = "zfar must be greater than znear.";
-        public const string INVALID_GL_VALUE = "Invalid value {0} for GL type '{1}'.";
-        public const string KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES = "outerConeAngle ({1}) is less than or equal to innerConeAngle({0}).";
-        public const string MESH_INVALID_WEIGHTS_COUNT = "The length of weights array ({0}) does not match the number of morph targets({1}).";
-        public const string MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT = "All primitives must have the same number of morph targets.";
-        public const string MESH_PRIMITIVE_INDEXED_SEMANTIC_CONTINUITY = "Indices for indexed attribute semantic '{0}' must start with 0 and be continuous.Total expected indices: {1}, total provided indices: {2}.";
-        public const string MESH_PRIMITIVE_INVALID_ATTRIBUTE = "Invalid attribute name.";
-        public const string MESH_PRIMITIVE_JOINTS_WEIGHTS_MISMATCH = "Number of JOINTS attribute semantics must match number of WEIGHTS.";
-        public const string NODE_MATRIX_NON_TRS = "Matrix must be decomposable to TRS.";
-        public const string NODE_MATRIX_TRS = "A node can have either a matrix or any combination of translation/rotation/scale (TRS) properties.";
-        public const string ROTATION_NON_UNIT = "Rotation quaternion must be normalized.";
-        public const string UNKNOWN_ASSET_MAJOR_VERSION = "Unknown glTF major asset version: {0}.";
-        public const string UNUSED_EXTENSION_REQUIRED = "Unused extension '{0}' cannot be required.";
-
-        #pragma warning restore SA1310 // Field names should not contain underscore
-    }
-}

+ 119 - 0
src/SharpGLTF.Core/Validation/ErrorCodes.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    static class ErrorCodes
+    {
+        #region DATA
+
+        public const string ACCESSOR_ANIMATION_INPUT_NEGATIVE = "Animation input accessor element at index {0} is negative: {1}.";
+        public const string ACCESSOR_ANIMATION_INPUT_NON_INCREASING = "Animation input accessor element at index {0} is less than or equal to previous: {1} <= {2}.";
+        public const string ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND = "Accessor contains {0} element(s) greater than declared maximum value {1}.";
+        public const string ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND = "Accessor contains {0} element(s) less than declared minimum value {1}.";
+        public const string ACCESSOR_INDECOMPOSABLE_MATRIX = "Matrix element at index {0} is not decomposable to TRS.";
+        public const string ACCESSOR_INDEX_OOB = "Indices accessor element at index {0} has vertex index {1} that exceeds number of available vertices {2}.";
+        public const string ACCESSOR_INDEX_PRIMITIVE_RESTART = "Indices accessor contains primitive restart value ({0}) at index {1}.";
+        public const string ACCESSOR_INVALID_FLOAT = "Accessor element at index {0} is NaN or Infinity.";
+        public const string ACCESSOR_INVALID_SIGN = "Accessor element at index {0} has invalid w component: {1}. Must be 1.0 or -1.0.";
+        public const string ACCESSOR_MAX_MISMATCH = "Declared maximum value for this component ({0}) does not match actual maximum({1}).";
+        public const string ACCESSOR_MIN_MISMATCH = "Declared minimum value for this component({0}) does not match actual minimum({1}).";
+        public const string ACCESSOR_NON_UNIT = "Accessor element at index {0} is not of unit length: {1}.";
+        public const string ACCESSOR_SPARSE_INDEX_OOB = "Accessor sparse indices element at index {0} is greater than or equal to the number of accessor elements: {1} >= {2}.";
+        public const string ACCESSOR_SPARSE_INDICES_NON_INCREASING = "Accessor sparse indices element at index {0} is less than or equal to previous: {1} <= {2}.";
+        public const string BUFFER_EMBEDDED_BYTELENGTH_MISMATCH = "Actual data length {0} is not equal to the declared buffer byteLength {1}.";
+        public const string BUFFER_EXTERNAL_BYTELENGTH_MISMATCH = "Actual data length {0} is less than the declared buffer byteLength {1}.";
+        public const string IMAGE_DATA_INVALID = "Image data is invalid. {0}";
+        public const string IMAGE_MIME_TYPE_INVALID = "Recognized image format '{0}' does not match declared image format '{1}'.";
+        public const string IMAGE_UNEXPECTED_EOS = "Unexpected end of image stream.";
+
+        #endregion
+
+        #region LINK
+
+        public const string ACCESSOR_SMALL_BYTESTRIDE = "Referenced bufferView's byteStride value {0} is less than accessor element's length {1}.";
+        public const string ACCESSOR_TOO_LONG = "Accessor(offset: {0}, length: {1}) does not fit referenced bufferView[% 3] length %4.";
+        public const string ACCESSOR_TOTAL_OFFSET_ALIGNMENT = "Accessor's total byteOffset {0} isn't a multiple of componentType length {1}.";
+        public const string ACCESSOR_USAGE_OVERRIDE = "Override of previously set accessor usage.Initial: '{0}', new: '{1}'.";
+        public const string ANIMATION_CHANNEL_TARGET_NODE_MATRIX = "Animation channel cannot target TRS properties of node with defined matrix.";
+        public const string ANIMATION_CHANNEL_TARGET_NODE_WEIGHTS_NO_MORPHS = "Animation channel cannot target WEIGHTS when mesh does not have morph targets.";
+        public const string ANIMATION_DUPLICATE_TARGETS = "Animation channel has the same target as channel {0}. 	Error";
+        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_INVALID_FORMAT = "Invalid Animation sampler input accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
+        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_TOO_FEW_ELEMENTS = "Animation sampler output accessor with '{0}' interpolation must have at least {1} elements.Got {2}.";
+        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for animation input accessor.";
+        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_COUNT = "Animation sampler output accessor of count {0} expected.Found {1}.";
+        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_FORMAT = "Invalid animation sampler output accessor format '{0}' for path '{2}'. Must be one of('%a', '%b', '%c').";
+        public const string ANIMATION_SAMPLER_OUTPUT_INTERPOLATION = "The same output accessor cannot be used both for spline and linear data.";
+        public const string BUFFER_MISSING_GLB_DATA = "Buffer refers to an unresolved GLB binary chunk.";
+        public const string BUFFER_NON_FIRST_GLB = "Buffer referring to GLB binary chunk must be the first.";
+        public const string BUFFER_VIEW_TARGET_OVERRIDE = "Override of previously set bufferView target or usage. Initial: '{0}', new: '{1}'.";
+        public const string BUFFER_VIEW_TOO_LONG = "BufferView does not fit buffer({0}) byteLength({1}).";
+        public const string INVALID_IBM_ACCESSOR_COUNT = "Accessor of count {0} expected.Found {1}.";
+        public const string MESH_PRIMITIVE_ACCESSOR_UNALIGNED = "Vertex attribute data must be aligned to 4-byte boundaries.";
+        public const string MESH_PRIMITIVE_ACCESSOR_WITHOUT_BYTESTRIDE = "bufferView.byteStride must be defined when two or more accessors use the same buffer view.";
+        public const string MESH_PRIMITIVE_ATTRIBUTES_ACCESSOR_INVALID_FORMAT = "Invalid accessor format '{0}' for this attribute semantic. Must be one of ('%a', '%b', '%c').";
+        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT = "Invalid indices accessor format '{0}'. Must be one of('{1}', '{2}', '{3}').";
+        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_WITH_BYTESTRIDE = "bufferView.byteStride must not be defined for indices accessor.";
+        public const string MESH_PRIMITIVE_MORPH_TARGET_INVALID_ATTRIBUTE_COUNT = "Base accessor has different count.";
+        public const string MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR = "No base accessor for this attribute semantic.";
+        public const string MESH_PRIMITIVE_POSITION_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for POSITION attribute accessor.";
+        public const string MESH_PRIMITIVE_TOO_FEW_TEXCOORDS = "Material is incompatible with mesh primitive: Texture binding '{0}' needs 'TEXCOORD_{1}' attribute.";
+        public const string MESH_PRIMITIVE_UNEQUAL_ACCESSOR_COUNT = "All accessors of the same primitive must have the same count.";
+        public const string NODE_LOOP = "Node is a part of a node loop.";
+        public const string NODE_PARENT_OVERRIDE = "Value overrides parent of node {0}.";
+        public const string NODE_SKIN_WITH_NON_SKINNED_MESH = "Node has skin defined, but mesh has no joints data.";
+        public const string NODE_WEIGHTS_INVALID = "The length of weights array ({0}) does not match the number of morph targets({1}).";
+        public const string SCENE_NON_ROOT_NODE = "Node {0} is not a root node.";
+        public const string SKIN_IBM_INVALID_FORMAT = "Invalid IBM accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
+        public const string UNDECLARED_EXTENSION = "Extension was not declared in extensionsUsed.";
+        public const string UNEXPECTED_EXTENSION_OBJECT = "Unexpected location for this extension.";
+        public const string UNRESOLVED_REFERENCE = "Unresolved reference: {0}.";
+
+        #endregion
+
+        #region SCHEMA
+
+        public const string ARRAY_LENGTH_NOT_IN_LIST = "Invalid array length {0}. Valid lengths are: ('%a', '%b', '%c').";
+        public const string ARRAY_TYPE_MISMATCH = "Type mismatch. Array element '{0}' is not a '{1}'.";
+        public const string DUPLICATE_ELEMENTS = "Duplicate element.";
+        public const string EMPTY_ENTITY = "Entity cannot be empty.";
+        public const string INVALID_INDEX = "Index must be a non-negative integer.";
+        public const string INVALID_JSON = "Invalid JSON data.Parser output: {0}";
+        public const string INVALID_URI = "Invalid URI '{0}'. Parser output: {1}";
+        public const string ONE_OF_MISMATCH = "Exactly one of ('{0}', '{1}', '{2}', '{3}') properties must be defined.";
+        public const string PATTERN_MISMATCH = "Value '{0}' does not match regexp pattern '{1}'.";
+        public const string TYPE_MISMATCH = "Type mismatch. Property value '{0}' is not a '{1}'.";
+        public const string UNDEFINED_PROPERTY = "Property '{0}' must be defined.";
+        public const string UNSATISFIED_DEPENDENCY = "Dependency failed. '{0}' must be defined.";
+        public const string VALUE_MULTIPLE_OF = "Value {0} is not a multiple of {1}.";
+        public const string VALUE_NOT_IN_RANGE = "Value {0} is out of range.";
+
+        #endregion
+
+        #region SEMANTIC
+
+        public const string ACCESSOR_MATRIX_ALIGNMENT = "Matrix accessors must be aligned to 4-byte boundaries.";
+        public const string ACCESSOR_NORMALIZED_INVALID = "Only (u)byte and (u)short accessors can be normalized.";
+        public const string ACCESSOR_OFFSET_ALIGNMENT = "Offset {0} is not a multiple of componentType length {1}.";
+        public const string ACCESSOR_SPARSE_COUNT_OUT_OF_RANGE = "Sparse accessor overrides more elements ({0}) than the base accessor contains({1}).";
+        public const string BUFFER_DATA_URI_MIME_TYPE_INVALID = "Buffer's Data URI MIME-Type must be 'application/octet-stream' or 'application/gltf-buffer'. Found '{0}' instead.";
+        public const string BUFFER_VIEW_INVALID_BYTE_STRIDE = "Only buffer views with raw vertex data can have byteStride.";
+        public const string BUFFER_VIEW_TOO_BIG_BYTE_STRIDE = "Buffer view's byteStride ({0}) is smaller than byteLength ({1}).";
+        public const string CAMERA_ZFAR_LEQUAL_ZNEAR = "zfar must be greater than znear.";
+        public const string INVALID_GL_VALUE = "Invalid value {0} for GL type '{1}'.";
+        public const string KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES = "outerConeAngle ({1}) is less than or equal to innerConeAngle({0}).";
+        public const string MESH_INVALID_WEIGHTS_COUNT = "The length of weights array ({0}) does not match the number of morph targets({1}).";
+        public const string MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT = "All primitives must have the same number of morph targets.";
+        public const string MESH_PRIMITIVE_INDEXED_SEMANTIC_CONTINUITY = "Indices for indexed attribute semantic '{0}' must start with 0 and be continuous.Total expected indices: {1}, total provided indices: {2}.";
+        public const string MESH_PRIMITIVE_INVALID_ATTRIBUTE = "Invalid attribute name.";
+        public const string MESH_PRIMITIVE_JOINTS_WEIGHTS_MISMATCH = "Number of JOINTS attribute semantics must match number of WEIGHTS.";
+        public const string NODE_MATRIX_NON_TRS = "Matrix must be decomposable to TRS.";
+        public const string NODE_MATRIX_TRS = "A node can have either a matrix or any combination of translation/rotation/scale (TRS) properties.";
+        public const string ROTATION_NON_UNIT = "Rotation quaternion must be normalized.";
+        public const string UNKNOWN_ASSET_MAJOR_VERSION = "Unknown glTF major asset version: {0}.";
+        public const string UNUSED_EXTENSION_REQUIRED = "Unused extension '{0}' cannot be required.";
+
+        #endregion
+    }
+}

+ 33 - 0
src/SharpGLTF.Core/Validation/InfoCodes.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    static class InfoCodes
+    {
+        #region DATA
+
+        public const string ACCESSOR_INDEX_TRIANGLE_DEGENERATE = "Indices accessor contains {0} degenerate triangles.";
+        public const string DATA_URI_GLB = "Data URI is used in GLB container.";
+        public const string IMAGE_NPOT_DIMENSIONS = "Image has non-power-of-two dimensions: {0}x{1}.";
+
+        #endregion
+
+        #region LINK
+
+        public const string MESH_PRIMITIVE_UNUSED_TEXCOORD = "Material does not use texture coordinates sets with indices ('%a', '%b', '%c').";
+        public const string UNUSED_OBJECT = "This object may be unused.";
+
+        #endregion
+
+        #region SEMANTIC
+
+        public const string EXTRA_PROPERTY = "This property should not be defined as it will not be used.";
+        public const string NODE_EMPTY = "Empty node encountered.";
+        public const string NODE_MATRIX_DEFAULT = "Do not specify default transform matrix.";
+        public const string NON_OBJECT_EXTRAS = "Prefer JSON Objects for extras.";
+
+        #endregion
+    }
+}

+ 158 - 63
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -9,149 +9,244 @@ namespace SharpGLTF.Validation
     /// <summary>
     /// <summary>
     /// Utility class used in the process of model validation.
     /// Utility class used in the process of model validation.
     /// </summary>
     /// </summary>
-    class ValidationContext
+    [System.Diagnostics.DebuggerStepThrough]
+    public struct ValidationContext
     {
     {
-        // we should try to align validation errors to these issues:
-        // https://github.com/KhronosGroup/glTF-Validator/blob/master/ISSUES.md
+        #region constructor
 
 
-        #region data
+        public ValidationContext(ValidationResult result, TARGET target)
+        {
+            _Result = result;
+            _Target = target;
+        }
 
 
-        private readonly List<Exception> _Errors = new List<Exception>();
-        private readonly List<Exception> _Warnings = new List<Exception>();
+        #endregion
 
 
-        public IEnumerable<Exception> Errors => _Errors;
+        #region data
 
 
-        public bool HasErrors => _Errors.Count > 0;
+        private readonly TARGET _Target;
+        private readonly ValidationResult _Result;
 
 
         #endregion
         #endregion
 
 
-        #region errors
+        #region properties
 
 
-        private void _AddError(Exception ex)
-        {
-            throw ex;
-            _Errors.Add(ex);
-        }
+        public ValidationResult Result => _Result;
 
 
-        public void AddError(TARGET target, String message)
-        {
-            _AddError(new ModelException(target, message));
-        }
+        #endregion
+
+        #region API
+
+        public ValidationContext GetContext(TARGET target) { return _Result.GetContext(target); }
 
 
         #endregion
         #endregion
 
 
         #region schema errors
         #region schema errors
 
 
-        public void CheckIsDefined<T>(TARGET target, string property, T? value)
+        public bool CheckIsDefined<T>(string property, T? value)
             where T : struct
             where T : struct
         {
         {
-            if (!value.HasValue) AddSchemaError(target, property, ErrorCodes.UNDEFINED_PROPERTY);
+            if (value.HasValue) return true;
+
+            AddSchemaError(property, ErrorCodes.UNDEFINED_PROPERTY);
+
+            return false;
         }
         }
 
 
-        public void CheckIndex(TARGET target, string property, int? index, int maxExclusive)
+        public void CheckIndex(string property, int? index, int maxExclusive)
         {
         {
-            if (index.HasValue)
-            {
-                if (index.Value < 0) AddSchemaError(target, property, ErrorCodes.INVALID_INDEX, index);
-                if (index.Value >= maxExclusive) AddSchemaError(target, property, ErrorCodes.VALUE_NOT_IN_RANGE, index);
-            }
+            if (!index.HasValue) return;
+
+            if (index.Value < 0) AddSchemaError(property, ErrorCodes.INVALID_INDEX, index);
+            if (index.Value >= maxExclusive) AddSchemaError(property, ErrorCodes.VALUE_NOT_IN_RANGE, index);
         }
         }
 
 
-        public void CheckMultipleOf(TARGET target, string property,  int value, int multiple)
+        public void CheckMultipleOf(string property,  int value, int multiple)
         {
         {
-            if ((value % multiple) != 0) AddSchemaError(target, property, ErrorCodes.VALUE_MULTIPLE_OF, value, multiple);
+            if ((value % multiple) == 0) return;
+
+            AddSchemaError(property, ErrorCodes.VALUE_MULTIPLE_OF, value, multiple);
         }
         }
 
 
-        public void CheckJsonSerializable(TARGET target, string property, Object value)
+        public void CheckJsonSerializable(string property, Object value)
         {
         {
-            if (!IO.JsonUtils.IsSerializable(value)) AddSchemaError(target, property, ErrorCodes.INVALID_JSON, string.Empty);
+            if (IO.JsonUtils.IsSerializable(value)) return;
+
+            AddSchemaError(property, ErrorCodes.INVALID_JSON, string.Empty);
         }
         }
 
 
-        public void AddSchemaError(TARGET target, string property, String format, params object[] args)
+        public void AddSchemaError(string property, String format, params object[] args)
         {
         {
-            _AddError(new SchemaException(target, property + " " + String.Format(format, args)));
+            var message = property + " " + String.Format(format, args);
+
+            var ex = new SchemaException(_Target, message);
+
+            _Result.AddError(ex);
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region semantic errors
         #region semantic errors
 
 
-        public void AddSemanticError(TARGET target, String message)
+        public void AddSemanticError(String message)
         {
         {
-            _Errors.Add(new SemanticException(target, message));
+            var ex = new SemanticException(_Target, message);
+
+            _Result.AddError(ex);
         }
         }
 
 
-        public void AddSemanticWarning(TARGET target, String format, params object[] args)
+        public void AddSemanticWarning(String format, params object[] args)
         {
         {
-            _Warnings.Add(new SemanticException(target, String.Format(format, args)));
+            var message = String.Format(format, args);
+
+            var ex = new SemanticException(_Target, message);
+
+            _Result.AddWarning(ex);
         }
         }
 
 
-        public void AddSemanticError(TARGET target, String format, params object[] args)
+        public void AddSemanticError(String format, params object[] args)
         {
         {
-            _AddError(new SemanticException(target, String.Format(format, args)));
+            var message = String.Format(format, args);
+
+            var ex = new SemanticException(_Target, message);
+
+            _Result.AddError(ex);
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region data errors
         #region data errors
 
 
-        public void CheckVertexIndex(Schema2.Accessor target, int index, UInt32 vertexIndex, UInt32 vertexCount, UInt32 vertexRestart)
+        public void CheckVertexIndex(int index, UInt32 vertexIndex, UInt32 vertexCount, UInt32 vertexRestart)
         {
         {
-            if (vertexIndex == vertexRestart) AddDataError(target, ErrorCodes.ACCESSOR_INDEX_PRIMITIVE_RESTART, index, vertexIndex);
-            else if (vertexIndex >= vertexCount) AddDataError(target, ErrorCodes.ACCESSOR_INDEX_OOB, index, vertexIndex, vertexCount);
+            if (vertexIndex == vertexRestart) AddDataError(ErrorCodes.ACCESSOR_INDEX_PRIMITIVE_RESTART, index, vertexIndex);
+            else if (vertexIndex >= vertexCount) AddDataError(ErrorCodes.ACCESSOR_INDEX_OOB, index, vertexIndex, vertexCount);
         }
         }
 
 
-        public void CheckDataIsFinite(Schema2.Accessor target, int index, System.Numerics.Vector3 v)
+        public void CheckDataIsFinite(int index, System.Numerics.Vector3 v)
         {
         {
-            if (!v._IsFinite()) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
+            if (v._IsFinite()) return;
+
+            AddDataError(ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
         }
         }
 
 
-        public void CheckDataIsFinite(Schema2.Accessor target, int index, System.Numerics.Vector4 v)
+        public void CheckDataIsFinite(int index, System.Numerics.Vector4 v)
         {
         {
-            if (!v._IsFinite()) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
+            if (v._IsFinite()) return;
+
+            AddDataError(ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
         }
         }
 
 
-        public void CheckDataIsUnitLength(Schema2.Accessor target, int index, System.Numerics.Vector3 v)
+        public void CheckDataIsUnitLength(int index, System.Numerics.Vector3 v)
         {
         {
-            if (!v.IsValidNormal()) AddDataError(target, ErrorCodes.ACCESSOR_NON_UNIT, index, v.Length());
+            if (v.IsValidNormal()) return;
+
+            AddDataError(ErrorCodes.ACCESSOR_NON_UNIT, index, v.Length());
         }
         }
 
 
-        public void CheckDataIsInRange(Schema2.Accessor target, int index, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
+        public void CheckDataIsInRange(int index, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
         {
         {
-            CheckDataIsInRange(target, index, v.X, minInclusive, maxInclusive);
-            CheckDataIsInRange(target, index, v.Y, minInclusive, maxInclusive);
-            CheckDataIsInRange(target, index, v.Z, minInclusive, maxInclusive);
-            CheckDataIsInRange(target, index, v.W, minInclusive, maxInclusive);
+            CheckDataIsInRange(index, v.X, minInclusive, maxInclusive);
+            CheckDataIsInRange(index, v.Y, minInclusive, maxInclusive);
+            CheckDataIsInRange(index, v.Z, minInclusive, maxInclusive);
+            CheckDataIsInRange(index, v.W, minInclusive, maxInclusive);
         }
         }
 
 
-        public void CheckDataIsInRange(Schema2.Accessor target, int index, float v, float minInclusive, float maxInclusive)
+        public void CheckDataIsInRange(int index, float v, float minInclusive, float maxInclusive)
         {
         {
-            if (v < minInclusive) AddDataError(target, ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND, index, v);
-            if (v > maxInclusive) AddDataError(target, ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND, index, v);
+            if (v < minInclusive) AddDataError(ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND, index, v);
+            if (v > maxInclusive) AddDataError(ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND, index, v);
         }
         }
 
 
-        public void CheckDataIsValidSign(Schema2.Accessor target, int index, float w)
+        public void CheckDataIsValidSign(int index, float w)
         {
         {
-            if (w != 1 && w != -1) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_SIGN, index, w);
+            if (w == 1 || w == -1) return;
+
+            AddDataError(ErrorCodes.ACCESSOR_INVALID_SIGN, index, w);
         }
         }
 
 
-        public void AddDataError(TARGET target, String format, params object[] args)
+        public void AddDataError(String format, params object[] args)
         {
         {
-            _AddError(new DataException(target, String.Format(format, args)));
+            var message = String.Format(format, args);
+
+            var ex = new DataException(_Target, message);
+
+            _Result.AddError(ex);
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region link errors
         #region link errors
 
 
-        public void UnsupportedExtensionError(TARGET target, String message)
+        public bool CheckReferenceIndex<T>(string property, int? index, IReadOnlyList<T> collection)
+        {
+            if (!index.HasValue) return true;
+            if (index.Value >= 0 && index.Value < collection.Count) return true;
+
+            AddLinkError(ErrorCodes.UNRESOLVED_REFERENCE, property);
+
+            return false;
+        }
+
+        public void UnsupportedExtensionError(String message)
+        {
+            AddLinkError(message);
+        }
+
+        public void AddLinkError(String format, params object[] args)
+        {
+            var message = String.Format(format, args);
+
+            var ex = new LinkException(_Target, message);
+
+            _Result.AddError(ex);
+        }
+
+        public void AddLinkWarning(String format, params object[] args)
+        {
+            var message = String.Format(format, args);
+
+            var ex = new LinkException(_Target, message);
+
+            _Result.AddWarning(ex);
+        }
+
+        #endregion
+    }
+
+    public sealed class ValidationResult
+    {
+        #region data
+
+        private readonly List<Exception> _Errors = new List<Exception>();
+        private readonly List<Exception> _Warnings = new List<Exception>();
+
+        #endregion
+
+        #region properties
+
+        public IEnumerable<Exception> Errors => _Errors;
+
+        public bool HasErrors => _Errors.Count > 0;
+
+        #endregion
+
+        #region API
+
+        public ValidationContext GetContext(TARGET target) { return new ValidationContext(this, target); }
+
+        public void AddWarning(ModelException ex)
         {
         {
-            AddLinkError(target, message);
+            _Warnings.Add(ex);
         }
         }
 
 
-        public void AddLinkError(TARGET target, String format, params object[] args)
+        public void AddError(ModelException ex)
         {
         {
-            _AddError(new LinkException(target, String.Format(format, args)));
+            #if DEBUG
+            throw ex;
+            #else
+            _Errors.Add(ex);
+            #endif
         }
         }
 
 
         #endregion
         #endregion

+ 48 - 0
src/SharpGLTF.Core/Validation/WarnCodes.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    static class WarnCodes
+    {
+        #region DATA
+
+        public const string BUFFER_GLB_CHUNK_TOO_BIG = "GLB-stored BIN chunk contains {0} extra padding byte (s).";
+        public const string IMAGE_UNRECOGNIZED_FORMAT = "Image format not recognized.";
+
+        #endregion
+
+        #region LINK
+
+        public const string MESH_PRIMITIVE_INCOMPATIBLE_MODE = "Number of vertices or indices({0}) is not compatible with used drawing mode('{1}').";
+        public const string NODE_SKINNED_MESH_WITHOUT_SKIN = "Node uses skinned mesh, but has no skin defined.";
+        public const string UNSUPPORTED_EXTENSION = "Cannot validate an extension as it is not supported by the validator: '{0}'.";
+
+        #endregion
+
+        #region SCHEMA
+
+        public const string UNEXPECTED_PROPERTY = "Unexpected property.";
+        public const string VALUE_NOT_IN_LIST = "Invalid value '{0}'. Valid values are ('%a', '%b', '%c').";
+
+        #endregion
+
+        #region SEMANTIC
+
+        public const string ASSET_MIN_VERSION_GREATER_THAN_VERSION = "Asset minVersion '{0}' is greater than version '{1}'.";
+        public const string CAMERA_XMAG_YMAG_ZERO = "xmag and ymag must not be zero.";
+        public const string INTEGER_WRITTEN_AS_FLOAT = "Integer value is written with fractional part: {0}.";
+        public const string MATERIAL_ALPHA_CUTOFF_INVALID_MODE = "Alpha cutoff is supported only for 'MASK' alpha mode.";
+        public const string MESH_PRIMITIVES_UNEQUAL_JOINTS_COUNT = "All primitives should contain the same number of 'JOINTS' and 'WEIGHTS' attribute sets.";
+        public const string MESH_PRIMITIVE_TANGENT_POINTS = "TANGENT attribute defined for POINTS rendering mode.";
+        public const string MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL = "TANGENT attribute without NORMAL found.";
+        public const string MULTIPLE_EXTENSIONS = "Multiple extensions are defined for this object: ('%a', '%b', '%c').";
+        public const string MESH_PRIMITIVE_NO_POSITION = "No POSITION attribute found.";
+        public const string NON_RELATIVE_URI = "Non-relative URI found: {0}.";
+        public const string UNKNOWN_ASSET_MINOR_VERSION = "Unknown glTF minor asset version: {0}.";
+        public const string UNRESERVED_EXTENSION_PREFIX = "Extension uses unreserved extension prefix '{0}'.";
+
+        #endregion
+    }
+}