Beispiel #1
0
            // ReSharper disable once InconsistentNaming
            // data is object[] { bool exportAttachments, string materialReference, string modelName, bool onlyOneLOD, bool skipCollision }
            public void Write(ICLIFlags flags, Chunked chunked, Stream output, List <byte> LODs, object[] data, FindLogic.Combo.ModelInfoNew modelInfo)
            {
                byte?flagLOD = null;

                if (flags is ExtractFlags extractFlags)
                {
                    flagLOD = extractFlags.LOD;
                }

                IChunk chunk = chunked.FindNextChunk("MNRM").Value;

                if (chunk == null)
                {
                    return;
                }
                MNRM model = (MNRM)chunk;

                chunk = chunked.FindNextChunk("CLDM").Value;
                CLDM materials = null;

                if (chunk != null)
                {
                    materials = (CLDM)chunk;
                }
                chunk = chunked.FindNextChunk("lksm").Value;
                lksm skeleton = null;

                if (chunk != null)
                {
                    skeleton = (lksm)chunk;
                }
                chunk = chunked.FindNextChunk("PRHM").Value;
                PRHM hardpoints = null;

                if (chunk != null)
                {
                    hardpoints = (PRHM)chunk;
                }
                HTLC cloth = chunked.FindNextChunk("HTLC").Value as HTLC;

                short[] hierarchy = (short[])skeleton?.Hierarchy.Clone();
                Dictionary <int, HTLC.ClothNode> nodeMap = new Dictionary <int, HTLC.ClothNode>();

                if (cloth != null)
                {
                    uint clothIndex = 0;
                    foreach (HTLC.ClothNode[] nodeCollection in cloth.Nodes)
                    {
                        if (nodeCollection == null)
                        {
                            continue;
                        }
                        int nodeIndex = 0;
                        foreach (HTLC.ClothNode node in nodeCollection)
                        {
                            int parentRaw = node.VerticalParent;
                            if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex) &&
                                cloth.NodeBones[clothIndex].ContainsKey(parentRaw))
                            {
                                if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                                {
                                    hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] =
                                        cloth.NodeBones[clothIndex][parentRaw];
                                    if (cloth.NodeBones[clothIndex][parentRaw] == -1)
                                    {
                                        HTLC.ClothNodeWeight weightedBone =
                                            node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2);
                                        hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone;
                                    }
                                }
                            }
                            else
                            {
                                if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex))
                                {
                                    if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                                    {
                                        hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = -1;
                                        HTLC.ClothNodeWeight weightedBone =
                                            node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2);
                                        hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone;
                                    }
                                }
                            }
                            if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex))
                            {
                                if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                                {
                                    nodeMap[cloth.NodeBones[clothIndex][nodeIndex]] = node;
                                }
                            }

                            nodeIndex++;
                        }
                        clothIndex++;
                    }
                }

                using (BinaryWriter writer = new BinaryWriter(output)) {
                    writer.Write((ushort)1);  // version major
                    writer.Write((ushort)5);  // version minor

                    if (data.Length > 1 && data[1] is string && ((string)data[1]).Length > 0)
                    {
                        writer.Write((string)data[1]);
                    }
                    else
                    {
                        writer.Write((byte)0);
                    }

                    if (data.Length > 2 && data[2] is string && ((string)data[2]).Length > 0)
                    {
                        writer.Write((string)data[2]);
                    }
                    else
                    {
                        writer.Write((byte)0);
                    }

                    if (skeleton == null)
                    {
                        writer.Write((ushort)0);  // number of bones
                    }
                    else
                    {
                        writer.Write(skeleton.Data.bonesAbs);
                    }

                    // ReSharper disable once InconsistentNaming
                    Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                    uint sz         = 0;
                    uint lookForLod = 0;

                    if (model.Submeshes.Any(x => x.lod == flagLOD))
                    {
                        lookForLod = (byte)flagLOD;
                    }
                    else if (flagLOD != null)
                    {
                        SubmeshDescriptor nextLowest = model.Submeshes.Where(p => p.lod < flagLOD).OrderBy(x => x.lod).LastOrDefault();
                        if (nextLowest.verticesToDraw == 0 && nextLowest.indexCount == 0)   // not real mesh
                        {
                            SubmeshDescriptor nextHighest = model.Submeshes.Where(p => p.lod > flagLOD).OrderBy(x => x.lod).FirstOrDefault();
                            lookForLod = nextHighest.lod;
                        }
                        else
                        {
                            lookForLod = nextLowest.lod;
                        }
                    }

                    for (int i = 0; i < model.Submeshes.Length; ++i)
                    {
                        SubmeshDescriptor submesh = model.Submeshes[i];
                        if (data.Length > 4 && data[4] is bool && (bool)data[4])
                        {
                            if (submesh.flags == SubmeshFlags.COLLISION_MESH)
                            {
                                continue;
                            }
                        }
                        if (lookForLod > 0 && submesh.lod != lookForLod && submesh.lod != 255)
                        {
                            continue;
                        }

                        if (!LODMap.ContainsKey(submesh.lod))
                        {
                            LODMap.Add(submesh.lod, new List <int>());
                        }
                        sz++;
                        LODMap[submesh.lod].Add(i);
                    }

                    writer.Write(sz);
                    writer.Write(hardpoints?.HardPoints.Length ?? 0);

                    if (skeleton != null)
                    {
                        for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                        {
                            writer.Write(IdToString("bone", skeleton.IDs[i]));
                            short parent = hierarchy[i];
                            if (parent == -1)
                            {
                                parent = (short)i;
                            }
                            writer.Write(parent);

                            Matrix3x4  bone = skeleton.Matrices34[i];
                            Quaternion rot  = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]);
                            Vector3    scl  = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]);
                            Vector3    pos  = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]);
                            if (nodeMap.ContainsKey(i))
                            {
                                HTLC.ClothNode thisNode = nodeMap[i];
                                pos.X = thisNode.X;
                                pos.Y = thisNode.Y;
                                pos.Z = thisNode.Z;
                            }
                            writer.Write(pos.X);
                            writer.Write(pos.Y);
                            writer.Write(pos.Z);
                            writer.Write(scl.X);
                            writer.Write(scl.Y);
                            writer.Write(scl.Z);
                            writer.Write(rot.X);
                            writer.Write(rot.Y);
                            writer.Write(rot.Z);
                            writer.Write(rot.W);
                        }
                    }

                    foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                    {
                        foreach (int i in kv.Value)
                        {
                            SubmeshDescriptor submesh = model.Submeshes[i];
                            ModelVertex[]     vertex  = model.Vertices[i];
                            ModelVertex[]     normal  = model.Normals[i];
                            ModelUV[][]       uv      = model.TextureCoordinates[i];
                            ModelIndice[]     index   = model.Indices[i];

                            ModelBoneData[] bones = model.Bones[i];
                            writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}");
                            writer.Write(materials.Materials[submesh.material]);
                            writer.Write((byte)uv.Length);

                            writer.Write(vertex.Length);
                            writer.Write(index.Length);
                            for (int j = 0; j < vertex.Length; ++j)
                            {
                                writer.Write(vertex[j].x);
                                writer.Write(vertex[j].y);
                                writer.Write(vertex[j].z);
                                writer.Write(-normal[j].x);
                                writer.Write(-normal[j].y);
                                writer.Write(-normal[j].z);
                                foreach (ModelUV[] t in uv)
                                {
                                    writer.Write(t[j].u);
                                    writer.Write(t[j].v);
                                }
                                if (skeleton != null && bones != null && bones[j].boneIndex != null &&
                                    bones[j].boneWeight != null)
                                {
                                    writer.Write((byte)4);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]);
                                    writer.Write(bones[j].boneWeight[0]);
                                    writer.Write(bones[j].boneWeight[1]);
                                    writer.Write(bones[j].boneWeight[2]);
                                    writer.Write(bones[j].boneWeight[3]);
                                }
                                else
                                {
                                    // bone -> size + index + weight
                                    writer.Write((byte)0);
                                }
                            }
                            List <ModelIndiceModifiable> indexNew = new List <ModelIndiceModifiable>();
                            foreach (ModelIndice indice in index)
                            {
                                indexNew.Add(new ModelIndiceModifiable {
                                    v1 = indice.v1,
                                    v2 = indice.v2,
                                    v3 = indice.v3
                                });
                            }
                            foreach (ModelIndiceModifiable indice in indexNew)
                            {
                                writer.Write((byte)3);
                                writer.Write(indice.v1);
                                writer.Write(indice.v2);
                                writer.Write(indice.v3);
                            }
                        }
                    }

                    if (hardpoints != null)
                    {
                        // attachments
                        foreach (PRHM.HardPoint hp in hardpoints.HardPoints)
                        {
                            writer.Write(IdToString("hardpoint", GUID.Index(hp.HardPointGUID)));
                            Matrix4 mat = hp.Matrix.ToOpenTK();

                            Vector3    pos = mat.ExtractTranslation();
                            Quaternion rot = mat.ExtractRotation();

                            writer.Write(pos.X);
                            writer.Write(pos.Y);
                            writer.Write(pos.Z);
                            writer.Write(rot.X);
                            writer.Write(rot.Y);
                            writer.Write(rot.Z);
                            writer.Write(rot.W);
                        }
                        // extension 1.1
                        foreach (PRHM.HardPoint hp in hardpoints.HardPoints)
                        {
                            writer.Write(IdToString("bone", GUID.Index(hp.GUIDx012)));
                        }
                    }

                    // ext 1.3: cloth
                    writer.Write(0);

                    // ext 1.4: embedded refpose
                    if (skeleton != null)
                    {
                        for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                        {
                            writer.Write(IdToString("bone", skeleton.IDs[i]));
                            short parent = hierarchy[i];
                            writer.Write(parent);

                            Matrix3x4 bone = skeleton.Matrices34Inverted[i];

                            Quaternion3D quat = new Quaternion3D(bone[0, 3], bone[0, 0], bone[0, 1], bone[0, 2]);

                            Vector3D rot = C3D.ToEulerAngles(quat);
                            // ReSharper disable CompareOfFloatsByEqualityOperator
                            if (rot.X == -3.14159274f && rot.Y == 0 && rot.Z == 0)
                            {
                                rot = new Vector3D(0, 3.14159274f, 3.14159274f);
                                // effectively the same but you know, eulers.
                            }
                            // ReSharper restore CompareOfFloatsByEqualityOperator
                            Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]);
                            Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]);
                            writer.Write(pos.X);
                            writer.Write(pos.Y);
                            writer.Write(pos.Z);
                            writer.Write(scl.X);
                            writer.Write(scl.Y);
                            writer.Write(scl.Z);
                            writer.Write(rot.X);
                            writer.Write(rot.Y);
                            writer.Write(rot.Z);
                        }
                    }

                    // ext 1.5: guid
                    writer.Write(GUID.Index(modelInfo.GUID));

                    // ext 1.6: cloth 2.0
                    if (cloth == null)
                    {
                        writer.Write(0);
                    }
                    else
                    {
                        writer.Write(cloth.Descriptors.Length);

                        for (int i = 0; i < cloth.Descriptors.Length; i++)
                        {
                            var desc = cloth.Descriptors[i];

                            writer.Write(desc.Name);
                            writer.Write(cloth.Nodes[i].Length);
                            foreach (HTLC.ClothNode clothNode in cloth.Nodes[i])
                            {
                                writer.Write(clothNode.Bones.Length);

                                foreach (HTLC.ClothNodeWeight clothNodeWeight in clothNode.Bones)
                                {
                                    writer.Write(clothNodeWeight.Bone);
                                    writer.Write(clothNodeWeight.Weight);
                                }
                            }
                        }
                    }
                }
            }
