示例#1
0
 public IPXPmx Import(string path, IPERunArgs args)
 {
     try
     {
         form = new ObjImportForm(path, args);
         form.ShowDialog();
         if (form.DialogResult == DialogResult.OK)
         {
             //New importer parses OBJ/MTL files into IPX*-types
             ObjFileImporter importer = new ObjFileImporter(path, builder, form.Settings);
             //If there are errors, notify the user
             if (importer.ErrorNum + importer.ErrorNumMtl > 0)
             {
                 if (MessageBox.Show($"There have been errors during import:\n{importer.ErrorNum} while processing OBJ\n{importer.ErrorNumMtl} while processing MTL\n\nWould you like to open the log file to find out what happened?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
                 {
                     System.Diagnostics.Process.Start(importer.LogFileUrl);
                 }
             }
             return(importer.ToPmx());
         }
     }
     catch (Exception ex)
     {
         MessageBox.Show(ex.ToString());
     }
     //If execution reaches this point, either an error has occured or the user pressed Cancel.
     pmx = builder.Pmx();
     return(pmx);
 }
示例#2
0
        //DoWorkイベントハンドラ
        private void ProgressDialog_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;

            bw.ReportProgress(0, "ファイル読み込み中(バーは動きません (^-^;)");
            using (FileFormat.MQOFile mqo = FileFormat.MQOFile.load(mqopath, true)) // 三角面化して読み込む
            {
                if (mqo == null) throw new Exception("読み込み失敗。おそらくmqoファイルの構文エラー。");

                if (mqo.Object.Count == 0) throw new Exception("オブジェクトが空です。");

                // pmx作成
                bld = PEStaticBuilder.Pmx;
                pmx = bld.Pmx();
                pmx.Clear();

                // モデル名は最初のオブジェクト名を利用する
                pmx.ModelInfo.ModelName = mqo.Object[0].Name;

                // 材質
                int mc = mqo.Material.Count;
                if (mc == 0) throw new Exception("材質がありません。少なくとも1つ材質が必要です。");

                int cw = 100 / mc;
                int pc = 0;
                mqo.Material.ForEach(m =>
                {
                    bw.ReportProgress(cw * pc++, "材質の変換中");
                    IPXMaterial pm = bld.Material();
                    pm.Name = m.Name;
                    pm.Diffuse.R = (float)(m.Color.R * m.Diffuse);
                    pm.Diffuse.G = (float)(m.Color.G * m.Diffuse);
                    pm.Diffuse.B = (float)(m.Color.B * m.Diffuse);
                    pm.Diffuse.A = (float)m.Color.A;
                    pm.Ambient.R = (float)(m.Color.R * m.Ambient);
                    pm.Ambient.G = (float)(m.Color.G * m.Ambient);
                    pm.Ambient.B = (float)(m.Color.B * m.Ambient);
                    pm.Specular.R = (float)(m.Color.R * m.Specular);
                    pm.Specular.G = (float)(m.Color.G * m.Specular);
                    pm.Specular.B = (float)(m.Color.B * m.Specular);
                    pm.Power = (float)m.Power;
                    pm.Tex = m.Tex;
                    pmx.Material.Add(pm);
                });

                // 各オブジェクトを処理
                // ただし、非表示オブジェクトはスキップ
                mc = mqo.Object.Count;
                cw = 100 / mc;
                bw.ReportProgress(0, "法線を計算中");
                Parallel.ForEach(mqo.Object, mObj =>
                {
                    if (mObj.Visible) mObj.CalcNormals();
                    bw.ReportProgress(cw, 1);
                });

                // 先に頂点をすべて登録してから面を登録する
                // 頂点登録と面登録を交互に行うととんでもなく遅くなる
                mc = mqo.Material.Count;
                WorkFaceList workfacelist = new WorkFaceList(mc);
                WorkVertexDict workvertexdict = new WorkVertexDict();

                mc = mqo.Object.Count;
                cw = 100 / mc;
                pc = 0;
                for (int objID=0; objID<mc; objID++)
                {
                    var mObj = mqo.Object[objID];
                    bw.ReportProgress(cw * pc++, String.Format("'{0}'の変換中", mObj.Name));
                    mObj.Face.ForEach(fc =>
                    {
                        if (!mObj.Visible) return; // 非表示オブジェクトは無視

                        // 材質割り当てのない面は材質0として処理
                        int matID = fc.MatID < 0 ? 0 : fc.MatID;

                        Func<int, int> get_vertex = i => workvertexdict.RegistVertex(objID, fc.VertexID[i], fc.UVID[i], fc.NormalID[i]);
                        workfacelist.AddFace(matID, get_vertex(0), get_vertex(1), get_vertex(2));
                    });
                }

                workvertexdict.RegistToPmx(pmx, bld, mqo, bw);
                workfacelist.RegistToPmx(pmx, bld, mqo, workvertexdict, bw);
            }
        }
示例#3
0
        /// <summary>
        /// Parses the Wavefront Object file at the provided path and returns the operation's result.
        /// </summary>
        public static ImportResult Import(string path, IPXPmxBuilder builder, ImportSettings settings, IOProgress progress)
        {
            // Cancel the process if needed
            if (progress.CancellationToken.IsCancellationRequested)
            {
                progress.CancellationToken.ThrowIfCancellationRequested();
            }

            IPXPmx pmx = builder.Pmx();

            pmx.Clear();
            pmx.ModelInfo.ModelName = pmx.ModelInfo.ModelNameE = Path.GetFileNameWithoutExtension(path);
            pmx.ModelInfo.Comment   = pmx.ModelInfo.CommentE = "(Imported from OBJ by WPlugins.ObjIO)";
            StreamReader reader = null;

            System.Globalization.NumberFormatInfo fi = System.Globalization.NumberFormatInfo.InvariantInfo;
            System.Globalization.NumberStyles     ns = System.Globalization.NumberStyles.Float;

            // Model elements
            List <V3> vList  = new List <V3>();
            List <V2> vtList = new List <V2>();
            List <V3> vnList = new List <V3>();
            Dictionary <Tuple <int, int, int>, int> vertexDictionary = new Dictionary <Tuple <int, int, int>, int>();
            Dictionary <string, IPXMaterial>        materials        = new Dictionary <string, IPXMaterial>();
            IPXMaterial currentMaterial = null;

            // Values derived from settings
            V3 positionScale = new V3(settings.ScaleX, settings.ScaleY, settings.ScaleZ) * (settings.UseMetricUnits ? 0.254f : 0.1f);

            // Statistics
            int lineNumber = 0;

            try
            {
                reader = new StreamReader(path);
                char[] separator = { ' ' };
                while (!reader.EndOfStream)
                {
                    System.Threading.Thread.Sleep(2);

                    // Cancel the process if needed
                    if (progress.CancellationToken.IsCancellationRequested)
                    {
                        progress.CancellationToken.ThrowIfCancellationRequested();
                    }

                    string line = reader.ReadLine().Trim();
                    ++lineNumber;
                    ++progress.LineNumber;
                    progress.Report(IOProgress.Percent(reader.BaseStream.Position, reader.BaseStream.Length));

                    // Skip empty lines and comments
                    if (string.IsNullOrWhiteSpace(line) || line[0] == '#')
                    {
                        continue;
                    }

                    string[] split = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
                    switch (split[0])
                    {
                    // Vertex position
                    case "v":
                        try
                        {
                            float x = float.Parse(split[1], ns, fi);
                            float y = float.Parse(split[2], ns, fi);
                            float z = float.Parse(split[3], ns, fi);
                            vList.Add(new V3(x, y, -z));
                        }
                        catch (FormatException ex)
                        {
                            if (progress.ReportError(string.Format("A format exception has occured: {0}", line)))
                            {
                                return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                            }
                            vList.Add(new V3());
                        }
                        break;

                    // Vertex texture coordinates
                    case "vt":
                        try
                        {
                            // Technically this can be a V3 or any vector, but PMX only uses the first two elements for the main UV channel.
                            float x = float.Parse(split[1], ns, fi);
                            float y = float.Parse(split[2], ns, fi);
                            vtList.Add(new V2(x, -y));
                        }
                        catch (FormatException ex)
                        {
                            if (progress.ReportError(string.Format("A format exception has occured: {0}", line)))
                            {
                                return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                            }
                            vtList.Add(new V2());
                        }
                        break;

                    // Vertex normal
                    case "vn":
                        try
                        {
                            float x = float.Parse(split[1], ns, fi);
                            float y = float.Parse(split[2], ns, fi);
                            float z = float.Parse(split[3], ns, fi);
                            vnList.Add(new V3(x, y, -z));
                        }
                        catch (FormatException ex)
                        {
                            if (progress.ReportError(string.Format("A format exception has occured: {0}", line)))
                            {
                                return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                            }
                            vnList.Add(new V3());
                        }
                        break;

                    // Face definition
                    case "f":
                        if (currentMaterial == null)
                        {
                            progress.ReportWarning(string.Format("Encountered a face record when no active group was set.", lineNumber));
                            currentMaterial = builder.Material();
                        }

                        // Triangle
                        if (split.Length == 4)
                        {
                            int  v  = 0;
                            int  vt = 0;
                            int  vn = 0;
                            bool newVertex;

                            try
                            {
                                // Split each vertex assignment triple into its respective v/vt/vn indices.
                                GetVertexElements(split[1], out v, out vt, out vn);
                                // Based on the indices, determine if the vertex assignment is unique or already exists. A vertex is considered unique if one or more index is different, regardless of the vectors they represent.
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index1);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);       // The new vertex is added to the end of the list, making its index equal to the list's count before the addition.
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex1 = pmx.Vertex[index1];

                                // Repeat the same process for the rest of the vertex triples.
                                GetVertexElements(split[2], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index2);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex2 = pmx.Vertex[index2];

                                GetVertexElements(split[3], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index3);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex3 = pmx.Vertex[index3];

                                // Build the triangle and assign the vertices; use reverse order and negative normal vectors if the triangles are reversed.
                                IPXFace face = builder.Face();
                                if (settings.FlipFaces)
                                {
                                    vertex1.Normal *= -1;
                                    vertex2.Normal *= -1;
                                    vertex3.Normal *= -1;
                                    face.Vertex1    = vertex1;
                                    face.Vertex2    = vertex2;
                                    face.Vertex3    = vertex3;
                                }
                                else
                                {
                                    face.Vertex1 = vertex3;
                                    face.Vertex2 = vertex2;
                                    face.Vertex3 = vertex1;
                                }
                                currentMaterial.Faces.Add(face);
                            }
                            catch (Exception ex)
                            {
                                if (progress.ReportError(ex.ToString()))
                                {
                                    return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                                }
                            }
                        }
                        // Quad
                        else if (split.Length == 5)
                        {
                            int  v  = 0;
                            int  vt = 0;
                            int  vn = 0;
                            bool newVertex;
                            try
                            {
                                GetVertexElements(split[1], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index1);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex1 = pmx.Vertex[index1];

                                GetVertexElements(split[2], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index2);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex2 = pmx.Vertex[index2];

                                GetVertexElements(split[3], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index3);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex3 = pmx.Vertex[index3];

                                GetVertexElements(split[4], out v, out vt, out vn);
                                newVertex = GetUniqueVertex(v, vt, vn, vertexDictionary, pmx.Vertex.Count, out int index4);
                                if (newVertex)
                                {
                                    IPXVertex vert = builder.Vertex();
                                    pmx.Vertex.Add(vert);
                                    if (v >= 0)
                                    {
                                        vert.Position = vList[v] * positionScale;
                                    }
                                    if (vt >= 0)
                                    {
                                        vert.UV = vtList[vt];
                                    }
                                    if (vn >= 0)
                                    {
                                        vert.Normal = vnList[vn];
                                    }
                                }
                                IPXVertex vertex4 = pmx.Vertex[index4];

                                int     faceIndex1 = 0, faceIndex2 = 0;
                                IPXFace face = builder.Face();
                                if (settings.FlipFaces)
                                {
                                    face.Vertex3 = settings.TurnQuads ? vertex3 : vertex4;
                                    face.Vertex2 = vertex2;
                                    face.Vertex1 = vertex1;
                                    currentMaterial.Faces.Add(face);
                                    faceIndex1 = currentMaterial.Faces.Count - 1;

                                    face         = builder.Face();
                                    face.Vertex1 = settings.TurnQuads ? vertex1 : vertex2;
                                    face.Vertex2 = vertex3;
                                    face.Vertex3 = vertex4;
                                    currentMaterial.Faces.Add(face);
                                    faceIndex2 = currentMaterial.Faces.Count - 1;
                                }
                                else
                                {
                                    face.Vertex1 = settings.TurnQuads ? vertex3 : vertex4;
                                    face.Vertex2 = vertex2;
                                    face.Vertex3 = vertex1;
                                    currentMaterial.Faces.Add(face);
                                    faceIndex1 = currentMaterial.Faces.Count - 1;

                                    face         = builder.Face();
                                    face.Vertex3 = settings.TurnQuads ? vertex1 : vertex2;
                                    face.Vertex2 = vertex3;
                                    face.Vertex1 = vertex4;
                                    currentMaterial.Faces.Add(face);
                                    faceIndex2 = currentMaterial.Faces.Count - 1;
                                }

                                if (settings.SaveTrianglePairs)
                                {
                                    currentMaterial.Memo += string.Format("({0},{1})", faceIndex1, faceIndex2);
                                }
                            }
                            catch (Exception ex)
                            {
                                if (progress.ReportError(ex.ToString()))
                                {
                                    return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                                }
                            }
                        }
                        else
                        {
                            if (progress.ReportError(string.Format("The OBJ file contains a polygon with an invalid number of vertices. Currently only triangles and quads are supported. Line content: {0}", line)))
                            {
                                return(ImportResult.Fail(new InvalidOperationException("Invalid polygon"), progress.WarningCount, progress.ErrorCount));
                            }
                        }
                        break;

                    // Group assignment defines which PMX object (IPXMaterial instance) the subsequent faces belong to.
                    case "g":
                        currentMaterial      = builder.Material();
                        currentMaterial.Name = currentMaterial.NameE = line.Trim().Substring(2);
                        progress.Report("New object: " + currentMaterial.Name);
                        pmx.Material.Add(currentMaterial);
                        // Set default properties
                        currentMaterial.Diffuse = new V4(1, 1, 1, 1);
                        currentMaterial.Ambient = new V3(0.5f, 0.5f, 0.5f);
                        break;

                    // Material assignment defines which material template should be applied to the currently active PMX object. Any number of PMX objects can refer to a single material template.
                    case "usemtl":
                        if (currentMaterial == null)
                        {
                            progress.ReportWarning(string.Format("Encountered a material template reference when no active group was set.", lineNumber));
                            currentMaterial = builder.Material();
                        }
                        {
                            string      name = line.Trim().Substring(7);
                            IPXMaterial m    = currentMaterial;     // Active material
                            IPXMaterial t    = materials[name];     // Template material
                            m.Diffuse       = t.Diffuse;
                            m.Specular      = t.Specular;
                            m.Power         = t.Power;
                            m.Ambient       = t.Ambient;
                            m.Diffuse       = t.Diffuse;
                            m.SelfShadow    = t.SelfShadow;
                            m.SelfShadowMap = t.SelfShadowMap;
                            m.Shadow        = t.Shadow;
                            m.Tex           = t.Tex;
                            m.EdgeSize      = t.EdgeSize;
                            m.EdgeColor     = t.EdgeColor;
                            m.Edge          = t.Edge;
                        }
                        break;

                    // Material library, may occur multiple times in a model.
                    case "mtllib":
                        string materialLibraryName = line.Substring(7);
                        progress.Report("Importing materials from " + materialLibraryName);
                        // Try relative path
                        string materialLibraryPath = Path.Combine(Path.GetDirectoryName(path), materialLibraryName);
                        if (!File.Exists(materialLibraryPath))
                        {
                            // Try absolute path
                            materialLibraryPath = materialLibraryName;
                            if (!File.Exists(materialLibraryPath))
                            {
                                progress.ReportError(string.Format("Material library not found ({0}).", materialLibraryName));
                                break;
                            }
                        }
                        Dictionary <string, IPXMaterial> tempDict = ImportMaterials(materialLibraryPath, builder, settings, progress);
                        foreach (KeyValuePair <string, IPXMaterial> kvp in tempDict)
                        {
                            if (materials.ContainsKey(kvp.Key))
                            {
                                progress.ReportWarning(string.Format("Duplicate material {0} imported from {1} has been discarded.", kvp.Key, materialLibraryName));
                            }
                            else
                            {
                                materials.Add(kvp.Key, kvp.Value);
                            }
                        }
                        progress.Report(string.Format("Imported {0} materials from {1}.", tempDict.Count, materialLibraryName));
                        break;

                    // Smoothing group assignment (unused)
                    case "s":
                        break;

                    default:
                        break;
                    }
                }

                // Second pass for bone weights and transformations because I'm lazy
                IPXBone bone = null;
                if (settings.CreateBone != ImportSettings.CreateBoneMode.None)
                {
                    bone      = builder.Bone();
                    bone.Name = bone.NameE = pmx.ModelInfo.ModelName.Replace(' ', '_');
                }
                foreach (IPXVertex vertex in pmx.Vertex)
                {
                    // Bone
                    if (settings.CreateBone == ImportSettings.CreateBoneMode.Average)
                    {
                        bone.Position += vertex.Position;
                    }
                    vertex.Bone1   = settings.CreateBone != ImportSettings.CreateBoneMode.None ? bone : null;
                    vertex.Weight1 = 1.0f;
                    vertex.Bone2   = vertex.Bone3 = vertex.Bone4 = null;
                    vertex.Weight2 = vertex.Weight3 = vertex.Weight4 = 0;

                    // Axis swap
                    if (settings.SwapYZ)
                    {
                        float temp = vertex.Position.Y;
                        vertex.Position.Y = vertex.Position.Z;
                        vertex.Position.Z = temp;
                        temp            = vertex.Normal.Y;
                        vertex.Normal.Y = vertex.Normal.Z;
                        vertex.Normal.Z = temp;
                    }
                }
                if (settings.CreateBone == ImportSettings.CreateBoneMode.Average)
                {
                    bone.Position /= pmx.Vertex.Count;
                }
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                if (progress.ReportError(ex.ToString()))
                {
                    return(ImportResult.Fail(ex, progress.WarningCount, progress.ErrorCount));
                }
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader = null;
                }
            }

            return(ImportResult.Success(pmx, progress.WarningCount, progress.ErrorCount));
        }
