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); }