Beispiel #2
0
        public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts)
        {
            culture.NumberFormat.NumberDecimalSeparator          = ".";
            System.Threading.Thread.CurrentThread.CurrentCulture = culture;

            IChunk chunk = chunked.FindNextChunk("MNRM").Value;

            if (chunk == null)
            {
                return(false);
            }
            MNRM model = (MNRM)chunk;

            chunk = chunked.FindNextChunk("CLDM").Value;
            CLDM materials = null;

            if (chunk != null)
            {
                materials = (CLDM)chunk;
            }
            chunk = chunked.FindNextChunk("lksm").Value;
            lksm skeleton = null;

            if (chunk != null)
            {
                skeleton = (lksm)chunk;
            }

            //Console.Out.WriteLine("Writing ASCII");
            using (StreamWriter writer = new StreamWriter(output)) {
                if (skeleton != null)
                {
                    writer.WriteLine(skeleton.Data.bonesAbs);
                    for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                    {
                        writer.WriteLine("bone_{0:X4}", skeleton.IDs[i]);
                        writer.WriteLine(skeleton.Hierarchy[i]);
                        OpenTK.Vector3 bonePos = skeleton.Matrices[i].ExtractTranslation();
                        writer.WriteLine("{0:0.000000} {1:0.000000} {2:0.000000}", bonePos.X, bonePos.Y, bonePos.Z);
                    }
                }
                else
                {
                    writer.WriteLine("0");
                }

                Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                uint sz         = 0;
                uint lookForLod = 0;
                bool lodOnly    = false;
                if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true)
                {
                    lodOnly = true;
                }
                for (int i = 0; i < model.Submeshes.Length; ++i)
                {
                    SubmeshDescriptor submesh = model.Submeshes[i];
                    if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true)
                    {
                        if (submesh.flags == SubmeshFlags.COLLISION_MESH)
                        {
                            continue;
                        }
                    }
                    if (LODs != null && !LODs.Contains(submesh.lod))
                    {
                        continue;
                    }
                    if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod)
                    {
                        continue;
                    }
                    if (!LODMap.ContainsKey(submesh.lod))
                    {
                        LODMap.Add(submesh.lod, new List <int>());
                    }
                    lookForLod = submesh.lod;
                    sz++;
                    LODMap[submesh.lod].Add(i);
                }

                writer.WriteLine(sz);
                foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                {
                    //Console.Out.WriteLine("Writing LOD {0}", kv.Key);
                    foreach (int i in kv.Value)
                    {
                        SubmeshDescriptor submesh = model.Submeshes[i];
                        ModelVertex[]     vertex  = model.Vertices[i];
                        ModelVertex[]     normal  = model.Normals[i];
                        ModelUV[][]       uv      = model.TextureCoordinates[i];
                        ModelIndice[]     index   = model.Indices[i];
                        ModelBoneData[]   bones   = model.Bones[i];

                        ulong materialKey = submesh.material;
                        if (materials != null)
                        {
                            materialKey = materials.Materials[submesh.material];
                        }

                        writer.WriteLine("Submesh_{0}.{1}.{2:X16}", i, kv.Key, materialKey);
                        writer.WriteLine(uv.Length);
                        if (layers.ContainsKey(materialKey))
                        {
                            List <ImageLayer> materialLayers = layers[materialKey];
                            uint            count            = 0;
                            HashSet <ulong> done             = new HashSet <ulong>();
                            for (int j = 0; j < materialLayers.Count; ++j)
                            {
                                if (done.Add(materialLayers[j].Key))
                                {
                                    count += 1;
                                }
                            }
                            writer.WriteLine(count);
                            done.Clear();
                            for (int j = 0; j < materialLayers.Count; ++j)
                            {
                                if (done.Add(materialLayers[j].Key))
                                {
                                    writer.WriteLine($"{GUID.LongKey(materialLayers[j].Key):X12}.dds");
                                    writer.WriteLine(0);
                                }
                            }
                        }
                        else
                        {
                            writer.WriteLine(uv.Length);
                            for (int j = 0; j < uv.Length; ++j)
                            {
                                writer.WriteLine("{0:X16}_UV{1}.dds", materialKey, j);
                                writer.WriteLine(j);
                            }
                        }

                        writer.WriteLine(vertex.Length);
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.WriteLine("{0} {1} {2}", vertex[j].x, vertex[j].y, vertex[j].z);
                            writer.WriteLine("{0} {1} {2}", -normal[j].x, -normal[j].y, -normal[j].z);
                            writer.WriteLine("255 255 255 255");
                            for (int k = 0; k < uv.Length; ++k)
                            {
                                writer.WriteLine("{0:0.######} {1:0.######}", uv[k][j].u, uv[k][j].v);
                            }
                            if (skeleton != null && skeleton.Data.bonesAbs > 0)
                            {
                                if (bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null)
                                {
                                    writer.WriteLine("{0} {1} {2} {3}", skeleton.Lookup[bones[j].boneIndex[0]], skeleton.Lookup[bones[j].boneIndex[1]], skeleton.Lookup[bones[j].boneIndex[2]], skeleton.Lookup[bones[j].boneIndex[3]]);
                                    writer.WriteLine("{0:0.######} {1:0.######} {2:0.######} {3:0.######}", bones[j].boneWeight[0], bones[j].boneWeight[1], bones[j].boneWeight[2], bones[j].boneWeight[3]);
                                }
                                else
                                {
                                    writer.WriteLine("0 0 0 0");
                                    writer.WriteLine("0 0 0 0");
                                }
                            }
                        }
                        writer.WriteLine(index.Length);
                        for (int j = 0; j < index.Length; ++j)
                        {
                            writer.WriteLine("{0} {1} {2}", index[j].v1, index[j].v2, index[j].v3);
                        }
                    }
                }
                writer.WriteLine("");
            }
            return(true);
        }