示例#4
0
        //DoWorkイベントハンドラ
        private void ProgressDialog_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;

            bw.ReportProgress(0, "ファイル読み込み中(バーは動きません (^-^;)");
            using (FileFormat.MQOFile mqo = FileFormat.MQOFile.load(mqopath, true)) // 三角面化して読み込む
            {
                if (mqo == null)
                {
                    throw new Exception("読み込み失敗。おそらくmqoファイルの構文エラー。");
                }

                if (mqo.Object.Count == 0)
                {
                    throw new Exception("オブジェクトが空です。");
                }

                // pmx作成
                bld = PEStaticBuilder.Pmx;
                pmx = bld.Pmx();
                pmx.Clear();

                // モデル名は最初のオブジェクト名を利用する
                pmx.ModelInfo.ModelName = mqo.Object[0].Name;

                // 材質
                int mc = mqo.Material.Count;
                if (mc == 0)
                {
                    throw new Exception("材質がありません。少なくとも1つ材質が必要です。");
                }

                int cw = 100 / mc;
                int pc = 0;
                mqo.Material.ForEach(m =>
                {
                    bw.ReportProgress(cw * pc++, "材質の変換中");
                    IPXMaterial pm = bld.Material();
                    pm.Name        = m.Name;
                    pm.Diffuse.R   = (float)(m.Color.R * m.Diffuse);
                    pm.Diffuse.G   = (float)(m.Color.G * m.Diffuse);
                    pm.Diffuse.B   = (float)(m.Color.B * m.Diffuse);
                    pm.Diffuse.A   = (float)m.Color.A;
                    pm.Ambient.R   = (float)(m.Color.R * m.Ambient);
                    pm.Ambient.G   = (float)(m.Color.G * m.Ambient);
                    pm.Ambient.B   = (float)(m.Color.B * m.Ambient);
                    pm.Specular.R  = (float)(m.Color.R * m.Specular);
                    pm.Specular.G  = (float)(m.Color.G * m.Specular);
                    pm.Specular.B  = (float)(m.Color.B * m.Specular);
                    pm.Power       = (float)m.Power;
                    pm.Tex         = m.Tex;
                    pmx.Material.Add(pm);
                });

                // 各オブジェクトを処理
                // ただし、非表示オブジェクトはスキップ
                mc = mqo.Object.Count;
                cw = 100 / mc;
                bw.ReportProgress(0, "法線を計算中");
                Parallel.ForEach(mqo.Object, mObj =>
                {
                    if (mObj.Visible)
                    {
                        mObj.CalcNormals();
                    }
                    bw.ReportProgress(cw, 1);
                });

                // 先に頂点をすべて登録してから面を登録する
                // 頂点登録と面登録を交互に行うととんでもなく遅くなる
                mc = mqo.Material.Count;
                WorkFaceList   workfacelist   = new WorkFaceList(mc);
                WorkVertexDict workvertexdict = new WorkVertexDict();

                mc = mqo.Object.Count;
                cw = 100 / mc;
                pc = 0;
                for (int objID = 0; objID < mc; objID++)
                {
                    var mObj = mqo.Object[objID];
                    bw.ReportProgress(cw * pc++, String.Format("'{0}'の変換中", mObj.Name));
                    mObj.Face.ForEach(fc =>
                    {
                        if (!mObj.Visible)
                        {
                            return;                // 非表示オブジェクトは無視
                        }
                        // 材質割り当てのない面は材質0として処理
                        int matID = fc.MatID < 0 ? 0 : fc.MatID;

                        Func <int, int> get_vertex = i => workvertexdict.RegistVertex(objID, fc.VertexID[i], fc.UVID[i], fc.NormalID[i]);
                        workfacelist.AddFace(matID, get_vertex(0), get_vertex(1), get_vertex(2));
                    });
                }

                workvertexdict.RegistToPmx(pmx, bld, mqo, bw);
                workfacelist.RegistToPmx(pmx, bld, mqo, workvertexdict, bw);
            }
        }