Browse Source

Added test to dump models geometry to wavefront

Vicente Penades 6 years ago
parent
commit
89af9c27c0

+ 15 - 0
src/SharpGLTF.DOM/Memory/Arrays.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
@@ -66,6 +67,20 @@ namespace SharpGLTF.Memory
 
 
     public static class EncodedArrayUtils
     public static class EncodedArrayUtils
     {
     {
+        public static IntegerArray IndicesRange(int start, int count)
+        {
+            var data = new Byte[count * 4];
+
+            var array = new IntegerArray(data, Schema2.IndexType.UNSIGNED_INT);
+
+            for (int i = 0; i < count; ++i)
+            {
+                array[i] = (UInt32)(start + i);
+            }
+
+            return array;
+        }
+
         public static void CopyFrom<T>(this IEncodedArray<T> dst, int index, params T[] src)
         public static void CopyFrom<T>(this IEncodedArray<T> dst, int index, params T[] src)
             where T : unmanaged
             where T : unmanaged
         {
         {

+ 2 - 2
src/SharpGLTF.DOM/Schema2/gltf.Accessors.cs

@@ -108,7 +108,7 @@ namespace SharpGLTF.Schema2
             _UpdateBounds();
             _UpdateBounds();
         }
         }
 
 
-        public Memory.Matrix4x4Array CastToMatrix4x4Accessor()
+        public Memory.Matrix4x4Array AsMatrix4x4Array()
         {
         {
             return _GetMemoryAccessor().AsMatrix4x4Array();
             return _GetMemoryAccessor().AsMatrix4x4Array();
         }
         }
@@ -143,7 +143,7 @@ namespace SharpGLTF.Schema2
             _UpdateBounds();
             _UpdateBounds();
         }
         }
 
 
-        public Memory.IntegerArray CastToIndicesAccessor()
+        public Memory.IntegerArray AsIndicesArray()
         {
         {
             Guard.IsFalse(this.IsSparse, nameof(IsSparse));
             Guard.IsFalse(this.IsSparse, nameof(IsSparse));
             Guard.IsTrue(this.Dimensions == ElementType.SCALAR, nameof(Dimensions));
             Guard.IsTrue(this.Dimensions == ElementType.SCALAR, nameof(Dimensions));

+ 1 - 1
src/SharpGLTF.DOM/Schema2/gltf.MeshPrimitive.cs

@@ -186,7 +186,7 @@ namespace SharpGLTF.Schema2
                 .ToArray();
                 .ToArray();
         }
         }
 
 
-        public Memory.IntegerArray GetIndices() => IndexAccessor.CastToIndicesAccessor();
+        public Memory.IntegerArray GetIndices() => IndexAccessor.AsIndicesArray();
 
 
         public Geometry.MemoryAccessor GetVertices(string attributeKey) => GetVertexAccessor(attributeKey)._GetMemoryAccessor();
         public Geometry.MemoryAccessor GetVertices(string attributeKey) => GetVertexAccessor(attributeKey)._GetMemoryAccessor();
 
 

+ 2 - 2
src/SharpGLTF.DOM/Schema2/gltf.Scene.cs

@@ -34,7 +34,7 @@ namespace SharpGLTF.Schema2
 
 
         public Node VisualParent => this.LogicalParent._GetVisualParentNode(this);
         public Node VisualParent => this.LogicalParent._GetVisualParentNode(this);
 
 
-        public IEnumerable<Node> VisualChildren => GetVisualChildren(0);
+        public IEnumerable<Node> VisualChildren => GetVisualChildren();
 
 
         public Boolean IsSkinJoint => Skin.GetSkinsUsing(this).Any();
         public Boolean IsSkinJoint => Skin.GetSkinsUsing(this).Any();
 
 
@@ -146,7 +146,7 @@ namespace SharpGLTF.Schema2
 
 
         internal bool _HasVisualChild(int nodeIndex) { return _children.Contains(nodeIndex); }
         internal bool _HasVisualChild(int nodeIndex) { return _children.Contains(nodeIndex); }
 
 
-        public IEnumerable<Node> GetVisualChildren(int lod)
+        public IEnumerable<Node> GetVisualChildren()
         {
         {
             // TODO: handle MSFT_Lod here ?
             // TODO: handle MSFT_Lod here ?
             // maybe we can have a VisualHierarchyManager abstract class with a default implementation
             // maybe we can have a VisualHierarchyManager abstract class with a default implementation

+ 1 - 1
src/SharpGLTF.DOM/Schema2/gltf.Skin.cs

@@ -75,7 +75,7 @@ namespace SharpGLTF.Schema2
 
 
             var node = this.LogicalParent.LogicalNodes[nodeIdx];
             var node = this.LogicalParent.LogicalNodes[nodeIdx];
 
 
-            var matrix = (Matrix4x4)GetInverseBindMatricesAccessor().CastToMatrix4x4Accessor()[idx];
+            var matrix = (Matrix4x4)GetInverseBindMatricesAccessor().AsMatrix4x4Array()[idx];
 
 
             return new KeyValuePair<Node, Matrix4x4>(node, matrix);
             return new KeyValuePair<Node, Matrix4x4>(node, matrix);
         }
         }

