Browse Source

More tests and validations.
Refactored Validation system to allow future improvements.

Vicente Penades 6 years ago
parent
commit
52b95d71d0
35 changed files with 787 additions and 692 deletions
  1. 1 1
      src/Shared/_Extensions.cs
  2. 3 3
      src/SharpGLTF.Core/IO/JsonSerializable.cs
  3. 0 83
      src/SharpGLTF.Core/IO/ModelException.cs
  4. 4 3
      src/SharpGLTF.Core/Memory/ColorArray.cs
  5. 9 20
      src/SharpGLTF.Core/Memory/EncodedArrays.cs
  6. 28 15
      src/SharpGLTF.Core/Memory/FloatingArrays.cs
  7. 6 3
      src/SharpGLTF.Core/Memory/IntegerArrays.cs
  8. 48 88
      src/SharpGLTF.Core/Memory/MemoryAccessor.cs
  9. 4 8
      src/SharpGLTF.Core/Memory/SparseArrays.cs
  10. 110 26
      src/SharpGLTF.Core/Schema2/gltf.Accessors.cs
  11. 3 3
      src/SharpGLTF.Core/Schema2/gltf.Animations.cs
  12. 6 6
      src/SharpGLTF.Core/Schema2/gltf.Asset.cs
  13. 0 4
      src/SharpGLTF.Core/Schema2/gltf.Buffer.cs
  14. 0 4
      src/SharpGLTF.Core/Schema2/gltf.BufferView.cs
  15. 2 2
      src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs
  16. 1 1
      src/SharpGLTF.Core/Schema2/gltf.LogicalChildOfRoot.cs
  17. 0 2
      src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs
  18. 7 4
      src/SharpGLTF.Core/Schema2/gltf.Mesh.cs
  19. 27 9
      src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs
  20. 331 0
      src/SharpGLTF.Core/Schema2/gltf.Node.cs
  21. 5 5
      src/SharpGLTF.Core/Schema2/gltf.Root.cs
  22. 3 316
      src/SharpGLTF.Core/Schema2/gltf.Scene.cs
  23. 8 12
      src/SharpGLTF.Core/Schema2/gltf.Skin.cs
  24. 0 2
      src/SharpGLTF.Core/Schema2/gltf.TextureInfo.cs
  25. 0 1
      src/SharpGLTF.Core/Schema2/khr.lights.cs
  26. 82 0
      src/SharpGLTF.Core/Validation/ModelException.cs
  27. 82 0
      src/SharpGLTF.Core/Validation/ValidationContext.cs
  28. 0 1
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  29. 1 1
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs
  30. 5 5
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs
  31. 5 27
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  32. 1 1
      tests/SharpGLTF.Tests/Geometry/LoadMeshTests.cs
  33. 0 31
      tests/SharpGLTF.Tests/GltfUtils.cs
  34. 1 1
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs
  35. 4 4
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

+ 1 - 1
src/Shared/_Extensions.cs

@@ -29,7 +29,7 @@ namespace SharpGLTF
 
         internal static int WordPadded(this int length)
         {
-            var padding = (length & 3);
+            var padding = length & 3;
 
             return length + (padding == 0 ? 0 : 4 - padding);
         }

+ 3 - 3
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -14,14 +14,14 @@ namespace SharpGLTF.IO
 
         public IEnumerable<Exception> Validate()
         {
-            var result = new List<Exception>();
+            var result = new Validation.ValidationContext();
 
             Validate(result);
 
-            return result;
+            return result.Exceptions;
         }
 
-        internal virtual void Validate(IList<Exception> result)
+        internal virtual void Validate(Validation.ValidationContext result)
         {
         }
 

+ 0 - 83
src/SharpGLTF.Core/IO/ModelException.cs

