Example #1
0
 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));
     }
 }
Example #2
0
        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);
        }
Example #3
0
        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;
                }
            }
        }
Example #4
0
        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();
        }
Example #5
0
        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);
        }
Example #6
0
        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);
        }
Example #8
0
        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();
            }
        }