+ 8 - 8
tests/SharpGLTF.Tests/Schema2/LoadModelTests.cs

@@ -22,10 +22,12 @@ namespace SharpGLTF.Schema2
         #region testing models of https://github.com/bghgary/glTF-Asset-Generator.git
         #region testing models of https://github.com/bghgary/glTF-Asset-Generator.git
 
 
         [Test]
         [Test]
-        public void GeneratedModelsLoadTest()
+        public void TestLoadGeneratedModels()
         {
         {
             foreach (var f in TestFiles.GetGeneratedFilePaths())
             foreach (var f in TestFiles.GetGeneratedFilePaths())
             {
             {
+                TestContext.Progress.WriteLine($"Processing {f.ToShortDisplayPath()}...");
+
                 var model = GltfUtils.LoadModel(f);
                 var model = GltfUtils.LoadModel(f);
 
 
                 Assert.NotNull(model);
                 Assert.NotNull(model);
@@ -35,7 +37,7 @@ namespace SharpGLTF.Schema2
         
         
         [TestCase(0)]        
         [TestCase(0)]        
         [TestCase(6)]
         [TestCase(6)]
-        public void LoadCompatibilityModelTest(int idx)
+        public void TestLoadCompatibleModels(int idx)
         {
         {
             var filePath = TestFiles.GetCompatibilityFilePath(idx);
             var filePath = TestFiles.GetCompatibilityFilePath(idx);
 
 
@@ -49,7 +51,7 @@ namespace SharpGLTF.Schema2
         [TestCase(3)]
         [TestCase(3)]
         [TestCase(4)]
         [TestCase(4)]
         [TestCase(5)]
         [TestCase(5)]
-        public void LoadInvalidModelsTest(int idx)
+        public void TestLoadInvalidModels(int idx)
         {
         {
             var filePath = TestFiles.GetCompatibilityFilePath(idx);
             var filePath = TestFiles.GetCompatibilityFilePath(idx);
 
 
@@ -70,21 +72,19 @@ namespace SharpGLTF.Schema2
         #region testing models of https://github.com/KhronosGroup/glTF-Sample-Models.git
         #region testing models of https://github.com/KhronosGroup/glTF-Sample-Models.git
 
 
         [Test]
         [Test]
-        public void SampleModelsLoadTest()
+        public void TestLoadSampleModels()
         {
         {
             foreach (var f in TestFiles.GetSampleFilePaths())
             foreach (var f in TestFiles.GetSampleFilePaths())
             {
             {
                 var root = GltfUtils.LoadModel(f);
                 var root = GltfUtils.LoadModel(f);
                 Assert.NotNull(root);
                 Assert.NotNull(root);
 
 
-                // var fileName = System.IO.Path.GetFileNameWithoutExtension(f);
-
-                // root.DefaultScene._DumpWavefrontToTest(f);
+                TestContext.CurrentContext.AttachToCurrentTestAsWavefrontObject(System.IO.Path.GetFileName(f), root);
             }
             }
         }
         }
 
 
         [Test]
         [Test]
-        public void MaterialSpecularGlossinessModelsLoadTest()
+        public void TestLoadSampleModelsWithMaterialSpecularGlossiness()
         {
         {
             foreach (var f in TestFiles.GetFilePathsWithSpecularGlossinessPBR())
             foreach (var f in TestFiles.GetFilePathsWithSpecularGlossinessPBR())
             {
             {

+ 107 - 0
tests/SharpGLTF.Tests/Schema2/ModelDumpUtils.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Schema2
+{
+    /// <summary>
+    /// Utility class that hierarchicaly traverses a gltf model, evaluates the geometry and yields a collection of worldspace triangles.
+    /// </summary>
+    static class ModelDumpUtils
+    {
+        public static void AttachToCurrentTestAsWavefrontObject(this NUnit.Framework.TestContext context, string fileName, ModelRoot model)
+        {
+            fileName = System.IO.Path.ChangeExtension(fileName, ".obj");
+
+            fileName = context.GetAttachmentPath(fileName, true);
+
+            var wavefront = model.ToWavefrontWriter().ToString();
+
+            System.IO.File.WriteAllText(fileName, wavefront);
+
+            NUnit.Framework.TestContext.AddTestAttachment(fileName);
+        }
+
+        public static WavefrontWriter ToWavefrontWriter(this ModelRoot model)
+        {
+            var writer = new WavefrontWriter();
+
+            foreach(var triangle in model.Triangulate())
+            {
+                writer.AddTriangle(triangle.Item1, triangle.Item2, triangle.Item3);
+            }
+
+            return writer;
+        }
+
+        /// <summary>
+        /// Yields all the triangles in the model's default scene, in world space
+        /// </summary>
+        /// <param name="model"The input model</param>
+        /// <returns>A collection of worldspace triangles</returns>
+        public static IEnumerable<(Vector3,Vector3,Vector3)> Triangulate(this ModelRoot model)
+        {
+            return model.DefaultScene.Triangulate();
+        }
+
+        /// <summary>
+        /// Yields all the triangles in the scene, in world space
+        /// </summary>
+        /// <param name="scene"The input scene</param>
+        /// <returns>A collection of worldspace triangles</returns>
+        public static IEnumerable<(Vector3, Vector3, Vector3)> Triangulate(this Scene scene)
+        {
+            return Node.Flatten(scene).SelectMany(item => item.Triangulate(true));
+        }
+
+        /// <summary>
+        /// Yields all the triangles in the node
+        /// </summary>
+        /// <param name="node">The input node</param>
+        /// <param name="inWorldSpace">true if we want to transform the local triangles to worldspace</param>
+        /// <returns>A collection of triangles in the node's space, or in worldspace</returns>
+        public static IEnumerable<(Vector3,Vector3,Vector3)> Triangulate(this Node node, bool inWorldSpace)
+        {
+            var mesh = node.Mesh;
+            if (mesh == null) return Enumerable.Empty<(Vector3, Vector3, Vector3)>();
+
+            var xform = inWorldSpace ? node.WorldMatrix : Matrix4x4.Identity;
+
+            return mesh.Primitives.SelectMany(item => item.Triangulate(xform));
+        }
+
+        /// <summary>
+        /// Yields all the triangles in the mesh
+        /// </summary>
+        /// <param name="prim">The input primitive</param>
+        /// <param name="xform">The transform matrix</param>
+        /// <returns>A collection of triangles transformed by <paramref name="xform"/> </returns>
+        public static IEnumerable<(Vector3, Vector3, Vector3)> Triangulate(this MeshPrimitive prim, Matrix4x4 xform)
+        {
+            var positions = prim.GetVertexAccessor("POSITION").AsVector3Array();
+
+            var indices = prim.IndexAccessor != null
+                ?
+                prim.IndexAccessor.AsIndicesArray()
+                :
+                Memory.EncodedArrayUtils.IndicesRange(0,positions.Count);
+
+            if (prim.DrawPrimitiveType != PrimitiveType.TRIANGLES) yield break;
+
+            for(int i=0; i< indices.Count; i+=3)
+            {
+                var a = (int)indices[i + 0];
+                var b = (int)indices[i + 1];
+                var c = (int)indices[i + 2];
+
+                var aa = Vector3.Transform(positions[a], xform);
+                var bb = Vector3.Transform(positions[b], xform);
+                var cc = Vector3.Transform(positions[c], xform);
+
+                yield return (aa, bb, cc);
+            }
+        }
+    }
+}

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

@@ -5,7 +5,7 @@
 
 
     <IsPackable>false</IsPackable>
     <IsPackable>false</IsPackable>
 
 
-    <RootNamespace>glTF2Sharp</RootNamespace>
+    <RootNamespace>SharpGLTF</RootNamespace>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 76 - 0
tests/SharpGLTF.Tests/WavefrontWriter.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using static System.FormattableString;
+
+namespace SharpGLTF
+{
+    /// <summary>
+    /// Tiny wavefront object writer
+    /// </summary>
+    class WavefrontWriter
+    {
+        #region data
+
+        private readonly List<Vector3> _Vertices = new List<Vector3>();
+        private readonly List<(int, int, int)> _Indices = new List<(int, int, int)>();
+        private readonly Dictionary<Vector3, int> _VertexCache = new Dictionary<Vector3, int>();
+
+        #endregion
+
+        #region API
+
+        private int _UseVertex(Vector3 v)
+        {
+            if (_VertexCache.TryGetValue(v, out int index)) return index;
+
+            index = _Vertices.Count;
+
+            _Vertices.Add(v);
+            _VertexCache[v] = index;
+
+            return index;
+        }
+
+        public void AddTriangle(Vector3 a, Vector3 b, Vector3 c)
+        {
+            var aa = _UseVertex(a);
+            var bb = _UseVertex(b);
+            var cc = _UseVertex(c);
+
+            // check for degenerated triangles:
+            if (aa == bb) return;
+            if (aa == cc) return;
+            if (bb == cc) return;
+
+            _Indices.Add((aa, bb, cc));
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+
+            sb.AppendLine();
+
+            foreach (var v in _Vertices)
+            {
+                sb.AppendLine(Invariant($"v {v.X} {v.Y} {v.Z}"));
+            }
+
+            sb.AppendLine();
+
+            sb.AppendLine("g default");
+
+            foreach (var p in _Indices)
+            {
+                sb.AppendLine(Invariant($"f {p.Item1 + 1} {p.Item2 + 1} {p.Item3 + 1}"));
+            }
+
+            return sb.ToString();
+        }
+
+        #endregion
+    }
+}