public Dictionary <int, Vector4> defaultPos     = new Dictionary <int, Vector4>();    //Bind pose translations for animations


        //Basically, bone nodes have a matrix with world coordinates, but we need coords local to the parent. Therefore, we grab these here.
        public void GetDefaultTransformsFromBones(AquaNode bones)
        {
            nodeNames.Clear();
            defaultRots.Clear();
            for (int i = 0; i <= endRange; i++) //<= since we do want to hit that last one
            {
                var       bone = bones.nodeList[i];
                Matrix4x4 inverseWorldMatrix = new Matrix4x4(bone.m1.X, bone.m1.Y, bone.m1.Z, bone.m1.W, bone.m2.X, bone.m2.Y, bone.m2.Z, bone.m2.W,
                                                             bone.m3.X, bone.m3.Y, bone.m3.Z, bone.m3.W, bone.m4.X, bone.m4.Y, bone.m4.Z, bone.m4.W);
                Matrix4x4.Invert(inverseWorldMatrix, out Matrix4x4 worldMatrix);

                Quaternion localRot;
                Vector4    localPos;
                if (bone.parentId == -1)
                {
                    localRot = Quaternion.Inverse(Quaternion.CreateFromRotationMatrix(inverseWorldMatrix));
                    localPos = new Vector4(inverseWorldMatrix.M41, inverseWorldMatrix.M42, inverseWorldMatrix.M43, inverseWorldMatrix.M44);
                }
                else
                {
                    var       boneParent = bones.nodeList[bone.parentId];
                    Matrix4x4 parentInverseWorldMatrix = new Matrix4x4(boneParent.m1.X, boneParent.m1.Y, boneParent.m1.Z, boneParent.m1.W, boneParent.m2.X,
                                                                       boneParent.m2.Y, boneParent.m2.Z, boneParent.m2.W, boneParent.m3.X, boneParent.m3.Y, boneParent.m3.Z, boneParent.m3.W, boneParent.m4.X,
                                                                       boneParent.m4.Y, boneParent.m4.Z, boneParent.m4.W);

                    var localMatrix = Matrix4x4.Multiply(worldMatrix, parentInverseWorldMatrix);

                    localRot = Quaternion.CreateFromRotationMatrix(localMatrix);
                    localPos = new Vector4(localMatrix.M41, localMatrix.M42, localMatrix.M43, localMatrix.M44);
                }

                nodeNames.Add(bone.boneName.GetString());
                defaultRots.Add(i, localRot);
                defaultPos.Add(i, localPos);
            }
        }
        private static void IterateAiNodesAQP(AquaObject aqp, AquaNode aqn, Assimp.Scene aiScene, Assimp.Node aiNode, Matrix4x4 parentTfm, float baseScale)
        {
            //Decide if this is an effect node or not
            string nodeName   = aiNode.Name;
            var    nodeParent = aiNode.Parent;

            if (ParseNodeId(nodeName, out int nodeId))
            {
                AquaNode.NODE node = new AquaNode.NODE();
                node.animatedFlag = 1;
                node.unkNode      = -1;
                node.firstChild   = -1;
                node.nextSibling  = -1;
                node.const0_2     = 0;
                node.ngsSibling   = 0; //Unsure how this is truly set. Seems to correlate to an id or bone count subtracted from 0xFFFFFFFF. However 0 seems to work so we just leave it as that.

                //If there's a parent, do things reliant on that
                if (nodeParent != null && ParseNodeId(nodeParent.Name, out int parNodeId))
                {
                    node.parentId = parNodeId;

                    //Fix up parent node associations
                    if (aqn.nodeList[parNodeId].firstChild == -1 || (aqn.nodeList[parNodeId].firstChild > nodeId))
                    {
                        var parNode = aqn.nodeList[parNodeId];
                        parNode.firstChild      = nodeId;
                        aqn.nodeList[parNodeId] = parNode;
                    }

                    //Set next sibling. We loop through the parent node's children and set the smallest id that's larger than the present node's id. If nothing is found, keep it -1.
                    foreach (var childNode in nodeParent.Children)
                    {
                        if (childNode.Name != aiNode.Name)
                        {
                            ParseNodeId(childNode.Name, out int sibCandidate);
                            if (sibCandidate > nodeId && (node.nextSibling == -1 || sibCandidate < node.nextSibling))
                            {
                                node.nextSibling = sibCandidate;
                            }
                        }
                    }
                }
                else
                {
                    throw new Exception("Error: Parent node not processed before its child");
                }
                ParseShorts(nodeName, out node.boneShort1, out node.boneShort2);

                //Assign transform data ===TODO==
                var localMat = GetMat4FromAssimpMat4(aiNode.Transform);
                var worldMat = GetMat4FromAssimpMat4(GetWorldMatrix(aiNode));
                node.m1     = new Vector4(worldMat.M11, worldMat.M12, worldMat.M13, worldMat.M14);
                node.m2     = new Vector4(worldMat.M21, worldMat.M22, worldMat.M23, worldMat.M24);
                node.m3     = new Vector4(worldMat.M31, worldMat.M32, worldMat.M33, worldMat.M34);
                node.m4     = new Vector4(worldMat.M41, worldMat.M42, worldMat.M43, worldMat.M44);
                node.pos    = localMat.Translation;
                node.eulRot = QuaternionToEuler(Quaternion.CreateFromRotationMatrix(localMat));
                node.scale  = new Vector3(1, 1, 1); //This is a bit of a weird thing

                //Put in  list at appropriate id
                if (aqn.nodeList.Count < nodeId + 1)
                {
                    while (aqn.nodeList.Count < nodeId + 1)
                    {
                        aqn.nodeList.Add(new AquaNode.NODE());
                    }
                }
                aqn.nodeList[nodeId] = node;
            }
            else
            {
                if (aiNode.HasChildren)
                {
                    throw new Exception("Error: Effect nodes CANNOT have children. Add an id to the name to treat it as a standard node instead.");
                }
                AquaNode.NODO nodo = new AquaNode.NODO();
                nodo.animatedFlag = 1;
                nodo.boneName.SetString(nodeName);
                if (ParseNodeId(nodeParent.Name, out int parNodeId))
                {
                    nodo.parentId = parNodeId;
                }
                else
                {
                    throw new Exception("Error: Parent node not processed before its child");
                }
                ParseShorts(nodeName, out nodo.boneShort1, out nodo.boneShort2);

                //Assign transform data
                var mat4 = GetMat4FromAssimpMat4(aiNode.Transform);
                nodo.pos = new Vector3(mat4.M14 * baseScale, mat4.M24 * baseScale, mat4.M34 * baseScale);

                nodo.eulRot = QuaternionToEuler(Quaternion.CreateFromRotationMatrix(mat4));

                aqn.nodoList.Add(nodo);
            }

            Matrix4x4 nodeMat = Matrix4x4.Transpose(GetMat4FromAssimpMat4(aiNode.Transform));

            nodeMat = Matrix4x4.Multiply(nodeMat, parentTfm);

            foreach (int meshId in aiNode.MeshIndices)
            {
                var mesh = aiScene.Meshes[meshId];
                AddAiMeshToAQP(aqp, mesh, nodeMat, baseScale);
            }

            foreach (var childNode in aiNode.Children)
            {
                IterateAiNodesAQP(aqp, aqn, aiScene, childNode, nodeMat, baseScale);
            }
        }
        public static Assimp.Scene AssimpExport(string filePath, AquaObject aqp, AquaNode aqn)
        {
            if (aqp is NGSAquaObject)
            {
                //NGS aqps will give lots of isolated vertices if we don't handle them
                //Since we're not actually altering the data so much as rearranging references, we can just do this
                aqp = aqp.Clone();
                aqp.splitVSETPerMesh();
            }
            Assimp.Scene aiScene = new Assimp.Scene();

            //Create an array to hold references to these since Assimp lacks a way to grab these by order or id
            //We don't need the nodo count in this since they can't be parents
            Assimp.Node[] boneArray = new Assimp.Node[aqn.nodeList.Count];

            //Set up root node
            var root       = aqn.nodeList[0];
            var aiRootNode = new Assimp.Node("RootNode", null);

            aiRootNode.Transform = Assimp.Matrix4x4.Identity;

            aiScene.RootNode = aiRootNode;

            //Assign bones
            for (int i = 0; i < aqn.nodeList.Count; i++)
            {
                var         bn = aqn.nodeList[i];
                Assimp.Node parentNode;
                var         parentTfm = Matrix4x4.Identity;
                if (bn.parentId == -1)
                {
                    parentNode = aiRootNode;
                }
                else
                {
                    parentNode = boneArray[bn.parentId];
                    var pn = aqn.nodeList[bn.parentId];
                    parentTfm = 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 * 100, pn.m4.Y * 100, pn.m4.Z * 100, pn.m4.W);
                }
                var aiNode = new Assimp.Node($"({i})" + bn.boneName.GetString(), parentNode);

                //Use inverse bind matrix as base
                var bnMat = new Matrix4x4(bn.m1.X, bn.m1.Y, bn.m1.Z, bn.m1.W,
                                          bn.m2.X, bn.m2.Y, bn.m2.Z, bn.m2.W,
                                          bn.m3.X, bn.m3.Y, bn.m3.Z, bn.m3.W,
                                          bn.m4.X * 100, bn.m4.Y * 100, bn.m4.Z * 100, bn.m4.W);
                Matrix4x4.Invert(bnMat, out bnMat);

                //Get local transform
                aiNode.Transform = GetAssimpMat4(bnMat * parentTfm);

                parentNode.Children.Add(aiNode);
                boneArray[i] = aiNode;
            }

            foreach (AquaNode.NODO bn in aqn.nodoList)
            {
                var parentNodo = boneArray[bn.parentId];
                var aiNode     = new Assimp.Node(bn.boneName.GetString(), parentNodo);

                //NODOs are a bit more primitive. We need to generate the matrix for these ones.
                var matrix   = Assimp.Matrix4x4.Identity;
                var rotation = Assimp.Matrix4x4.FromRotationX(bn.eulRot.X) *
                               Assimp.Matrix4x4.FromRotationY(bn.eulRot.Y) *
                               Assimp.Matrix4x4.FromRotationZ(bn.eulRot.Z);

                matrix          *= rotation;
                matrix          *= Assimp.Matrix4x4.FromTranslation(new Assimp.Vector3D(bn.pos.X * 100, bn.pos.Y * 100, bn.pos.Z * 100));
                aiNode.Transform = matrix;

                parentNodo.Children.Add(aiNode);
            }

            //Assign meshes and materials
            foreach (AquaObject.MESH msh in aqp.meshList)
            {
                var vtxl = aqp.vtxlList[msh.vsetIndex];

                //Mesh
                var  aiMeshName       = string.Format("mesh[{4}]_{0}_{1}_{2}_{3}_mesh", msh.mateIndex, msh.rendIndex, msh.shadIndex, msh.tsetIndex, aiScene.Meshes.Count);
                bool hasVertexWeights = aqp.vtxlList[msh.vsetIndex].vertWeightIndices.Count > 0;

                var aiMesh = new Assimp.Mesh(aiMeshName, Assimp.PrimitiveType.Triangle);

                //Vertex face data - PSO2 Actually doesn't do this, it just has per vertex data so we can just map a vertice's data to each face using it
                //It may actually be possible to add this to the previous loop, but my reference didn't so I'm doing it in a separate loop for safety
                //Reference: https://github.com/TGEnigma/Amicitia/blob/master/Source/AmicitiaLibrary/Graphics/RenderWare/RWClumpNode.cs
                //UVs will have dummied data to ensure that if the game arbitrarily writes them, they will still be exported back in the same order
                for (int vertId = 0; vertId < vtxl.vertPositions.Count; vertId++)
                {
                    if (vtxl.vertPositions.Count > 0)
                    {
                        var pos = vtxl.vertPositions[vertId] * 100;
                        aiMesh.Vertices.Add(new Assimp.Vector3D(pos.X, pos.Y, pos.Z));
                    }

                    if (vtxl.vertNormals.Count > 0)
                    {
                        var nrm = vtxl.vertNormals[vertId];
                        aiMesh.Normals.Add(new Assimp.Vector3D(nrm.X, nrm.Y, nrm.Z));
                    }

                    if (vtxl.vertColors.Count > 0)
                    {
                        //Vert colors are bgra
                        var rawClr = vtxl.vertColors[vertId];
                        var clr    = new Assimp.Color4D(clrToFloat(rawClr[2]), clrToFloat(rawClr[1]), clrToFloat(rawClr[0]), clrToFloat(rawClr[3]));
                        aiMesh.VertexColorChannels[0].Add(clr);
                    }

                    if (vtxl.vertColor2s.Count > 0)
                    {
                        //Vert colors are bgra
                        var rawClr = vtxl.vertColor2s[vertId];
                        var clr    = new Assimp.Color4D(clrToFloat(rawClr[2]), clrToFloat(rawClr[1]), clrToFloat(rawClr[0]), clrToFloat(rawClr[3]));
                        aiMesh.VertexColorChannels[1].Add(clr);
                    }

                    if (vtxl.uv1List.Count > 0)
                    {
                        var textureCoordinate   = vtxl.uv1List[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(textureCoordinate.X, textureCoordinate.Y, 0f);
                        aiMesh.TextureCoordinateChannels[0].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[0].Add(aiTextureCoordinate);
                    }

                    if (vtxl.uv2List.Count > 0)
                    {
                        var textureCoordinate   = vtxl.uv2List[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(textureCoordinate.X, textureCoordinate.Y, 0f);
                        aiMesh.TextureCoordinateChannels[1].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[1].Add(aiTextureCoordinate);
                    }

                    if (vtxl.uv3List.Count > 0)
                    {
                        var textureCoordinate   = vtxl.uv3List[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(textureCoordinate.X, textureCoordinate.Y, 0f);
                        aiMesh.TextureCoordinateChannels[2].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[2].Add(aiTextureCoordinate);
                    }

                    if (vtxl.uv4List.Count > 0)
                    {
                        var textureCoordinate   = vtxl.uv4List[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(textureCoordinate.X, textureCoordinate.Y, 0f);
                        aiMesh.TextureCoordinateChannels[3].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[3].Add(aiTextureCoordinate);
                    }

                    if (vtxl.vert0x22.Count > 0)
                    {
                        var textureCoordinate   = vtxl.vert0x22[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(uvShortToFloat(textureCoordinate[0]), uvShortToFloat(textureCoordinate[1]), 0f);
                        aiMesh.TextureCoordinateChannels[4].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[4].Add(aiTextureCoordinate);
                    }

                    if (vtxl.vert0x23.Count > 0)
                    {
                        var textureCoordinate   = vtxl.vert0x23[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(uvShortToFloat(textureCoordinate[0]), uvShortToFloat(textureCoordinate[1]), 0f);
                        aiMesh.TextureCoordinateChannels[5].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[5].Add(aiTextureCoordinate);
                    }

                    if (vtxl.vert0x24.Count > 0)
                    {
                        var textureCoordinate   = vtxl.vert0x24[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(uvShortToFloat(textureCoordinate[0]), uvShortToFloat(textureCoordinate[1]), 0f);
                        aiMesh.TextureCoordinateChannels[6].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[6].Add(aiTextureCoordinate);
                    }

                    if (vtxl.vert0x25.Count > 0)
                    {
                        var textureCoordinate   = vtxl.vert0x25[vertId];
                        var aiTextureCoordinate = new Assimp.Vector3D(uvShortToFloat(textureCoordinate[0]), uvShortToFloat(textureCoordinate[1]), 0f);
                        aiMesh.TextureCoordinateChannels[7].Add(aiTextureCoordinate);
                    }
                    else
                    {
                        var aiTextureCoordinate = new Assimp.Vector3D(0, 0, 0f);
                        aiMesh.TextureCoordinateChannels[7].Add(aiTextureCoordinate);
                    }
                }

                //Assimp Bones - Assimp likes to store vertex weights in bones and bones references in meshes
                if (hasVertexWeights)
                {
                    //Get bone palette
                    List <uint> bonePalette;
                    if (aqp.objc.bonePaletteOffset > 0)
                    {
                        bonePalette = aqp.bonePalette;
                    }
                    else
                    {
                        bonePalette = new List <uint>();
                        for (int bn = 0; bn < vtxl.bonePalette.Count; bn++)
                        {
                            bonePalette.Add(vtxl.bonePalette[bn]);
                        }
                    }
                    var aiBoneMap = new Dictionary <int, Assimp.Bone>();

                    //Iterate through vertices
                    for (int vertId = 0; vertId < vtxl.vertWeightIndices.Count; vertId++)
                    {
                        var boneIndices = vtxl.vertWeightIndices[vertId];
                        var boneWeights = Vector4ToFloatArray(vtxl.vertWeights[vertId]);

                        //Iterate through weights
                        for (int wt = 0; wt < 4; wt++)
                        {
                            var boneIndex  = boneIndices[wt];
                            var boneWeight = boneWeights[wt];

                            if (boneWeight == 0.0f)
                            {
                                continue;
                            }

                            if (!aiBoneMap.Keys.Contains(boneIndex))
                            {
                                var aiBone  = new Assimp.Bone();
                                var aqnBone = boneArray[bonePalette[boneIndex]];
                                var rawBone = aqn.nodeList[(int)bonePalette[boneIndex]];

                                aiBone.Name = $"({bonePalette[boneIndex]})" + rawBone.boneName.GetString();
                                aiBone.VertexWeights.Add(new Assimp.VertexWeight(vertId, boneWeight));

                                var invTransform = new Assimp.Matrix4x4(rawBone.m1.X, rawBone.m2.X, rawBone.m3.X, rawBone.m4.X,
                                                                        rawBone.m1.Y, rawBone.m2.Y, rawBone.m3.Y, rawBone.m4.Y,
                                                                        rawBone.m1.Z, rawBone.m2.Z, rawBone.m3.Z, rawBone.m4.Z,
                                                                        rawBone.m1.W, rawBone.m2.W, rawBone.m3.W, rawBone.m4.W);

                                aiBone.OffsetMatrix = invTransform;

                                aiBoneMap[boneIndex] = aiBone;
                            }

                            if (!aiBoneMap[boneIndex].VertexWeights.Any(x => x.VertexID == vertId))
                            {
                                aiBoneMap[boneIndex].VertexWeights.Add(new Assimp.VertexWeight(vertId, boneWeight));
                            }
                        }
                    }

                    //Add the bones to the mesh
                    aiMesh.Bones.AddRange(aiBoneMap.Values);
                }
                else   //Handle rigid meshes
                {
                    var aiBone  = new Assimp.Bone();
                    var aqnBone = boneArray[msh.baseMeshNodeId];

                    // Name
                    aiBone.Name = aqnBone.Name;

                    // VertexWeights
                    for (int i = 0; i < aiMesh.Vertices.Count; i++)
                    {
                        var aiVertexWeight = new Assimp.VertexWeight(i, 1f);
                        aiBone.VertexWeights.Add(aiVertexWeight);
                    }

                    aiBone.OffsetMatrix = Assimp.Matrix4x4.Identity;

                    aiMesh.Bones.Add(aiBone);
                }

                //Faces
                foreach (var face in aqp.strips[msh.vsetIndex].GetTriangles(true))
                {
                    aiMesh.Faces.Add(new Assimp.Face(new int[] { (int)face.X, (int)face.Y, (int)face.Z }));
                }

                //Material
                var             mat        = aqp.mateList[msh.mateIndex];
                var             shaderSet  = AquaObjectMethods.GetShaderNames(aqp, msh.shadIndex);
                var             textureSet = AquaObjectMethods.GetTexListNames(aqp, msh.tsetIndex);
                Assimp.Material mate       = new Assimp.Material();

                mate.ColorDiffuse = new Assimp.Color4D(mat.diffuseRGBA.X, mat.diffuseRGBA.Y, mat.diffuseRGBA.Z, mat.diffuseRGBA.W);
                if (mat.alphaType.GetString().Equals("add"))
                {
                    mate.BlendMode = Assimp.BlendMode.Additive;
                }
                mate.Name = "|[]{}~`!@#$%^&*;:'\"?><,./(" + shaderSet[0] + "," + shaderSet[1] + ")" + "{" + mat.alphaType.GetString() + "}" + mat.matName.GetString();

                //Set textures - PSO2 Texture slots are NOT consistent and depend entirely on the selected shader. As such, slots will be somewhat arbitrary after albedo/diffuse
                for (int i = 0; i < textureSet.Count; i++)
                {
                    switch (i)
                    {
                    case 0:
                        mate.TextureDiffuse = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Diffuse, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 1:
                        mate.TextureSpecular = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Specular, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 2:
                        mate.TextureNormal = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Normals, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 3:
                        mate.TextureLightMap = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Lightmap, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 4:
                        mate.TextureDisplacement = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Displacement, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 5:
                        mate.TextureOpacity = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Opacity, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 6:
                        mate.TextureHeight = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Height, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 7:
                        mate.TextureEmissive = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Emissive, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 8:
                        mate.TextureAmbient = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Ambient, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    case 9:
                        mate.TextureReflection = new Assimp.TextureSlot(
                            textureSet[i], Assimp.TextureType.Reflection, i, Assimp.TextureMapping.FromUV, aqp.tstaList[aqp.tsetList[msh.tsetIndex].tstaTexIDs[i]].modelUVSet, 0,
                            Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                        break;

                    default:
                        break;
                    }
                }

                mate.ShadingMode = Assimp.ShadingMode.Phong;


                var meshNodeName = string.Format("mesh[{4}]_{0}_{1}_{2}_{3}#{4}#{5}", msh.mateIndex, msh.rendIndex, msh.shadIndex, msh.tsetIndex, aiScene.Meshes.Count, msh.baseMeshNodeId, msh.baseMeshDummyId);

                // Add mesh to meshes
                aiScene.Meshes.Add(aiMesh);

                // Add material to materials
                aiScene.Materials.Add(mate);

                // MaterialIndex
                aiMesh.MaterialIndex = aiScene.Materials.Count - 1;

                // Set up mesh node and add this mesh's index to it (This tells assimp to export it as a mesh for various formats)
                var meshNode = new Assimp.Node(meshNodeName, aiScene.RootNode);
                meshNode.Transform = Assimp.Matrix4x4.Identity;

                aiScene.RootNode.Children.Add(meshNode);

                meshNode.MeshIndices.Add(aiScene.Meshes.Count - 1);
            }

            return(aiScene);
        }
        //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);
        }