فهرست منبع

more code cleanup
exceptions

Vicente Penades 6 سال پیش
والد
کامیت
f79d192123

+ 2 - 2
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -8,8 +8,8 @@
 
   <ItemGroup>
     <PackageReference Include="LibGit2Sharp" Version="0.26.0" />
-    <PackageReference Include="NJsonSchema.CodeGeneration" Version="9.13.28" />
-    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="9.13.28" />
+    <PackageReference Include="NJsonSchema.CodeGeneration" Version="9.13.29" />
+    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="9.13.29" />
   </ItemGroup>
 
 </Project>

+ 39 - 0
src/Shared/_Extensions.cs

@@ -100,6 +100,45 @@ namespace SharpGLTF
             return (value - r) == Vector4.Zero;
         }
 
+        internal static void Validate(this Vector3 vector, string msg)
+        {
+            if (!vector._IsReal()) throw new NotFiniteNumberException($"{msg} is invalid.");
+        }
+
+        internal static void ValidateNormal(this Vector3 normal, string msg)
+        {
+            if (!normal._IsReal()) throw new NotFiniteNumberException($"{msg} is invalid.");
+
+            var len = normal.Length();
+
+            if (len < 0.99f || len > 1.01f) throw new ArithmeticException($"{msg} is not unit length.");
+        }
+
+        internal static void ValidateTangent(this Vector4 tangent, string msg)
+        {
+            if (tangent.W != 1 && tangent.W != -1) throw new ArithmeticException(msg);
+
+            new Vector3(tangent.X, tangent.Y, tangent.Z).ValidateNormal(msg);
+        }
+
+        internal static bool IsValidNormal(this Vector3 normal)
+        {
+            if (!normal._IsReal()) return false;
+
+            var len = normal.Length();
+
+            if (len < 0.99f || len > 1.01f) return false;
+
+            return true;
+        }
+
+        internal static bool IsValidTangent(this Vector4 tangent)
+        {
+            if (tangent.W != 1 && tangent.W != -1) return false;
+
+            return new Vector3(tangent.X, tangent.Y, tangent.Z).IsValidNormal();
+        }
+
         #endregion
 
         #region linq

+ 1 - 5
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -439,11 +439,7 @@ namespace SharpGLTF.Schema2
             {
                 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");
+                if (!nrm.IsValidNormal()) result.AddError(this, $"NORMAL[{i}] value {nrm} is invalid");
             }
         }
 

