Exemplo n.º 1
0
        private void InitSkinInfo(Assimp.Mesh mesh, AssimpSceneContainer container)
        {
            var          boneIDs     = new uvec4[mesh.VertexCount];
            var          boneWeights = new vec4[mesh.VertexCount];
            AllBoneInfos allBones    = container.GetAllBoneInfos();
            Dictionary <string, uint> nameIndexDict = allBones.nameIndexDict;

            for (int i = 0; i < mesh.BoneCount; i++)
            {
                Assimp.Bone bone      = mesh.Bones[i]; // bones that influence this mesh.
                uint        boneIndex = nameIndexDict[bone.Name];

                for (int j = 0; j < bone.VertexWeightCount; j++)
                {
                    Assimp.VertexWeight vertexWeight = bone.VertexWeights[j];
                    uint vertexID = vertexWeight.VertexID;
                    for (int t = 0; t < 4; t++)
                    {
                        if (boneWeights[vertexID][t] == 0.0f) // fill in x y z w.
                        {
                            boneIDs[vertexID][t]     = boneIndex;
                            boneWeights[vertexID][t] = vertexWeight.Weight;
                            break;
                        }
                    }
                }
            }
            this.boneIDs     = boneIDs;
            this.boneWeights = boneWeights;
        }
Exemplo n.º 2
0
        public override Assimp.Node assimpExport(ref Assimp.Scene scn, ref Dictionary <int, int> meshImportStatus)
        {
            Assimp.Mesh amesh = new Assimp.Mesh();
            Assimp.Node node;
            amesh.Name = name;

            int meshHash = meshVao.GetHashCode();

            //TESTING
            if (scn.MeshCount > 20)
            {
                node = base.assimpExport(ref scn, ref meshImportStatus);
                return(node);
            }

            if (!meshImportStatus.ContainsKey(meshHash))
            //if (false)
            {
                meshImportStatus[meshHash] = scn.MeshCount;

                int          vertcount = metaData.vertrend_graphics - metaData.vertrstart_graphics + 1;
                MemoryStream vms       = new MemoryStream(gobject.meshDataDict[metaData.Hash].vs_buffer);
                MemoryStream ims       = new MemoryStream(gobject.meshDataDict[metaData.Hash].is_buffer);
                BinaryReader vbr       = new BinaryReader(vms);
                BinaryReader ibr       = new BinaryReader(ims);


                //Initialize Texture Component Channels
                if (gobject.bufInfo[1] != null)
                {
                    List <Assimp.Vector3D> textureChannel = new List <Assimp.Vector3D>();
                    amesh.TextureCoordinateChannels.Append(textureChannel);
                    amesh.UVComponentCount[0] = 2;
                }

                //Generate bones only for the joints related to the mesh
                Dictionary <int, Assimp.Bone> localJointDict = new Dictionary <int, Assimp.Bone>();

                //Export Bone Structure
                if (Skinned)
                //if (false)
                {
                    for (int i = 0; i < meshVao.BoneRemapIndicesCount; i++)
                    {
                        int joint_id = meshVao.BoneRemapIndices[i];
                        //Fetch name
                        Joint relJoint = null;

                        foreach (Joint jnt in parentScene.jointDict.Values)
                        {
                            if (jnt.jointIndex == joint_id)
                            {
                                relJoint = jnt;
                                break;
                            }
                        }

                        //Generate bone
                        Assimp.Bone b = new Assimp.Bone();
                        if (relJoint != null)
                        {
                            b.Name         = relJoint.name;
                            b.OffsetMatrix = MathUtils.convertMatrix(relJoint.invBMat);
                        }


                        localJointDict[i] = b;
                        amesh.Bones.Add(b);
                    }
                }



                //Write geometry info

                vbr.BaseStream.Seek(0, SeekOrigin.Begin);
                for (int i = 0; i < vertcount; i++)
                {
                    Assimp.Vector3D v, vN;

                    for (int j = 0; j < gobject.bufInfo.Count; j++)
                    {
                        bufInfo buf = gobject.bufInfo[j];
                        if (buf is null)
                        {
                            continue;
                        }

                        switch (buf.semantic)
                        {
                        case 0:     //vPosition
                        {
                            switch (buf.type)
                            {
                            case VertexAttribPointerType.HalfFloat:
                                uint v1 = vbr.ReadUInt16();
                                uint v2 = vbr.ReadUInt16();
                                uint v3 = vbr.ReadUInt16();
                                uint v4 = vbr.ReadUInt16();

                                //Transform vector with worldMatrix
                                v = new Assimp.Vector3D(Utils.Half.decompress(v1), Utils.Half.decompress(v2), Utils.Half.decompress(v3));
                                break;

                            case VertexAttribPointerType.Float:             //This is used in my custom vbos
                                float f1 = vbr.ReadSingle();
                                float f2 = vbr.ReadSingle();
                                float f3 = vbr.ReadSingle();
                                //Transform vector with worldMatrix
                                v = new Assimp.Vector3D(f1, f2, f3);
                                break;

                            default:
                                throw new Exception("Unimplemented Vertex Type");
                            }
                            amesh.Vertices.Add(v);
                            break;
                        }

                        case 1:     //uvPosition
                        {
                            Assimp.Vector3D uv;
                            uint            v1 = vbr.ReadUInt16();
                            uint            v2 = vbr.ReadUInt16();
                            uint            v3 = vbr.ReadUInt16();
                            uint            v4 = vbr.ReadUInt16();
                            //uint v4 = Convert.ToUInt16(vbr.ReadUInt16());
                            uv = new Assimp.Vector3D(Utils.Half.decompress(v1), Utils.Half.decompress(v2), 0.0f);

                            amesh.TextureCoordinateChannels[0].Add(uv);         //Add directly to the first channel
                            break;
                        }

                        case 2:     //nPosition
                        case 3:     //tPosition
                        {
                            switch (buf.type)
                            {
                            case (VertexAttribPointerType.Float):
                                float f1, f2, f3;
                                f1 = vbr.ReadSingle();
                                f2 = vbr.ReadSingle();
                                f3 = vbr.ReadSingle();
                                vN = new Assimp.Vector3D(f1, f2, f3);
                                break;

                            case (VertexAttribPointerType.HalfFloat):
                                uint v1, v2, v3;
                                v1 = vbr.ReadUInt16();
                                v2 = vbr.ReadUInt16();
                                v3 = vbr.ReadUInt16();
                                vN = new Assimp.Vector3D(Utils.Half.decompress(v1), Utils.Half.decompress(v2), Utils.Half.decompress(v3));
                                break;

                            case (VertexAttribPointerType.Int2101010Rev):
                                int    i1, i2, i3;
                                uint   value;
                                byte[] a32 = new byte[4];
                                a32 = vbr.ReadBytes(4);

                                value = BitConverter.ToUInt32(a32, 0);
                                //Convert Values
                                i1 = _2sComplement.toInt((value >> 00) & 0x3FF, 10);
                                i2 = _2sComplement.toInt((value >> 10) & 0x3FF, 10);
                                i3 = _2sComplement.toInt((value >> 20) & 0x3FF, 10);
                                //int i4 = _2sComplement.toInt((value >> 30) & 0x003, 10);
                                float norm = (float)Math.Sqrt(i1 * i1 + i2 * i2 + i3 * i3);

                                vN = new Assimp.Vector3D(Convert.ToSingle(i1) / norm,
                                                         Convert.ToSingle(i2) / norm,
                                                         Convert.ToSingle(i3) / norm);

                                //Debug.WriteLine(vN);
                                break;

                            default:
                                throw new Exception("UNIMPLEMENTED NORMAL TYPE. PLEASE REPORT");
                            }

                            if (j == 2)
                            {
                                amesh.Normals.Add(vN);
                            }
                            else if (j == 3)
                            {
                                amesh.Tangents.Add(vN);
                                amesh.BiTangents.Add(new Assimp.Vector3D(0.0f, 0.0f, 1.0f));
                            }
                            break;
                        }

                        case 4:               //bPosition
                            vbr.ReadBytes(4); // skip
                            break;

                        case 5:     //BlendIndices + BlendWeights
                        {
                            int[]   joint_ids = new int[4];
                            float[] weights   = new float[4];

                            for (int k = 0; k < 4; k++)
                            {
                                joint_ids[k] = vbr.ReadByte();
                            }


                            for (int k = 0; k < 4; k++)
                            {
                                weights[k] = Utils.Half.decompress(vbr.ReadUInt16());
                            }

                            if (Skinned)
                            //if (false)
                            {
                                for (int k = 0; k < 4; k++)
                                {
                                    int joint_id = joint_ids[k];

                                    Assimp.VertexWeight vw = new Assimp.VertexWeight();
                                    vw.VertexID = i;
                                    vw.Weight   = weights[k];
                                    localJointDict[joint_id].VertexWeights.Add(vw);
                                }
                            }


                            break;
                        }

                        case 6:
                            break;     //Handled by 5

                        default:
                        {
                            throw new Exception("UNIMPLEMENTED BUF Info. PLEASE REPORT");
                            break;
                        }
                        }
                    }
                }

                //Export Faces
                //Get indices
                ibr.BaseStream.Seek(0, SeekOrigin.Begin);
                bool start  = false;
                int  fstart = 0;
                for (int i = 0; i < metaData.batchcount / 3; i++)
                {
                    int f1, f2, f3;
                    //NEXT models assume that all gstream meshes have uint16 indices
                    f1 = ibr.ReadUInt16();
                    f2 = ibr.ReadUInt16();
                    f3 = ibr.ReadUInt16();

                    if (!start && this.type != TYPES.COLLISION)
                    {
                        fstart = f1; start = true;
                    }
                    else if (!start && this.type == TYPES.COLLISION)
                    {
                        fstart = 0; start = true;
                    }

                    int f11, f22, f33;
                    f11 = f1 - fstart;
                    f22 = f2 - fstart;
                    f33 = f3 - fstart;


                    Assimp.Face face = new Assimp.Face();
                    face.Indices.Add(f11);
                    face.Indices.Add(f22);
                    face.Indices.Add(f33);


                    amesh.Faces.Add(face);
                }

                scn.Meshes.Add(amesh);
            }

            node = base.assimpExport(ref scn, ref meshImportStatus);
            node.MeshIndices.Add(meshImportStatus[meshHash]);

            return(node);
        }
        public static Assimp.Scene AssimpPRMExport(string filePath, PRMModel prm)
        {
            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[2];

            //Set up root node
            var aiRootNode = new Assimp.Node("RootNode", null);

            aiRootNode.Transform = Assimp.Matrix4x4.Identity;

            boneArray[0]     = aiRootNode;
            aiScene.RootNode = aiRootNode;

            //Set up single child node
            var aiNode = new Assimp.Node(Path.GetFileNameWithoutExtension(filePath) + "_node", aiRootNode);

            //Use inverse bind matrix as base

            //Get local transform
            aiNode.Transform = aiRootNode.Transform;

            aiRootNode.Children.Add(aiNode);
            boneArray[1] = aiNode;

            //Mesh
            string aiMeshName = Path.GetFileNameWithoutExtension(filePath);

            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
            for (int vertId = 0; vertId < prm.vertices.Count; vertId++)
            {
                var prmVert = prm.vertices[vertId];

                var pos = prmVert.pos * 100;
                aiMesh.Vertices.Add(new Assimp.Vector3D(pos.X, pos.Y, pos.Z));

                var nrm = prmVert.normal;
                aiMesh.Normals.Add(new Assimp.Vector3D(nrm.X, nrm.Y, nrm.Z));

                //Vert colors are bgra
                var rawClr = prmVert.color;
                var clr    = new Assimp.Color4D(clrToFloat(rawClr[2]), clrToFloat(rawClr[1]), clrToFloat(rawClr[0]), clrToFloat(rawClr[3]));
                aiMesh.VertexColorChannels[0].Add(clr);

                var uv1   = prmVert.uv1;
                var aiUV1 = new Assimp.Vector3D(uv1.X, uv1.Y, 0f);
                aiMesh.TextureCoordinateChannels[0].Add(aiUV1);


                var uv2   = prmVert.uv2;
                var aiUV2 = new Assimp.Vector3D(uv2.X, uv2.Y, 0f);
                aiMesh.TextureCoordinateChannels[1].Add(aiUV2);
            }

            //Handle rigid meshes
            {
                var aiBone  = new Assimp.Bone();
                var aqnBone = boneArray[0];

                // Name
                aiBone.Name = aiNode.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 prm.faces)
            {
                aiMesh.Faces.Add(new Assimp.Face(new int[] { (int)face.X, (int)face.Y, (int)face.Z }));
            }

            //Material
            Assimp.Material mate = new Assimp.Material();

            mate.ColorDiffuse = new Assimp.Color4D(1, 1, 1, 1);
            mate.Name         = aiMeshName + "_material";

            mate.ShadingMode = Assimp.ShadingMode.Phong;

            var meshNodeName = Path.GetFileNameWithoutExtension(filePath);

            // 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);
        }
