public static void AssimpPRMConvert(string initialFilePath, string finalFilePath) { 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); PRMModel prm = new PRMModel(); int totalVerts = 0; //Iterate through and combine meshes. PRMs can only have a single mesh IterateAiNodesPRM(prm, ref totalVerts, aiScene, aiScene.RootNode, Matrix4x4.Transpose(GetMat4FromAssimpMat4(aiScene.RootNode.Transform))); AquaUtil.WritePRMToFile(prm, finalFilePath, 4); }
public static void Main(string[] args) { if (args.Length < 1) { DisplayUsage(); } else { foreach (var arg in args) { var aqua = new AquaUtil(); switch (Path.GetExtension(arg)) { case ".aqp": case ".aqo": case ".trp": case ".tro": string backup = Path.ChangeExtension(arg, ".org.aqp"); if (File.Exists(backup)) { File.Delete(backup); } File.Copy(arg, backup); var aqpName = arg; aqua.ReadModel(aqpName); LegacyObj.LegacyObjIO.ExportObj(Path.ChangeExtension(aqpName, ".obj"), aqua.aquaModels[0].models[0]); break; case ".obj": aqua.ReadModel(Path.ChangeExtension(arg, ".org.aqp")); aqua.aquaModels[0].models[0] = LegacyObj.LegacyObjIO.ImportObj(arg, aqua.aquaModels[0].models[0]); string outName = Path.ChangeExtension(arg, ".aqp"); if (aqua.aquaModels[0].models[0].objc.type >= 0xC32) { aqua.WriteNGSNIFLModel(outName, outName); } else { aqua.WriteClassicNIFLModel(outName, outName); } break; default: DisplayUsage(); break; } } } }
//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); }
public static void AssimpAQMConvert(string initialFilePath, bool playerExport, bool useScaleFrames, float scaleFactor) { 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); string inputFilename = Path.GetFileNameWithoutExtension(initialFilePath); List <string> aqmNames = new List <string>(); //Leave off extensions in case we want this to be .trm later List <AquaMotion> aqmList = new List <AquaMotion>(); Dictionary <int, Assimp.Node> aiNodes = GetAnimatedNodes(aiScene); var nodeKeys = aiNodes.Keys.ToList(); nodeKeys.Sort(); int animatedNodeCount = nodeKeys.Last() + 1; AquaUtil aqua = new AquaUtil(); for (int i = 0; i < aiScene.Animations.Count; i++) { if (aiScene.Animations[i] == null) { continue; } AquaMotion aqm = new AquaMotion(); var anim = aiScene.Animations[i]; int animEndFrame = 0; //We'll fill this later. Assumes frame 0 to be the start if (anim.Name != null && anim.Name != "") { //Make sure we're not overwriting anims that somehow have duplicate names if (aqmNames.Contains(anim.Name)) { aqmNames.Add($"Anim_{i}_" + anim.Name + ".aqm"); } else { aqmNames.Add(anim.Name + ".aqm"); } } else { aqmNames.Add($"Anim_{i}_" + inputFilename + ".aqm"); } aqm.moHeader = new AquaMotion.MOHeader(); //Get anim fps if (anim.TicksPerSecond == 0) { //Default to 30 aqm.moHeader.frameSpeed = 30; } else { aqm.moHeader.frameSpeed = (float)anim.TicksPerSecond; } aqm.moHeader.unkInt0 = 2; //Always, always 2 for NIFL aqm.moHeader.variant = 0x2; //These are flags for the animation to tell the game what type it is. Since this is a skeletal animation, we always put 2 here. //If it's a player one specifically, the game generally adds 0x10 to this. aqm.moHeader.nodeCount = animatedNodeCount; if (playerExport) { aqm.moHeader.variant += 0x10; aqm.moHeader.nodeCount++; //Add an extra for the nodeTreeFlag 'node' } aqm.moHeader.testString.SetString("test"); //Set this ahead of time in case these are out of order aqm.motionKeys = new List <AquaMotion.KeyData>(new AquaMotion.KeyData[aqm.moHeader.nodeCount]); //Nodes foreach (var animNode in anim.NodeAnimationChannels) { if (animNode == null) { continue; } int id = GetNodeNumber(animNode.NodeName); var node = aqm.motionKeys[id] = new AquaMotion.KeyData(); node.mseg.nodeName.SetString(animNode.NodeName); node.mseg.nodeId = id; node.mseg.nodeType = 2; node.mseg.nodeDataCount = useScaleFrames ? 3 : 2; if (animNode.HasPositionKeys) { AquaMotion.MKEY posKeys = new AquaMotion.MKEY(); posKeys.keyType = 1; posKeys.dataType = 1; var first = true; foreach (var pos in animNode.PositionKeys) { posKeys.vector4Keys.Add(new Vector4(pos.Value.X * baseScale, pos.Value.Y * baseScale, pos.Value.Z * baseScale, 0)); //Account for first frame difference if (first) { posKeys.frameTimings.Add(1); first = false; } else { posKeys.frameTimings.Add((ushort)(pos.Time * 0x10)); } posKeys.keyCount++; } posKeys.frameTimings[posKeys.keyCount - 1] += 2; //Account for final frame bitflags animEndFrame = Math.Max(animEndFrame, posKeys.keyCount); node.keyData.Add(posKeys); } if (animNode.HasRotationKeys) { AquaMotion.MKEY rotKeys = new AquaMotion.MKEY(); rotKeys.keyType = 2; rotKeys.dataType = 3; var first = true; foreach (var rot in animNode.RotationKeys) { rotKeys.vector4Keys.Add(new Vector4(rot.Value.X, rot.Value.Y, rot.Value.Z, rot.Value.W)); //Account for first frame difference if (first) { rotKeys.frameTimings.Add(1); first = false; } else { rotKeys.frameTimings.Add((ushort)(rot.Time * 0x10)); } rotKeys.keyCount++; } rotKeys.frameTimings[rotKeys.keyCount - 1] += 2; //Account for final frame bitflags animEndFrame = Math.Max(animEndFrame, rotKeys.keyCount); node.keyData.Add(rotKeys); } if (animNode.HasScalingKeys) { AquaMotion.MKEY sclKeys = new AquaMotion.MKEY(); sclKeys.keyType = 2; sclKeys.dataType = 3; var first = true; foreach (var scl in animNode.ScalingKeys) { sclKeys.vector4Keys.Add(new Vector4(scl.Value.X, scl.Value.Y, scl.Value.Z, 0)); //Account for first frame difference if (first) { sclKeys.frameTimings.Add(1); first = false; } else { sclKeys.frameTimings.Add((ushort)(scl.Time * 0x10)); } sclKeys.keyCount++; } sclKeys.frameTimings[sclKeys.keyCount - 1] += 2; //Account for final frame bitflags animEndFrame = Math.Max(animEndFrame, sclKeys.keyCount); node.keyData.Add(sclKeys); } } //NodeTreeFlag if (playerExport) { var node = aqm.motionKeys[aqm.motionKeys.Count - 1] = new AquaMotion.KeyData(); node.mseg.nodeName.SetString("__NodeTreeFlag__"); node.mseg.nodeId = aqm.motionKeys.Count - 1; node.mseg.nodeType = 0x10; node.mseg.nodeDataCount = useScaleFrames ? 3 : 2; //Position AquaMotion.MKEY posKeys = new AquaMotion.MKEY(); posKeys.keyType = 0x10; posKeys.dataType = 5; posKeys.keyCount = animEndFrame + 1; for (int frame = 0; frame < posKeys.keyCount; frame++) { if (frame == 0) { posKeys.frameTimings.Add(0x9); } else if (frame == posKeys.keyCount - 1) { posKeys.frameTimings.Add((ushort)((frame * 0x10) + 0xA)); } else { posKeys.frameTimings.Add((ushort)((frame * 0x10) + 0x8)); } posKeys.intKeys.Add(0x31); } node.keyData.Add(posKeys); //Rotation AquaMotion.MKEY rotKeys = new AquaMotion.MKEY(); rotKeys.keyType = 0x11; rotKeys.dataType = 5; rotKeys.keyCount = animEndFrame + 1; for (int frame = 0; frame < rotKeys.keyCount; frame++) { if (frame == 0) { rotKeys.frameTimings.Add(0x9); } else if (frame == rotKeys.keyCount - 1) { rotKeys.frameTimings.Add((ushort)((frame * 0x10) + 0xA)); } else { rotKeys.frameTimings.Add((ushort)((frame * 0x10) + 0x8)); } rotKeys.intKeys.Add(0x31); } node.keyData.Add(rotKeys); //Scale if (useScaleFrames) { AquaMotion.MKEY sclKeys = new AquaMotion.MKEY(); sclKeys.keyType = 0x12; sclKeys.dataType = 5; sclKeys.keyCount = animEndFrame + 1; for (int frame = 0; frame < sclKeys.keyCount; frame++) { if (frame == 0) { sclKeys.frameTimings.Add(0x9); } else if (frame == sclKeys.keyCount - 1) { sclKeys.frameTimings.Add((ushort)((frame * 0x10) + 0xA)); } else { sclKeys.frameTimings.Add((ushort)((frame * 0x10) + 0x8)); } sclKeys.intKeys.Add(0x31); } node.keyData.Add(sclKeys); } } //Sanity check foreach (var aiPair in aiNodes) { var node = aqm.motionKeys[aiPair.Key]; var aiNode = aiPair.Value; if (node == null) { node = aqm.motionKeys[aiPair.Key] = new AquaMotion.KeyData(); node.mseg.nodeName.SetString(aiNode.Name); node.mseg.nodeId = aiPair.Key; node.mseg.nodeType = 2; node.mseg.nodeDataCount = useScaleFrames ? 3 : 2; //Position AddOnePosFrame(node, aiNode, baseScale); //Rotation AddOneRotFrame(node, aiNode); //Scale AddOneScaleFrame(useScaleFrames, node); } else { if (node.keyData[0].vector4Keys.Count < 1) { AddOnePosFrame(node, aiNode, baseScale); } if (node.keyData[1].vector4Keys.Count < 1) { AddOneRotFrame(node, aiNode); } if (useScaleFrames && node.keyData[2].vector4Keys.Count < 1) { AddOneScaleFrame(useScaleFrames, node, aiNode); } } } aqmList.Add(aqm); } for (int i = 0; i < aqmList.Count; i++) { var aqm = aqmList[i]; AquaUtil.AnimSet set = new AquaUtil.AnimSet(); set.anims.Add(aqm); aqua.aquaMotions.Add(set); aqua.WriteNIFLMotion(initialFilePath + "_" + aqmNames[i]); aqua.aquaMotions.Clear(); } }