ソースを参照

Adding OBJ exporter

Deltakosh 12 年 前
コミット
579a8ac0e4

+ 0 - 1
.gitignore

@@ -1,6 +1,5 @@
 # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
 [Bb]in/
-[Oo]bj/
 
 # mstest test results
 TestResults

+ 62 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/Document.cs

@@ -0,0 +1,62 @@
+
+using System.Collections.Generic;
+using System.IO;
+using System.Collections.ObjectModel;
+
+namespace BabylonExport
+{
+    internal class Document<T> where T : Line, new()
+    {
+        ReadOnlyCollection<List<T>> blocks;
+
+        public Document(string text)
+        {
+            int lineIndex = 0;
+            List<T> tempLines = null;
+            List<List<T>> tempBlocks = new List<List<T>>();
+            string previousHeader = "";
+            var lines = text.Split('\n');
+
+            for (int index = 0; index < lines.Length; index++)
+            {
+                T line = new T { Index = lineIndex++ };
+
+                string lineContent = "";
+
+                bool multiline;
+                do
+                {
+                    multiline = false;
+                    lineContent += lines[index].Trim();
+                    if (lineContent.EndsWith("\\"))
+                    {
+                        multiline = true;
+                        lineContent = lineContent.Substring(0, lineContent.Length - 1);
+                        index++;
+                    }
+                }
+                while (multiline);
+
+                line.SetLine(lineContent);
+
+                if (!line.IsValid)
+                    continue;
+
+                if ((line.Tokens[0] == line.BlockSperator && previousHeader != line.BlockSperator) || tempLines == null)
+                {
+                    tempLines = new List<T>();
+                    tempBlocks.Add(tempLines);
+                }
+                tempLines.Add(line);
+                previousHeader = line.Tokens[0];
+            }
+
+            blocks = new ReadOnlyCollection<List<T>>(tempBlocks);
+        }
+
+        public ReadOnlyCollection<List<T>> Blocks
+        {
+            get { return blocks; }
+        }
+    }
+}

+ 73 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/Line.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Globalization;
+using SharpDX;
+
+namespace BabylonExport
+{
+    internal abstract class Line
+    {
+        string[] tokens;
+
+        public bool IsValid
+        {
+            get
+            {
+                return Tokens.Length > 0 && !string.IsNullOrEmpty(Tokens[0]);
+            }
+        }
+
+        public bool IsComment
+        {
+            get
+            {
+                return Tokens[0].StartsWith("#");
+            }
+        }
+
+        public int Index { get; internal set; }
+
+        public abstract string BlockSperator { get; }
+
+        public string[] Tokens
+        {
+            get { return tokens; }
+        }
+
+        internal void SetLine(string line)
+        {
+            char[] blank = { ' ', '\t' };
+            tokens = line.Split(blank, StringSplitOptions.RemoveEmptyEntries);
+        }
+
+        public float ToFloat()
+        {
+            return float.Parse(Tokens[1], CultureInfo.InvariantCulture);
+        }
+
+        public Vector2 ToVector2()
+        {
+            return new Vector2(float.Parse(Tokens[1], CultureInfo.InvariantCulture),
+                               float.Parse(Tokens[2], CultureInfo.InvariantCulture));
+        }
+
+        public Vector3 ToVector3()
+        {
+            return new Vector3(float.Parse(Tokens[1], CultureInfo.InvariantCulture),
+                               float.Parse(Tokens[2], CultureInfo.InvariantCulture),
+                               float.Parse(Tokens[3], CultureInfo.InvariantCulture));
+        }
+
+
+        public Color3 ToColor()
+        {
+            return new Color3(float.Parse(Tokens[1], CultureInfo.InvariantCulture),
+                             float.Parse(Tokens[2], CultureInfo.InvariantCulture),
+                             float.Parse(Tokens[3], CultureInfo.InvariantCulture));
+        }
+
+        public override string ToString()
+        {
+            return string.Join(" ", Tokens);
+        }
+    }
+}

+ 25 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/MtlHeader.cs

@@ -0,0 +1,25 @@
+namespace BabylonExport
+{
+    internal enum MtlHeader
+    {
+        None,
+        Comment,
+        Material,
+        DiffuseColor,
+        DiffuseTexture,
+        SpecularColor,
+        SpecularTexture,
+        SpecularPower,
+        EmissiveColor,
+        Alpha,
+        IlluminationModel,
+        BumpTexture,
+        ReflectionTexture,
+        TransparencyTexture,
+        AmbientTexture,
+        AmbientColor,
+        RefractionIndex,
+        Transparency,
+        TransmissionFiter,
+    }
+}

