Exemple #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);
                }
        }
        //Takes in an Assimp model and generates a full PSO2 model and skeleton from it.
        public static AquaObject AssimpAquaConvertFull(string initialFilePath, float scaleFactor, bool preAssignNodeIds, bool isNGS)
        {
            AquaUtil aquaUtil  = new AquaUtil();
            float    baseScale = 1f / 100f * scaleFactor; //We assume that this will be 100x the true scale because 1 unit to 1 meter isn't the norm

            Assimp.AssimpContext context = new Assimp.AssimpContext();
            context.SetConfig(new Assimp.Configs.FBXPreservePivotsConfig(false));
            Assimp.Scene aiScene = context.ImportFile(initialFilePath, Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.FlipUVs);

            AquaObject aqp;
            AquaNode   aqn = new AquaNode();

            if (isNGS)
            {
                aqp = new NGSAquaObject();
            }
            else
            {
                aqp = new ClassicAquaObject();
            }

            //Construct Materials
            Dictionary <string, int> matNameTracker = new Dictionary <string, int>();

            foreach (var aiMat in aiScene.Materials)
            {
                string name;
                if (matNameTracker.ContainsKey(aiMat.Name))
                {
                    name = $"{aiMat.Name} ({matNameTracker[aiMat.Name]})";
                    matNameTracker[aiMat.Name] += 1;
                }
                else
                {
                    name = aiMat.Name;
                    matNameTracker.Add(aiMat.Name, 1);
                }

                AquaObject.GenericMaterial genMat = new AquaObject.GenericMaterial();
                List <string> shaderList          = new List <string>();
                AquaObjectMethods.GetMaterialNameData(ref name, shaderList, out string alphaType, out string playerFlag);
                genMat.matName     = name;
                genMat.shaderNames = shaderList;
                genMat.blendType   = alphaType;
                genMat.specialType = playerFlag;
                genMat.texNames    = new List <string>();
                genMat.texUVSets   = new List <int>();

                //Texture assignments. Since we can't rely on these to export properly, we dummy them or just put diffuse if a playerFlag isn't defined.
                //We'll have the user set these later if needed.
                if (genMat.specialType != null)
                {
                    AquaObjectMethods.GenerateSpecialMaterialParameters(genMat);
                }
                else if (aiMat.TextureDiffuse.FilePath != null)
                {
                    genMat.texNames.Add(Path.GetFileName(aiMat.TextureDiffuse.FilePath));
                }
                else
                {
                    genMat.texNames.Add("tex0_d.dds");
                }
                genMat.texUVSets.Add(0);

                AquaObjectMethods.GenerateMaterial(aqp, genMat, true);
            }

            //Default to this so ids can be assigned by order if needed
            Dictionary <string, int> boneDict = new Dictionary <string, int>();

            if (aiScene.RootNode.Name == null || !aiScene.RootNode.Name.Contains("(") || preAssignNodeIds == true)
            {
                int nodeCounter = 0;
                BuildAiNodeDictionary(aiScene.RootNode, ref nodeCounter, boneDict);
            }

            IterateAiNodesAQP(aqp, aqn, aiScene, aiScene.RootNode, Matrix4x4.Transpose(GetMat4FromAssimpMat4(aiScene.RootNode.Transform)), baseScale);

            //Assimp data is gathered, proceed to processing model data for PSO2
            AquaUtil.ModelSet set = new AquaUtil.ModelSet();
            set.models.Add(aqp);
            aquaUtil.aquaModels.Add(set);
            aquaUtil.ConvertToNGSPSO2Mesh(false, false, false, true, false, false, true);

            //AQPs created this way will require more processing to finish.
            //-Texture lists in particular, MUST be generated as what exists is not valid without serious errors
            return(aqp);
        }