public static Assimp.Scene ImportScene(string path, bool allowWeights) { using (var aiContext = new Assimp.AssimpContext()) { aiContext.SetConfig(new Assimp.Configs.VertexBoneWeightLimitConfig(allowWeights ? 3 : 1)); aiContext.SetConfig(new Assimp.Configs.FBXPreservePivotsConfig(false)); return(aiContext.ImportFile(path, Assimp.PostProcessSteps.FindDegenerates | Assimp.PostProcessSteps.FindInvalidData | Assimp.PostProcessSteps.FlipUVs | Assimp.PostProcessSteps.ImproveCacheLocality | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.LimitBoneWeights | Assimp.PostProcessSteps.SplitByBoneCount | Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.ValidateDataStructure | Assimp.PostProcessSteps.GenerateUVCoords)); } }
private bool loadFile(string path) { Assimp.AssimpContext importer = new Assimp.AssimpContext(); importer.SetConfig(new NormalSmoothingAngleConfig(66f)); //TODO check which other post processes we need m_scene = importer.ImportFile(path, Assimp.PostProcessSteps.CalculateTangentSpace | Assimp.PostProcessSteps.GenerateNormals | Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.OptimizeMeshes); //failed loading :( if (!m_scene.HasMeshes) { textBoxInfo.Text = "No Valid Meshes found."; return(false); } //display some info string msg = "Mesh Count: " + m_scene.MeshCount.ToString() + Environment.NewLine + "Material count: " + m_scene.MaterialCount.ToString() + " (Maximum material count is 32)"; textBoxInfo.Text = msg; buttonSave.Enabled = (m_scene.MeshCount > 0 && m_scene.MaterialCount <= maxMaterials); return(true); }
public void ImportModel() { OpenFileDialog dlg = new OpenFileDialog() { DefaultExt = "sa1mdl", Filter = "Model Files|*.sa1mdl;*.obj;*.objf", RestoreDirectory = true }; if (dlg.ShowDialog() == DialogResult.OK) { switch (Path.GetExtension(dlg.FileName).ToLowerInvariant()) { case ".obj": case ".fbx": case ".dae": case ".objf": Assimp.AssimpContext context = new Assimp.AssimpContext(); context.SetConfig(new Assimp.Configs.FBXPreservePivotsConfig(false)); Assimp.Scene scene = context.ImportFile(dlg.FileName, Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.FlipUVs); NJS_OBJECT newmodel = SAEditorCommon.Import.AssimpStuff.AssimpImport(scene, scene.RootNode, ModelFormat.BasicDX, LevelData.TextureBitmaps[LevelData.leveltexs].Select(a => a.Name).ToArray(), true); Model.Attach = newmodel.Attach; Model.ProcessVertexData(); Mesh = Model.Attach.CreateD3DMesh(); break; case ".sa1mdl": ModelFile mf = new ModelFile(dlg.FileName); Model.Attach = mf.Model.Attach; Model.ProcessVertexData(); Mesh = Model.Attach.CreateD3DMesh(); break; } } }
public void ImportModel(string filePath, bool legacyImport = false) { NJS_OBJECT newmodel; // Old OBJ import (with vcolor face) for NodeTable and legacy import if (legacyImport) { newmodel = new NJS_OBJECT { Attach = SAModel.Direct3D.Extensions.obj2nj(filePath, LevelData.TextureBitmaps != null ? LevelData.TextureBitmaps[LevelData.leveltexs].Select(a => a.Name).ToArray() : null), }; COL.Model.Attach = newmodel.Attach; COL.Model.ProcessVertexData(); Visible = true; Solid = true; mesh = COL.Model.Attach.CreateD3DMesh(); return; } Assimp.AssimpContext context = new Assimp.AssimpContext(); context.SetConfig(new Assimp.Configs.FBXPreservePivotsConfig(false)); Assimp.Scene scene = context.ImportFile(filePath, Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.JoinIdenticalVertices | Assimp.PostProcessSteps.FlipUVs); newmodel = SAEditorCommon.Import.AssimpStuff.AssimpImport(scene, scene.RootNode, ModelFormat.BasicDX, LevelData.TextureBitmaps[LevelData.leveltexs].Select(a => a.Name).ToArray(), true); COL.Model.Attach = newmodel.Attach; COL.Model.ProcessVertexData(); Visible = true; Solid = true; mesh = COL.Model.Attach.CreateD3DMesh(); }
private LoadedModel LoadModelOfd() { OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { if (ofd.CheckFileExists) { string dirName = Path.GetDirectoryName(ofd.FileName); try { using (Assimp.AssimpContext importer = new Assimp.AssimpContext()) { importer.SetConfig(new NormalSmoothingAngleConfig(66.0f)); Assimp.Scene model = importer.ImportFile(ofd.FileName, Assimp.PostProcessPreset.TargetRealTimeMaximumQuality); LoadedModel loadedModel = new LoadedModel(model, dirName); loadedModel.Name = Path.GetFileName(ofd.FileName); return(loadedModel); } } catch { System.Windows.MessageBox.Show("Unsupported file type.", "Error"); } } } return(null); }
static Assimp.Scene LoadAssimpScene(string fileName) { Assimp.AssimpContext cont = new Assimp.AssimpContext(); // AssImp adds dummy nodes for pivots from FBX, so we'll force them off cont.SetConfig(new Assimp.Configs.FBXPreservePivotsConfig(false)); cont.ZAxisRotation = -90.0f; return(cont.ImportFile(fileName, Assimp.PostProcessSteps.Triangulate)); }
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 List <Item> ImportFromFile(string filePath, EditorCamera camera, out bool errorFlag, out string errorMsg, EditorItemSelection selectionManager, OnScreenDisplay osd, bool multiple = false) { List <Item> createdItems = new List <Item>(); if (!File.Exists(filePath)) { errorFlag = true; errorMsg = "File does not exist!"; return(null); } DirectoryInfo filePathInfo = new DirectoryInfo(filePath); bool importError = false; string importErrorMsg = ""; Vector3 pos = camera.Position + (-20 * camera.Look); switch (filePathInfo.Extension) { case ".sa1mdl": ModelFile mf = new ModelFile(filePath); NJS_OBJECT objm = mf.Model; osd.ClearMessageList(); osd.AddMessage("Importing models, please wait...", 3000); osd.ClearMessageList(); createdItems.AddRange(ImportFromHierarchy(objm, selectionManager, osd, multiple)); osd.AddMessage("Stage import complete!", 100); break; case ".obj": case ".objf": LevelItem item = new LevelItem(filePath, new Vertex(pos.X, pos.Y, pos.Z), new Rotation(), levelItems.Count, selectionManager) { Visible = true }; createdItems.Add(item); break; case ".txt": NodeTable.ImportFromFile(filePath, out importError, out importErrorMsg, selectionManager); break; case ".dae": case ".fbx": Assimp.AssimpContext context = new Assimp.AssimpContext(); Assimp.Configs.FBXPreservePivotsConfig conf = new Assimp.Configs.FBXPreservePivotsConfig(false); context.SetConfig(conf); Assimp.Scene scene = context.ImportFile(filePath, Assimp.PostProcessSteps.Triangulate); for (int i = 0; i < scene.RootNode.ChildCount; i++) { osd.ClearMessageList(); osd.AddMessage("Importing model " + i.ToString() + " of " + scene.RootNode.ChildCount.ToString() + "...", 3000); Assimp.Node child = scene.RootNode.Children[i]; List <Assimp.Mesh> meshes = new List <Assimp.Mesh>(); foreach (int j in child.MeshIndices) { meshes.Add(scene.Meshes[j]); } bool isVisible = true; for (int j = 0; j < child.MeshCount; j++) { if (scene.Materials[meshes[j].MaterialIndex].Name.Contains("Collision")) { isVisible = false; break; } } ModelFormat mfmt = ModelFormat.Basic; if (isVisible) { switch (geo.Format) { case LandTableFormat.SA2: mfmt = ModelFormat.Chunk; break; case LandTableFormat.SA2B: mfmt = ModelFormat.GC; break; } } NJS_OBJECT obj = AssimpStuff.AssimpImport(scene, child, mfmt, TextureBitmaps[leveltexs].Select(a => a.Name).ToArray(), !multiple); { //sa2 collision patch if (obj.Attach.GetType() == typeof(BasicAttach)) { BasicAttach ba = obj.Attach as BasicAttach; foreach (NJS_MATERIAL mats in ba.Material) { mats.DoubleSided = true; } } //cant check for transparent texture so i gotta force alpha for now, temporary else if (obj.Attach.GetType() == typeof(ChunkAttach)) { ChunkAttach ca = obj.Attach as ChunkAttach; foreach (PolyChunk polys in ca.Poly) { if (polys.GetType() == typeof(PolyChunkMaterial)) { PolyChunkMaterial mat = polys as PolyChunkMaterial; mat.SourceAlpha = AlphaInstruction.SourceAlpha; mat.DestinationAlpha = AlphaInstruction.InverseSourceAlpha; } else if (polys.GetType() == typeof(PolyChunkStrip)) { PolyChunkStrip str = polys as PolyChunkStrip; //str.UseAlpha = true; } } } } obj.Attach.ProcessVertexData(); LevelItem newLevelItem = new LevelItem(obj.Attach, new Vertex(obj.Position.X + pos.X, obj.Position.Y + pos.Y, obj.Position.Z + pos.Z), obj.Rotation, levelItems.Count, selectionManager) { Visible = isVisible }; createdItems.Add(newLevelItem); } osd.ClearMessageList(); osd.AddMessage("Stage import complete!", 100); break; default: errorFlag = true; errorMsg = "Invalid file format!"; return(null); } StateChanged(); errorFlag = importError; errorMsg = importErrorMsg; return(createdItems); }
static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("No arguments!"); } var objectFile = args[0]; Console.WriteLine("Loading mesh..."); Geometry.Geometry geo; if (Path.GetExtension(objectFile) == ".hfe") { // Loading an hfe file geo = Geometry.Geometry.LoadFromBinary(objectFile); } else if (Path.GetExtension(objectFile) == ".json") { // Loading geometry from json geo = Geometry.Geometry.LoadFromJson(objectFile); } else { // Loading an external file format var importer = new Assimp.AssimpContext(); importer.SetConfig(new Assimp.Configs.NormalSmoothingAngleConfig(66.0f)); var scene = importer.ImportFile(objectFile, Assimp.PostProcessPreset.TargetRealTimeMaximumQuality); if (!scene.HasMeshes) { Console.WriteLine("Scene has no meshes!"); return; } Console.WriteLine("Processing mesh..."); geo = Geometry.Geometry.FromAssimp(scene.Meshes[0], true, true); scene.Clear(); importer.Dispose(); } bool bUseSymmetricLaplacian = false; var indx = Array.FindIndex(args, t => t == "-sym"); if (indx != -1) { Console.WriteLine("Using symmetric laplacian..."); bUseSymmetricLaplacian = true; } indx = Array.FindIndex(args, t => t == "-lapout"); if (indx != -1) { Console.WriteLine("Creating differential structure..."); var diff = new DifferentialStructure(geo); Console.WriteLine("Writing mesh laplacian to file..."); var outputFile = args[indx + 1]; if (bUseSymmetricLaplacian) { DifferentialStructure.WriteSparseMatrix(diff.InteriorSymmetrizedLaplacian, outputFile); } else { DifferentialStructure.WriteSparseMatrix(diff.InteriorLaplacian, outputFile); } } indx = Array.FindIndex(args, t => t == "-modesin"); Mat modes = null; Vec spec = null; GeometryVisualMode visMode = GeometryVisualMode.ViewMesh; if (indx != -1) { Console.WriteLine("Reading mode data..."); var inputFile = args[indx + 1]; DifferentialStructure.ReadModeData(inputFile, out modes, out spec); var diff = new DifferentialStructure(geo); if (bUseSymmetricLaplacian) { modes = diff.HalfInverseMassMatrix * modes; } if (geo.HasBoundary) { modes = diff.InteriorToClosureMap * modes; } visMode = GeometryVisualMode.ViewModes; } indx = Array.FindIndex(args, t => t == "-funcin"); if (indx != -1) { Console.WriteLine("Reading function data..."); var inputFile = args[indx + 1]; DifferentialStructure.ReadFunctionData(inputFile, out modes); var diff = new DifferentialStructure(geo); if (geo.HasBoundary) { modes = diff.InteriorToClosureMap * modes; } visMode = GeometryVisualMode.ViewModes; spec = Vec.Build.Dense(modes.RowCount, 0d); } indx = Array.FindIndex(args, t => t == "-viewmass"); if (indx != -1) { visMode = GeometryVisualMode.ViewDegreeVector; } indx = Array.FindIndex(args, t => t == "-viewlapdiag"); if (indx != -1) { visMode = GeometryVisualMode.ViewLapDiagonal; } indx = Array.FindIndex(args, t => t == "-meshout"); if (indx != -1) { Console.WriteLine("Saving processed mesh..."); var path = args[indx + 1]; if (Path.GetExtension(path) == ".hfe") { geo.SaveToBinary(path); } else if (Path.GetExtension(path) == ".json") { geo.SaveToJson(path); } else { Console.WriteLine("Unrecognized meshout file extension!"); } } indx = Array.FindIndex(args, t => t == "-noview"); if (indx != -1) { return; } using (var window = new GeometryDisplayWindow(geo)) { window.VisualMode = visMode; window.ObjectModes = modes; window.ObjectEigenvalues = spec; window.Run(); } }
//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(); } }