+ 60 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/MtlLine.cs

@@ -0,0 +1,60 @@
+using System;
+
+namespace BabylonExport
+{
+    internal class MtlLine : Line
+    {
+        public override string BlockSperator { get { return "newmtl"; }}
+
+        public MtlHeader Header
+        {
+            get
+            {
+                if (IsComment)
+                    return MtlHeader.Comment;
+
+                string lineType = Tokens[0].ToLower();
+                switch (lineType)
+                {
+                    case "newmtl":
+                        return MtlHeader.Material;
+                    case "kd":
+                        return MtlHeader.DiffuseColor;
+                    case "map_kd":
+                        return MtlHeader.DiffuseTexture;
+                    case "ks":
+                        return MtlHeader.SpecularColor;
+                    case "ns":
+                        return MtlHeader.SpecularPower;
+                    case "map_ks":
+                        return MtlHeader.SpecularTexture;
+                    case "ke":
+                        return MtlHeader.EmissiveColor;
+                    case "d":
+                        return MtlHeader.Alpha;
+                    case "illum":
+                        return MtlHeader.IlluminationModel;
+                    case "ni":
+                        return MtlHeader.RefractionIndex;
+                    case "tr":
+                        return MtlHeader.Transparency;
+                    case "map_d":
+                        return MtlHeader.TransparencyTexture;
+                    case "tf":
+                        return MtlHeader.TransmissionFiter;
+                    case "ka":
+                        return MtlHeader.AmbientColor;
+                    case "map_ka":
+                        return MtlHeader.AmbientTexture;
+                    case "bump":
+                    case "map_bump":
+                        return MtlHeader.BumpTexture;
+                    case "map_refl":
+                        return MtlHeader.ReflectionTexture;
+                }
+
+                throw new Exception(string.Format("Unsupported line type [{0}] at line {1}", lineType, Index));
+            }
+        }
+    }
+}

+ 64 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/ObjExporter.Material.cs

