Sfoglia il codice sorgente

World map: basic data extractor added to the installer, WorldMapManager structure started.

Iñigo Valentin 2 anni fa
parent
commit
c63e08ecb9

+ 2 - 0
src/CMakeLists.txt

@@ -134,6 +134,7 @@ set(VGEARS_SOURCE_FILES
     core/UiWidget.cpp
     core/Utilites.cpp
     core/Walkmesh.cpp
+    core/WorldMapManager.cpp
     core/XmlBackground2DFile.cpp
     core/XmlBattleCharactersFile.cpp
     core/XmlBattleScenesFile.cpp
@@ -153,6 +154,7 @@ set(VGEARS_SOURCE_FILES
     core/XmlTextFile.cpp
     core/XmlTextsFile.cpp
     core/XmlWalkmeshFile.cpp
+    core/XmlWorldMapFile.cpp
     data/VGearsAFile.cpp
     data/VGearsAFileManager.cpp
     data/VGearsAFileSerializer.cpp

+ 96 - 0
src/core/WorldMapManager.cpp

@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <cmath>
+#include <OgreEntity.h>
+#include <OgreRoot.h>
+#include <OgreViewport.h>
+#include "FF7Character.h"
+#include "core/AudioManager.h"
+#include "core/WorldMapManager.h"
+#include "core/WorldMapManager.h"
+#include "core/CameraManager.h"
+#include "core/Console.h"
+#include "core/DialogsManager.h"
+#include "core/EntityManager.h"
+#include "core/ConfigVar.h"
+#include "core/InputManager.h"
+#include "core/Logger.h"
+#include "core/ScriptManager.h"
+#include "core/TextHandler.h"
+#include "core/UiManager.h"
+
+/**
+ * World Map manager singleton.
+ */
+template<>WorldMapManager *Ogre::Singleton<WorldMapManager>::msSingleton = nullptr;
+
+WorldMapManager::WorldMapManager(){
+    LOG_TRIVIAL("WorldMapManager created.");
+    scene_node_ = Ogre::Root::getSingleton().getSceneManager("Scene")
+      ->getRootSceneNode()->createChildSceneNode("WorldMapManager");
+}
+
+WorldMapManager::~WorldMapManager(){
+    Clear();
+    Ogre::Root::getSingleton().getSceneManager("Scene")->getRootSceneNode()->removeAndDestroyChild(
+      "WorldMapManager"
+    );
+    LOG_TRIVIAL("WorldMapManager destroyed.");
+}
+
+void WorldMapManager::Input(const VGears::Event& event){}
+
+void WorldMapManager::UpdateDebug(){}
+
+void WorldMapManager::OnResize(){}
+
+void WorldMapManager::ClearField(){}
+
+void WorldMapManager::ClearBattle(){}
+
+void WorldMapManager::ClearWorld(){
+    // TODO
+}
+
+void WorldMapManager::AddTerrain(
+  const unsigned int index, const Ogre::String mesh, const Ogre::Vector3 pos
+){
+    if (module_ != Module::WORLD){
+        LOG_ERROR(
+          "Tried to add terrain to the WorldMapManager, but the manager was not in world map mode."
+        );
+        return;
+    }
+    /*Enemy* enemy = new Enemy(id, pos, front, visible, targeteable, active, cover);
+    enemies_.push_back(*enemy);
+    EntityManager::getSingleton().AddEntity(
+      enemy->GetName() + "_" + std::to_string(enemies_.size() - 1),
+      "enemies/" + enemy->GetModel() + ".mesh", enemy->GetPos(), Ogre::Degree(1),
+      Ogre::Vector3(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE), Ogre::Quaternion(1, 1, 0, 0), id
+    );
+    EntityManager::getSingleton().GetEntity(
+      enemy->GetName() + "_" + std::to_string(enemies_.size() - 1)
+    )->SetRotation(Ogre::Degree(180));
+    */
+}
+
+void WorldMapManager::UpdateField(){}
+
+void WorldMapManager::UpdateBattle(){}
+
+void WorldMapManager::UpdateWorld(){}

+ 114 - 0
src/core/WorldMapManager.h

@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include <OgreSingleton.h>
+#include "Event.h"
+#include "Manager.h"
+
+/**
+ * The world map manager.
+ */
+class WorldMapManager : public Manager, public Ogre::Singleton<WorldMapManager>{
+
+    public:
+
+        /**
+         * Constructor.
+         */
+        WorldMapManager();
+
+        /**
+         * Destructor.
+         */
+        virtual ~WorldMapManager();
+
+        /**
+         * Handles an input event.
+         *
+         * @param[in] event Event to handle.
+         */
+        void Input(const VGears::Event& event) override;
+
+        /**
+         * Updates the world map manager  with debug information.
+         *
+         * It's automatically called from {@see Update}.
+         */
+        void UpdateDebug() override;
+
+        /**
+         * Handles resizing events
+         */
+        void OnResize() override;
+
+        /**
+         * Clears all field information in the field manager.
+         *
+         * Does nothing.
+         */
+        void ClearField() override;
+
+        /**
+         * Clears all battle information in the battle manager.
+         *
+         * Does nothing
+         */
+        void ClearBattle() override;
+
+        /**
+         * Clears all world map information in the world map manager.
+         */
+        void ClearWorld() override;
+
+        /**
+         * Adds a terrain block.
+         *
+         * @param[in] index Block index.
+         * @param[in] mesh Path to the mesh file.
+         * @param[in] pos Terrain position (x, y, z).
+         */
+        void AddTerrain(
+          const unsigned int id, const Ogre::String mesh, const Ogre::Vector3 pos
+        );
+
+    private:
+
+        /**
+         * Updates the manager while in the field.
+         *
+         * It does nothing
+         */
+        void UpdateField() override;
+
+        /**
+         * Updates the manager during a battle.
+         */
+        void UpdateBattle() override;
+
+        /**
+         * Updates manager while in the world map.
+         *
+         * It does nothing
+         */
+        void UpdateWorld() override;
+
+        /**
+         * The scene node.
+         */
+        Ogre::SceneNode* scene_node_;
+
+};

+ 21 - 20
src/core/XmlWorldMapFile.cpp

@@ -17,6 +17,7 @@
 #include "core/EntityManager.h"
 #include "core/Logger.h"
 #include "core/ScriptManager.h"
+#include "core/WorldMapManager.h"
 #include "core/XmlBackground2DFile.h"
 #include "core/XmlWorldMapFile.h"
 #include "map/VGearsBackground2DFileManager.h"
@@ -27,7 +28,7 @@ XmlWorldMapFile::XmlWorldMapFile(const Ogre::String& file): XmlFile(file){}
 
 XmlWorldMapFile::~XmlWorldMapFile(){}
 
-void XmlWorldMapFile::LoadMap(){
+void XmlWorldMapFile::LoadWorldMap(const unsigned int current_progress){
     TiXmlNode* node = file_.RootElement();
     if (node == nullptr || node->ValueStr() != "world_map"){
         LOG_ERROR(file_.ValueStr() + " is not a valid fields map file! No <world_map> in root.");
@@ -57,6 +58,25 @@ void XmlWorldMapFile::LoadMap(){
         else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "texts"){
             TextHandler::getSingleton().LoadFieldText(GetString(node, "file_name"));
         }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "terrain"){
+            TiXmlNode* terrain_node = node->FirstChild();
+            while (terrain_node != nullptr){
+                if (terrain_node->ValueStr() == "block"){
+                    int index(GetInt(terrain_node, "index"));
+                    Ogre::String file_name(GetString(terrain_node, "file_name"));
+                    Ogre::Vector3 position(GetVector3(node, "position"));
+                    Ogre::String alt_file_name(GetString(terrain_node, "alt_file_name", ""));
+                    int alt_story_change(GetInt(terrain_node, "alt_story_change", -1));
+                    // TODO: Get current story point and determine whether to load alt models.
+                    if ("" != alt_file_name && alt_story_change > current_progress)
+                        WorldMapManager::getSingleton().AddTerrain(index, alt_file_name, position);
+                    else WorldMapManager::getSingleton().AddTerrain(index, file_name, position);
+
+
+                }
+                terrain_node = terrain_node->NextSibling();
+            }
+        }
         else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "entity_model"){
             Ogre::String name = GetString(node, "name");
             if (name == ""){
@@ -122,22 +142,3 @@ void XmlWorldMapFile::LoadMap(){
         node = node->NextSibling();
     }
 }
-
-const Ogre::String XmlMapFile::GetWalkmeshFileName(){
-    TiXmlNode* node = file_.RootElement();
-    if (node == nullptr || node->ValueStr() != "map"){
-        LOG_ERROR(
-          "Field Map XML Manager: " + file_.ValueStr()
-          + " is not a valid fields map file! No <map> in root."
-        );
-        return "";
-    }
-    node = node->FirstChild();
-    while (node != nullptr){
-        if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "walkmesh")
-            return GetString(node, "file_name");
-        node = node->NextSibling();
-    }
-    LOG_WARNING("Can't find field's walkmesh in " + file_.ValueStr() + ".");
-    return "";
-}

+ 3 - 1
src/core/XmlWorldMapFile.h

@@ -38,6 +38,8 @@ class XmlWorldMapFile : public XmlFile{
 
         /**
          * Parses the file and loads the map data.
+         *
+         * @param[in] current_progress Current story progress.
          */
-        void LoadWorldMap();
+        void LoadWorldMap(const unsigned int current_progress);
 };

+ 14 - 5
src/installer/DataInstaller.cpp

@@ -75,7 +75,7 @@ float DataInstaller::Progress(){
               input_dir_, output_dir_, application_.ResMgr()
             );
             world_installer_ = std::make_unique<WorldInstaller>(
-              input_dir_, output_dir_, options_.keep_originals
+              input_dir_, output_dir_, options_.keep_originals, application_.ResMgr()
             );
             installation_state_ = BATTLE_SCENES_INIT;
             return CalcProgress();
@@ -390,7 +390,7 @@ float DataInstaller::Progress(){
                 installation_state_ = CLEAN;
                 return CalcProgress();
             }
-            write_output_line_("Extracting world map models...", 2, true);
+            write_output_line_("Extracting world map data...", 2, true);
             substeps_ = world_installer_->Initialize();
             cur_substep_ = 0;
             installation_state_ = WM_MAPS;
@@ -399,10 +399,16 @@ float DataInstaller::Progress(){
             if (world_installer_->ProcessMap() == false) cur_substep_ ++;
             else{
                 // TODO: Next step: map scripts, etc
-                installation_state_ = CLEAN;
+                installation_state_ = WM_MODELS;
                 cur_substep_ = 0;
             }
             return CalcProgress();
+        case WM_MODELS:
+            if (options_.skip_wm_models)
+                write_output_line_("Skipping world map model installation...", 2, true);
+            else world_installer_->ProcessModels();
+            installation_state_ = CLEAN;
+            return CalcProgress();
         case CLEAN:
             write_output_line_("Cleaning up...", 2, true);
             if (!options_.keep_originals) CleanInstall();
@@ -444,6 +450,7 @@ void DataInstaller::CreateDirectories(){
     CreateDir("temp");
     CreateDir("temp/char");
     CreateDir("temp/battle_models");
+    CreateDir("temp/world_models");
     CreateDir("temp/spell_models");
     CreateDir("temp/wm");
     CreateDir("gamedata");
@@ -469,8 +476,7 @@ void DataInstaller::CreateDirectories(){
     CreateDir("models/world/terrain/0");
     CreateDir("models/world/terrain/1");
     CreateDir("models/world/terrain/2");
-    CreateDir("models/world/buildings/");
-    CreateDir("models/world/characters/");
+    CreateDir("models/world/elements/");
     CreateDir("world/0");
     CreateDir("world/1");
     CreateDir("world/2");
@@ -481,6 +487,9 @@ void DataInstaller::CreateDirectories(){
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "temp/battle_models/", "FileSystem", "FFVII", true, true
     );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "temp/world_models/", "FileSystem", "FFVII", true, true
+    );
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "models/", "FileSystem", "FFVII", true, true
     );

+ 6 - 1
src/installer/DataInstaller.h

@@ -412,10 +412,15 @@ class DataInstaller{
             WM_INIT,
 
             /**
-             * Step that extracts world map meshes.
+             * Step that extracts world map terrain data.
              */
             WM_MAPS,
 
+            /**
+             * Step that extracts world map models.
+             */
+            WM_MODELS,
+
             /**
              * Cleans up after the installation.
              */

+ 4 - 0
src/installer/MainWindow.cpp

@@ -186,6 +186,10 @@ void MainWindow::on_btn_data_run_clicked(){
           "data/battle/scene.bin",
           "data/battle/battle.lgp",
           "data/battle/magic.lgp",
+          "data/wm/WM0.MAP",
+          "data/wm/WM2.MAP",
+          "data/wm/WM3.MAP",
+          "data/wm/world_us.lgp",
           "ff7.exe"
         };
         // Ensure required files are in the input dir

+ 184 - 6
src/installer/WorldInstaller.cpp

@@ -21,13 +21,23 @@
 #include <OgreMeshSerializer.h>
 #include <boost/filesystem.hpp>
 #include "WorldInstaller.h"
+#include "TexFile.h"
 #include "common/Lzs.h"
+#include "common/VGearsStringUtil.h"
+#include "data/VGearsLGPArchive.h"
 #include "data/WorldMapWalkmesh.h"
+#include "data/VGearsHRCFileManager.h"
+#include "data/FF7Data.h"
+
+std::string WorldInstaller::ELEMENT_MODELS_DIR("models/world/element");
+
+std::string WorldInstaller::TERRAIN_MODELS_DIR("models/world/terrain");
 
 WorldInstaller::WorldInstaller(
-  std::string input_dir, std::string output_dir, const bool keep_originals
+  const std::string input_dir, const std::string output_dir,
+  const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
 ):
-  input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals)
+  input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals), res_mgr_(res_mgr)
 {}
 
 WorldInstaller::~WorldInstaller(){}
@@ -41,6 +51,67 @@ unsigned int WorldInstaller::Initialize(){
     return wm_map_.size();
 }
 
+void WorldInstaller::ProcessModels(){
+    // Open world_us.lgp
+    File world_file(input_dir_ + "data/wm/world_us.lgp");
+    VGears::LGPArchive world_lgp(input_dir_ + "data/wm/world_us.lgp", "LGP");
+    world_lgp.open(input_dir_ + "data/wm/world_us.lgp", true);
+    world_lgp.load();
+    VGears::LGPArchive::FileList file_list = world_lgp.GetFiles();
+    for (int i = 0; i < file_list.size(); i ++){
+        VGears::LGPArchive::FileEntry f = file_list.at(i);
+        if (f.data_offset + f.data_size <= world_file.GetFileSize()){
+            File w_lgp_file(&world_file, f.data_offset, f.data_size);
+            std::string file_name = f.file_name;
+            w_lgp_file.WriteFile(output_dir_ + "temp/world_models/" + file_name);
+            if (file_name.substr(4, 3) == "hrc"){
+
+                std::string id = file_name.substr(0, 3);
+                std::string model_name = FF7Data::GetWorldMapModelName(id);
+                std::cout << "CONVERTING WORLD MODEL: " << file_name << " " << i
+                  << "/" << file_list.size() << std::endl;
+
+
+
+                // TODO: Can this be done with declare resource?
+                // TODO: If not, do aloop to save all the hrc files, then call this once, then
+                // another loop to process them.
+                /*res_mgr_->declareResource(
+                  output_dir_ + "temp/world_models/" + file_name, "Skeleton", "FFVII"
+                );*/
+                res_mgr_->removeResourceLocation(output_dir_ + "temp/world_models/", "FFVII");
+                res_mgr_->addResourceLocation(
+                  output_dir_ + "temp/world_models/", "FileSystem", "FFVII", true, true
+                );
+
+
+
+                // TODO: This needs work to assemble all the pieces. Similar to field models.
+
+
+                Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
+                  file_name, "FFVII"
+                );
+                auto mesh_name = model_name + ".mesh";
+                Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
+                Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
+                // TODO: a files??
+                /*for (std::string anim : model.a){
+                    VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
+                    Ogre::String a_base_name;
+                    VGears::StringUtil::splitBase(anim, a_base_name);
+                    VGears::AFilePtr a
+                      = afl_mgr.load(a_base_name + ".a", "FFVII").staticCast<VGears::AFile>();
+                    // Convert the FF7 name to a more readable name set in the meta data.
+                    VGears::StringUtil::splitBase(anim, base_name);
+                    a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
+                }*/
+                ExportMesh(output_dir_ + "data/models/world/" + mesh_name, mesh);
+            }
+        }
+    }
+}
+
 bool WorldInstaller::ProcessMap(){
     //std::cout << "[WI] Processing map " << processed_maps_ << "/" << wm_map_.size() << std::endl;
     if (processed_maps_ >= wm_map_.size()) return true;
@@ -118,8 +189,11 @@ bool WorldInstaller::ProcessMap(){
                 t.vertex_index[1] = mesh_data.readU8();
                 t.vertex_index[2] = mesh_data.readU8();
                 u8 walkability_function = mesh_data.readU8();
-                t.walkability = walkability_function >> 5; // 5 bits
-                t.function_id = walkability_function & 0x7; // 3 bits
+                //t.walkability = walkability_function >> 5; // 5 bits
+                //t.function_id = walkability_function & 0x7; // 3 bits
+                t.walkability =  walkability_function & 0x1F; // Lowest 5 bits
+                t.function_id =  walkability_function >> 3; // 3 bits
+                // FIXME: I think function_id is wrong. It must be 3 bits
                 t.vertex_coord[0].u = mesh_data.readU8();
                 t.vertex_coord[0].v = mesh_data.readU8();
                 t.vertex_coord[1].u = mesh_data.readU8();
@@ -222,7 +296,7 @@ bool WorldInstaller::ProcessMap(){
         //  << std::endl;
         mesh_serializer.exportMesh(
           mesh.getPointer(),
-          output_dir_ + "models/world/terrain/" + std::to_string(processed_maps_)
+          output_dir_ + TERRAIN_MODELS_DIR + "/" + std::to_string(processed_maps_)
             + "/" + mesh->getName() + ".mesh"
         );
         // WM0 is te main worldmap, its shape changes according to the history progress, it has
@@ -273,10 +347,114 @@ bool WorldInstaller::ProcessMap(){
     // Close and save XML file,
     xml->LinkEndChild(xml_terrain.release());
     doc.LinkEndChild(xml.release());
-    doc.SaveFile(output_dir_ + "/world/" + std::to_string(processed_maps_) + "/world" + std::to_string(processed_maps_) + ".xml");
+    // TODO: Add models, entities...
+    doc.SaveFile(
+      output_dir_ + "/world/" + std::to_string(processed_maps_)
+      + "/world" + std::to_string(processed_maps_) + ".xml"
+    );
+    // Generate the walkmesh xml file
+    walkmesh.generate(output_dir_ + "/world/" + std::to_string(processed_maps_) + "/wm.xml");
+    // TODO: Generate other files: background, script, texts...
     // Ready for next map or next step.
     processed_maps_ ++;
     if (processed_maps_ >= wm_map_.size()) return true;
     else return false;
 }
 
+void WorldInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh){
+
+    // TODO: Share function with pc model exporter
+    VGears::String base_mesh_name;
+    VGears::StringUtil::splitFull(mesh->getName(), base_mesh_name);
+    Ogre::MeshSerializer mesh_serializer;
+    Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
+    Ogre::SkeletonSerializer skeleton_serializer;
+    skeleton_serializer.exportSkeleton(
+      skeleton.getPointer(), outdir + base_mesh_name + ".skeleton"
+    );
+    mesh->setSkeletonName(base_mesh_name + ".skeleton");
+    mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
+    Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
+    Ogre::MaterialSerializer mat_ser;
+    size_t i(0);
+    std::set<std::string> textures;
+    while (it.hasMoreElements()){
+        Ogre::SubMesh *sub_mesh(it.getNext());
+        Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(
+          sub_mesh->getMaterialName())
+        );
+        if (mat != nullptr){
+            for (size_t techs = 0; techs < mat->getNumTechniques(); techs ++){
+                Ogre::Technique* tech = mat->getTechnique(techs);
+                if (tech){
+                    for (size_t pass_num = 0; pass_num < tech->getNumPasses(); pass_num ++){
+                        Ogre::Pass* pass = tech->getPass(pass_num);
+                        if (pass){
+                            for (
+                              size_t texture_unit_num = 0;
+                              texture_unit_num < pass->getNumTextureUnitStates();
+                              texture_unit_num ++
+                            ){
+                                Ogre::TextureUnitState* unit
+                                  = pass->getTextureUnitState(texture_unit_num);
+                                if (unit && unit->getTextureName().empty() == false){
+                                    // Convert the texture from .tex to .png.
+                                    TexFile tex(
+                                      output_dir_ + "temp/wm/" + unit->getTextureName()
+                                    );
+
+                                    // Ensure the output material script references png files
+                                    // rather than tex files.
+                                    Ogre::String base_name;
+                                    VGears::StringUtil::splitBase(
+                                      unit->getTextureName(), base_name
+                                    );
+                                    unit->setTextureName(base_mesh_name + "_" + base_name + ".png");
+
+                                    tex.SavePng(
+                                      output_dir_ + ELEMENT_MODELS_DIR + "/" + base_name + ".png", 0
+                                    );
+                                    // Copy subtexture (xxxx.png) to model_xxxx.png
+                                    // TODO: obtain the "data" folder
+                                    // programatically.
+                                    boost::filesystem::copy_file(
+                                        output_dir_ + ELEMENT_MODELS_DIR + "/" + base_name + ".png",
+                                        output_dir_ + ELEMENT_MODELS_DIR + "/"
+                                        + base_mesh_name + "_" + base_name + ".png",
+                                      boost::filesystem::copy_option::overwrite_if_exists
+                                    );
+                                    textures.insert(unit->getTextureName());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            // TODO: Check what to do with materials
+            /**
+            if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
+                mat_ser.queueForExport(mat);
+                materials_.push_back(sub_mesh->getMaterialName());
+            }*/
+
+        }
+        ++ i;
+    }
+    mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
+    for (auto& texture_name : textures){
+        std::string tex_name = texture_name.c_str();
+        try{
+            Ogre::TexturePtr texture_ptr
+              = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures" /*"FFVII"*/);
+            Ogre::Image image;
+            texture_ptr->convertToImage(image);
+            Ogre::String base_name;
+            VGears::StringUtil::splitBase(texture_name, base_name);
+            image.save(outdir + base_mesh_name + "_" + base_name + ".png");
+        }
+        catch (std::exception const& ex){
+            std::cerr << "[ERROR] Exception: " << ex.what() << std::endl;
+        }
+    }
+}
+

+ 46 - 3
src/installer/WorldInstaller.h

@@ -33,9 +33,11 @@ class WorldInstaller{
          * @param[in] input_dir Path to the directory containing the original data to parse.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] keep_originals True to keep original data after conversion, false to remove.
+         * @param[in] res_mgr The application resource manager.
          */
         WorldInstaller(
-          std::string input_dir, std::string output_dir, const bool keep_originals
+          const std::string input_dir, const std::string output_dir,
+          const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
         );
 
         /**
@@ -50,6 +52,11 @@ class WorldInstaller{
          */
         unsigned int Initialize();
 
+        /**
+         * Extracts and processes the models in data/wm/world_us.lgp
+         */
+        void ProcessModels();
+
         /**
          * Processes the next map to process.
          *
@@ -95,14 +102,14 @@ class WorldInstaller{
              *
              * Shares byte with {@see function_id}, 5 bytes.
              */
-            u8 walkability;
+            int walkability;
 
             /**
              * ID of the function triggered when entering the triangle.
              *
              * Shares byte with {@see walkability}, 3 bytes.
              */
-            u8 function_id;
+            int function_id;
 
             /**
              * UV coordinates in texture for each vertex.
@@ -215,8 +222,42 @@ class WorldInstaller{
             std::vector<Block> blocks;
         };
 
+        /**
+         * Path to the world map elements models directory.
+         *
+         * Elements can be locations, playable characters, enemies, transportations... anything
+         * that it's not in itself part of the terrain.
+         */
+        static std::string ELEMENT_MODELS_DIR;
+
+        /**
+         * Path to the world map terrain models directory.
+         */
+        static std::string TERRAIN_MODELS_DIR;
+
+        /**
+         * Decompresses data compressed in LZSS or LZS formats.
+         *
+         * @param[in] compressed_data The compresed data as bytes.
+         * @return The data, decompressed, as bytes.
+         */
         std::vector<u8> DecompressLZSSData(u32* compressed_data);
 
+        /**
+         * Exports a mesh to a file.
+         *
+         * The file will have the mesh name.
+         *
+         * @param[in] outdir Path to the directory where the file will be saved.
+         * @param[in] mesh The mesh to export.
+         */
+        void ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh);
+
+        /**
+         * Pointer to the application resource manager.
+         */
+        Ogre::ResourceGroupManager* res_mgr_;
+
         /**
          * Each of the original WM*.MAP in the installation disk.
          */
@@ -241,4 +282,6 @@ class WorldInstaller{
          * Number of maps already processed.
          */
         unsigned int processed_maps_;
+
+
 };

+ 44 - 0
src/installer/data/FF7Data.h

@@ -15,6 +15,8 @@
 
 #pragma once
 
+#include <cctype>
+#include <string>
 #include <algorithm>
 
 /**
@@ -3535,4 +3537,46 @@ class FF7Data{
             }*/
             return info;
         }
+
+        /**
+         * Retrieves a human readable name for a world map model from it's ID.
+         *
+         * @param[in] model_id ID of the model (three characters).
+         * @return A human readable model. If no name found, the fist three characters of the
+         * provided id.
+         */
+        static std::string GetWorldMapModelName(std::string model_id){
+            std::string id = model_id;
+            if (id.size() > 3) id = id.substr(0, 3);
+            if ("aaa" == id) return "barrier";
+            else if ("aba" == id) return "buggy";
+            else if ("aia" == id) return "junon_cannon";
+            else if ("aja" == id) return "chocobo_white";
+            else if ("ata" == id) return "cid";
+            else if ("bbe" == id) return "cloud";
+            else if ("bkd" == id) return "condor";
+            else if ("ble" == id) return "ancients_key";
+            else if ("bna" == id) return "diamond_weapon";
+            else if ("bud" == id) return "emerald_weapon";
+            else if ("cec" == id) return "gelnika";
+            else if ("cfc" == id) return "gold_saucer";
+            else if ("cgd" == id) return "highwind_v1";
+            else if ("cid" == id) return "highwind_v2";
+            else if ("cmb" == id) return "sister_ray";
+            else if ("cnb" == id) return "forbidden_forest";
+            else if ("coc" == id) return "rocket";
+            else if ("cpc" == id) return "rocket_support";
+            else if ("cqc" == id) return "ruby_weapon";
+            else if ("ddd" == id) return "submarine";
+            else if ("dga" == id) return "submarine_red_sunken";
+            else if ("dic" == id) return "weird_rocks";
+            else if ("djc" == id) return "snow_flag";
+            else if ("dkc" == id) return "weird_antenna";
+            else if ("dlb" == id) return "tifa";
+            else if ("dva" == id) return "tiny_bronco";
+            else if ("dyb" == id) return "ultima_weapon";
+            else if ("eje" == id) return "underwater_reactor";
+            else if ("eke" == id) return "cargo_ship";
+            return id;
+        }
 };

+ 153 - 9
src/installer/data/WorldMapWalkmesh.cpp

@@ -14,12 +14,13 @@
  */
 
 #include <algorithm>
+#include <tinyxml.h>
 #include "data/WorldMapWalkmesh.h"
 
 WorldMapWalkmesh::Triangle::Triangle(
   const unsigned int id, const Ogre::Vector3 a, const Ogre::Vector3 b,
   const Ogre::Vector3 c, const unsigned int walkability, const int event
-): id_(id), a_(a), b_(b), c_(c){
+): id_(id), a_(a), b_(b), c_(c), walkability_(walkability), event_(event){
     ab_.id = -1;
     ab_.warp = false;
     bc_.id = -1;
@@ -28,17 +29,23 @@ WorldMapWalkmesh::Triangle::Triangle(
     ca_.warp = false;
 }
 
-Ogre::Vector3 WorldMapWalkmesh::Triangle::getA(){return a_;}
+unsigned int WorldMapWalkmesh::Triangle::getId() const{return id_;}
 
-Ogre::Vector3 WorldMapWalkmesh::Triangle::getB(){return b_;}
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getA() const {return a_;}
 
-Ogre::Vector3 WorldMapWalkmesh::Triangle::getC(){return c_;}
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getB() const {return b_;}
 
-WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getAB(){return ab_;}
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getC() const {return c_;}
 
-WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getBC(){return bc_;}
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getAB() const {return ab_;}
 
-WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getCA(){return ca_;}
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getBC() const {return bc_;}
+
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getCA() const {return ca_;}
+
+unsigned int WorldMapWalkmesh::Triangle::getWalkability() const {return walkability_;}
+
+int WorldMapWalkmesh::Triangle::getEvent() const {return event_;}
 
 void WorldMapWalkmesh::Triangle::setAB(const int id, const bool warp){
     ab_.id = std::max(id, -1);
@@ -66,14 +73,151 @@ void WorldMapWalkmesh::addTriangle(
 ){
     calculated_ = false;
     Triangle tri(triangles_.size(), a, b, c, walkability, event);
+    if (id_ == 0 && triangles_.size() < 50){
+        std::cout << "Adding triangle " << tri.getId() << " W: "<< walkability << " - " << tri.getWalkability() << "     E: " << event << " - " << tri.getEvent() << std::endl;
+    }
     triangles_.push_back(tri);
 }
 
-void WorldMapWalkmesh::generate(std::string path){\
+void WorldMapWalkmesh::generate(std::string path){
     if (!calculated_) calculate();
+    TiXmlDocument doc;
+    std::unique_ptr<TiXmlElement> xml(new TiXmlElement("walkmesh"));
+    for (Triangle tri: triangles_){
+        std::unique_ptr<TiXmlElement> xml_tri(new TiXmlElement("triangle"));
+        xml_tri->SetAttribute("id", std::to_string(tri.getId()));
+        xml_tri->SetAttribute(
+          "a",
+          std::to_string(static_cast<int>(tri.getA()[0])) + " "
+          + std::to_string(static_cast<int>(tri.getA()[1])) + " "
+          + std::to_string(static_cast<int>(tri.getA()[2]))
+        );
+        xml_tri->SetAttribute(
+          "b",
+          std::to_string(static_cast<int>(tri.getB()[0])) + " "
+          + std::to_string(static_cast<int>(tri.getB()[1])) + " "
+          + std::to_string(static_cast<int>(tri.getB()[2]))
+        );
+        xml_tri->SetAttribute(
+          "c",
+          std::to_string(static_cast<int>(tri.getA()[0])) + " "
+          + std::to_string(static_cast<int>(tri.getC()[1])) + " "
+          + std::to_string(static_cast<int>(tri.getC()[2]))
+        );
+        // WARN: This is never actually calculated. See the comments on calculate()
+        if (tri.getAB().id > -1)
+            xml_tri->SetAttribute(
+              "a_b", std::to_string(tri.getAB().id) + (tri.getAB().warp ? " warp" : "")
+            );
+        if (tri.getBC().id > -1)
+            xml_tri->SetAttribute(
+              "b_c", std::to_string(tri.getBC().id) + (tri.getBC().warp ? " warp" : "")
+            );
+        if (tri.getCA().id > -1)
+            xml_tri->SetAttribute(
+              "c_a", std::to_string(tri.getCA().id) + (tri.getCA().warp ? " warp" : "")
+            );
+        xml_tri->SetAttribute("walkability", std::to_string(tri.getWalkability()));
+        if (tri.getEvent() > -1) xml_tri->SetAttribute("event", std::to_string(tri.getEvent()));
+        xml->LinkEndChild(xml_tri.release());
+    }
+    doc.LinkEndChild(xml.release());
+    doc.SaveFile(path);
 }
 
 void WorldMapWalkmesh::calculate(){
-    // TODO
+    // FIXME: This is super time-consumming. Either optimize it or skip it and calculate it on
+    // runtime.
+    calculated_ = true;
+    return;
+    // FIXME: The actual function starts here.
+    if (calculated_) return;
+    for (Triangle tri: triangles_){
+        tri.setAB(-1, false);
+        tri.setBC(-1, false);
+        tri.setCA(-1, false);
+    }
+    for (Triangle tri: triangles_){
+        if (tri.getAB().id != -1) continue;
+        for (Triangle compare: triangles_){
+            // For each triangle combination, check if any two vertices match,
+            // and set AB/BC/CA for both of them.
+            if (tri.getId() != compare.getId()){
+                if (
+                  (tri.getA() == compare.getA() && tri.getB() == compare.getB())
+                  || (tri.getB() == compare.getA() && tri.getA() == compare.getB())
+                ){
+                    // AB <-> AB match
+                    tri.setAB(compare.getId(), false);
+                    compare.setAB(tri.getId(), false);
+                }
+                if (
+                  (tri.getA() == compare.getB() && tri.getB() == compare.getC())
+                  || (tri.getB() == compare.getB() && tri.getA() == compare.getC())
+                ){
+                    // AB <-> BC match
+                    tri.setAB(compare.getId(), false);
+                    compare.setBC(tri.getId(), false);
+                }
+                if (
+                  (tri.getA() == compare.getC() && tri.getB() == compare.getA())
+                  || (tri.getB() == compare.getC() && tri.getA() == compare.getA())
+                ){
+                    // AB <-> CA match
+                    tri.setAB(compare.getId(), false);
+                    compare.setCA(tri.getId(), false);
+                }
+                if (
+                  (tri.getB() == compare.getA() && tri.getC() == compare.getB())
+                  || (tri.getC() == compare.getA() && tri.getB() == compare.getB())
+                ){
+                    // BC <-> AB match
+                    tri.setBC(compare.getId(), false);
+                    compare.setAB(tri.getId(), false);
+                }
+                if (
+                  (tri.getB() == compare.getB() && tri.getC() == compare.getC())
+                  || (tri.getC() == compare.getB() && tri.getB() == compare.getC())
+                ){
+                    // BC <-> BC match
+                    tri.setBC(compare.getId(), false);
+                    compare.setBC(tri.getId(), false);
+                }
+                if (
+                  (tri.getB() == compare.getC() && tri.getC() == compare.getA())
+                  || (tri.getC() == compare.getC() && tri.getB() == compare.getA())
+                ){
+                    // BC <-> CA match
+                    tri.setBC(compare.getId(), false);
+                    compare.setCA(tri.getId(), false);
+                }
+                if (
+                  (tri.getC() == compare.getA() && tri.getA() == compare.getB())
+                  || (tri.getA() == compare.getA() && tri.getC() == compare.getB())
+                ){
+                    // CA <-> AB match
+                    tri.setBC(compare.getId(), false);
+                    compare.setAB(tri.getId(), false);
+                }
+                if (
+                  (tri.getC() == compare.getB() && tri.getA() == compare.getC())
+                  || (tri.getA() == compare.getB() && tri.getC() == compare.getC())
+                ){
+                    // CA <-> BC match
+                    tri.setCA(compare.getId(), false);
+                    compare.setBC(tri.getId(), false);
+                }
+                if (
+                  (tri.getC() == compare.getC() && tri.getA() == compare.getA())
+                  || (tri.getA() == compare.getC() && tri.getC() == compare.getA())
+                ){
+                    // CA <-> CA match
+                    tri.setCA(compare.getId(), false);
+                    compare.setCA(tri.getId(), false);
+                }
+                // TODO: For warping worlds, check warping
+            }
+        }
+    }
     calculated_ = true;
 }

+ 27 - 6
src/installer/data/WorldMapWalkmesh.h

@@ -66,26 +66,33 @@ class WorldMapWalkmesh{
                   const Ogre::Vector3 c, const unsigned int walkability, const int event
                 );
 
+                /**
+                 * Retrieves the triangle ID in the walkmesh.
+                 *
+                 * @return The triangle ID.
+                 */
+                unsigned int getId() const;
+
                 /**
                  * Retrieves the coordinates of the first vertex
                  *
                  * @return Vertex coordinates (X, Y, Z).
                  */
-                Ogre::Vector3 getA();
+                Ogre::Vector3 getA() const;
 
                 /**
                  * Retrieves the coordinates of the second vertex
                  *
                  * @return Vertex coordinates (X, Y, Z).
                  */
-                Ogre::Vector3 getB();
+                Ogre::Vector3 getB() const;
 
                 /**
                  * Retrieves the coordinates of the third vertex
                  *
                  * @return Vertex coordinates (X, Y, Z).
                  */
-                Ogre::Vector3 getC();
+                Ogre::Vector3 getC() const;
 
                 /**
                  * Retrieves the triangle reached when exiting the triangle by the side between the
@@ -93,7 +100,7 @@ class WorldMapWalkmesh{
                  *
                  * @return Target triangle.
                  */
-                Target getAB();
+                Target getAB() const;
 
                 /**
                  * Retrieves the triangle reached when exiting the triangle by the side between the
@@ -101,7 +108,7 @@ class WorldMapWalkmesh{
                  *
                  * @return Target triangle.
                  */
-                Target getBC();
+                Target getBC() const;
 
                 /**
                  * Retrieves the triangle reached when exiting the triangle by the side between the
@@ -109,7 +116,21 @@ class WorldMapWalkmesh{
                  *
                  * @return Target triangle.
                  */
-                Target getCA();
+                Target getCA() const;
+
+                /**
+                 * Retrieves the triangle walkability mode.
+                 *
+                 * @return The walkability mode number.
+                 */
+                unsigned int getWalkability() const;
+
+                /**
+                 * Retrieves the event triggered upon entering the triangle.
+                 *
+                 * @return The ID of the event triggered on entering, or -1 if none.
+                 */
+                int getEvent() const;
 
                 /**
                  * Sets the triangle reached when exiting the triangle by the side between the