+ 210 - 0
src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+using Newtonsoft.Json;
+
+namespace SharpGLTF.Schema2
+{
+    using BYTES = ArraySegment<Byte>;
+    using MODEL = ModelRoot;
+
+    /// <summary>
+    /// Callback used for loading associated files of current model.
+    /// </summary>
+    /// <param name="assetName">the asset relative path.</param>
+    /// <returns>The file contents as a <see cref="byte"/> array.</returns>
+    public delegate BYTES AssetReader(String assetName);
+
+    /// <summary>
+    /// Configuration settings for reading model files.
+    /// </summary>
+    public class ReadSettings
+    {
+        public ReadSettings()
+        {
+        }
+
+        internal ReadSettings(AssetReader reader)
+        {
+            FileReader = reader;
+        }
+
+        internal ReadSettings(string filePath)
+        {
+            Guard.FilePathMustExist(filePath, nameof(filePath));
+
+            var dir = Path.GetDirectoryName(filePath);
+
+            FileReader = assetFileName => new BYTES(File.ReadAllBytes(Path.Combine(dir, assetFileName)));
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="AssetReader"/> delegate used to read satellite files.
+        /// </summary>
+        public AssetReader FileReader { get; set; }
+    }
+
+    partial class ModelRoot
+    {
+        /// <summary>
+        /// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
+        /// </summary>
+        /// <param name="filePath">A valid file path.</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL Load(string filePath)
+        {
+            Guard.FilePathMustExist(filePath, nameof(filePath));
+
+            var settings = new ReadSettings(filePath);
+
+            using (var s = File.OpenRead(filePath))
+            {
+                return Read(s, settings);
+            }
+        }
+
+        /// <summary>
+        /// Parses a <see cref="MODEL"/> instance from a <see cref="byte"/> array representing a GLB file
+        /// </summary>
+        /// <param name="glb">A <see cref="byte"/> array representing a GLB file</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL ParseGLB(BYTES glb)
+        {
+            Guard.NotNull(glb, nameof(glb));
+
+            using (var m = new MemoryStream(glb.Array, glb.Offset, glb.Count, false))
+            {
+                return ReadGLB(m, new ReadSettings());
+            }
+        }
+
+        /// <summary>
+        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB or a GLTF file.
+        /// </summary>
+        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
+        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL Read(Stream stream, ReadSettings settings)
+        {
+            bool binaryFile = glb._Identify(stream);
+
+            if (binaryFile) return ReadGLB(stream, settings);
+            else            return ReadGLTF(stream, settings);
+        }
+
+        /// <summary>
+        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLTF file.
+        /// </summary>
+        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
+        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL ReadGLTF(Stream stream, ReadSettings settings)
+        {
+            Guard.NotNull(stream, nameof(stream));
+            Guard.NotNull(settings, nameof(settings));
+
+            string content = null;
+
+            using (var streamReader = new StreamReader(stream))
+            {
+                content = streamReader.ReadToEnd();
+            }
+
+            return ParseGLTF(content, settings);
+        }
+
+        /// <summary>
+        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB file.
+        /// </summary>
+        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
+        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL ReadGLB(Stream stream, ReadSettings settings)
+        {
+            Guard.NotNull(stream, nameof(stream));
+            Guard.NotNull(settings, nameof(settings));
+
+            var chunks = glb.ReadBinaryFile(stream);
+
+            var dom = Encoding.UTF8.GetString(chunks[glb.CHUNKJSON]);
+
+            if (chunks.ContainsKey(glb.CHUNKBIN))
+            {
+                settings.FileReader = key => string.IsNullOrEmpty(key) ? new BYTES(chunks[glb.CHUNKBIN]) : settings.FileReader.Invoke(key);
+            }
+
+            return ParseGLTF(dom, settings);
+        }
+
+        /// <summary>
+        /// Parses a <see cref="MODEL"/> instance from a <see cref="String"/> JSON content representing a GLTF file.
+        /// </summary>
+        /// <param name="jsonContent">A <see cref="String"/> JSON content representing a GLTF file.</param>
+        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
+        /// <returns>A <see cref="MODEL"/> instance.</returns>
+        public static MODEL ParseGLTF(String jsonContent, ReadSettings settings)
+        {
+            Guard.NotNullOrEmpty(jsonContent, nameof(jsonContent));
+            Guard.NotNull(settings, nameof(settings));
+
+            using (var tr = new StringReader(jsonContent))
+            {
+                return _Read(tr, settings);
+            }
+        }
+
+        public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
+        {
+            var jsonBytes = files[fileName];
+
+            var settings = new ReadSettings(fn => files[fn]);
+
+            using (var m = new MemoryStream(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count))
+            {
+                using (var s = new StreamReader(m))
+                {
+                    return _Read(s, settings);
+                }
+            }
+        }
+
+        private static MODEL _Read(TextReader textReader, ReadSettings settings)
+        {
+            Guard.NotNull(textReader, nameof(textReader));
+            Guard.NotNull(settings, nameof(settings));
+
+            using (var reader = new JsonTextReader(textReader))
+            {
+                var root = new MODEL();
+
+                try
+                {
+                    reader.Read();
+                    root.Deserialize(reader);
+                }
+                catch (JsonReaderException rex)
+                {
+                    throw new Validation.SchemaException(root, rex);
+                }
+
+                foreach (var buffer in root._buffers)
+                {
+                    buffer._ResolveUri(settings.FileReader);
+                }
+
+                foreach (var image in root._images)
+                {
+                    image._ResolveUri(settings.FileReader);
+                }
+
+                var ex = root.Validate().FirstOrDefault();
+                if (ex != null) throw ex;
+
+                return root;
+            }
+        }
+    }
+}

+ 7 - 209
src/SharpGLTF.Core/Schema2/gltf.Serialization.cs → src/SharpGLTF.Core/Schema2/gltf.Serialization.Write.cs

@@ -11,49 +11,6 @@ namespace SharpGLTF.Schema2
     using BYTES = ArraySegment<Byte>;
     using MODEL = ModelRoot;
 
-    /// <summary>
-    /// Callback used for loading associated files of current model.
-    /// </summary>
-    /// <param name="assetName">the asset relative path.</param>
-    /// <returns>The file contents as a <see cref="byte"/> array.</returns>
-    public delegate BYTES AssetReader(String assetName);
-
-    /// <summary>
-    /// Callback used for saving associated files of the current model.
-    /// </summary>
-    /// <param name="assetName">The asset relative path.</param>
-    /// <param name="assetData">The file contents as a <see cref="byte"/> array.</param>
-    public delegate void AssetWriter(String assetName, BYTES assetData);
-
-    /// <summary>
-    /// Configuration settings for reading model files.
-    /// </summary>
-    public class ReadSettings
-    {
-        public ReadSettings()
-        {
-        }
-
-        internal ReadSettings(AssetReader reader)
-        {
-            FileReader = reader;
-        }
-
-        internal ReadSettings(string filePath)
-        {
-            Guard.FilePathMustExist(filePath, nameof(filePath));
-
-            var dir = Path.GetDirectoryName(filePath);
-
-            FileReader = assetFileName => new BYTES(File.ReadAllBytes(Path.Combine(dir, assetFileName)));
-        }
-
-        /// <summary>
-        /// Gets or sets the <see cref="AssetReader"/> delegate used to read satellite files.
-        /// </summary>
-        public AssetReader FileReader { get; set; }
-    }
-
     /// <summary>
     /// Determines the way in which <see cref="Image"/> instances are stored.
     /// </summary>
@@ -75,6 +32,13 @@ namespace SharpGLTF.Schema2
         Embedded
     }
 