Beispiel #3
0
        public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] data)
        {
            IChunk chunk = chunked.FindNextChunk("MNRM").Value;

            if (chunk == null)
            {
                return(false);
            }
            MNRM model = (MNRM)chunk;

            chunk = chunked.FindNextChunk("CLDM").Value;
            CLDM materials = null;

            if (chunk != null)
            {
                materials = (CLDM)chunk;
            }
            chunk = chunked.FindNextChunk("lksm").Value;
            lksm skeleton = null;

            if (chunk != null)
            {
                skeleton = (lksm)chunk;
            }
            chunk = chunked.FindNextChunk("PRHM").Value;
            PRHM hardpoints = null;

            if (chunk != null)
            {
                hardpoints = (PRHM)chunk;
            }

            //Console.Out.WriteLine("Writing OWMDL");
            using (BinaryWriter writer = new BinaryWriter(output)) {
                writer.Write((ushort)1); // version major
                writer.Write((ushort)1); // version minor

                if (data.Length > 1 && data[1] != null && data[1].GetType() == typeof(string) && ((string)data[1]).Length > 0)
                {
                    writer.Write((string)data[1]);
                }
                else
                {
                    writer.Write((byte)0);
                }

                if (data.Length > 2 && data[2] != null && data[2].GetType() == typeof(string) && ((string)data[2]).Length > 0)
                {
                    writer.Write((string)data[2]);
                }
                else
                {
                    writer.Write((byte)0);
                }

                if (skeleton == null)
                {
                    writer.Write((ushort)0); // number of bones
                }
                else
                {
                    writer.Write(skeleton.Data.bonesAbs);
                }

                Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                uint sz         = 0;
                uint lookForLod = 0;
                bool lodOnly    = false;
                if (data.Length > 3 && data[3] != null && data[3].GetType() == typeof(bool) && (bool)data[3] == true)
                {
                    lodOnly = true;
                }
                for (int i = 0; i < model.Submeshes.Length; ++i)
                {
                    SubmeshDescriptor submesh = model.Submeshes[i];
                    if (data.Length > 4 && data[4] != null && data[4].GetType() == typeof(bool) && (bool)data[4] == true)
                    {
                        if ((SubmeshFlags)submesh.flags == SubmeshFlags.COLLISION_MESH)
                        {
                            continue;
                        }
                    }
                    if (LODs != null && !LODs.Contains(submesh.lod))
                    {
                        continue;
                    }
                    if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod)
                    {
                        continue;
                    }
                    if (!LODMap.ContainsKey(submesh.lod))
                    {
                        LODMap.Add(submesh.lod, new List <int>());
                    }
                    lookForLod = submesh.lod;
                    sz++;
                    LODMap[submesh.lod].Add(i);
                }

                writer.Write(sz);

                if (hardpoints != null)
                {
                    writer.Write(hardpoints.HardPoints.Length);
                }
                else
                {
                    writer.Write((int)0); // number of attachments
                }

                if (skeleton != null)
                {
                    for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                    {
                        writer.Write(IdToString("bone", skeleton.IDs[i]));
                        short parent = skeleton.Hierarchy[i];
                        if (parent == -1)
                        {
                            parent = (short)i;
                        }
                        writer.Write(parent);

                        Matrix3x4  bone = skeleton.Matrices34[i];
                        Quaternion rot  = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]);
                        Vector3    scl  = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]);
                        Vector3    pos  = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]);
                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);
                        writer.Write(scl.X);
                        writer.Write(scl.X);
                        writer.Write(scl.X);
                        writer.Write(rot.X);
                        writer.Write(rot.Y);
                        writer.Write(rot.Z);
                        writer.Write(rot.W);
                    }
                }

                foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                {
                    //Console.Out.WriteLine("Writing LOD {0}", kv.Key);
                    foreach (int i in kv.Value)
                    {
                        SubmeshDescriptor submesh = model.Submeshes[i];
                        ModelVertex[]     vertex  = model.Vertices[i];
                        ModelVertex[]     normal  = model.Normals[i];
                        ModelUV[][]       uv      = model.TextureCoordinates[i];
                        ModelIndice[]     index   = model.Indices[i];
                        ModelBoneData[]   bones   = model.Bones[i];
                        writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}");
                        writer.Write(materials.Materials[submesh.material]);
                        writer.Write((byte)uv.Length);
                        writer.Write(vertex.Length);
                        writer.Write(index.Length);
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.Write(vertex[j].x);
                            writer.Write(vertex[j].y);
                            writer.Write(vertex[j].z);
                            writer.Write(-normal[j].x);
                            writer.Write(-normal[j].y);
                            writer.Write(-normal[j].z);
                            for (int k = 0; k < uv.Length; ++k)
                            {
                                writer.Write((float)uv[k][j].u);
                                writer.Write((float)uv[k][j].v);
                            }
                            if (skeleton != null && bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null)
                            {
                                writer.Write((byte)4);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]);
                                writer.Write(bones[j].boneWeight[0]);
                                writer.Write(bones[j].boneWeight[1]);
                                writer.Write(bones[j].boneWeight[2]);
                                writer.Write(bones[j].boneWeight[3]);
                            }
                            else
                            {
                                // bone -> size + index + weight
                                writer.Write((byte)0);
                            }
                        }
                        for (int j = 0; j < index.Length; ++j)
                        {
                            writer.Write((byte)3);
                            writer.Write((int)index[j].v1);
                            writer.Write((int)index[j].v2);
                            writer.Write((int)index[j].v3);
                        }
                    }
                }
                if (hardpoints != null)
                {
                    // attachments
                    for (int i = 0; i < hardpoints.HardPoints.Length; ++i)
                    {
                        PRHM.HardPoint hp = hardpoints.HardPoints[i];
                        writer.Write(IdToString("attachment_", hp.id));
                        Matrix4    mat = hp.matrix.ToOpenTK();
                        Vector3    pos = mat.ExtractTranslation();
                        Quaternion rot = mat.ExtractRotation();
                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);
                        writer.Write(rot.X);
                        writer.Write(rot.Y);
                        writer.Write(rot.Z);
                        writer.Write(rot.W);
                    }
                    // extension 1.1
                    for (int i = 0; i < hardpoints.HardPoints.Length; ++i)
                    {
                        PRHM.HardPoint hp = hardpoints.HardPoints[i];
                        writer.Write(IdToString("bone", hp.id));
                    }
                }
            }
            return(true);
        }