Exemplo n.º 4
0
        public static Assimp.Scene ToAssimpScene(RwClumpNode clumpNode)
        {
            // Scene
            var aiScene = new Assimp.Scene();

            // RootNode
            var rootFrame  = clumpNode.FrameList[0];
            var aiRootNode = new Assimp.Node("RootNode", null);

            aiRootNode.Transform = new Assimp.Matrix4x4(rootFrame.Transform.M11, rootFrame.Transform.M21, rootFrame.Transform.M31, rootFrame.Transform.M41,
                                                        rootFrame.Transform.M12, rootFrame.Transform.M22, rootFrame.Transform.M32, rootFrame.Transform.M42,
                                                        rootFrame.Transform.M13, rootFrame.Transform.M23, rootFrame.Transform.M33, rootFrame.Transform.M43,
                                                        rootFrame.Transform.M14, rootFrame.Transform.M24, rootFrame.Transform.M34, rootFrame.Transform.M44);

            aiScene.RootNode = aiRootNode;

            for (int i = 1; i < clumpNode.FrameList.Count; i++)
            {
                var frame     = clumpNode.FrameList[i];
                var frameName = "_" + frame.HAnimFrameExtensionNode.NameId;

                Assimp.Node aiParentNode = null;
                if (frame.Parent != null)
                {
                    string parentName = "RootNode";
                    if (frame.Parent.HasHAnimExtension)
                    {
                        parentName = "_" + frame.Parent.HAnimFrameExtensionNode.NameId;
                    }

                    aiParentNode = aiRootNode.FindNode(parentName);
                }

                var aiNode = new Assimp.Node(frameName, aiParentNode);
                aiNode.Transform = new Assimp.Matrix4x4(frame.Transform.M11, frame.Transform.M21, frame.Transform.M31, frame.Transform.M41,
                                                        frame.Transform.M12, frame.Transform.M22, frame.Transform.M32, frame.Transform.M42,
                                                        frame.Transform.M13, frame.Transform.M23, frame.Transform.M33, frame.Transform.M43,
                                                        frame.Transform.M14, frame.Transform.M24, frame.Transform.M34, frame.Transform.M44);
                aiParentNode.Children.Add(aiNode);
            }

            // Meshes, Materials
            for (int atomicIndex = 0; atomicIndex < clumpNode.Atomics.Count; atomicIndex++)
            {
                var atomic   = clumpNode.Atomics[atomicIndex];
                var geometry = clumpNode.GeometryList[atomic.GeometryIndex];
                var frame    = clumpNode.FrameList[atomic.FrameIndex];

                var aiNodeName          = $"Atomic{atomicIndex}";
                var aiNode              = new Assimp.Node(aiNodeName, aiScene.RootNode);
                var frameWorldTransform = frame.WorldTransform;
                aiNode.Transform = new Assimp.Matrix4x4(frameWorldTransform.M11, frameWorldTransform.M21, frameWorldTransform.M31, frameWorldTransform.M41,
                                                        frameWorldTransform.M12, frameWorldTransform.M22, frameWorldTransform.M32, frameWorldTransform.M42,
                                                        frameWorldTransform.M13, frameWorldTransform.M23, frameWorldTransform.M33, frameWorldTransform.M43,
                                                        frameWorldTransform.M14, frameWorldTransform.M24, frameWorldTransform.M34, frameWorldTransform.M44);
                aiScene.RootNode.Children.Add(aiNode);

                bool hasVertexWeights = geometry.SkinNode != null;

                for (int meshIndex = 0; meshIndex < geometry.MeshListNode.MaterialMeshes.Length; meshIndex++)
                {
                    var mesh   = geometry.MeshListNode.MaterialMeshes[meshIndex];
                    var aiMesh = new Assimp.Mesh($"Atomic{atomicIndex}_Geometry{atomic.GeometryIndex}_Mesh{meshIndex}", Assimp.PrimitiveType.Triangle);

                    // get triangle list indices
                    int[] indices;

                    if (geometry.MeshListNode.PrimitiveType == RwPrimitiveType.TriangleList)
                    {
                        indices = mesh.Indices;
                    }
                    else
                    {
                        indices = MeshUtilities.ToTriangleList(mesh.Indices, false);
                    }

                    // Faces
                    for (int i = 0; i < indices.Length; i += 3)
                    {
                        var faceIndices = new[] { i, i + 1, i + 2 };
                        var aiFace      = new Assimp.Face(faceIndices);
                        aiMesh.Faces.Add(aiFace);
                    }

                    // TextureCoordinateChannels, VertexColorChannels, Vertices, MaterialIndex, Normals
                    for (int triIdx = 0; triIdx < indices.Length; triIdx += 3)
                    {
                        for (int triVertIdx = 0; triVertIdx < 3; triVertIdx++)
                        {
                            int vertexIndex = indices[triIdx + triVertIdx];

                            // TextureCoordinateChannels
                            if (geometry.HasTextureCoordinates)
                            {
                                for (int channelIdx = 0; channelIdx < geometry.TextureCoordinateChannelCount; channelIdx++)
                                {
                                    var textureCoordinate   = geometry.TextureCoordinateChannels[channelIdx][vertexIndex];
                                    var aiTextureCoordinate = new Assimp.Vector3D(textureCoordinate.X, textureCoordinate.Y, 0f);
                                    aiMesh.TextureCoordinateChannels[channelIdx].Add(aiTextureCoordinate);
                                }
                            }

                            // VertexColorChannels
                            if (geometry.HasColors)
                            {
                                var color   = geometry.Colors[vertexIndex];
                                var aiColor = new Assimp.Color4D(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
                                aiMesh.VertexColorChannels[0].Add(aiColor);
                            }

                            // Vertices
                            if (geometry.HasVertices)
                            {
                                var vertex   = geometry.Vertices[vertexIndex];
                                var aiVertex = new Assimp.Vector3D(vertex.X, vertex.Y, vertex.Z);
                                aiMesh.Vertices.Add(aiVertex);
                            }

                            // Normals
                            if (geometry.HasNormals)
                            {
                                var normal   = geometry.Normals[vertexIndex];
                                var aiNormal = new Assimp.Vector3D(normal.X, normal.Y, normal.Z);
                                aiMesh.Normals.Add(aiNormal);
                            }
                        }
                    }

                    // Bones
                    if (hasVertexWeights)
                    {
                        var skinNode  = geometry.SkinNode;
                        var aiBoneMap = new Dictionary <int, Assimp.Bone>();

                        for (int i = 0; i < indices.Length; i++)
                        {
                            var vertexIndex     = indices[i];
                            int realVertexIndex = i;

                            for (int j = 0; j < 4; j++)
                            {
                                var boneIndex  = skinNode.VertexBoneIndices[vertexIndex][j];
                                var boneWeight = skinNode.VertexBoneWeights[vertexIndex][j];

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

                                if (!aiBoneMap.Keys.Contains(boneIndex))
                                {
                                    var aiBone    = new Assimp.Bone();
                                    var boneFrame = clumpNode.FrameList.GetFrameByHierarchyIndex(boneIndex);

                                    aiBone.Name = boneFrame.HasHAnimExtension ? "_" + boneFrame.HAnimFrameExtensionNode.NameId : "RootNode";
                                    aiBone.VertexWeights.Add(new Assimp.VertexWeight(realVertexIndex, boneWeight));

                                    Matrix4x4.Invert(frame.WorldTransform, out Matrix4x4 invertedFrameWorldTransform);
                                    Matrix4x4.Invert(boneFrame.WorldTransform * invertedFrameWorldTransform, out Matrix4x4 offsetMatrix);
                                    aiBone.OffsetMatrix = new Assimp.Matrix4x4(offsetMatrix.M11, offsetMatrix.M21, offsetMatrix.M31, offsetMatrix.M41,
                                                                               offsetMatrix.M12, offsetMatrix.M22, offsetMatrix.M32, offsetMatrix.M42,
                                                                               offsetMatrix.M13, offsetMatrix.M23, offsetMatrix.M33, offsetMatrix.M43,
                                                                               offsetMatrix.M14, offsetMatrix.M24, offsetMatrix.M34, offsetMatrix.M44);
                                    aiBoneMap[boneIndex] = aiBone;
                                }

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

                        aiMesh.Bones.AddRange(aiBoneMap.Values);
                    }
                    else
                    {
                        var aiBone = new Assimp.Bone();

                        // Name
                        aiBone.Name = frame.HasHAnimExtension ? "_" + frame.HAnimFrameExtensionNode.NameId : "RootNode";

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

                        // OffsetMatrix

                        /*
                         * Matrix4x4.Invert( frame.WorldTransform, out Matrix4x4 offsetMatrix );
                         * aiBone.OffsetMatrix = new Assimp.Matrix4x4( offsetMatrix.M11, offsetMatrix.M21, offsetMatrix.M31, offsetMatrix.M41,
                         *                                          offsetMatrix.M12, offsetMatrix.M22, offsetMatrix.M32, offsetMatrix.M42,
                         *                                          offsetMatrix.M13, offsetMatrix.M23, offsetMatrix.M33, offsetMatrix.M43,
                         *                                          offsetMatrix.M14, offsetMatrix.M24, offsetMatrix.M34, offsetMatrix.M44 );
                         */
                        aiBone.OffsetMatrix = Assimp.Matrix4x4.Identity;

                        aiMesh.Bones.Add(aiBone);
                    }

                    var material   = geometry.Materials[mesh.MaterialIndex];
                    var aiMaterial = new Assimp.Material();

                    if (material.IsTextured)
                    {
                        // TextureDiffuse
                        var texture = material.TextureReferenceNode;
                        aiMaterial.TextureDiffuse = new Assimp.TextureSlot(
                            texture.Name + ".png", Assimp.TextureType.Diffuse, 0, Assimp.TextureMapping.FromUV, 0, 0, Assimp.TextureOperation.Add, Assimp.TextureWrapMode.Wrap, Assimp.TextureWrapMode.Wrap, 0);
                    }

                    // Name
                    aiMaterial.Name = material.Name ?? $"Geometry{atomic.GeometryIndex}_Material{mesh.MaterialIndex}";
                    if (material.IsTextured && material.Name == null)
                    {
                        aiMaterial.Name = material.TextureReferenceNode.Name;
                    }

                    aiMaterial.ShadingMode = Assimp.ShadingMode.Phong;

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

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

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

                    // Add mesh index to node
                    aiNode.MeshIndices.Add(aiScene.Meshes.Count - 1);
                }
            }

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