+    /// <summary>
+    /// Callback used for saving associated files of the current model.
+    /// </summary>
+    /// <param name="assetName">The asset relative path.</param>
+    /// <param name="assetData">The file contents as a <see cref="byte"/> array.</param>
+    public delegate void AssetWriter(String assetName, BYTES assetData);
+
     /// <summary>
     /// Configuration settings for writing model files.
     /// </summary>
@@ -259,170 +223,6 @@ namespace SharpGLTF.Schema2
 
     partial class ModelRoot
     {
-        #region Read API
-
-        /// <summary>
-        /// Reads a <see cref="MODEL"/> instance from a path pointing to a GLB or a GLTF file
-        /// </summary>
-        /// <param name="filePath">A valid file path.</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL Load(string filePath)
-        {
-            Guard.FilePathMustExist(filePath, nameof(filePath));
-
-            var settings = new ReadSettings(filePath);
-
-            using (var s = File.OpenRead(filePath))
-            {
-                return Read(s, settings);
-            }
-        }
-
-        /// <summary>
-        /// Parses a <see cref="MODEL"/> instance from a <see cref="byte"/> array representing a GLB file
-        /// </summary>
-        /// <param name="glb">A <see cref="byte"/> array representing a GLB file</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL ParseGLB(BYTES glb)
-        {
-            Guard.NotNull(glb, nameof(glb));
-
-            using (var m = new MemoryStream(glb.Array, glb.Offset, glb.Count, false))
-            {
-                return ReadGLB(m, new ReadSettings());
-            }
-        }
-
-        /// <summary>
-        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB or a GLTF file.
-        /// </summary>
-        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
-        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL Read(Stream stream, ReadSettings settings)
-        {
-            bool binaryFile = glb._Identify(stream);
-
-            if (binaryFile) return ReadGLB(stream, settings);
-            else            return ReadGLTF(stream, settings);
-        }
-
-        /// <summary>
-        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLTF file.
-        /// </summary>
-        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
-        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL ReadGLTF(Stream stream, ReadSettings settings)
-        {
-            Guard.NotNull(stream, nameof(stream));
-            Guard.NotNull(settings, nameof(settings));
-
-            string content = null;
-
-            using (var streamReader = new StreamReader(stream))
-            {
-                content = streamReader.ReadToEnd();
-            }
-
-            return ParseGLTF(content, settings);
-        }
-
-        /// <summary>
-        /// Reads a <see cref="MODEL"/> instance from a <see cref="Stream"/> containing a GLB file.
-        /// </summary>
-        /// <param name="stream">A <see cref="Stream"/> to read from.</param>
-        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL ReadGLB(Stream stream, ReadSettings settings)
-        {
-            Guard.NotNull(stream, nameof(stream));
-            Guard.NotNull(settings, nameof(settings));
-
-            var chunks = glb.ReadBinaryFile(stream);
-
-            var dom = Encoding.UTF8.GetString(chunks[glb.CHUNKJSON]);
-
-            if (chunks.ContainsKey(glb.CHUNKBIN))
-            {
-                settings.FileReader = key => string.IsNullOrEmpty(key) ? new BYTES(chunks[glb.CHUNKBIN]) : settings.FileReader.Invoke(key);
-            }
-
-            return ParseGLTF(dom, settings);
-        }
-
-        /// <summary>
-        /// Parses a <see cref="MODEL"/> instance from a <see cref="String"/> JSON content representing a GLTF file.
-        /// </summary>
-        /// <param name="jsonContent">A <see cref="String"/> JSON content representing a GLTF file.</param>
-        /// <param name="settings">A <see cref="ReadSettings"/> instance defining the reading options.</param>
-        /// <returns>A <see cref="MODEL"/> instance.</returns>
-        public static MODEL ParseGLTF(String jsonContent, ReadSettings settings)
-        {
-            Guard.NotNullOrEmpty(jsonContent, nameof(jsonContent));
-            Guard.NotNull(settings, nameof(settings));
-
-            using (var tr = new StringReader(jsonContent))
-            {
-                return _Read(tr, settings);
-            }
-        }
-
-        public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
-        {
-            var jsonBytes = files[fileName];
-
-            var settings = new ReadSettings(fn => files[fn]);
-
-            using (var m = new MemoryStream(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count))
-            {
-                using (var s = new StreamReader(m))
-                {
-                    return _Read(s, settings);
-                }
-            }
-        }
-
-        private static MODEL _Read(TextReader textReader, ReadSettings settings)
-        {
-            Guard.NotNull(textReader, nameof(textReader));
-            Guard.NotNull(settings, nameof(settings));
-
-            using (var reader = new JsonTextReader(textReader))
-            {
-                var root = new MODEL();
-
-                try
-                {
-                    reader.Read();
-                    root.Deserialize(reader);
-                }
-                catch (JsonReaderException rex)
-                {
-                    throw new Validation.SchemaException(root, rex);
-                }
-
-                foreach (var buffer in root._buffers)
-                {
-                    buffer._ResolveUri(settings.FileReader);
-                }
-
-                foreach (var image in root._images)
-                {
-                    image._ResolveUri(settings.FileReader);
-                }
-
-                var ex = root.Validate().FirstOrDefault();
-                if (ex != null) throw ex;
-
-                return root;
-            }
-        }
-
-        #endregion
-
-        #region Write API
-
         // TODO: usually when we save the gltf file, we need to amend/fix several features,
         // which goes against good practices of not modifying any file when it is being saved.
         // a possible solution would be to do a shallow copy of RootObject and update Buffers, BufferViews, etc
@@ -608,7 +408,5 @@ namespace SharpGLTF.Schema2
             foreach (var b in model._buffers) b._ClearAfterWrite();
             foreach (var i in model._images) i._ClearAfterWrite();
         }