Beispiel #4
0
        public bool Write(Chunked chunked, Stream stream, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts)
        {
            IChunk chunk = chunked.FindNextChunk("MNRM").Value;

            if (chunk == null)
            {
                return(false);
            }
            MNRM model = (MNRM)chunk;

            chunk = chunked.FindNextChunk("CLDM").Value;
            CLDM materials = null;

            if (chunk != null)
            {
                materials = (CLDM)chunk;
            }
            chunk = chunked.FindNextChunk("lksm").Value;
            lksm skeleton = null;

            if (chunk != null)
            {
                skeleton = (lksm)chunk;
            }

            //Console.Out.WriteLine("Writing BIN");
            using (BinaryWriter writer = new BinaryWriter(stream)) {
                writer.Write((uint)323232);
                writer.Write((ushort)2);
                writer.Write((ushort)99);
                WriteString(writer, "XNAaraL");
                writer.Write((uint)5);
                WriteString(writer, "OVERWATCH");
                WriteString(writer, "BLIZZARD");
                WriteString(writer, "NULL");
                writer.Write((uint)180); // hash
                writer.Write((uint)1);   // items
                                         // item 1
                writer.Write((uint)1);   // type; 1 = pose; 2 = flags; 255 = padding
                                         // pose
                writer.Write((uint)0);   // size pow 4
                writer.Write((uint)0);   // op info; bone count

                /*
                 * pose data is always ASCII.
                 * Each line is:
                 * for each bone:
                 * boneName:rotx roty rotz posx posy posz scalex scaley scalez
                 */

                if (skeleton != null)
                {
                    writer.Write((uint)skeleton.Data.bonesAbs);
                    for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                    {
                        WriteString(writer, $"bone_{skeleton.IDs[i]:X4}");
                        short parent = skeleton.Hierarchy[i];
                        if (parent == -1)
                        {
                            parent = (short)i;
                        }
                        writer.Write(parent);
                        OpenTK.Vector3 bonePos = skeleton.Matrices[i].ExtractTranslation();
                        writer.Write(bonePos.X);
                        writer.Write(bonePos.Y);
                        writer.Write(bonePos.Z);
                    }
                }
                else
                {
                    writer.Write((uint)0);
                }

                Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                uint sz         = 0;
                uint lookForLod = 0;
                bool lodOnly    = false;
                if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true)
                {
                    lodOnly = true;
                }
                for (int i = 0; i < model.Submeshes.Length; ++i)
                {
                    SubmeshDescriptor submesh = model.Submeshes[i];
                    if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true)
                    {
                        if (submesh.flags == SubmeshFlags.COLLISION_MESH)
                        {
                            continue;
                        }
                    }
                    if (LODs != null && !LODs.Contains(submesh.lod))
                    {
                        continue;
                    }
                    if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod)
                    {
                        continue;
                    }
                    if (!LODMap.ContainsKey(submesh.lod))
                    {
                        LODMap.Add(submesh.lod, new List <int>());
                    }
                    lookForLod = submesh.lod;
                    sz++;
                    LODMap[submesh.lod].Add(i);
                }
                writer.Write(sz);
                foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                {
                    //Console.Out.WriteLine("Writing LOD {0}", kv.Key);
                    foreach (int i in kv.Value)
                    {
                        SubmeshDescriptor submesh     = model.Submeshes[i];
                        ModelVertex[]     vertex      = model.Vertices[i];
                        ModelVertex[]     normal      = model.Normals[i];
                        ModelUV[][]       uv          = model.TextureCoordinates[i];
                        ModelIndice[]     index       = model.Indices[i];
                        ModelBoneData[]   bones       = model.Bones[i];
                        ulong             materialKey = submesh.material;
                        if (materials != null)
                        {
                            materialKey = materials.Materials[submesh.material];
                        }
                        WriteString(writer, $"Submesh_{i}.{kv.Key}.{materialKey:X16}");
                        writer.Write((uint)uv.Length);
                        if (layers.ContainsKey(materialKey))
                        {
                            List <ImageLayer> materialLayers = layers[materialKey];
                            uint            count            = 0;
                            HashSet <ulong> done             = new HashSet <ulong>();
                            for (int j = 0; j < materialLayers.Count; ++j)
                            {
                                if (done.Add(materialLayers[j].Key))
                                {
                                    count += 1;
                                }
                            }
                            writer.Write(count);
                            done.Clear();
                            for (int j = 0; j < materialLayers.Count; ++j)
                            {
                                if (done.Add(materialLayers[j].Key))
                                {
                                    writer.Write($"{GUID.LongKey(materialLayers[j].Key):X12}.dds");
                                    writer.Write((uint)0);
                                }
                            }
                        }
                        else
                        {
                            writer.Write((uint)uv.Length);
                            for (int j = 0; j < uv.Length; ++j)
                            {
                                writer.Write($"{materialKey:X16}_UV{j}.dds");
                                writer.Write((uint)j);
                            }
                        }

                        writer.Write((uint)vertex.Length);
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.Write(vertex[j].x);
                            writer.Write(vertex[j].y);
                            writer.Write(vertex[j].z);
                            writer.Write(-normal[j].x);
                            writer.Write(-normal[j].y);
                            writer.Write(-normal[j].z);
                            writer.Write((byte)255);
                            writer.Write((byte)255);
                            writer.Write((byte)255);
                            writer.Write((byte)255);
                            for (int k = 0; k < uv.Length; ++k)
                            {
                                writer.Write((float)uv[k][j].u);
                                writer.Write((float)uv[k][j].v);
                            }
                            if (skeleton != null && skeleton.Data.bonesAbs > 0)
                            {
                                if (bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null)
                                {
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]);
                                    writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]);
                                    writer.Write(bones[j].boneWeight[0]);
                                    writer.Write(bones[j].boneWeight[1]);
                                    writer.Write(bones[j].boneWeight[2]);
                                    writer.Write(bones[j].boneWeight[3]);
                                }
                                else
                                {
                                    writer.Write((ushort)0);
                                    writer.Write((ushort)0);
                                    writer.Write((ushort)0);
                                    writer.Write((ushort)0);
                                    writer.Write(0.0f);
                                    writer.Write(0.0f);
                                    writer.Write(0.0f);
                                    writer.Write(0.0f);
                                }
                            }
                        }
                        writer.Write((uint)index.Length);
                        for (int j = 0; j < index.Length; ++j)
                        {
                            writer.Write((uint)index[j].v1);
                            writer.Write((uint)index[j].v2);
                            writer.Write((uint)index[j].v3);
                        }
                    }
                }
            }
            return(true);
        }