@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.IO;
+using BabylonExport;
+using SharpDX;
+
+namespace BabylonExport.Exporters
+{
+    public partial class ObjExporter
+    {
+        readonly List<StandardMaterial> materials = new List<StandardMaterial>();
+
+        void ImportMaterialLibraries(ObjLine materialLine, BabylonScene scene)
+        {
+            for (int i = 1; i < materialLine.Tokens.Length; i++)
+            {
+                string fileName = materialLine.Tokens[i];
+
+                var mtlDocument = new Document<MtlLine>(File.ReadAllText(fileName));
+
+                StandardMaterial currentMaterial = null;
+
+                foreach (var lines in mtlDocument.Blocks)
+                {
+                    foreach (MtlLine line in lines)
+                    {
+
+                        switch (line.Header)
+                        {
+                            case MtlHeader.Material:
+                                currentMaterial = new StandardMaterial(line.Tokens[1]);
+                                currentMaterial.BackFaceCulling = false;
+                                materials.Add(currentMaterial);
+                                break;
+                            case MtlHeader.DiffuseColor:
+                                currentMaterial.Diffuse = line.ToColor();
+                                break;
+                            case MtlHeader.DiffuseTexture:
+                                currentMaterial.Diffuse = new Color3(1, 1, 1);
+                                currentMaterial.DiffuseTexture = line.Tokens[1].Replace("//", @"\");
+                                break;
+                            case MtlHeader.Alpha:
+                                currentMaterial.Alpha = line.ToFloat();
+                                break;
+                            case MtlHeader.EmissiveColor:
+                                currentMaterial.Emissive = line.ToColor();
+                                break;
+                            case MtlHeader.SpecularColor:
+                                currentMaterial.Specular = line.ToColor();
+                                break;
+                            case MtlHeader.SpecularPower:
+                                currentMaterial.SpecularPower = line.ToFloat();
+                                break;
+                        }
+                    }
+                }
+            }
+
+            foreach (var material in materials)
+            {
+                material.CreateBabylonMaterial(scene);
+            }
+        }
+    }
+}

+ 254 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/ObjExporter.cs

@@ -0,0 +1,254 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System;
+using System.Linq;
+using System.Runtime.Serialization.Json;
+using SharpDX;
+using BabylonExport;
+
+namespace BabylonExport.Exporters
+{
+    public partial class ObjExporter : IExporter
+    {
+        readonly List<Vector3> positions = new List<Vector3>();
+        readonly List<Vector3> normals = new List<Vector3>();
+        readonly List<Vector2> textureCoordinates = new List<Vector2>();
+        readonly List<PositionNormalTextured> stagingVertices = new List<PositionNormalTextured>();
+        readonly List<int> stagingIndices = new List<int>();
+        readonly Dictionary<string, int> registeredVertices = new Dictionary<string, int>();
+        readonly Dictionary<StandardMaterial, Mesh<PositionNormalTextured>> meshParts = new Dictionary<StandardMaterial, Mesh<PositionNormalTextured>>();
+
+        int positionsIndexOffset = 1;
+        int normalsIndexOffset = 1;
+        int textureCoordinatesIndexOffset = 1;
+
+        StandardMaterial defaultMaterial;
+
+        public event Action<int> OnImportProgressChanged;
+
+        public string SupportedExtensions
+        {
+            get
+            {
+                return ".obj";
+            }
+        }
+
+        public void GenerateBabylonFile(string file, string outputFile, bool skinned)
+        {
+            var text = File.ReadAllText(file);
+
+            Environment.CurrentDirectory = Path.GetDirectoryName(file);
+
+            // Scene
+            var scene = new BabylonScene(Path.GetDirectoryName(outputFile));
+
+            // Loading
+            var objDocument = new Document<ObjLine>(text);
+
+            materials.Clear();
+
+            int currentProgression = 0;
+
+            int total = objDocument.Blocks.Sum(lines => lines.Count);
+
+            string currentName = "";
+            StandardMaterial currentMaterial = null;
+
+            foreach (var lines in objDocument.Blocks)
+            {
+                if (lines[0].Tokens[0] == lines[0].BlockSperator)
+                {
+                    AppendNewPart(currentName, currentMaterial);
+                }
+                foreach (ObjLine line in lines)
+                {
+                    currentProgression++;
+                    ReportProgressChanged((currentProgression * 100) / total);
+
+                    switch (line.Header)
+                    {
+                        case ObjHeader.Vertices:
+                            positions.Add(line.ToVector3());
+                            break;
+                        case ObjHeader.TextureCoordinates:
+                            Vector2 tv = line.ToVector2();
+                            textureCoordinates.Add(tv);
+                            break;
+                        case ObjHeader.Normals:
+                            normals.Add(line.ToVector3());
+                            break;
+                        case ObjHeader.Group:
+                            currentName = line.Tokens[1];
+                            break;
+                        case ObjHeader.Faces:
+                            AppendFace(line);
+                            break;
+                        case ObjHeader.MaterialLibrary:
+                            ImportMaterialLibraries(line, scene);
+                            break;
+                        case ObjHeader.Material:
+                            string materialName = line.Tokens[1];
+                            currentMaterial = materials.FirstOrDefault(e => e.Name == materialName);
+                            break;
+                    }
+                }
+            }
+
+            if (positions.Count > 0)
+            {
+                AppendNewPart(currentName, currentMaterial);
+            }
+
+            if (meshParts.Count > 1)
+            {
+                var proxyID = ProxyMesh.CreateBabylonMesh(currentName, scene);
+                foreach (Mesh<PositionNormalTextured> part in meshParts.Values)
+                {
+                    part.CreateBabylonMesh(scene, proxyID);
+                }
+            }
+            else
+            {
+                meshParts.Values.First().CreateBabylonMesh(scene);
+            }
+
+            // Output
+            scene.Prepare();
+            using (var outputStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
+            {
+                var ser = new DataContractJsonSerializer(typeof (BabylonScene));
+                ser.WriteObject(outputStream, scene);
+            }
+        }
+
+        void ReportProgressChanged(int progress)
+        {
+            if (OnImportProgressChanged != null)
+            {
+                OnImportProgressChanged(progress);
+            }
+        }
+
+        void AppendFace(ObjLine line)
+        {
+            // Line
+            if (line.Tokens.Length == 3)
+            {
+                AppendIndex(1, line);
+                AppendIndex(2, line);
+                AppendIndex(2, line);
+
+                return;
+            }
+
+            // Triangle
+            if (line.Tokens.Length == 4)
+            {
+                for (int index = 1; index <= 3; index++)
+                {
+                    AppendIndex(index, line);
+                }
+
+                return;
+            }
+
+            // Quad
+            if (line.Tokens.Length == 5)
+            {
+                for (int index = 1; index <= 3; index++)
+                {
+                    AppendIndex(index, line);
+                }
+
+                AppendIndex(1, line);
+                AppendIndex(3, line);
+                AppendIndex(4, line);
+            }
+        }
+
+        void AppendIndex(int index, ObjLine line)
+        {
+            string[] indices = line.Tokens[index].Split('/');
+
+            // Required: Position
+            int positionIndex = int.Parse(indices[0], CultureInfo.InvariantCulture) - positionsIndexOffset;
+            int texCoordIndex = -1;
+            int normalIndex = -1;
+
+            // Optional: Texture coordinate
+            if (indices.Length > 1 && indices[1].Equals(string.Empty) == false)
+            {
+                texCoordIndex = int.Parse(indices[1]) - textureCoordinatesIndexOffset;
+            }
+
+            // Optional: Normal
+            if (indices.Length > 2 && indices[2].Equals(string.Empty) == false)
+            {
+                normalIndex = int.Parse(indices[2]) - normalsIndexOffset;
+            }
+
+            // Build vertex
+            var vertex = new PositionNormalTextured { Position = positions[positionIndex] };
+
+            if (texCoordIndex >= 0)
+                vertex.TextureCoordinates = textureCoordinates[texCoordIndex];
+
+            if (normalIndex >= 0)
+                vertex.Normal = normals[normalIndex];
+
+            // check if the vertex does not already exists
+            string hash = vertex.ToString();
+            int vertexIndex;
+
+            if (!registeredVertices.TryGetValue(hash, out vertexIndex))
+            {
+                stagingVertices.Add(vertex);
+                vertexIndex = stagingVertices.Count - 1;
+                registeredVertices.Add(hash, vertexIndex);
+            }
+
+            stagingIndices.Add(vertexIndex);
+        }
+
+        void AppendNewPart(string name, StandardMaterial currentMaterial)
+        {
+            if (stagingVertices.Count == 0)
+                return;
+
+            if (currentMaterial == null)
+            {
+                if (defaultMaterial == null)
+                {
+                    defaultMaterial = new StandardMaterial("empty");
+                }
+
+                currentMaterial = defaultMaterial;
+            }
+
+            Mesh<PositionNormalTextured> part;
+
+            if (!meshParts.TryGetValue(currentMaterial, out part))
+            {
+                part = new Mesh<PositionNormalTextured>(currentMaterial == defaultMaterial ? null : currentMaterial);
+                meshParts.Add(currentMaterial, part);
+            }
+
+            part.AddPart(name, stagingVertices, stagingIndices);
+
+            positionsIndexOffset += positions.Count;
+            positions.Clear();
+
+            normalsIndexOffset += normals.Count;
+            normals.Clear();
+
+            textureCoordinatesIndexOffset += textureCoordinates.Count;
+            textureCoordinates.Clear();
+
+            stagingVertices.Clear();
+            stagingIndices.Clear();
+            registeredVertices.Clear();
+        }
+    }
+}

+ 18 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/ObjHeader.cs

@@ -0,0 +1,18 @@
+namespace BabylonExport
+{
+    internal enum ObjHeader
+    {
+        None,
+        Comment,
+        Object,
+        Vertices,
+        TextureCoordinates,
+        Normals,
+        Group,
+        SmoothingGroup,
+        Line,
+        Faces,
+        MaterialLibrary,
+        Material,
+    }
+}

+ 44 - 0
Exporters/FBX - OBJ/BabylonExport/Exporters/OBJ/ObjLine.cs

@@ -0,0 +1,44 @@
+using System;
+
+namespace BabylonExport
+{
+    internal class ObjLine : Line
+    {
+        public override string BlockSperator { get { return "v"; } }
+        public ObjHeader Header
+        {
+            get
+            {
+                if (IsComment)
+                    return ObjHeader.Comment;
+
+                string lineType = Tokens[0].ToLower();
+                switch (lineType)
+                {
+                    case "o":
+                        return ObjHeader.Object;
+                    case "v":
+                        return ObjHeader.Vertices;
+                    case "vt":
+                        return ObjHeader.TextureCoordinates;
+                    case "vn":
+                        return ObjHeader.Normals;
+                    case "g":
+                        return ObjHeader.Group;
+                    case "s":
+                        return ObjHeader.None;
+                    case "l":
+                        return ObjHeader.Line;
+                    case "f":
+                        return ObjHeader.Faces;
+                    case "mtllib":
+                        return ObjHeader.MaterialLibrary;
+                    case "usemtl":
+                        return ObjHeader.Material;
+                }
+
+                throw new Exception(string.Format("Unsupported line type [{0}] at line {1}", lineType, Index));
+            }
+        }
+    }
+}