@@ -1,83 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Text;
-
-namespace SharpGLTF.IO
-{
-    /// <summary>
-    /// Represents an exception produced by the serialization or validation of a gltf model
-    /// </summary>
-    public class ModelException : Exception
-    {
-        #region lifecycle
-
-        internal ModelException(JsonSerializable target, String message)
-            : base(_CreateBaseMessage(target, message))
-        {
-            _Target = target;
-        }
-
-        internal ModelException(JsonSerializable target, String message, Action fix, String fixDesc)
-            : base(message)
-        {
-            _Target = target;
-            _ProposedFix = fix;
-            _ProposedFixDescription = fixDesc;
-        }
-
-        private static string _CreateBaseMessage(JsonSerializable target, String message)
-        {
-            if (target == null) return message;
-
-            var targetTypeInfo = target.GetType().GetTypeInfo();
-
-            var logicalIndexProp = targetTypeInfo.GetProperty("LogicalIndex");
-
-            var logicalIndex = logicalIndexProp != null ? (int)logicalIndexProp.GetValue(target) : -1;
-
-            if (logicalIndex >= 0) return $"{targetTypeInfo.Name}[{logicalIndex}] {message}";
-
-            return $"{targetTypeInfo.Name} {message}";
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly JsonSerializable _Target;
-
-        private String _ProposedFixDescription;
-        private Action _ProposedFix;
-
-        #endregion
-
-        #region properties
-
-        public bool HasFix => _ProposedFix != null;
-        public String FixDescription => _ProposedFixDescription;
-        public void ApplyFix() { _ProposedFix.Invoke(); }
-
-        #endregion
-    }
-
-    /// <summary>
-    /// Represents an exception produced when a required extension is missing
-    /// </summary>
-    public class UnsupportedExtensionException : ModelException
-    {
-        #region lifecycle
-
-        internal UnsupportedExtensionException(JsonSerializable target, String message)
-            : base(target, message)
-        {
-        }
-
-        internal UnsupportedExtensionException(JsonSerializable target, String message, Action fix, String fixDesc)
-            : base(target, message, fix, fixDesc)
-        {
-        }
-
-        #endregion
-    }
-}

+ 4 - 3
src/SharpGLTF.Core/Memory/ColorArray.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Text;
 using System.Numerics;
 using System.Collections;
 using System.Linq;
@@ -76,9 +75,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Vector4 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Vector4 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(Vector4[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(Vector4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+
+        public void Fill(IEnumerable<Vector4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
 

+ 9 - 20
src/SharpGLTF.Core/Memory/EncodedArrays.cs

@@ -1,9 +1,6 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Memory
 {
@@ -54,37 +51,29 @@ namespace SharpGLTF.Memory
 
     static class EncodedArrayUtils
     {
-        public static void FillFrom(this IList<UInt32> dst, int dstIndex, IEnumerable<Int32> src)
+        public static void _CopyTo(this IEnumerable<Int32> src, IList<UInt32> dst, int dstOffset = 0)
         {
-            using (var ator = src.GetEnumerator())
+            using (var ptr = src.GetEnumerator())
             {
-                while (dstIndex < dst.Count && ator.MoveNext())
+                while (dstOffset < dst.Count && ptr.MoveNext())
                 {
-                    dst[dstIndex++] = (UInt32)ator.Current;
+                    dst[dstOffset++] = (UInt32)ptr.Current;
                 }
             }
         }
 
-        public static void FillFrom<T>(this IList<T> dst, int dstIndex, IEnumerable<T> src)
+        public static void _CopyTo<T>(this IEnumerable<T> src, IList<T> dst, int dstOffset = 0)
         {
-            using (var ator = src.GetEnumerator())
+            using (var ptr = src.GetEnumerator())
             {
-                while (dstIndex < dst.Count && ator.MoveNext())
+                while (dstOffset < dst.Count && ptr.MoveNext())
                 {
-                    dst[dstIndex++] = ator.Current;
+                    dst[dstOffset++] = ptr.Current;
                 }
             }
         }
 
-        public static void CopyTo<T>(this IReadOnlyList<T> src, IList<T> dst, int dstOffset = 0)
-        {
-            for (int i = 0; i < src.Count; ++i)
-            {
-                dst[i + dstOffset] = src[i];
-            }
-        }
-
-        public static int FirstIndexOf<T>(this IReadOnlyList<T> src, T value)
+        public static int _FirstIndexOf<T>(this IReadOnlyList<T> src, T value)
         {
             var comparer = EqualityComparer<T>.Default;
 

+ 28 - 15
src/SharpGLTF.Core/Memory/FloatingArrays.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Text;
 using System.Numerics;
 using System.Collections;
 using System.Linq;
@@ -299,9 +298,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Single item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Single item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Single item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(Single[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(Single[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+
+        public void Fill(IEnumerable<Single> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Single>.Insert(int index, Single item) { throw new NotSupportedException(); }
 
@@ -408,9 +409,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Vector2 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Vector2 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Vector2 item) { return this._FirstIndexOf(item); }
+
+        public void CopyTo(Vector2[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
 
-        public void CopyTo(Vector2[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void Fill(IEnumerable<Vector2> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Vector2>.Insert(int index, Vector2 item) { throw new NotSupportedException(); }
 
@@ -518,9 +521,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Vector3 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Vector3 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Vector3 item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(Vector3[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(Vector3[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+
+        public void Fill(IEnumerable<Vector3> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Vector3>.Insert(int index, Vector3 item) { throw new NotSupportedException(); }
 
@@ -595,9 +600,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Vector4 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Vector4 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
+
+        public void CopyTo(Vector4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
 
-        public void CopyTo(Vector4[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void Fill(IEnumerable<Vector4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
 
@@ -672,9 +679,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Quaternion item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Quaternion item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Quaternion item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(Quaternion[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(Quaternion[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+
+        public void Fill(IEnumerable<Quaternion> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Quaternion>.Insert(int index, Quaternion item) { throw new NotSupportedException(); }
 
@@ -767,9 +776,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Matrix4x4 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Matrix4x4 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Matrix4x4 item) { return this._FirstIndexOf(item); }
+
+        public void CopyTo(Matrix4x4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
 
-        public void CopyTo(Matrix4x4[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void Fill(IEnumerable<Matrix4x4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Matrix4x4>.Insert(int index, Matrix4x4 item) { throw new NotSupportedException(); }
 
@@ -860,9 +871,11 @@ namespace SharpGLTF.Memory
 
         public bool Contains(Single[] item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(Single[] item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(Single[] item) { return this._FirstIndexOf(item); }
+
+        public void CopyTo(Single[][] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
 
-        public void CopyTo(Single[][] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void Fill(IEnumerable<Single[]> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<Single[]>.Insert(int index, Single[] item) { throw new NotSupportedException(); }
 

+ 6 - 3
src/SharpGLTF.Core/Memory/IntegerArrays.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Text;
 using System.Collections;
 using System.Linq;
 
@@ -144,9 +143,13 @@ namespace SharpGLTF.Memory
 
         public bool Contains(UInt32 item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(UInt32 item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(UInt32 item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(UInt32[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(UInt32[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+
+        public void Fill(IEnumerable<Int32> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+
+        public void Fill(IEnumerable<UInt32> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
 
         void IList<UInt32>.Insert(int index, UInt32 item) { throw new NotSupportedException(); }
 

+ 48 - 88
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Memory
 {
@@ -193,63 +192,74 @@ namespace SharpGLTF.Memory
             this._Data = default;
         }
 
-        #endregion
-
-        #region data
-
-        private MemoryAccessInfo _Attribute;
-        private ArraySegment<Byte> _Data;
-
-        #endregion
-
-        #region properties
-
-        public MemoryAccessInfo Attribute => _Attribute;
+        public static IList<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
+        {
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
-        public ArraySegment<Byte> Data => _Data;
+            return new SparseArray<Single>(bottom.AsScalarArray(), topValues.AsScalarArray(), topKeys);
+        }
 
-        #endregion
+        public static IList<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
+        {
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
-        #region API
+            return new SparseArray<Vector2>(bottom.AsVector2Array(), topValues.AsVector2Array(), topKeys);
+        }
 
-        internal void SetName(string name)
+        public static IList<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            _Attribute.Name = name;
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
+
+            return new SparseArray<Vector3>(bottom.AsVector3Array(), topValues.AsVector3Array(), topKeys);
         }
 
-        public void SetIndexDataSource(ArraySegment<Byte> data, int byteOffset, int itemsCount)
+        public static IList<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(_Attribute.IsValidIndexer, nameof(_Attribute));
-            _Data = data;
-            _Attribute.ByteOffset = byteOffset;
-            _Attribute.ItemsCount = itemsCount;
-            _Attribute.ByteStride = 0;
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
+
+            return new SparseArray<Vector4>(bottom.AsVector4Array(), topValues.AsVector4Array(), topKeys);
         }
 
-        public void SetVertexDataSource(ArraySegment<Byte> data, int byteOffset, int itemsCount, int byteStride)
+        public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
-            _Data = data;
-            _Attribute.ByteOffset = byteOffset;
-            _Attribute.ItemsCount = itemsCount;
-            _Attribute.ByteStride = byteStride;
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
+
+            return new SparseArray<Vector4>(bottom.AsColorArray(), topValues.AsColorArray(), topKeys);
         }
 
-        public void Fill(IReadOnlyList<Int32> values) { AsIntegerArray().FillFrom(0, values); }
+        #endregion
 
-        public void Fill(IReadOnlyList<UInt32> values) { values.CopyTo(AsIntegerArray()); }
+        #region data
 
-        public void Fill(IReadOnlyList<Single> values) { values.CopyTo(AsScalarArray()); }
+        private MemoryAccessInfo _Attribute;
+        private ArraySegment<Byte> _Data;
 
-        public void Fill(IReadOnlyList<Vector2> values) { values.CopyTo(AsVector2Array()); }
+        #endregion
+
+        #region properties
 
-        public void Fill(IReadOnlyList<Vector3> values) { values.CopyTo(AsVector3Array()); }
+        public MemoryAccessInfo Attribute => _Attribute;
 
-        public void Fill(IReadOnlyList<Vector4> values) { values.CopyTo(AsVector4Array()); }
+        public ArraySegment<Byte> Data => _Data;
 
-        public void Fill(IReadOnlyList<Quaternion> values) { values.CopyTo(AsQuaternionArray()); }
+        #endregion
 
-        public void Fill(IReadOnlyList<Matrix4x4> values) { values.CopyTo(AsMatrix4x4Array()); }
+        #region API
 
         public IntegerArray AsIntegerArray()
         {
@@ -307,56 +317,6 @@ namespace SharpGLTF.Memory
             return new Matrix4x4Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
-        public static IList<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Single>(bottom.AsScalarArray(), topValues.AsScalarArray(), topKeys);
-        }
-
-        public static IList<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector2>(bottom.AsVector2Array(), topValues.AsVector2Array(), topKeys);
-        }
-
-        public static IList<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector3>(bottom.AsVector3Array(), topValues.AsVector3Array(), topKeys);
-        }
-
-        public static IList<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(bottom.AsVector4Array(), topValues.AsVector4Array(), topKeys);
-        }
-
-        public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(bottom.AsColorArray(), topValues.AsColorArray(), topKeys);
-        }
-
         #endregion
     }
 }

+ 4 - 8
src/SharpGLTF.Core/Memory/SparseArrays.cs

@@ -2,7 +2,6 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
 
 namespace SharpGLTF.Memory
 {
@@ -52,15 +51,12 @@ namespace SharpGLTF.Memory
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public int Count => _BottomItems.Count;
 
-        public bool IsReadOnly => false;
+        public bool IsReadOnly => true;
 
         public T this[int index]
         {
             get => _Mapping.TryGetValue(index, out int topIndex) ? _TopItems[topIndex] : _BottomItems[index];
-            set
-            {
-                if (_Mapping.TryGetValue(index, out int topIndex)) _TopItems[topIndex] = value;
-            }
+            set => throw new NotSupportedException("Collection is read only.");
         }
 
         public IEnumerator<T> GetEnumerator() { return new EncodedArrayEnumerator<T>(this); }
@@ -69,9 +65,9 @@ namespace SharpGLTF.Memory
 
         public bool Contains(T item) { return IndexOf(item) >= 0; }
 
-        public int IndexOf(T item) { return EncodedArrayUtils.FirstIndexOf(this, item); }
+        public int IndexOf(T item) { return this._FirstIndexOf(item); }
 
-        public void CopyTo(T[] array, int arrayIndex) { EncodedArrayUtils.CopyTo(this, array, arrayIndex); }
+        public void CopyTo(T[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
 
         void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); }
 

+ 110 - 26
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -1,17 +1,11 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
     using Memory;
 
-    using EXCEPTION = IO.ModelException;
-
-    using ROOT = ModelRoot;
-
     // https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
 
     [System.Diagnostics.DebuggerDisplay("Accessor[{LogicalIndex}] BufferView[{SourceBufferView.LogicalIndex}][{ByteOffset}...] => 0 => {Dimensions}x{Encoding}x{Normalized} => [{Count}]")]
@@ -324,41 +318,41 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
-            if (!_bufferView.HasValue) { result.Add(new EXCEPTION(this, $"BufferView index missing")); return; }
-            if (_bufferView < 0 || _bufferView >= LogicalParent.LogicalBufferViews.Count) result.Add(new EXCEPTION(this, $"BufferView index out of range"));
+            if (!_bufferView.HasValue) { result.AddError(this, $"BufferView index missing"); return; }
+            if (_bufferView < 0 || _bufferView >= LogicalParent.LogicalBufferViews.Count) result.AddError(this, $"BufferView index out of range");
 
-            if (_count < 0) result.Add(new EXCEPTION(this, $"Count is out of range"));
-            if (_byteOffset < 0) result.Add(new EXCEPTION(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 (SourceBufferView.DeviceBufferTarget == BufferMode.ARRAY_BUFFER)
             {
                 var len = Encoding.ByteLength() * Dimensions.DimCount();
-                if (len > 0 && (len & 3) != 0) result.Add(new EXCEPTION(this, $"Expected length to be multiple of 4, found {len}"));
+                if (len > 0 && (len & 3) != 0) result.AddError(this, $"Expected length to be multiple of 4, found {len}");
             }
 
             if (SourceBufferView.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER)
             {
                 var len = Encoding.ByteLength() * Dimensions.DimCount();
-                if (len != 1 && len != 2 && len != 4) result.Add(new EXCEPTION(this, $"Expected length to be 1, 2 or 4, found {len}"));
+                if (len != 1 && len != 2 && len != 4) result.AddError(this, $"Expected length to be 1, 2 or 4, found {len}");
             }
         }
 
-        internal void ValidateBounds(IList<Exception> result)
+        internal void ValidateBounds(Validation.ValidationContext result)
         {
             if (_min.Count == 0 && _max.Count == 0) return;
 
             var dimensions = this.Dimensions.DimCount();
 
-            if (_min.Count != dimensions) { result.Add(new EXCEPTION(this, $"min bounds length mismatch; expected {dimensions} but found {_min.Count}")); return; }
-            if (_max.Count != dimensions) { result.Add(new EXCEPTION(this, $"max bounds length mismatch; expected {dimensions} but found {_max.Count}")); return; }
+            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; }
 
             for (int i = 0; i < _min.Count; ++i)
             {
-                if (_min[i] > _max[i]) result.Add(new EXCEPTION(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;
@@ -377,36 +371,36 @@ namespace SharpGLTF.Schema2
                 {
                     var v = current[j];
 
-                    if (!v._IsReal()) result.Add(new EXCEPTION(this, $"Item[{j}][{i}] is not a finite number: {v}"));
+                    if (!v._IsReal()) result.AddError(this, $"Item[{j}][{i}] is not a finite number: {v}");
 
                     var min = minimum[j];
                     var max = maximum[j];
 
-                    if (v < min || v > max) result.Add(new EXCEPTION(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(IList<Exception> result, uint vertexCount, PrimitiveType drawingType)
+        internal void ValidateIndices(Validation.ValidationContext result, uint vertexCount, PrimitiveType drawingType)
         {
             switch (drawingType)
             {
                 case PrimitiveType.LINE_LOOP:
                 case PrimitiveType.LINE_STRIP:
-                    if (this.Count < 2) result.Add(new EXCEPTION(this, $"Indices count {this.Count} is less than 2"));
+                    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.Add(new EXCEPTION(this, $"Indices count {this.Count} is less than 3"));
+                    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.Add(new EXCEPTION(this, $"Indices count {this.Count} incompatible with Primitive.{drawingType}"));
+                    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.Add(new EXCEPTION(this, $"Indices count {this.Count} incompatible with Primitive.{drawingType}"));
+                    if (!this.Count.IsMultipleOf(3)) result.AddError(this, $"Indices count {this.Count} incompatible with Primitive.{drawingType}");
                     break;
             }
 
@@ -420,8 +414,98 @@ namespace SharpGLTF.Schema2
             {
                 var idx = indices[i];
 
-                if (idx == restart_value) result.Add(new EXCEPTION(this, $"PRIMITIVE RESTART value {restart_value} found at index {i}"));
-                else if (idx >= vertexCount) result.Add(new EXCEPTION(this, $"Invalid vertex index {idx} found at index {i}"));
+                if (idx == restart_value) result.AddError(this, $"Index[{i}] value {idx} is invalid PRIMITIVE RESTART value");
+                else if (idx >= vertexCount) result.AddError(this, $"Index[{i}] value {idx} is out of range {0}-{vertexCount}");
+            }
+        }
+
+        internal void ValidatePositions(Validation.ValidationContext result)
+        {
+            var positions = this.AsVector3Array();
+
+            for (int i = 0; i < positions.Count; ++i)
+            {
+                var pos = positions[i];
+
+                if (!pos._IsReal()) result.AddError(this, $"POSITION[{i}] value {pos} has non finite values");
+            }
+        }
+
+        internal void ValidateNormals(Validation.ValidationContext result)
+        {
+            var normals = this.AsVector3Array();
+
+            for (int i = 0; i < normals.Count; ++i)
+            {
+                var nrm = normals[i];
+
+                if (!nrm._IsReal()) result.AddError(this, $"NORMAL[{i}] value {nrm} has non finite values");
+
+                var len = nrm.Length();
+
+                if (len < 0.99f || len > 1.01f) result.AddError(this, $"NORMAL[{i}] length {len} is not unit length");
+            }
+        }
+
+        internal void ValidateTangents(Validation.ValidationContext result)
+        {
+            var tangents = this.AsVector4Array();
+
+            for (int i = 0; i < tangents.Count; ++i)
+            {
+                var tgt = tangents[i];
+
+                if (!tgt._IsReal()) result.AddError(this, $"TANGENT[{i}] value {tgt} has non finite values");
+
+                var len = new Vector3(tgt.X, tgt.Y, tgt.Z).Length();
+
+                if (len < 0.99f || len > 1.01f) result.AddError(this, $"TANGENT[{i}] length {len} is not unit length");
+
+                if (tgt.W != 1 && tgt.W != -1) result.AddError(this, $"TANGENT[{i}].W {tgt.W} has invalid value");
+            }
+        }
+
+        internal void ValidateJoints(Validation.ValidationContext result, int jwset, int jointsCount)
+        {
+            var jj = this.AsVector4Array();
+
+            void _CheckJoint(Validation.ValidationContext r, float v, int idx, string n)
+            {
+                if (!v._IsReal()) result.AddError(this, $"JOINTS_{jwset}[{idx}].{n} value {v} is not finite");
+                if ((v % 1) != 0) result.AddError(this, $"JOINTS_{jwset}[{idx}].{n} value {v} should be a round value");
+                if (v < 0 || v >= jointsCount) result.AddError(this, $"JOINTS_{jwset}[{idx}].{n} value {v} is out of range 0-{jointsCount}");
+            }
+
+            for (int i = 0; i < jj.Count; ++i)
+            {
+                var jjjj = jj[i];
+                _CheckJoint(result, jjjj.X, i, "X");
+                _CheckJoint(result, jjjj.Y, i, "Y");
+                _CheckJoint(result, jjjj.Z, i, "Z");
+                _CheckJoint(result, jjjj.W, i, "W");
+            }
+        }
+
+        internal void ValidateWeights(Validation.ValidationContext result, int jwset)
+        {
+            var ww = this.AsVector4Array();
+
+            void _CheckWeight(Validation.ValidationContext r, float v, int idx, string n)
+            {
+                if (!v._IsReal()) result.AddError(this, $"WEIGHTS_{jwset}[{idx}].{n} value {v} is not finite");
+                if (v < 0 || v > 1) result.AddError(this, $"WEIGHTS_{jwset}[{idx}].{n} value {v} is out of range 0-1");
+            }
+
+            for (int i = 0; i < ww.Count; ++i)
+            {
+                var wwww = ww[i];
+                _CheckWeight(result, wwww.X, i, "X");
+                _CheckWeight(result, wwww.Y, i, "Y");
+                _CheckWeight(result, wwww.Z, i, "Z");
+                _CheckWeight(result, wwww.W, i, "W");
+
+                // 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.
             }
         }
 

+ 3 - 3
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -299,7 +299,7 @@ namespace SharpGLTF.Schema2
 
             accessor.SetData(buffer, 0, input.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
 
-            Memory.EncodedArrayUtils.CopyTo(input, accessor.AsScalarArray());
+            Memory.EncodedArrayUtils._CopyTo(input, accessor.AsScalarArray());
 
             accessor.UpdateBounds();
 
@@ -318,7 +318,7 @@ namespace SharpGLTF.Schema2
 
             accessor.SetData(buffer, 0, output.Count, DimensionType.VEC3, EncodingType.FLOAT, false);
 
-            Memory.EncodedArrayUtils.CopyTo(output, accessor.AsVector3Array());
+            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector3Array());
 
             accessor.UpdateBounds();
 
@@ -334,7 +334,7 @@ namespace SharpGLTF.Schema2
 
             accessor.SetData(buffer, 0, output.Count, DimensionType.VEC4, EncodingType.FLOAT, false);
 
-            Memory.EncodedArrayUtils.CopyTo(output, accessor.AsQuaternionArray());
+            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsQuaternionArray());
 
             accessor.UpdateBounds();
 

+ 6 - 6
src/SharpGLTF.Core/Schema2/gltf.Asset.cs

@@ -4,8 +4,6 @@ using System.Linq;
 
 namespace SharpGLTF.Schema2
 {
-    using EXCEPTION = IO.ModelException;
-
     [System.Diagnostics.DebuggerDisplay("{Version} {MinVersion} {Generator} {Copyright}")]
     public sealed partial class Asset
     {
@@ -69,14 +67,16 @@ namespace SharpGLTF.Schema2
 
         #region API
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
-            if (string.IsNullOrWhiteSpace(_version)) result.Add(new EXCEPTION(this, "version number is missing"));
+            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 (Version < MINVERSION) result.Add(new EXCEPTION(this, $"Minimum supported version is {MINVERSION} but found:{MinVersion}"));
-            if (MinVersion > MAXVERSION) result.Add(new EXCEPTION(this, $"Maximum supported version is {MAXVERSION} but found:{MinVersion}"));
+            if (MinVersion > Version) result.AddSemanticError(this, $"Asset minVersion {MinVersion} is greater than version {Version}.");
         }
 
         private string _GetExtraInfo(string key)

+ 0 - 4
src/SharpGLTF.Core/Schema2/gltf.Buffer.cs

@@ -1,9 +1,5 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
 using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {

+ 0 - 4
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -1,15 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
     using BYTES = ArraySegment<Byte>;
 
-    using ENCODING = EncodingType;
-
     [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._BufferDebugProxy))]
     public sealed partial class BufferView
     {

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

@@ -117,13 +117,13 @@ namespace SharpGLTF.Schema2
 
         #region validation
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
             if (this._extras != null)
             {
-                if (!IO.JsonUtils.IsSerializable(this._extras)) result.Add(new ModelException(this, $"Invalid {Extras} content."));
+                if (!IO.JsonUtils.IsSerializable(this._extras)) result.InvalidJson(this, "Extras");
             }
         }
 

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

@@ -39,7 +39,7 @@ namespace SharpGLTF.Schema2
             return items.All(item => Object.ReferenceEquals(this.LogicalParent, item.LogicalParent));
         }
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 

+ 0 - 2
src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs

@@ -1,7 +1,5 @@
 using System;
-using System.Collections.Generic;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {

+ 7 - 4
src/SharpGLTF.Core/Schema2/gltf.Mesh.cs

@@ -1,8 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
@@ -58,7 +56,7 @@ namespace SharpGLTF.Schema2
             return mp;
         }
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
@@ -68,8 +66,13 @@ namespace SharpGLTF.Schema2
             }
         }
 
+        internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)
+        {
+            foreach (var p in Primitives) p.ValidateSkinning(result, jointsCount);
+        }
+
         #endregion
-    }
+        }
 
     public partial class ModelRoot
     {

+ 27 - 9
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -1,16 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
     using Collections;
 
-    using EXCEPTION = IO.ModelException;
-    using ROOT = ModelRoot;
-
     [System.Diagnostics.DebuggerDisplay("MeshPrimitive[{LogicalIndex}] {_mode} {_DebuggerDisplay_TryIdentifyContent()}")]
     public sealed partial class MeshPrimitive : IChildOf<Mesh>
     {
@@ -197,7 +192,7 @@ namespace SharpGLTF.Schema2
 
         #region validation
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
@@ -205,11 +200,34 @@ namespace SharpGLTF.Schema2
                 .Select(item => item.Value.Count)
                 .Distinct();
 
-            if (vertexCounts.Count() != 1) result.Add(new EXCEPTION(this, $"Vertex Accessors have mismatching vertices count."));
+            if (vertexCounts.Count() != 1) result.AddError(this, $"Vertex Accessors have mismatching vertices count.");
+
+            if (IndexAccessor != null) IndexAccessor.ValidateIndices(result, (uint)vertexCounts.First(), DrawPrimitiveType);
 
-            var vertexCount = (uint)vertexCounts.First();
+            GetVertexAccessor("POSITION")?.ValidatePositions(result);
+            GetVertexAccessor("NORMAL")?.ValidateNormals(result);
+            GetVertexAccessor("TANGENT")?.ValidateTangents(result);
+        }
+
+        internal void ValidateSkinning(Validation.ValidationContext result, int jointsCount)
+        {
+            var j0 = GetVertexAccessor("JOINTS_0");
+            var w0 = GetVertexAccessor("WEIGHTS_0");
+            ValidateSkinning(result, j0, w0, 0, jointsCount);
+
+            var j1 = GetVertexAccessor("JOINTS_1");
+            var w1 = GetVertexAccessor("WEIGHTS_1");
+            if (j1 != null || w1 != null) ValidateSkinning(result, j1, w1, 1, 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 || w == null) return;
 
-            if (IndexAccessor != null) IndexAccessor.ValidateIndices(result, vertexCount, DrawPrimitiveType);
+            j.ValidateJoints(result, jwset, jointsCount);
+            w.ValidateWeights(result, jwset);
         }
 
         #endregion

+ 331 - 0
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -0,0 +1,331 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace SharpGLTF.Schema2
+{
+    public interface IVisualNodeContainer
+    {
+        IEnumerable<Node> VisualChildren { get; }
+
+        Node CreateNode(string name);
+    }
+
+    [System.Diagnostics.DebuggerDisplay("Node[{LogicalIndex}] {Name} SkinJoint:{IsSkinJoint} T:{LocalTransform.Translation.X} {LocalTransform.Translation.Y} {LocalTransform.Translation.Z}")]
+    public sealed partial class Node : IVisualNodeContainer
+    {
+        #region lifecycle
+
+        internal Node()
+        {
+            _children = new List<int>();
+            _weights = new List<double>();
+        }
+
+        #endregion
+
+        #region properties - hierarchy
+
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Node"/> at <see cref="ModelRoot.LogicalNodes"/>
+        /// </summary>
+        public int LogicalIndex => this.LogicalParent.LogicalNodes.IndexOfReference(this);
+
+        /// <summary>
+        /// Gets the visual parent <see cref="Node"/> instance that contains this <see cref="Node"/>.
+        /// </summary>
+        public Node VisualParent => this.LogicalParent._FindVisualParentNode(this);
+
+        /// <summary>
+        /// Gets the visual root <see cref="Scene"/> instance that contains this <see cref="Node"/>.
+        /// </summary>
+        public Scene VisualScene
+        {
+            get
+            {
+                var rootNode = this;
+                while (rootNode.VisualParent != null) rootNode = rootNode.VisualParent;
+                return LogicalParent.LogicalScenes.FirstOrDefault(item => item._ContainsVisualNode(rootNode, false));
+            }
+        }
+
+        /// <summary>
+        /// Gets the visual children <see cref="Node"/> instances contained in this <see cref="Node"/>.
+        /// </summary>
+        public IEnumerable<Node> VisualChildren => _GetVisualChildren();
+
+        /// <summary>
+        /// Gets a value indicating whether this node is used as a Bone joint in a <see cref="Skin"/>.
+        /// </summary>
+        public Boolean IsSkinJoint => Skin.FindSkinsUsingJoint(this).Any();
+
+        #endregion
+
+        #region properties - transform
+
+        /// <summary>
+        /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
+        /// </summary>
+        public Matrix4x4 LocalMatrix
+        {
+            get => Transforms.AffineTransform.Evaluate(_matrix, _scale, _rotation, _translation);
+            set
+            {
+                if (value == Matrix4x4.Identity) _matrix = null;
+                else _matrix = value;
+
+                _scale = null;
+                _rotation = null;
+                _translation = null;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the local Scale, Rotation and Translation of this <see cref="Node"/>.
+        /// </summary>
+        public Transforms.AffineTransform LocalTransform
+        {
+            get => new Transforms.AffineTransform(_matrix, _scale, _rotation, _translation);
+            set
+            {
+                _matrix = null;
+                _scale = value.Scale;
+                _rotation = value.Rotation.Sanitized();
+                _translation = value.Translation;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the world transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
+        /// </summary>
+        public Matrix4x4 WorldMatrix
+        {
+            get
+            {
+                var vs = VisualParent;
+                return vs == null ? LocalMatrix : Transforms.AffineTransform.LocalToWorld(vs.WorldMatrix, LocalMatrix);
+            }
+            set
+            {
+                var vs = VisualParent;
+                LocalMatrix = vs == null ? value : Transforms.AffineTransform.WorldToLocal(vs.WorldMatrix, value);
+            }
+        }
+
+        #endregion
+
+        #region properties - content
+
+        /// <summary>
+        /// Gets or sets the <see cref="Schema2.Mesh"/> of this <see cref="Node"/>.
+        /// </summary>
+        public Mesh Mesh
+        {
+            get => this._mesh.HasValue ? this.LogicalParent.LogicalMeshes[this._mesh.Value] : null;
+            set
+            {
+                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                var idx = this.LogicalParent.LogicalMeshes.IndexOfReference(value);
+                this._mesh = idx < 0 ? (int?)null : idx;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="Schema2.Skin"/> of this <see cref="Node"/>.
+        /// </summary>
+        public Skin Skin
+        {
+            get => this._skin.HasValue ? this.LogicalParent.LogicalSkins[this._skin.Value] : null;
+            set
+            {
+                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                var idx = this.LogicalParent.LogicalSkins.IndexOfReference(value);
+                this._skin = idx < 0 ? (int?)null : idx;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the Morph Weights of this <see cref="Node"/>.
+        /// </summary>
+        public IReadOnlyList<Single> MorphWeights
+        {
+            get => _weights == null ? Mesh?.MorphWeights : _weights.Select(item => (float)item).ToArray();
+            set
+            {
+                _weights.Clear();
+                _weights.AddRange(value.Select(item => (Double)item));
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Creates a new <see cref="Node"/> instance,
+        /// adds it to <see cref="ModelRoot.LogicalNodes"/>
+        /// and references it as a child in the current graph.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Node"/> instance.</returns>
+        public Node CreateNode(string name)
+        {
+            var node = this.LogicalParent._CreateLogicalNode(this._children);
+            node.Name = name;
+            return node;
+        }
+
+        /// <summary>
+        /// Returns all the <see cref="Node"/> instances of a visual hierarchy as a flattened list.
+        /// </summary>
+        /// <param name="container">A <see cref="IVisualNodeContainer"/> instance.</param>
+        /// <returns>A collection of <see cref="Node"/> instances.</returns>
+        public static IEnumerable<Node> Flatten(IVisualNodeContainer container)
+        {
+            if (container is Node n) yield return n;
+
+            foreach (var c in container.VisualChildren)
+            {
+                var cc = Flatten(c);
+
+                foreach (var ccc in cc) yield return ccc;
+            }
+        }
+
+        /// <summary>
+        /// Gets a collection of <see cref="Node"/> instances using <paramref name="mesh"/>.
+        /// </summary>
+        /// <param name="mesh">A <see cref="Mesh"/> instance.</param>
+        /// <returns>A collection of <see cref="Node"/> instances.</returns>
+        public static IEnumerable<Node> FindNodesUsingMesh(Mesh mesh)
+        {
+            if (mesh == null) return Enumerable.Empty<Node>();
+
+            var meshIdx = mesh.LogicalIndex;
+
+            return mesh.LogicalParent
+                .LogicalNodes
+                .Where(item => item._mesh.AsValue(int.MinValue) == meshIdx);
+        }
+
+        /// <summary>
+        /// Gets a collection of <see cref="Node"/> instances using <paramref name="skin"/>.
+        /// </summary>
+        /// <param name="skin">A <see cref="Skin"/> instance.</param>
+        /// <returns>A collection of <see cref="Node"/> instances.</returns>
+        public static IEnumerable<Node> FindNodesUsingSkin(Skin skin)
+        {
+            if (skin == null) return Enumerable.Empty<Node>();
+
+            var meshIdx = skin.LogicalIndex;
+
+            return skin.LogicalParent
+                .LogicalNodes
+                .Where(item => item._skin.AsValue(int.MinValue) == meshIdx);
+        }
+
+        internal bool _ContainsVisualNode(Node node, bool recursive)
+        {
+            Guard.NotNull(node, nameof(node));
+            Guard.MustShareLogicalParent(this, node, nameof(node));
+
+            if (!recursive) return VisualChildren.Any(item => item == node);
+
+            return VisualChildren.Any(item => item == node || item._ContainsVisualNode(node, recursive));
+        }
+
+        internal bool _HasVisualChild(int nodeIndex) { return _children.Contains(nodeIndex); }
+
+        internal IEnumerable<Node> _GetVisualChildren()
+        {
+            var allChildren = _children.Select(idx => LogicalParent.LogicalNodes[idx]);
+
+            return allChildren;
+        }
+
+        #endregion
+
+        #region validation
+
+        internal override void Validate(Validation.ValidationContext result)
+        {
+            base.Validate(result);
+
+            // check out of range indices
+            foreach (var idx in this._children)
+            {
+                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.AddError(this, $"references invalid Node[{idx}]");
+            }
+
+            // check duplicated indices
+            if (this._children.Distinct().Count() != this._children.Count) result.AddError(this, "has duplicated node references");
+
+            // check self references
+            if (this._children.Contains(this.LogicalIndex)) result.AddError(this, "has self references");
+
+            // check circular references
+            var p = this;
+            while (true)
+            {
+                p = p.VisualParent;
+                if (p == null) break;
+                if (p.LogicalIndex == this.LogicalIndex)
+                {
+                    result.AddError(this, "has a circular reference");
+                    break;
+                }
+            }
+
+            // check Transforms (out or range, NaN, etc)
+
+            // check morph weights
+
+            if (this._skin != null)
+            {
+                if (this._mesh == null)
+                {
+                    result.AddError(this, "Found a Skin, but Mesh is missing");
+                }
+                else
+                {
+                    this.Mesh.ValidateSkinning(result, this.Skin.JointsCount);
+                }
+            }
+        }
+
+        #endregion
+    }
+
+    public partial class ModelRoot
+    {
+        internal Node _FindVisualParentNode(Node childNode)
+        {
+            var childIdx = _nodes.IndexOf(childNode);
+            if (childIdx < 0) return null;
+
+            // find the logical owner
+            return _nodes.FirstOrDefault(item => item._HasVisualChild(childIdx));
+        }
+
+        internal Node _CreateLogicalNode()
+        {
+            var n = new Node();
+            _nodes.Add(n);
+            return n;
+        }
+
+        internal Node _CreateLogicalNode(IList<int> children)
+        {
+            var n = _CreateLogicalNode();
+            children.Add(n.LogicalIndex);
+            return n;
+        }
+
+        internal Boolean _CheckNodeIsJoint(Node n)
+        {
+            var idx = n.LogicalIndex;
+            return _skins.Any(s => s._ContainsJoint(idx));
+        }
+    }
+}

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

@@ -144,23 +144,23 @@ namespace SharpGLTF.Schema2
 
         #region validation
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             // 1st check version number
 
-            if (Asset == null) result.Add(new IO.ModelException(this, "missing Asset object, can't check glTF version")); // fix: create a default Asset
+            if (Asset == null) result.AddError(this, "missing Asset object, can't check glTF version"); // fix: create a default Asset
             else Asset.Validate(result);
 
-            if (result.Count > 0) return;
+            if (result.HasErrors) return;
 
             // 2nd check incompatible extensions
 
             foreach (var iex in this.IncompatibleExtensions)
             {
-                result.Add(new IO.UnsupportedExtensionException(this, iex)); // fix: attempt to remove given extension
+                result.UnsupportedExtensionError(this, iex);
             }
 
-            if (result.Count > 0) return;
+            if (result.HasErrors) return;
 
             // 3rd check base class
 

+ 3 - 316
src/SharpGLTF.Core/Schema2/gltf.Scene.cs

@@ -1,293 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
-    using EXCEPTION = IO.ModelException;
-
-    public interface IVisualNodeContainer
-    {
-        IEnumerable<Node> VisualChildren { get; }
-
-        Node CreateNode(string name);
-    }
-
-    [System.Diagnostics.DebuggerDisplay("Node[{LogicalIndex}] {Name} SkinJoint:{IsSkinJoint} T:{LocalTransform.Translation.X} {LocalTransform.Translation.Y} {LocalTransform.Translation.Z}")]
-    public sealed partial class Node : IVisualNodeContainer
-    {
-        #region lifecycle
-
-        internal Node()
-        {
-            _children = new List<int>();
-            _weights = new List<double>();
-        }
-
-        #endregion
-
-        #region properties - hierarchy
-
-        /// <summary>
-        /// Gets the zero-based index of this <see cref="Node"/> at <see cref="ModelRoot.LogicalNodes"/>
-        /// </summary>
-        public int LogicalIndex => this.LogicalParent.LogicalNodes.IndexOfReference(this);
-
-        /// <summary>
-        /// Gets the visual parent <see cref="Node"/> instance that contains this <see cref="Node"/>.
-        /// </summary>
-        public Node VisualParent => this.LogicalParent._FindVisualParentNode(this);
-
-        /// <summary>
-        /// Gets the visual root <see cref="Scene"/> instance that contains this <see cref="Node"/>.
-        /// </summary>
-        public Scene VisualScene
-        {
-            get
-            {
-                var rootNode = this;
-                while (rootNode.VisualParent != null) rootNode = rootNode.VisualParent;
-                return LogicalParent.LogicalScenes.FirstOrDefault(item => item._ContainsVisualNode(rootNode, false));
-            }
-        }
-
-        /// <summary>
-        /// Gets the visual children <see cref="Node"/> instances contained in this <see cref="Node"/>.
-        /// </summary>
-        public IEnumerable<Node> VisualChildren => _GetVisualChildren();
-
-        /// <summary>
-        /// Gets a value indicating whether this node is used as a Bone joint in a <see cref="Skin"/>.
-        /// </summary>
-        public Boolean IsSkinJoint => Skin.FindSkinsUsingJoint(this).Any();
-
-        #endregion
-
-        #region properties - transform
-
-        /// <summary>
-        /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
-        /// </summary>
-        public Matrix4x4 LocalMatrix
-        {
-            get => Transforms.AffineTransform.Evaluate(_matrix, _scale, _rotation, _translation);
-            set
-            {
-                if (value == Matrix4x4.Identity) _matrix = null;
-                else _matrix = value;
-
-                _scale = null;
-                _rotation = null;
-                _translation = null;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the local Scale, Rotation and Translation of this <see cref="Node"/>.
-        /// </summary>
-        public Transforms.AffineTransform LocalTransform
-        {
-            get => new Transforms.AffineTransform(_matrix, _scale, _rotation, _translation);
-            set
-            {
-                _matrix = null;
-                _scale = value.Scale;
-                _rotation = value.Rotation.Sanitized();
-                _translation = value.Translation;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the world transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
-        /// </summary>
-        public Matrix4x4 WorldMatrix
-        {
-            get
-            {
-                var vs = VisualParent;
-                return vs == null ? LocalMatrix : Transforms.AffineTransform.LocalToWorld(vs.WorldMatrix, LocalMatrix);
-            }
-            set
-            {
-                var vs = VisualParent;
-                LocalMatrix = vs == null ? value : Transforms.AffineTransform.WorldToLocal(vs.WorldMatrix, value);
-            }
-        }
-
-        #endregion
-
-        #region properties - content
-
-        /// <summary>
-        /// Gets or sets the <see cref="Schema2.Mesh"/> of this <see cref="Node"/>.
-        /// </summary>
-        public Mesh Mesh
-        {
-            get => this._mesh.HasValue ? this.LogicalParent.LogicalMeshes[this._mesh.Value] : null;
-            set
-            {
-                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
-                var idx = this.LogicalParent.LogicalMeshes.IndexOfReference(value);
-                this._mesh = idx < 0 ? (int?)null : idx;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the <see cref="Schema2.Skin"/> of this <see cref="Node"/>.
-        /// </summary>
-        public Skin Skin
-        {
-            get => this._skin.HasValue ? this.LogicalParent.LogicalSkins[this._skin.Value] : null;
-            set
-            {
-                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
-                var idx = this.LogicalParent.LogicalSkins.IndexOfReference(value);
-                this._skin = idx < 0 ? (int?)null : idx;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the Morph Weights of this <see cref="Node"/>.
-        /// </summary>
-        public IReadOnlyList<Single> MorphWeights
-        {
-            get => _weights == null ? Mesh?.MorphWeights : _weights.Select(item => (float)item).ToArray();
-            set
-            {
-                _weights.Clear();
-                _weights.AddRange(value.Select(item => (Double)item));
-            }
-        }
-
-        #endregion
-
-        #region API
-
-        /// <summary>
-        /// Creates a new <see cref="Node"/> instance,
-        /// adds it to <see cref="ModelRoot.LogicalNodes"/>
-        /// and references it as a child in the current graph.
-        /// </summary>
-        /// <param name="name">The name of the instance.</param>
-        /// <returns>A <see cref="Node"/> instance.</returns>
-        public Node CreateNode(string name)
-        {
-            var node = this.LogicalParent._CreateLogicalNode(this._children);
-            node.Name = name;
-            return node;
-        }
-
-        /// <summary>
-        /// Returns all the <see cref="Node"/> instances of a visual hierarchy as a flattened list.
-        /// </summary>
-        /// <param name="container">A <see cref="IVisualNodeContainer"/> instance.</param>
-        /// <returns>A collection of <see cref="Node"/> instances.</returns>
-        public static IEnumerable<Node> Flatten(IVisualNodeContainer container)
-        {
-            if (container is Node n) yield return n;
-
-            foreach (var c in container.VisualChildren)
-            {
-                var cc = Flatten(c);
-
-                foreach (var ccc in cc) yield return ccc;
-            }
-        }
-
-        /// <summary>
-        /// Gets a collection of <see cref="Node"/> instances using <paramref name="mesh"/>.
-        /// </summary>
-        /// <param name="mesh">A <see cref="Mesh"/> instance.</param>
-        /// <returns>A collection of <see cref="Node"/> instances.</returns>
-        public static IEnumerable<Node> FindNodesUsingMesh(Mesh mesh)
-        {
-            if (mesh == null) return Enumerable.Empty<Node>();
-
-            var meshIdx = mesh.LogicalIndex;
-
-            return mesh.LogicalParent
-                .LogicalNodes
-                .Where(item => item._mesh.AsValue(int.MinValue) == meshIdx);
-        }
-
-        /// <summary>
-        /// Gets a collection of <see cref="Node"/> instances using <paramref name="skin"/>.
-        /// </summary>
-        /// <param name="skin">A <see cref="Skin"/> instance.</param>
-        /// <returns>A collection of <see cref="Node"/> instances.</returns>
-        public static IEnumerable<Node> FindNodesUsingSkin(Skin skin)
-        {
-            if (skin == null) return Enumerable.Empty<Node>();
-
-            var meshIdx = skin.LogicalIndex;
-
-            return skin.LogicalParent
-                .LogicalNodes
-                .Where(item => item._skin.AsValue(int.MinValue) == meshIdx);
-        }
-
-        internal bool _ContainsVisualNode(Node node, bool recursive)
-        {
-            Guard.NotNull(node, nameof(node));
-            Guard.MustShareLogicalParent(this, node, nameof(node));
-
-            if (!recursive) return VisualChildren.Any(item => item == node);
-
-            return VisualChildren.Any(item => item == node || item._ContainsVisualNode(node, recursive));
-        }
-
-        internal bool _HasVisualChild(int nodeIndex) { return _children.Contains(nodeIndex); }
-
-        internal IEnumerable<Node> _GetVisualChildren()
-        {
-            var allChildren = _children.Select(idx => LogicalParent.LogicalNodes[idx]);
-
-            return allChildren;
-        }
-
-        #endregion
-
-        #region validation
-
-        internal override void Validate(IList<Exception> result)
-        {
-            base.Validate(result);
-
-            // check out of range indices
-            foreach (var idx in this._children)
-            {
-                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.Add(new EXCEPTION(this, $"references invalid Node[{idx}]"));
-            }
-
-            // check duplicated indices
-            if (this._children.Distinct().Count() != this._children.Count) result.Add(new EXCEPTION(this, "has duplicated node references"));
-
-            // check self references
-            if (this._children.Contains(this.LogicalIndex)) result.Add(new EXCEPTION(this, "has self references"));
-
-            // check circular references
-            var p = this;
-            while (true)
-            {
-                p = p.VisualParent;
-                if (p == null) break;
-                if (p.LogicalIndex == this.LogicalIndex)
-                {
-                    result.Add(new EXCEPTION(this, "has a circular reference"));
-                    break;
-                }
-            }
-
-            // check Transforms (out or range, NaN, etc)
-
-            // check morph weights
-        }
-
-        #endregion
-    }
-
     [System.Diagnostics.DebuggerDisplay("Scene[{LogicalIndex}] {Name}")]
     public sealed partial class Scene : IVisualNodeContainer
     {
@@ -343,18 +59,18 @@ namespace SharpGLTF.Schema2
 
         #region validation
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
             // check out of range indices
             foreach (var idx in this._nodes)
             {
-                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.Add(new EXCEPTION(this, $"references invalid Node[{idx}]"));
+                if (idx < 0 || idx >= this.LogicalParent.LogicalNodes.Count) result.AddError(this, $"references invalid Node[{idx}]");
             }
 
             // check duplicated indices
-            if (this._nodes.Distinct().Count() != this._nodes.Count) result.Add(new EXCEPTION(this, "has duplicated node references"));
+            if (this._nodes.Distinct().Count() != this._nodes.Count) result.AddError(this, "has duplicated node references");
         }
 
         #endregion
@@ -400,34 +116,5 @@ namespace SharpGLTF.Schema2
 
             return scene;
         }
-
-        internal Node _FindVisualParentNode(Node childNode)
-        {
-            var childIdx = _nodes.IndexOf(childNode);
-            if (childIdx < 0) return null;
-
-            // find the logical owner
-            return _nodes.FirstOrDefault(item => item._HasVisualChild(childIdx));
-        }
-
-        internal Node _CreateLogicalNode()
-        {
-            var n = new Node();
-            _nodes.Add(n);
-            return n;
-        }
-
-        internal Node _CreateLogicalNode(IList<int> children)
-        {
-            var n = _CreateLogicalNode();
-            children.Add(n.LogicalIndex);
-            return n;
-        }
-
-        internal Boolean _CheckNodeIsJoint(Node n)
-        {
-            var idx = n.LogicalIndex;
-            return _skins.Any(s => s._ContainsJoint(idx));
-        }
     }
 }

+ 8 - 12
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -2,14 +2,9 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {
-    using Collections;
-
-    using ROOT = ModelRoot;
-
     [System.Diagnostics.DebuggerDisplay("Skin[{LogicalIndex}] {Name}")]
     public sealed partial class Skin
     {
@@ -91,26 +86,27 @@ namespace SharpGLTF.Schema2
 
             var node = this.LogicalParent.LogicalNodes[nodeIdx];
 
-            var matrix = (Matrix4x4)GetInverseBindMatricesAccessor().AsMatrix4x4Array()[idx];
+            var accessor = GetInverseBindMatricesAccessor();
+
+            var matrix = accessor == null ? Matrix4x4.Identity : accessor.AsMatrix4x4Array()[idx];
 
             return new KeyValuePair<Node, Matrix4x4>(node, matrix);
         }
 
-        internal override void Validate(IList<Exception> result)
+        internal override void Validate(Validation.ValidationContext result)
         {
             base.Validate(result);
 
             // note: this check will fail if the buffers are not set
 
-            /*
-            for(int i=0; i < _joints.Count; ++i)
+            for (int i = 0; i < _joints.Count; ++i)
             {
                 var j = GetJoint(i);
 
                 var invXform = j.Value;
 
-                if (invXform.M44 != 1) exx.Add(new ModelException(this, $"Joint {i} has invalid inverse matrix"));
-            }*/
+                if (invXform.M44 != 1) result.AddError(this, $"Joint {i} has invalid inverse matrix");
+            }
         }
 
         /// <summary>
@@ -163,7 +159,7 @@ namespace SharpGLTF.Schema2
 
             var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
 
-            Memory.EncodedArrayUtils.FillFrom(matrices, 0, joints.Select(item => item.Value));
+            matrices.Fill(joints.Select(item => item.Value));
 
             var accessor = LogicalParent.CreateAccessor("Bind Matrices");
 

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

@@ -1,7 +1,5 @@
 using System;
-using System.Collections.Generic;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Schema2
 {

+ 0 - 1
src/SharpGLTF.Core/Schema2/khr.lights.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Text;
 using System.Numerics;
 using System.Linq;
 

+ 82 - 0
src/SharpGLTF.Core/Validation/ModelException.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    using TARGET = IO.JsonSerializable;
+
+    /// <summary>
+    /// Represents an exception produced by the serialization or validation of a gltf model.
+    /// </summary>
+    public class ModelException : Exception
+    {
+        #region lifecycle
+
+        internal ModelException(TARGET target, String message)
+            : base(_CreateBaseMessage(target, message))
+        {
+            _Target = target;
+        }
+
+        private static string _CreateBaseMessage(TARGET target, String message)
+        {
+            if (target == null) return message;
+
+            var targetTypeInfo = target.GetType().GetTypeInfo();
+
+            var logicalIndexProp = targetTypeInfo.GetProperty("LogicalIndex");
+
+            var logicalIndex = logicalIndexProp != null ? (int)logicalIndexProp.GetValue(target) : -1;
+
+            if (logicalIndex >= 0) return $"{targetTypeInfo.Name}[{logicalIndex}] {message}";
+
+            return $"{targetTypeInfo.Name} {message}";
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly TARGET _Target;
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Represents an exception produced by an invalid JSON document.
+    /// </summary>
+    public class SchemaException : ModelException
+    {
+        internal SchemaException(TARGET target, String message)
+            : base(target, message) { }
+    }
+
+    /// <summary>
+    /// Represents an esception produced by invalid values.
+    /// </summary>
+    public class SemanticException : ModelException
+    {
+        internal SemanticException(TARGET target, String message)
+            : base(target, message) { }
+    }
+
+    /// <summary>
+    /// Represents an exception produced by invalid objects relationships.
+    /// </summary>
+    public class LinkException : ModelException
+    {
+        internal LinkException(TARGET target, String message)
+            : base(target, message) { }
+    }
+
+    /// <summary>
+    /// Represents an exception produced by invalid data.
+    /// </summary>
+    public class DataException : ModelException
+    {
+        internal DataException(TARGET target, String message)
+            : base(target, message) { }
+    }
+}

+ 82 - 0
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Validation
+{
+    using TARGET = IO.JsonSerializable;
+
+    /// <summary>
+    /// Utility class used in the process of model validation.
+    /// </summary>
+    class ValidationContext
+    {
+        // we should try to align validation errors to these issues:
+        // https://github.com/KhronosGroup/glTF-Validator/blob/master/ISSUES.md
+
+        #region data
+
+        private readonly List<Exception> _Exceptions = new List<Exception>();
+
+        public IEnumerable<Exception> Exceptions => _Exceptions;
+
+        public bool HasErrors => _Exceptions.Count > 0;
+
+        #endregion
+
+        #region errors
+
+        public void AddError(TARGET target, String message)
+        {
+            _Exceptions.Add(new ModelException(target, message));
+        }
+
+        #endregion
+
+        #region schema errors
+
+        public void AddSchemaError(TARGET target, String message)
+        {
+            _Exceptions.Add(new SchemaException(target, message));
+        }
+
+        public void InvalidJson(TARGET target, string message)
+        {
+            AddSchemaError(target, $"Invalid JSON data. Parser output: {message}");
+        }
+
+        #endregion
+
+        #region semantic errors
+
+        public void AddSemanticError(TARGET target, String message)
+        {
+            _Exceptions.Add(new SemanticException(target, message));
+        }
+
+        #endregion
+
+        #region data errors
+
+        public void AddDataError(TARGET target, String message)
+        {
+            _Exceptions.Add(new DataException(target, message));
+        }
+
+        #endregion
+
+        #region link errors
+
+        public void AddLinkError(TARGET target, String message)
+        {
+            _Exceptions.Add(new LinkException(target, message));
+        }
+
+        public void UnsupportedExtensionError(TARGET target, String message)
+        {
+            AddLinkError(target, message);
+        }
+
+        #endregion
+    }
+}

+ 0 - 1
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -249,7 +249,6 @@ namespace SharpGLTF.Geometry
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning
         {
-
             var p1 = a.Item1.CloneAs<TvP>();
             var p2 = b.Item1.CloneAs<TvP>();
             var p3 = c.Item1.CloneAs<TvP>();

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -41,7 +41,7 @@ namespace SharpGLTF.Geometry.VertexTypes
                     {
                         var tmp = span[j];
                         span[j] = span[j + 1];
-                        span[j + 1 ] = tmp;
+                        span[j + 1] = tmp;
                         completed = false;
                     }
                 }

+ 5 - 5
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -39,10 +39,10 @@ namespace SharpGLTF.Geometry.VertexTypes
                 {
                     var columnFunc = GetItemValueFunc<TvP, TvM, TvS>(accessor.Attribute.Name);
 
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) accessor.Fill(block.GetScalarColumn(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) accessor.Fill(block.GetVector2Column(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC3) accessor.Fill(block.GetVector3Column(columnFunc));
-                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC4) accessor.Fill(block.GetVector4Column(columnFunc));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) accessor.AsScalarArray().Fill(block.GetScalarColumn(columnFunc));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) accessor.AsVector2Array().Fill(block.GetVector2Column(columnFunc));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC3) accessor.AsVector3Array().Fill(block.GetVector3Column(columnFunc));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC4) accessor.AsVector4Array().Fill(block.GetVector4Column(columnFunc));
                 }
 
                 yield return accessors;
@@ -67,7 +67,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             {
                 var accessor = new MemoryAccessor(ibuffer, attribute.Slice(baseIndicesIndex, block.Count));
 
-                accessor.Fill(block);
+                accessor.AsIntegerArray().Fill(block);
 
                 yield return accessor;
 

+ 5 - 27
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -85,28 +85,6 @@ namespace SharpGLTF.Schema2
 
         #region accessors
 
-        private static void FillFrom(this IList<UInt32> dst, int dstIndex, IEnumerable<Int32> src)
-        {
-            using (var ator = src.GetEnumerator())
-            {
-                while (dstIndex < dst.Count && ator.MoveNext())
-                {
-                    dst[dstIndex++] = (UInt32)ator.Current;
-                }
-            }
-        }
-
-        private static void FillFrom<T>(this IList<T> dst, int dstIndex, IEnumerable<T> src)
-        {
-            using (var ator = src.GetEnumerator())
-            {
-                while (dstIndex < dst.Count && ator.MoveNext())
-                {
-                    dst[dstIndex++] = ator.Current;
-                }
-            }
-        }
-
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Single> values)
         {
             var root = primitive.LogicalParent.LogicalParent;
@@ -114,7 +92,7 @@ namespace SharpGLTF.Schema2
             // create a vertex buffer and fill it
             var view = root.UseBufferView(new Byte[4 * values.Count], 0, null, 0, BufferMode.ARRAY_BUFFER);
             var array = new ScalarArray(view.Content);
-            array.FillFrom(0, values);
+            array.Fill(values);
 
             var accessor = root.CreateAccessor();
             primitive.SetVertexAccessor(attribute, accessor);
@@ -131,7 +109,7 @@ namespace SharpGLTF.Schema2
             // create a vertex buffer and fill it
             var view = root.UseBufferView(new Byte[8 * values.Count], 0, null, 0, BufferMode.ARRAY_BUFFER);
             var array = new Vector2Array(view.Content);
-            array.FillFrom(0, values);
+            array.Fill(values);
 
             var accessor = root.CreateAccessor();
             primitive.SetVertexAccessor(attribute, accessor);
@@ -148,7 +126,7 @@ namespace SharpGLTF.Schema2
             // create a vertex buffer and fill it
             var view = root.UseBufferView(new Byte[12 * values.Count], 0, null, 0, BufferMode.ARRAY_BUFFER);
             var array = new Vector3Array(view.Content);
-            array.FillFrom(0, values);
+            array.Fill(values);
 
             var accessor = root.CreateAccessor();
 
@@ -166,7 +144,7 @@ namespace SharpGLTF.Schema2
             // create a vertex buffer and fill it
             var view = root.UseBufferView(new Byte[16 * values.Count], 0, null, 0, BufferMode.ARRAY_BUFFER);
             var array = new Vector4Array(view.Content);
-            array.FillFrom(0, values);
+            array.Fill(values);
 
             var accessor = root.CreateAccessor();
 
@@ -194,7 +172,7 @@ namespace SharpGLTF.Schema2
             // create an index buffer and fill it
             var view = root.UseBufferView(new Byte[4 * values.Count], 0, null, 0, BufferMode.ELEMENT_ARRAY_BUFFER);
             var array = new IntegerArray(view.Content);
-            array.FillFrom(0, values);
+            array.Fill(values);
 
             var accessor = root.CreateAccessor();
 

+ 1 - 1
tests/SharpGLTF.Tests/Geometry/LoadMeshTests.cs

@@ -25,7 +25,7 @@ namespace SharpGLTF.Geometry
         {
             foreach (var f in TestFiles.GetSampleModelsPaths())
             {
-                var root = GltfUtils.LoadModel(f);
+                var root = Schema2.ModelRoot.Load(f);
                 Assert.NotNull(root);
             }
         }

+ 0 - 31
tests/SharpGLTF.Tests/GltfUtils.cs

@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF
-{    
-    using Schema2;
-
-    static class GltfUtils
-    {
-        #region model loading
-
-        public static ModelRoot LoadModel(string filePath)
-        {
-            try
-            {
-                return ModelRoot.Load(filePath);
-            }
-            catch (IO.UnsupportedExtensionException eex)
-            {
-                TestContext.WriteLine($"{filePath} ERROR: {eex.Message}");
-
-                return null;
-            }
-        }
-
-        #endregion
-    }
-}

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs

@@ -28,7 +28,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             TestContext.CurrentContext.AttachShowDirLink();
 
             // load Polly model
-            var model = GltfUtils.LoadModel(TestFiles.GetPollyFileModelPath());
+            var model = ModelRoot.Load(TestFiles.GetPollyFileModelPath());
 
             Assert.NotNull(model);
 

+ 4 - 4
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -40,7 +40,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
 
                 var perf = System.Diagnostics.Stopwatch.StartNew();                
 
-                var model = GltfUtils.LoadModel(f);
+                var model = ModelRoot.Load(f);
                 Assert.NotNull(model);
 
                 var perf_load = perf.ElapsedMilliseconds;
@@ -92,7 +92,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             var f = TestFiles.GetSampleModelsPaths()
                 .FirstOrDefault(item => item.EndsWith(filePath));
 
-            var model = GltfUtils.LoadModel(f);
+            var model = ModelRoot.Load(f);
             Assert.NotNull(model);
 
             // evaluate and save all the triangles to a Wavefront Object
@@ -114,7 +114,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             var f = TestFiles.GetSampleModelsPaths()
                 .FirstOrDefault(item => item.EndsWith(@"UnlitTest\glTF-Binary\UnlitTest.glb"));
 
-            var model = GltfUtils.LoadModel(f);
+            var model = ModelRoot.Load(f);
             Assert.NotNull(model);
 
             Assert.IsTrue(model.LogicalMaterials[0].Unlit);
@@ -132,7 +132,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
             var f = TestFiles.GetSchemaExtensionsModelsPaths()
                 .FirstOrDefault(item => item.EndsWith("lights.gltf"));
 
-            var model = GltfUtils.LoadModel(f);
+            var model = ModelRoot.Load(f);
             Assert.NotNull(model);
 
             Assert.AreEqual(3, model.LogicalPunctualLights.Count);