Beispiel #5
0
        // ReSharper disable once InconsistentNaming
        public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] data)
        {
            IChunk chunk = chunked.FindNextChunk("MNRM").Value;

            if (chunk == null)
            {
                return(false);
            }
            MNRM model = (MNRM)chunk;

            chunk = chunked.FindNextChunk("CLDM").Value;
            CLDM materials = null;

            if (chunk != null)
            {
                materials = (CLDM)chunk;
            }
            chunk = chunked.FindNextChunk("lksm").Value;
            lksm skeleton = null;

            if (chunk != null)
            {
                skeleton = (lksm)chunk;
            }
            chunk = chunked.FindNextChunk("PRHM").Value;
            PRHM hardpoints = null;

            if (chunk != null)
            {
                hardpoints = (PRHM)chunk;
            }

            short[] hierarchy = (short[])skeleton?.Hierarchy.Clone();
            Dictionary <int, HTLC.ClothNode> nodeMap = new Dictionary <int, HTLC.ClothNode>();

            if (chunked.FindNextChunk("HTLC").Value is HTLC cloth)
            {
                uint clothIndex = 0;
                foreach (HTLC.ClothNode[] nodeCollection in cloth.Nodes)
                {
                    if (nodeCollection == null)
                    {
                        continue;
                    }
                    int nodeIndex = 0;
                    foreach (HTLC.ClothNode node in nodeCollection)
                    {
                        int parentRaw = node.VerticalParent;
                        if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex) &&
                            cloth.NodeBones[clothIndex].ContainsKey(parentRaw))
                        {
                            if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                            {
                                hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = cloth.NodeBones[clothIndex][parentRaw];
                                if (cloth.NodeBones[clothIndex][parentRaw] == -1)
                                {
                                    HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2);
                                    hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone;
                                }
                            }
                        }
                        else
                        {
                            if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex))    // if on main skele
                            {
                                if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                                {
                                    hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = -1;
                                    HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2);
                                    hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone;
                                }
                            }
                        }
                        if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex))
                        {
                            // good code:
                            if (cloth.NodeBones[clothIndex][nodeIndex] != -1)
                            {
                                nodeMap[cloth.NodeBones[clothIndex][nodeIndex]] = node;
                            }
                        }

                        nodeIndex++;
                    }
                    clothIndex++;
                }
            }

            using (BinaryWriter writer = new BinaryWriter(output)) {
                writer.Write((ushort)1); // version major
                writer.Write((ushort)4); // version minor

                if (data.Length > 1 && data[1] is string && ((string)data[1]).Length > 0)
                {
                    writer.Write((string)data[1]);
                }
                else
                {
                    writer.Write((byte)0);
                }

                if (data.Length > 2 && data[2] is string && ((string)data[2]).Length > 0)
                {
                    writer.Write((string)data[2]);
                }
                else
                {
                    writer.Write((byte)0);
                }

                if (skeleton == null)
                {
                    writer.Write((ushort)0); // number of bones
                }
                else
                {
                    writer.Write(skeleton.Data.bonesAbs);
                }

                // ReSharper disable once InconsistentNaming
                Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                uint sz         = 0;
                uint lookForLod = 0;
                bool lodOnly    = data.Length > 3 && data[3] is bool && (bool)data[3];
                for (int i = 0; i < model.Submeshes.Length; ++i)
                {
                    SubmeshDescriptor submesh = model.Submeshes[i];
                    if (data.Length > 4 && data[4] is bool && (bool)data[4])
                    {
                        if (submesh.flags == SubmeshFlags.COLLISION_MESH)
                        {
                            continue;
                        }
                    }
                    if (LODs != null && !LODs.Contains(submesh.lod))
                    {
                        continue;
                    }
                    if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod)
                    {
                        continue;
                    }
                    if (!LODMap.ContainsKey(submesh.lod))
                    {
                        LODMap.Add(submesh.lod, new List <int>());
                    }
                    lookForLod = submesh.lod;
                    sz++;
                    LODMap[submesh.lod].Add(i);
                }

                //long meshCountPos = writer.BaseStream.Position;
                writer.Write(sz);

                writer.Write(hardpoints?.HardPoints.Length ?? 0);

                if (skeleton != null)
                {
                    for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                    {
                        writer.Write(IdToString("bone", skeleton.IDs[i]));
                        short parent = hierarchy[i];
                        if (parent == -1)
                        {
                            parent = (short)i;
                        }
                        writer.Write(parent);

                        Matrix3x4  bone = skeleton.Matrices34[i];
                        Quaternion rot  = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]);
                        Vector3    scl  = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]);
                        Vector3    pos  = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]);
                        if (nodeMap.ContainsKey(i))
                        {
                            HTLC.ClothNode thisNode = nodeMap[i];
                            pos.X = thisNode.X;
                            pos.Y = thisNode.Y;
                            pos.Z = thisNode.Z;
                        }
                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);
                        writer.Write(scl.X);
                        writer.Write(scl.Y);
                        writer.Write(scl.Z);
                        writer.Write(rot.X);
                        writer.Write(rot.Y);
                        writer.Write(rot.Z);
                        writer.Write(rot.W);
                    }
                }

                foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                {
                    foreach (int i in kv.Value)
                    {
                        SubmeshDescriptor submesh = model.Submeshes[i];
                        ModelVertex[]     vertex  = model.Vertices[i];
                        ModelVertex[]     normal  = model.Normals[i];
                        ModelUV[][]       uv      = model.TextureCoordinates[i];
                        ModelIndice[]     index   = model.Indices[i];

                        ModelBoneData[] bones = model.Bones[i];
                        writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}");
                        writer.Write(materials.Materials[submesh.material]);
                        writer.Write((byte)uv.Length);

                        writer.Write(vertex.Length);
                        writer.Write(index.Length);
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.Write(vertex[j].x);
                            writer.Write(vertex[j].y);
                            writer.Write(vertex[j].z);
                            writer.Write(-normal[j].x);
                            writer.Write(-normal[j].y);
                            writer.Write(-normal[j].z);
                            foreach (ModelUV[] t in uv)
                            {
                                writer.Write(t[j].u);
                                writer.Write(t[j].v);
                            }
                            if (skeleton != null && bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null)
                            {
                                writer.Write((byte)4);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]);
                                writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]);
                                writer.Write(bones[j].boneWeight[0]);
                                writer.Write(bones[j].boneWeight[1]);
                                writer.Write(bones[j].boneWeight[2]);
                                writer.Write(bones[j].boneWeight[3]);
                            }
                            else
                            {
                                // bone -> size + index + weight
                                writer.Write((byte)0);
                            }
                        }
                        List <ModelIndiceModifiable> indexNew = new List <ModelIndiceModifiable>();
                        foreach (ModelIndice indice in index)
                        {
                            indexNew.Add(new ModelIndiceModifiable {
                                v1 = indice.v1, v2 = indice.v2, v3 = indice.v3
                            });
                        }
                        foreach (ModelIndiceModifiable indice in indexNew)
                        {
                            writer.Write((byte)3);
                            writer.Write(indice.v1);
                            writer.Write(indice.v2);
                            writer.Write(indice.v3);
                        }
                    }
                }

                if (hardpoints != null)
                {
                    // attachments
                    foreach (PRHM.HardPoint hp in hardpoints.HardPoints)
                    {
                        writer.Write(IdToString("attachment_", GUID.Index(hp.HardPointGUID)));
                        Matrix4    mat = hp.Matrix.ToOpenTK();
                        Vector3    pos = mat.ExtractTranslation();
                        Quaternion rot = mat.ExtractRotation();
                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);
                        writer.Write(rot.X);
                        writer.Write(rot.Y);
                        writer.Write(rot.Z);
                        writer.Write(rot.W);
                    }
                    // extension 1.1
                    foreach (PRHM.HardPoint hp in hardpoints.HardPoints)
                    {
                        writer.Write(IdToString("bone", GUID.Index(hp.GUIDx012)));
                    }
                }

                // ext 1.3: cloth
                writer.Write(0);

                // ext 1.4: embedded refpose
                if (skeleton != null)
                {
                    for (int i = 0; i < skeleton.Data.bonesAbs; ++i)
                    {
                        writer.Write(IdToString("bone", skeleton.IDs[i]));
                        short parent = hierarchy[i];
                        // if (parent == -1) {
                        //     parent = (short)i;
                        // }
                        writer.Write(parent);

                        Matrix3x4 bone = skeleton.Matrices34Inverted[i];

                        // Quaternion3D quat = new Quaternion3D(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]);
                        // why are they different
                        Quaternion3D quat = new Quaternion3D(bone[0, 3], bone[0, 0], bone[0, 1], bone[0, 2]);

                        Vector3D rot = C3D.ToEulerAngles(quat);
                        if (rot.X == -3.14159274f && rot.Y == 0 && rot.Z == 0)
                        {
                            rot = new Vector3D(0, 3.14159274f, 3.14159274f); // effectively the same but you know, eulers.
                        }
                        Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]);
                        Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]);

                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);
                        writer.Write(scl.X);
                        writer.Write(scl.Y);
                        writer.Write(scl.Z);
                        writer.Write(rot.X);
                        writer.Write(rot.Y);
                        writer.Write(rot.Z);
                    }
                }
            }

            return(true);
        }