-
-        #endregion
     }
 }

+ 4 - 13
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -112,18 +112,7 @@ namespace SharpGLTF.Geometry
 
         public IReadOnlyList<int> Indices => _Indices;
 
-        public IEnumerable<(int, int, int)> Triangles
-        {
-            get
-            {
-                // TODO: use Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices()
-
-                for (int i = 2; i < _Indices.Count; i += 3)
-                {
-                    yield return (_Indices[i - 2], _Indices[i - 1], _Indices[i]);
-                }
-            }
-        }
+        public IEnumerable<(int, int, int)> Triangles => Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
 
         #endregion
 
@@ -160,10 +149,12 @@ namespace SharpGLTF.Geometry
             // check for degenerated triangles:
             if (aa == bb || aa == cc || bb == cc)
             {
-                if (_Scrict) throw new ArgumentException($"Invalid triangle {aa} {bb} {cc}");
+                if (_Scrict) throw new ArgumentException($"Invalid triangle indices {aa} {bb} {cc}");
                 return;
             }
 
+            // TODO: check if a triangle with indices aa-bb-cc already exists.
+
             _Indices.Add(aa);
             _Indices.Add(bb);
             _Indices.Add(cc);

+ 6 - 6
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPosition.cs

@@ -73,7 +73,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public void Validate()
         {
-            if (!Position._IsReal()) throw new NotFiniteNumberException(nameof(Position));
+            Position.Validate(nameof(Position));
         }
 
         #endregion
@@ -133,8 +133,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public void Validate()
         {
-            if (!Position._IsReal()) throw new NotFiniteNumberException(nameof(Position));
-            if (!Normal._IsReal()) throw new NotFiniteNumberException(nameof(Normal));
+            Position.Validate(nameof(Position));
+            Normal.ValidateNormal(nameof(Normal));
         }
 
         #endregion
@@ -196,9 +196,9 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public void Validate()
         {
-            if (!Position._IsReal()) throw new NotFiniteNumberException(nameof(Position));
-            if (!Normal._IsReal()) throw new NotFiniteNumberException(nameof(Normal));
-            if (!Tangent._IsReal()) throw new NotFiniteNumberException(nameof(Tangent));
+            Position.Validate(nameof(Position));
+            Normal.ValidateNormal(nameof(Normal));
+            Tangent.ValidateTangent(nameof(Tangent));
         }
 
         #endregion

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

@@ -40,7 +40,7 @@ namespace SharpGLTF.Geometry
             // break the file
             json = json.Substring(0, json.Length - 40);
 
-            Assert.Throws<Newtonsoft.Json.JsonReaderException>(() => Schema2.ModelRoot.ParseGLTF(json, new Schema2.ReadSettings()));
+            Assert.Throws<Validation.SchemaException>(() => Schema2.ModelRoot.ParseGLTF(json, new Schema2.ReadSettings()));
         }
     }
 }

+ 1 - 1
tests/SharpGLTF.Tests/SharpGLTF.Tests.csproj

@@ -10,7 +10,7 @@
 
   <ItemGroup>
     <PackageReference Include="LibGit2Sharp" Version="0.26.0" />
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0-beta4-final" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" />
     <PackageReference Include="nunit" Version="3.11.0" />
     <PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />