Пример #1
0
        //We're not realistically going to fully convert everything, but we can get vertex data and bones if nothing else
        //Returns an aqp ready for the ConvertToNGSPSO2Mesh method
        public static AquaObject ReadAXS(string filePath, out AquaNode aqn)
        {
            AquaObject aqp = new NGSAquaObject();

            aqn = new AquaNode();

            using (Stream stream = (Stream) new FileStream(filePath, FileMode.Open))
                using (var streamReader = new BufferedStreamReader(stream, 8192))
                {
                    Debug.WriteLine(Path.GetFileName(filePath));
                    long                            last__oaPos      = 0;
                    eertStruct                      eertNodes        = null;
                    ipnbStruct                      tempLpnbList     = null;
                    List <ffubStruct>               ffubList         = new List <ffubStruct>();
                    List <XgmiStruct>               xgmiList         = new List <XgmiStruct>();
                    List <string>                   texNames         = new List <string>();
                    List <MeshDefinitions>          meshDefList      = new List <MeshDefinitions>();
                    List <stamData>                 stamList         = new List <stamData>();
                    Dictionary <string, rddaStruct> rddaList         = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> imgRddaList      = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> vertRddaList     = new Dictionary <string, rddaStruct>();
                    Dictionary <string, rddaStruct> faceRddaList     = new Dictionary <string, rddaStruct>();
                    Dictionary <string, int>        xgmiIdByCombined = new Dictionary <string, int>();
                    Dictionary <string, int>        xgmiIdByUnique   = new Dictionary <string, int>();
                    ffubStruct                      imgFfub          = new ffubStruct();
                    ffubStruct                      vertFfub         = new ffubStruct();
                    ffubStruct                      faceFfub         = new ffubStruct();

                    var fType = streamReader.Read <int>();

                    var fsaLen = streamReader.Read <int>();
                    streamReader.Seek(0x8, SeekOrigin.Current);
                    //Go to Vert definition, node, material, and misc data
                    while (streamReader.Position() < fsaLen)
                    {
                        var tag  = streamReader.Peek <int>();
                        var test = Encoding.UTF8.GetString(BitConverter.GetBytes(tag));
                        Debug.WriteLine(streamReader.Position().ToString("X"));
                        Debug.WriteLine(test);
                        switch (tag)
                        {
                        case __oa:
                            last__oaPos = streamReader.Position();
                            streamReader.Seek(0xD0, SeekOrigin.Current);
                            break;

                        case FIA:
                            streamReader.Seek(0x10, SeekOrigin.Current);
                            break;

                        case __lm:
                            var stam = streamReader.ReadLM();
                            if (stam != null && stam.Count > 0)
                            {
                                stamList = stam;
                            }
                            break;

                        case __bm:
                            streamReader.ReadBM(meshDefList, tempLpnbList, stamList, last__oaPos);
                            break;

                        case lpnb:
                            tempLpnbList = streamReader.ReadIpnb();
                            break;

                        case eert:
                            eertNodes = streamReader.ReadEert();
                            break;

                        //case ssem:
                        //  streamReader.SkipBasicAXSStruct(); //Maybe use for material data later. Remember to store ordered id for _bm mesh entries for this
                        // break;
                        case Xgmi:
                            var xgmiData = streamReader.ReadXgmi();
                            if (!xgmiIdByCombined.ContainsKey(xgmiData.stamCombinedId))
                            {
                                xgmiIdByCombined.Add(xgmiData.stamCombinedId, xgmiList.Count);
                            }
                            xgmiIdByUnique.Add(xgmiData.stamUniqueId, xgmiList.Count);
                            xgmiList.Add(xgmiData);
                            break;

                        default:
                            streamReader.SkipBasicAXSStruct();
                            break;
                        }
                    }

                    //Assemble aqn from eert
                    if (eertNodes != null)
                    {
                        for (int i = 0; i < eertNodes.boneCount; i++)
                        {
                            var       rttaNode = eertNodes.rttaList[i];
                            Matrix4x4 mat      = Matrix4x4.Identity;

                            mat *= Matrix4x4.CreateScale(rttaNode.scale);

                            var rotation = Matrix4x4.CreateFromQuaternion(rttaNode.quatRot);

                            mat *= rotation;

                            mat *= Matrix4x4.CreateTranslation(rttaNode.pos);

                            var parentId = rttaNode.parentNodeId;

                            //If there's a parent, multiply by it
                            if (i != 0)
                            {
                                if (rttaNode.parentNodeId == -1)
                                {
                                    parentId = 0;
                                }
                                var pn           = aqn.nodeList[parentId];
                                var parentInvTfm = new Matrix4x4(pn.m1.X, pn.m1.Y, pn.m1.Z, pn.m1.W,
                                                                 pn.m2.X, pn.m2.Y, pn.m2.Z, pn.m2.W,
                                                                 pn.m3.X, pn.m3.Y, pn.m3.Z, pn.m3.W,
                                                                 pn.m4.X, pn.m4.Y, pn.m4.Z, pn.m4.W);
                                Matrix4x4.Invert(parentInvTfm, out var invParentInvTfm);
                                mat = mat * invParentInvTfm;
                            }
                            else
                            {
                                parentId = -1;
                            }
                            rttaNode.nodeMatrix = mat;

                            //Create AQN node
                            NODE aqNode = new NODE();
                            aqNode.animatedFlag = 1;
                            aqNode.parentId     = parentId;
                            aqNode.unkNode      = -1;
                            aqNode.pos          = rttaNode.pos;
                            aqNode.eulRot       = QuaternionToEuler(rttaNode.quatRot);

                            if (Math.Abs(aqNode.eulRot.Y) > 120)
                            {
                                aqNode.scale = new Vector3(-1, -1, -1);
                            }
                            else
                            {
                                aqNode.scale = new Vector3(1, 1, 1);
                            }
                            Matrix4x4.Invert(mat, out var invMat);
                            aqNode.m1       = new Vector4(invMat.M11, invMat.M12, invMat.M13, invMat.M14);
                            aqNode.m2       = new Vector4(invMat.M21, invMat.M22, invMat.M23, invMat.M24);
                            aqNode.m3       = new Vector4(invMat.M31, invMat.M32, invMat.M33, invMat.M34);
                            aqNode.m4       = new Vector4(invMat.M41, invMat.M42, invMat.M43, invMat.M44);
                            aqNode.boneName = rttaNode.nodeName;
                            Debug.WriteLine($"{i} " + aqNode.boneName.GetString());
                            aqn.nodeList.Add(aqNode);
                        }
                    }


                    //Go to mesh buffers
                    streamReader.Seek(fsaLen, SeekOrigin.Begin);
                    if (streamReader.Position() >= stream.Length)
                    {
                        return(null);
                    }

                    var fType2 = streamReader.Read <int>();
                    //Read mesh data
                    if (fType2 != FMA)
                    {
                        Debug.WriteLine("Unexpected struct in location of FMA!");
                        return(null);
                    }
                    streamReader.Seek(0xC, SeekOrigin.Current);

                    //Skip daeh
                    int meshSettingLen = streamReader.ReadDAEH();

                    //Read ffub and rdda
                    //Count mesh count here for now and store starts and ends of data
                    long meshSettingStart = streamReader.Position();
                    while (streamReader.Position() < meshSettingStart + meshSettingLen)
                    {
                        streamReader.ReadFFUBorRDDA(ffubList, rddaList, imgRddaList, vertRddaList, faceRddaList, ref imgFfub, ref vertFfub, ref faceFfub);
                    }

                    int meshCount = meshDefList.Count;

                    //Read image data
                    var ext = Path.GetExtension(filePath);
                    for (int i = 0; i < xgmiList.Count; i++)
                    {
                        var xgmiData      = xgmiList[i];
                        var imgBufferInfo = imgRddaList[$"{xgmiData.md5_1.ToString("X")}{xgmiData.md5_2.ToString("X")}"];
                        Debug.WriteLine($"Image set {i}: " + imgBufferInfo.md5_1.ToString("X") + " " + imgBufferInfo.dataStartOffset.ToString("X") + " " + imgBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset).ToString("X"));

                        var position     = meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset;
                        var buffer       = streamReader.ReadBytes(position, imgBufferInfo.dataSize);
                        var outImagePath = filePath.Replace(ext, $"_tex_{i}" + ".dds");
                        texNames.Add(Path.GetFileName(outImagePath));
                        try
                        {
                            string name = Path.GetFileName(filePath);
                            Debug.WriteLine($"{name}_xgmi_{ i}");
                            var image = AIFMethods.GetImage(xgmiData, buffer);
                            File.WriteAllBytes(filePath.Replace(ext, $"_tex_{i}" + ".dds"), image);
                        }
                        catch (Exception exc)
                        {
#if DEBUG
                            string name = Path.GetFileName(filePath);
                            Debug.WriteLine($"Extract tex {i} failed.");
                            File.WriteAllBytes($"C:\\{name}_xgmiHeader_{i}.bin", xgmiData.GetBytes());
                            File.WriteAllBytes($"C:\\{name}_xgmiBuffer_{i}.bin", buffer);
                            Debug.WriteLine(exc.Message);
#endif
                        }
                        buffer = null;
                    }


                    //Read model data - Since ffubs are initialized, they default to 0.
                    int vertFfubPadding = imgFfub.structSize;
                    int faceFfubPadding = imgFfub.structSize + vertFfub.structSize;

                    for (int i = 0; i < meshCount; i++)
                    {
                        var mesh       = meshDefList[i];
                        var nodeMatrix = Matrix4x4.Identity;
                        for (int bn = 0; bn < eertNodes.boneCount; bn++)
                        {
                            var node = eertNodes.rttaList[bn];
                            if (node.meshNodePtr == mesh.oaPos)
                            {
                                nodeMatrix = node.nodeMatrix;
                                break;
                            }
                        }
                        var vertBufferInfo = vertRddaList[$"{mesh.salvStr.md5_1.ToString("X")}{mesh.salvStr.md5_2.ToString("X")}"];
                        var faceBufferInfo = faceRddaList[$"{mesh.lxdiStr.md5_1.ToString("X")}{mesh.lxdiStr.md5_2.ToString("X")}"];
                        Debug.WriteLine($"Vert set {i}: " + vertBufferInfo.md5_1.ToString("X") + " " + vertBufferInfo.dataStartOffset.ToString("X") + " " + vertBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset).ToString("X"));
                        Debug.WriteLine($"Face set {i}: " + faceBufferInfo.md5_1.ToString("X") + " " + faceBufferInfo.dataStartOffset.ToString("X") + " " + faceBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset).ToString("X"));

                        //Vert data
                        var             vertCount = vertBufferInfo.dataSize / mesh.salvStr.vertLen;
                        AquaObject.VTXL vtxl      = new AquaObject.VTXL();

                        streamReader.Seek((meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset), SeekOrigin.Begin);
                        AquaObjectMethods.ReadVTXL(streamReader, mesh.vtxe, vtxl, vertCount, mesh.vtxe.vertDataTypes.Count);
                        vtxl.convertToLegacyTypes();

                        //Fix vert transforms
                        for (int p = 0; p < vtxl.vertPositions.Count; p++)
                        {
                            vtxl.vertPositions[p] = Vector3.Transform(vtxl.vertPositions[p], nodeMatrix);
                            if (vtxl.vertNormals.Count > 0)
                            {
                                vtxl.vertNormals[p] = Vector3.TransformNormal(vtxl.vertNormals[p], nodeMatrix);
                            }
                        }


                        //Handle bone indices
                        if (mesh.ipnbStr != null && mesh.ipnbStr.shortList.Count > 0)
                        {
                            vtxl.bonePalette = (mesh.ipnbStr.shortList.ConvertAll(delegate(short num) {
                                return((ushort)num);
                            }));

                            //Convert the indices based on the global bone list as pso2 will expect
                            for (int bn = 0; bn < vtxl.bonePalette.Count; bn++)
                            {
                                vtxl.bonePalette[bn] = (ushort)mesh.lpnbStr.shortList[vtxl.bonePalette[bn]];
                            }
                        }

                        aqp.vtxlList.Add(vtxl);

                        //Face data
                        AquaObject.GenericTriangles genMesh = new AquaObject.GenericTriangles();

                        int        faceIndexCount = faceBufferInfo.dataSize / 2;
                        List <int> faceList       = new List <int>();

                        streamReader.Seek((meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset), SeekOrigin.Begin);
                        int maxStep = streamReader.Read <ushort>();
                        for (int fId = 0; fId < faceIndexCount - 1; fId++)
                        {
                            faceList.Add(streamReader.Read <ushort>());
                        }

                        //Convert the data to something usable with this algorithm and then destripify it.
                        List <ushort> triList = unpackInds(inverseWatermarkTransform(faceList, maxStep)).ConvertAll(delegate(int num) {
                            return((ushort)num);
                        });
                        var tempFaceData = new AquaObject.stripData()
                        {
                            triStrips = triList, format0xC33 = true, triIdCount = triList.Count
                        };
                        genMesh.triList = tempFaceData.GetTriangles();

                        //Extra
                        genMesh.vertCount = vertCount;
                        genMesh.matIdList = new List <int>(new int[genMesh.triList.Count]);
                        for (int j = 0; j < genMesh.matIdList.Count; j++)
                        {
                            genMesh.matIdList[j] = aqp.tempMats.Count;
                        }
                        aqp.tempTris.Add(genMesh);

                        //Material
                        var mat = new AquaObject.GenericMaterial();
                        mat.texNames = GetTexNames(mesh, xgmiIdByCombined, xgmiIdByUnique, texNames);
                        aqp.tempMats.Add(mat);
                    }

                    return(aqp);
                }
        }
Пример #2
0
        //Import obj data, gather and reconstruct weighting data, reconstruct model with new geometry data, delete LOD models loaded
        public static AquaObject ImportObj(string fileName, AquaObject aqo)
        {
            bool            doBitangent = false;
            List <SkinData> skinData    = new List <SkinData>();

            AquaObjectMethods.GenerateGlobalBonePalette(aqo);

            //Add a local version of the global bonepalette. We're reworking things so we need to do this.
            for (int i = 0; i < aqo.vtxlList.Count; i++)
            {
                aqo.vtxlList[i].bonePalette = new List <ushort>();
                for (int b = 0; b < aqo.bonePalette.Count; b++)
                {
                    aqo.vtxlList[i].bonePalette.Add((ushort)aqo.bonePalette[b]);
                }
            }

            //Assign skin data for comparison later and make it relativitize it to the GlobalBonePalette
            if (aqo.vtxlList[0].vertWeights.Count > 0)
            {
                foreach (var vtxl in aqo.vtxlList)
                {
                    for (int i = 0; i < vtxl.vertPositions.Count; i++)
                    {
                        SkinData skin = new SkinData();
                        skin.pos = vtxl.vertPositions[i];
                        List <byte> indices = new List <byte>(new byte[4]);
                        for (int id = 0; id < 4; id++)
                        {
                            var temp = (byte)aqo.bonePalette.IndexOf(vtxl.bonePalette[vtxl.vertWeightIndices[i][id]]);
                            if (indices.Contains(temp)) //Repeats should only occur for index 0
                            {
                                temp = 0;
                            }
                            indices[id] = temp;
                        }
                        skin.indices = indices.ToArray();
                        skin.weights = AquaObject.VTXL.SumWeightsTo1(vtxl.vertWeights[i]);
                        skinData.Add(skin);
                    }
                }
            }

            if (aqo.vtxlList[0].vertBinormalList.Count > 0)
            {
                doBitangent = true;
            }

            //House cleaning since we can't really redo these this way
            aqo.tempTris.Clear();
            aqo.strips3.Clear();
            aqo.strips2.Clear();
            aqo.strips.Clear();
            aqo.pset2List.Clear();
            aqo.psetList.Clear();
            aqo.mesh2List.Clear();
            aqo.strips3Lengths.Clear();
            aqo.objc.pset2Count = 0;
            aqo.objc.mesh2Count = 0;

            var obj         = ObjFile.FromFile(fileName);
            var subMeshes   = obj.Meshes.SelectMany(i => i.SubMeshes).ToArray();
            var oldMESHList = aqo.meshList;

            aqo.meshList = new List <AquaObject.MESH>();
            int        totalStripsShorts = 0;
            int        totalVerts        = 0;
            int        boneLimit;
            AquaObject tempModel;

            if (aqo.objc.type >= 0xC32)
            {
                tempModel = new NGSAquaObject();
                boneLimit = 255;
            }
            else
            {
                tempModel = new ClassicAquaObject();
                boneLimit = 16;
            }

            //Assemble face and vertex data. Vert data is stored with faces so that it can be split as needed for proper UV mapping etc.
            foreach (var mesh in subMeshes)
            {
                var tempMesh = new AquaObject.GenericTriangles();
                tempMesh.name        = mesh.Name;
                tempMesh.bonePalette = aqo.bonePalette;
                List <int>            vertIds     = new List <int>();
                Dictionary <int, int> vertIdRemap = new Dictionary <int, int>();
                int greatestId = 0;

                for (int f = 0; f < mesh.PositionFaces.Count; ++f)
                {
                    var pf = mesh.PositionFaces[f];
                    var nf = mesh.NormalFaces[f];
                    var tf = mesh.TexCoordFaces[f];

                    tempMesh.triList.Add(new Vector3(pf.A, pf.B, pf.C)); //faces are 1 based
                    if (!vertIds.Contains(pf.A))
                    {
                        greatestId = pf.A > greatestId ? pf.A : greatestId;
                        vertIds.Add(pf.A);
                    }
                    if (!vertIds.Contains(pf.B))
                    {
                        greatestId = pf.B > greatestId ? pf.B : greatestId;
                        vertIds.Add(pf.B);
                    }
                    if (!vertIds.Contains(pf.C))
                    {
                        greatestId = pf.C > greatestId ? pf.C : greatestId;
                        vertIds.Add(pf.C);
                    }
                    tempMesh.matIdList.Add(0);

                    var vtxl = new AquaObject.VTXL();
                    vtxl.rawVertId = new List <int>()
                    {
                        pf.A, pf.B, pf.C
                    };
                    vtxl.rawFaceId = new List <int>()
                    {
                        f, f, f
                    };

                    //Undo scaling and rotate to Y up
                    vtxl.vertPositions = new List <Vector3>()
                    {
                        new Vector3(obj.Positions[pf.A].X / 100, obj.Positions[pf.A].Z / 100, -obj.Positions[pf.A].Y / 100),
                        new Vector3(obj.Positions[pf.B].X / 100, obj.Positions[pf.B].Z / 100, -obj.Positions[pf.B].Y / 100),
                        new Vector3(obj.Positions[pf.C].X / 100, obj.Positions[pf.C].Z / 100, -obj.Positions[pf.C].Y / 100)
                    };
                    vtxl.vertNormals = new List <Vector3>()
                    {
                        new Vector3(obj.Normals[nf.A].X, obj.Normals[nf.A].Z, -obj.Normals[nf.A].Y),
                        new Vector3(obj.Normals[nf.B].X, obj.Normals[nf.B].Z, -obj.Normals[nf.B].Y),
                        new Vector3(obj.Normals[nf.C].X, obj.Normals[nf.C].Z, -obj.Normals[nf.C].Y)
                    };

                    vtxl.uv1List = new List <Vector2>()
                    {
                        new Vector2(obj.TexCoords[tf.A].X, -obj.TexCoords[tf.A].Y), new Vector2(obj.TexCoords[tf.B].X, -obj.TexCoords[tf.B].Y),
                        new Vector2(obj.TexCoords[tf.C].X, -obj.TexCoords[tf.C].Y),
                    };

                    if (aqo.vtxlList[0].vertWeights.Count > 0)
                    {
                        //Autoskin magic - Essentially, iterate through and get weight values from the closest match to the original
                        for (int vt = 0; vt < vtxl.vertPositions.Count; vt++)
                        {
                            var maxDistance = float.MaxValue;
                            vtxl.vertWeights.Add(new Vector4());
                            vtxl.vertWeightIndices.Add(null);

                            SkinData tempWeight = new SkinData();
                            //Go through each original vertex and compare distances. Adjust references if it's closer
                            foreach (var skin in skinData)
                            {
                                var distance = (vtxl.vertPositions[vt] - skin.pos).LengthSquared(); //Not a full true distance, just to be quicker

                                if (distance < maxDistance)
                                {
                                    maxDistance = distance;
                                    tempWeight  = skin;
                                    if (distance == 0)
                                    {
                                        break;
                                    }
                                }
                            }
                            vtxl.vertWeights[vt]       = tempWeight.weights;
                            vtxl.vertWeightIndices[vt] = tempWeight.indices;

                            if (aqo.vtxlList[0].vertWeightsNGS.Count > 0)
                            {
                                ushort[] shortWeights = new ushort[] { (ushort)(tempWeight.weights.X * ushort.MaxValue), (ushort)(tempWeight.weights.Y * ushort.MaxValue),
                                                                       (ushort)(tempWeight.weights.Z * ushort.MaxValue), (ushort)(tempWeight.weights.W * ushort.MaxValue), };
                                vtxl.vertWeightsNGS.Add(shortWeights);
                            }
                        }
                    }
                    tempMesh.faceVerts.Add(vtxl);
                }

                //Set up remapp ids
                for (int i = 0; i < vertIds.Count; i++)
                {
                    var id = vertIds[i];
                    vertIdRemap.Add(id, i);
                }

                //Remap Ids to verts
                for (int f = 0; f < tempMesh.faceVerts.Count; f++)
                {
                    for (int v = 0; v < tempMesh.faceVerts[f].rawVertId.Count; v++)
                    {
                        tempMesh.faceVerts[f].rawVertId[v] = vertIdRemap[tempMesh.faceVerts[f].rawVertId[v]];
                    }
                }

                //Remap Ids to face ids
                for (int f = 0; f < tempMesh.triList.Count; f++)
                {
                    var tri = tempMesh.triList[f];
                    if (vertIdRemap.ContainsKey((int)tri.X))
                    {
                        tri.X = vertIdRemap[(int)tri.X];
                    }
                    if (vertIdRemap.ContainsKey((int)tri.Y))
                    {
                        tri.Y = vertIdRemap[(int)tri.Y];
                    }
                    if (vertIdRemap.ContainsKey((int)tri.Z))
                    {
                        tri.Z = vertIdRemap[(int)tri.Z];
                    }

                    tempMesh.triList[f] = tri;
                }

                tempMesh.vertCount = vertIds.Count;
                totalVerts        += vertIds.Count;

                aqo.tempTris.Add(tempMesh);
            }
            AquaObjectMethods.VTXLFromFaceVerts(aqo);
            if (aqo.objc.type < 0xC32)
            {
                AquaObjectMethods.BatchSplitByBoneCount(aqo, tempModel, boneLimit);
                aqo.tempTris = tempModel.tempTris;
                aqo.vtxlList = tempModel.vtxlList;
                tempModel    = null;

                AquaObjectMethods.RemoveAllUnusedBones(aqo);
            }

            //AquaObjectMethods.CalcUNRMs(aqo, aqo.applyNormalAveraging, aqo.objc.unrmOffset != 0);

            //Set up PSETs and strips, and other per mesh data
            for (int i = 0; i < aqo.tempTris.Count; i++)
            {
                //strips
                AquaObject.stripData strips;
                if (aqo.objc.type >= 0xC32)
                {
                    strips             = new AquaObject.stripData();
                    strips.format0xC33 = true;
                    strips.triStrips   = new List <ushort>(aqo.tempTris[i].toUshortArray());
                    strips.triIdCount  = strips.triStrips.Count;
                    strips.faceGroups.Add(strips.triStrips.Count);
                }
                else
                {
                    strips = new AquaObject.stripData(aqo.tempTris[i].toUshortArray());
                }
                aqo.strips.Add(strips);

                //PSET
                var pset = new AquaObject.PSET();
                pset.faceGroupCount = 0x1;
                pset.psetFaceCount  = strips.triIdCount;
                if (aqo.objc.type >= 0xC32)
                {
                    pset.tag             = 0x1000;
                    pset.stripStartCount = totalStripsShorts;
                }
                else
                {
                    pset.tag = 0x2100;
                }
                aqo.psetList.Add(pset);
                totalStripsShorts += strips.triIdCount; //Update this *after* setting the strip start count so that we don't direct to bad data.

                //MESH
                Match m;
                int   idx_MATE = 0;
                int   idx_REND = 0;
                int   idx_SHAD = 0;
                int   idx_TSET = 0;
                if (null == aqo.tempTris[i].name)
                {
                    m = null;
                }
                else
                {
                    m = RE_ObjName.Match(aqo.tempTris[i].name);
                    if (!m.Success)
                    {
                        m = null;
                    }
                    else
                    {
                        idx_MATE = int.Parse(m.Groups[1].Value);
                        idx_REND = int.Parse(m.Groups[2].Value);
                        idx_SHAD = int.Parse(m.Groups[3].Value);
                        idx_TSET = int.Parse(m.Groups[4].Value);
                    }
                }
                if (m == null)
                {
                    idx_MATE = oldMESHList[0].mateIndex;
                    idx_REND = oldMESHList[0].rendIndex;
                    idx_SHAD = oldMESHList[0].shadIndex;
                    idx_TSET = oldMESHList[0].tsetIndex;
                }

                var             mesh         = new AquaObject.MESH();
                AquaObject.MESH oldMesh      = new AquaObject.MESH();
                bool            oldMeshFound = false;

                //Compare
                for (int msh = 0; msh < oldMESHList.Count; msh++)
                {
                    var tempMesh = oldMESHList[msh];
                    if (tempMesh.mateIndex == idx_MATE && tempMesh.rendIndex == idx_REND && tempMesh.shadIndex == idx_SHAD && tempMesh.tsetIndex == idx_TSET)
                    {
                        oldMesh      = tempMesh;
                        oldMeshFound = true;
                        break;
                    }
                }

                if (oldMeshFound == false)
                {
                    mesh.flags           = 0x17; //No idea what this really does. Seems to vary a lot, but also not matter a lot.
                    mesh.unkShort0       = 0x0;
                    mesh.unkByte0        = 0x80;
                    mesh.unkByte1        = 0x64;
                    mesh.unkShort1       = 0;
                    mesh.mateIndex       = idx_MATE;
                    mesh.rendIndex       = idx_REND;
                    mesh.shadIndex       = idx_SHAD;
                    mesh.tsetIndex       = idx_TSET;
                    mesh.baseMeshNodeId  = 0;
                    mesh.baseMeshDummyId = 0;
                    mesh.unkInt0         = 0;
                }
                else
                {
                    mesh.flags           = oldMesh.flags;
                    mesh.unkShort0       = oldMesh.unkShort0;
                    mesh.unkByte0        = oldMesh.unkByte0;
                    mesh.unkByte1        = oldMesh.unkByte1;
                    mesh.unkShort1       = oldMesh.unkShort1;
                    mesh.mateIndex       = idx_MATE;
                    mesh.rendIndex       = idx_REND;
                    mesh.shadIndex       = idx_SHAD;
                    mesh.tsetIndex       = idx_TSET;
                    mesh.baseMeshNodeId  = oldMesh.baseMeshNodeId;
                    mesh.baseMeshDummyId = oldMesh.baseMeshDummyId;
                    mesh.unkInt0         = oldMesh.unkInt0;
                }
                mesh.vsetIndex = i;
                mesh.psetIndex = i;
                mesh.reserve0  = 0;
                aqo.meshList.Add(mesh);
            }

            //Generate VTXEs and VSETs
            int largestVertSize = 0;
            int vertCounter     = 0;

            totalVerts = 0;
            aqo.vsetList.Clear();
            aqo.vtxeList.Clear();
            for (int i = 0; i < aqo.vtxlList.Count; i++)
            {
                totalVerts += aqo.vtxlList[i].vertPositions.Count;
                AquaObject.VTXE vtxe = AquaObjectMethods.ConstructClassicVTXE(aqo.vtxlList[i], out int size);
                aqo.vtxeList.Add(vtxe);

                //Track this for objc
                if (size > largestVertSize)
                {
                    largestVertSize = size;
                }

                AquaObject.VSET vset = new AquaObject.VSET();
                vset.vertDataSize   = size;
                vset.vtxlCount      = aqo.vtxlList[i].vertPositions.Count;
                vset.edgeVertsCount = aqo.vtxlList[i].edgeVerts.Count;

                if (aqo.objc.type >= 0xC32)
                {
                    vset.vtxeCount     = aqo.vtxeList.Count - 1;
                    vset.vtxlStartVert = vertCounter;
                    vertCounter       += vset.vtxlCount;

                    vset.bonePaletteCount = -1;
                }
                else
                {
                    vset.vtxeCount        = vtxe.vertDataTypes.Count;
                    vset.bonePaletteCount = aqo.vtxlList[i].bonePalette.Count;
                }

                aqo.vsetList.Add(vset);
            }

            //Update OBJC
            aqo.objc.largetsVtxl             = largestVertSize;
            aqo.objc.totalStripFaces         = totalStripsShorts;
            aqo.objc.totalVTXLCount          = totalVerts;
            aqo.objc.unkStructCount          = aqo.vtxlList.Count;
            aqo.objc.vsetCount               = aqo.vsetList.Count;
            aqo.objc.psetCount               = aqo.psetList.Count;
            aqo.objc.meshCount               = aqo.meshList.Count;
            aqo.objc.mateCount               = aqo.mateList.Count;
            aqo.objc.rendCount               = aqo.rendList.Count;
            aqo.objc.shadCount               = aqo.shadList.Count;
            aqo.objc.tstaCount               = aqo.tstaList.Count;
            aqo.objc.tsetCount               = aqo.tsetList.Count;
            aqo.objc.texfCount               = aqo.texfList.Count;
            aqo.objc.vtxeCount               = aqo.vtxeList.Count;
            aqo.objc.fBlock0                 = -1;
            aqo.objc.fBlock1                 = -1;
            aqo.objc.fBlock2                 = -1;
            aqo.objc.fBlock3                 = -1;
            aqo.objc.globalStrip3LengthCount = 1;
            aqo.objc.unkCount3               = 1;
            aqo.objc.bounds = AquaObjectMethods.GenerateBounding(aqo.vtxlList);
            if (doBitangent)
            {
                AquaObjectMethods.ComputeTangentSpace(aqo, false, true);
            }

            return(aqo);
        }