Beispiel #6
0
        public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts)
        {
            IChunk chunk = chunked.FindNextChunk("MNRM").Value;

            if (chunk == null)
            {
                return(false);
            }
            MNRM model = (MNRM)chunk;

            chunk = chunked.FindNextChunk("CLDM").Value;
            CLDM materials = null;

            if (chunk != null)
            {
                materials = (CLDM)chunk;
            }

            NumberFormatInfo numberFormatInfo = new NumberFormatInfo();

            numberFormatInfo.NumberDecimalSeparator = ".";
            using (StreamWriter writer = new StreamWriter(output)) {
                uint faceOffset = 1;
                if (opts.Length > 1 && opts[1] != null && opts[1].GetType() == typeof(string) && ((string)opts[1]).Length > 0)
                {
                    writer.WriteLine("mtllib {0}", (string)opts[1]);
                }

                Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >();
                for (int i = 0; i < model.Submeshes.Length; ++i)
                {
                    SubmeshDescriptor submesh = model.Submeshes[i];
                    if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true)
                    {
                        if (submesh.flags == SubmeshFlags.COLLISION_MESH)
                        {
                            continue;
                        }
                    }
                    if (LODs != null && !LODs.Contains(submesh.lod))
                    {
                        continue;
                    }
                    if (!LODMap.ContainsKey(submesh.lod))
                    {
                        LODMap.Add(submesh.lod, new List <int>());
                    }
                    LODMap[submesh.lod].Add(i);
                }

                foreach (KeyValuePair <byte, List <int> > kv in LODMap)
                {
                    //Console.Out.WriteLine("Writing LOD {0}", kv.Key);
                    writer.WriteLine("o Submesh_{0}", kv.Key);
                    foreach (int i in kv.Value)
                    {
                        SubmeshDescriptor submesh = model.Submeshes[i];
                        if (materials != null)
                        {
                            writer.WriteLine("g Material_{0:X16}", materials.Materials[submesh.material]);
                            writer.WriteLine("usemtl {0:X16}", materials.Materials[submesh.material]);
                        }
                        else
                        {
                            writer.WriteLine("g Material_{0}", i);
                        }
                        ModelVertex[] vertex = model.Vertices[i];
                        ModelVertex[] normal = model.Normals[i];
                        ModelUV[][]   uvs    = model.TextureCoordinates[i];
                        ModelUV[]     uv     = uvs[0];
                        ModelIndice[] index  = model.Indices[i];
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.WriteLine("v {0} {1} {2}", vertex[j].x, vertex[j].y, vertex[j].z);
                        }
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.WriteLine("vt {0} {1}", uv[j].u.ToString("0.######", numberFormatInfo), uv[j].v.ToString("0.######", numberFormatInfo));
                        }
                        if (uvs.Length > 1)
                        {
                            for (int j = 0; j < uvs.Length; ++j)
                            {
                                for (int k = 0; k < vertex.Length; ++k)
                                {
                                    writer.WriteLine("vt{0} {0} {1}", j, uvs[j][k].u.ToString("0.######", numberFormatInfo), uvs[j][k].v.ToString("0.######", numberFormatInfo));
                                }
                            }
                        }
                        for (int j = 0; j < vertex.Length; ++j)
                        {
                            writer.WriteLine("vn {0} {1} {2}", normal[j].x, normal[j].y, normal[j].z);
                        }
                        writer.WriteLine("");
                        for (int j = 0; j < index.Length; ++j)
                        {
                            writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", index[j].v1 + faceOffset, index[j].v2 + faceOffset, index[j].v3 + faceOffset);
                        }
                        faceOffset += (uint)vertex.Length;
                        writer.WriteLine("");
                    }
                    if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true)
                    {
                        break;
                    }
                }
            }
            return(true);
        }