Bladeren bron

World map data installer improved. Meshes are properly exported and referenced in the new world map XML files. Their coordinates have been fixed.

Iñigo Valentin 2 jaren geleden
bovenliggende
commit
547f4d8f22

+ 1 - 0
.gitignore

@@ -22,4 +22,5 @@ doc/FFVII/RAW/
 .settings/
 .project
 .cproject
+.directory
 v-gears-new*.png

+ 1 - 1
CMakeLists.txt

@@ -19,7 +19,7 @@ project(V-Gears)
 set(CMAKE_PACKAGE_ICON v-gears.png)
 set(VGEARS_VERSION_MAJOR 0)
 set(VGEARS_VERSION_MINOR 1)
-set(VGEARS_VERSION_PATCH 17)
+set(VGEARS_VERSION_PATCH 18)
 set(VGEARS_VERSION ${VGEARS_VERSION_MAJOR}.${VGEARS_VERSION_MINOR}.${VGEARS_VERSION_PATCH})
 
 

+ 0 - 0
data/data/wm/README.txt → data/data/models/world/characters/README.txt


+ 3 - 0
data/data/models/world/locations/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 3 - 0
data/data/models/world/other/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 3 - 0
data/data/models/world/terrain/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 3 - 0
data/data/models/world/vehicles/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 2 - 0
data/data/scripts/field.lua

@@ -291,6 +291,8 @@ System["MapChanger"] = {
         end
         return 0
     end,
+    
+    
 
     --- Starts a battle.
     --

+ 21 - 0
data/data/scripts/world_map.lua

@@ -0,0 +1,21 @@
+--- Helper function that loads a world map through fade in and fadeout
+--
+-- @param map The world map to load. 0 is the normal world map, 1 is the submarine map, and 2 is the
+-- snowstorm map.
+-- @param x X coordinate in the map for the PC.
+-- @param y Y coordinate in the map for the PC.
+load_world_map_request = function(map, x, y)
+    if map >= 0 and map <= 2  then
+
+        -- TODO: Set propper names
+        -- System["MapChanger"].map_name = map_name
+        -- System["MapChanger"].point_name = point_name
+        player_lock(true) -- disable menu and pc movement while load map
+        script:request_end_sync(Script.UI, "Fade", "fade_out", 0)
+        world_map(map, x, y)
+        player_lock(false) -- enable menu and pc movement after load map
+        --self.current_map_name = self.map_name
+        --self.map_name = ""
+        script:request_end_sync(Script.UI, "Fade", "fade_in", 0)
+    end
+end

+ 3 - 0
data/data/world/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 3 - 3
src/Version.h

@@ -22,8 +22,8 @@
 /**
  * Application version.
  */
-#define VGEARS_VERSION "0.1.17"
+#define VGEARS_VERSION "0.1.18"
 #define VGEARS_VERSION_MAJOR "0"
 #define VGEARS_VERSION_MINOR "1"
-#define VGEARS_VERSION_PATCH "17"
-#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.17"
+#define VGEARS_VERSION_PATCH "18"
+#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.18"

+ 6 - 0
src/core/ScriptManagerBinds.h

@@ -67,6 +67,12 @@ void ScriptMap(const char* text){
     xml_map.LoadMap();
 }
 
+void ScriptWorldMap(const int map, const unsigned int x, const unsigned int y){
+    EntityManager::getSingleton().Clear();
+    XmlMapFile xml_map("./data/fields/wm" + std::to_string(map) + ".xml");
+    xml_map.LoadMap();
+}
+
 /**
  * Executes a script in the game console.
  *

+ 143 - 0
src/core/XmlWorldMapFile.cpp

@@ -0,0 +1,143 @@
+/*
+ * 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 "core/AudioManager.h"
+#include "core/EntityManager.h"
+#include "core/Logger.h"
+#include "core/ScriptManager.h"
+#include "core/XmlBackground2DFile.h"
+#include "core/XmlWorldMapFile.h"
+#include "map/VGearsBackground2DFileManager.h"
+#include "map/VGearsWalkmeshFileManager.h"
+#include "TextHandler.h"
+
+XmlWorldMapFile::XmlWorldMapFile(const Ogre::String& file): XmlFile(file){}
+
+XmlWorldMapFile::~XmlWorldMapFile(){}
+
+void XmlWorldMapFile::LoadMap(){
+    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.");
+        return;
+    }
+    node = node->FirstChild();
+    while (node != nullptr){
+        if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "walkmesh"){
+            Ogre::String name(GetString(node, "file_name"));
+            if (!name.empty()){
+                VGears::WalkmeshFileManager::getSingleton();
+                VGears::WalkmeshFilePtr walkmesh = VGears::WalkmeshFileManager::getSingleton()
+                  .load(name, "FIELDS").staticCast<VGears::WalkmeshFile>();
+                EntityManager::getSingleton().GetWalkmesh()->load(walkmesh);
+            }
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "background2d"){
+            Ogre::String name = GetString(node, "file_name");
+            if (name != ""){
+                // TODO Migrate this code to a Resource and use it's group to load the background.
+                VGears::Background2DFilePtr background
+                  = VGears::Background2DFileManager::getSingleton()
+                    .load(name, "FIELDS").staticCast<VGears::Background2DFile>();
+                EntityManager::getSingleton().GetBackground2D()->load(background);
+            }
+        }
+        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() == "entity_model"){
+            Ogre::String name = GetString(node, "name");
+            if (name == ""){
+                LOG_ERROR("There is no name specified for <entity_model> tag.");
+                continue;
+            }
+            Ogre::String file_name = GetString(node, "file_name");
+            if (file_name == ""){
+                LOG_ERROR("There is no file_name specified for <entity_model> tag.");
+                continue;
+            }
+            Ogre::Vector3 position(GetVector3(node, "position"));
+            Ogre::Degree direction(Ogre::Degree(GetFloat(node, "direction")));
+            Ogre::Vector3 scale(GetVector3(node, "scale", Ogre::Vector3::UNIT_SCALE));
+            Ogre::Quaternion orientation(GetQuaternion(node, "root_orientation"));
+            int index(GetInt(node, "index"));
+            EntityManager::getSingleton().AddEntity(
+              name, file_name, position, direction, scale, orientation, index
+            );
+        }
+        else if (
+          node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "entity_trigger"
+        ){
+            Ogre::String name = GetString(node, "name");
+            if (name == ""){
+                LOG_ERROR("There is no name specified for <entity_trigger> tag.");
+                continue;
+            }
+            Ogre::Vector3 point1 = GetVector3(node, "point1", Ogre::Vector3::ZERO);
+            Ogre::Vector3 point2 = GetVector3(node, "point2", Ogre::Vector3::ZERO);
+            bool enabled = GetBool(node, "enabled", false);
+            EntityManager::getSingleton().AddEntityTrigger(name, point1, point2, enabled);
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "entity_point"){
+            Ogre::String name = GetString(node, "name");
+            if (name == ""){
+                LOG_ERROR("There is no name specified for <entity_point> tag.");
+                continue;
+            }
+            Ogre::Vector3 position = GetVector3(node, "position");
+            float rotation = GetFloat(node, "rotation");
+            EntityManager::getSingleton().AddEntityPoint(name, position, rotation);
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "entity_script"){
+            Ogre::String name = GetString(node, "name");
+            if (name == ""){
+                LOG_ERROR("There is no name specified for <entity_script> tag.");
+                continue;
+            }
+            EntityManager::getSingleton().AddEntityScript(name);
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "script"){
+            Ogre::String file_name = GetString(node, "file_name");
+            if (file_name != "") ScriptManager::getSingleton().RunFile("fields/" + file_name);
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "tracks"){
+            for (TiXmlNode* track = node->FirstChild(); track; track = track->NextSibling()){
+                int id = GetInt(track, "id");
+                int track_id = GetInt(track, "track_id");
+                AudioManager::getSingleton().AddTrack(id, track_id);
+            }
+        }
+        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 "";
+}

+ 43 - 0
src/core/XmlWorldMapFile.h

@@ -0,0 +1,43 @@
+/*
+ * 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 "XmlFile.h"
+
+/**
+ * Handles XML world map files.
+ */
+class XmlWorldMapFile : public XmlFile{
+
+    public:
+
+        /**
+         * Constructor.
+         *
+         * @param[in] file Path to the XML world map file.
+         */
+        explicit XmlWorldMapFile(const Ogre::String& file);
+
+        /**
+         * Destructor.
+         */
+        virtual ~XmlWorldMapFile();
+
+        /**
+         * Parses the file and loads the map data.
+         */
+        void LoadWorldMap();
+};

+ 1 - 0
src/installer/CMakeLists.txt

@@ -31,6 +31,7 @@ set(INSTALLER_SOURCE_FILES
     data/AbFile.cpp
     data/BattleSceneFile.cpp
     data/DaFile.cpp
+    data/WorldMapWalkmesh.cpp
     decompiler/CodeGenerator.cpp
     decompiler/ControlFlow.cpp
     decompiler/DecompilerException.cpp

+ 8 - 1
src/installer/DataInstaller.cpp

@@ -466,7 +466,14 @@ void DataInstaller::CreateDirectories(){
     CreateDir("models/battle/scenes");
     CreateDir("models/battle/enemies");
     CreateDir("models/battle/attacks");
-    CreateDir("models/world/");
+    CreateDir("models/world/terrain/0");
+    CreateDir("models/world/terrain/1");
+    CreateDir("models/world/terrain/2");
+    CreateDir("models/world/buildings/");
+    CreateDir("models/world/characters/");
+    CreateDir("world/0");
+    CreateDir("world/1");
+    CreateDir("world/2");
 
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "temp/char/", "FileSystem", "FFVII", true, true

+ 5 - 3
src/installer/MediaDataInstaller.cpp

@@ -1395,15 +1395,17 @@ bool MediaDataInstaller::InstallSounds(){
               "-i %1%audio/sounds/%2%.wav %1%audio/sounds/%2%.ogg"
             ) % output_dir_ % processed_sounds_).str();
             std::system(command.c_str());
-            std::cout << "    ADD SOUND " << processed_sounds_ << ".ogg" << std::endl;
+            //std::cout << "    ADD SOUND " << processed_sounds_ << ".ogg" << std::endl;
             sounds_.push_back("audio/sounds/" + std::to_string(processed_sounds_) + ".ogg");
             // Remove the wav file.
             if (!keep_originals_)
-                std::remove((output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav").c_str());
+                std::remove((
+                  output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav"
+                ).c_str());
         }
         else{
             sounds_.push_back("audio/sounds/INVALID.ogg");
-            std::cout << "    -- ADD SOUND INVALID.ogg" << std::endl;
+            //std::cout << "    -- ADD SOUND INVALID.ogg" << std::endl;
         }
     }
     processed_sounds_ ++;

+ 111 - 17
src/installer/WorldInstaller.cpp

@@ -15,12 +15,14 @@
 
 #include <iostream>
 #include <fstream>
+#include <tinyxml.h>
 #include <OgreRoot.h>
 #include <OgreMesh.h>
 #include <OgreMeshSerializer.h>
 #include <boost/filesystem.hpp>
 #include "WorldInstaller.h"
 #include "common/Lzs.h"
+#include "data/WorldMapWalkmesh.h"
 
 WorldInstaller::WorldInstaller(
   std::string input_dir, std::string output_dir, const bool keep_originals
@@ -42,38 +44,54 @@ unsigned int WorldInstaller::Initialize(){
 bool WorldInstaller::ProcessMap(){
     //std::cout << "[WI] Processing map " << processed_maps_ << "/" << wm_map_.size() << std::endl;
     if (processed_maps_ >= wm_map_.size()) return true;
+    // Initialize the xml file.
+    TiXmlDocument doc;
+    std::unique_ptr<TiXmlElement> xml(new TiXmlElement("world_map"));
+    // Script.
+    std::unique_ptr<TiXmlElement> xml_script_element(new TiXmlElement("script"));
+    xml_script_element->SetAttribute("file_name", std::to_string(processed_maps_) + "/script.lua");
+    xml->LinkEndChild(xml_script_element.release());
+    // Background.
+    std::unique_ptr<TiXmlElement> xml_background_2d(new TiXmlElement("background2d"));
+    xml_background_2d->SetAttribute("file_name", std::to_string(processed_maps_) + "/bg.xml");
+    xml->LinkEndChild(xml_background_2d.release());
+    // Texts.
+    std::unique_ptr<TiXmlElement> xml_texts(new TiXmlElement("texts"));
+    xml_texts->SetAttribute("file_name", std::to_string(processed_maps_) + "/text.xml");
+    xml->LinkEndChild(xml_texts.release());
+    // Walkmesh.
+    std::unique_ptr<TiXmlElement> xml_walkmesh(new TiXmlElement("walkmesh"));
+    xml_walkmesh->SetAttribute("file_name", std::to_string(processed_maps_) + "/wm.xml");
+    xml->LinkEndChild(xml_walkmesh.release());
+    // Terrain (don't close yet).
+    std::unique_ptr<TiXmlElement> xml_terrain(new TiXmlElement("terrain"));
+
+    // Initialize the walkmesh data.
+    WorldMapWalkmesh walkmesh(processed_maps_, processed_maps_ == 0 ? true : false);
+
     // Read WM*.MAP file.
     Map map;
     Ogre::MeshSerializer mesh_serializer;
     boost::filesystem::path p(wm_map_[processed_maps_].GetFileName());
     std::string map_name = p.stem().native(); // w/o path or extension.
-
     for (int b = 0; b < wm_map_[processed_maps_].GetFileSize() / 0xB800; b ++){
-        //std::cout << "[WI]    Block " << b << std::endl;
+        // Calculate offsets for walkmesh.
+        // WM0 is 9x7 blocks, WM2 probably is too, WM3 no idea?
+        int wm_offset_x = 32 * (b % 9);
+        int wm_offset_y = 32 * (b / 9);
         Block block;
         for (int m = 0; m < 16; m ++){
-
             // Extract lzss compressed mesh to file.
             std::vector<unsigned char> lzss;
             wm_map_[processed_maps_].SetOffset(0xB800 * b + m * 4);
             u32 mesh_offset = 0xB800 * b + wm_map_[processed_maps_].readU32LE();
             wm_map_[processed_maps_].SetOffset(mesh_offset);
             u32 mesh_size = wm_map_[processed_maps_].readU32LE();
-
             // Add size to lzss
             wm_map_[processed_maps_].SetOffset(mesh_offset);
             for (int d = 0; d < 4; d ++)
                 lzss.push_back(wm_map_[processed_maps_].readU8());
-
-            //std::cout << "[WI]        Mesh " << m << " Size: " << mesh_size
-            //  << " Offset: " << mesh_offset << " - 0x";
-            //std::cout << std::hex << mesh_offset;
-            //std::cout << std::dec << std::endl;
-
             for (int d = 0; d < mesh_size ; d ++) lzss.push_back(wm_map_[processed_maps_].readU8());
-
-            //std::cout << "            LZSS size: " << lzss.size() << std::endl;
-
             // Extract the lzss file.
             std::vector<unsigned char> data = Lzs::Decompress(lzss);
             if (data.size() == 0) continue;
@@ -116,25 +134,51 @@ bool WorldInstaller::ProcessMap(){
             for (int ver = 0; ver < block.mesh[m].vertex_count; ver ++){
                 Vertex v;
                 v.x = mesh_data.readU16LE();
-                v.y = mesh_data.readU16LE();
                 v.z = mesh_data.readU16LE();
+                v.y = mesh_data.readU16LE();
                 v.unknown = mesh_data.readU16LE();
                 block.mesh[m].vertices.push_back(v);
             }
             for (int nor = 0; nor < block.mesh[m].vertex_count; nor ++){
                 Vertex v;
                 v.x = mesh_data.readU16LE();
-                v.y = mesh_data.readU16LE();
                 v.z = mesh_data.readU16LE();
+                v.y = mesh_data.readU16LE();
                 v.unknown = mesh_data.readU16LE();
                 block.mesh[m].normals.push_back(v);
             }
+
+            // Add data to the walkmesh
+            for (int tri = 0; tri < block.mesh[m].triangle_count; tri ++){
+                Triangle t = block.mesh[m].triangles.at(tri);
+                walkmesh.addTriangle(
+                  Ogre::Vector3(
+                    block.mesh[m].vertices.at(t.vertex_index[0]).x + wm_offset_x,
+                    block.mesh[m].vertices.at(t.vertex_index[0]).y + wm_offset_y,
+                    block.mesh[m].vertices.at(t.vertex_index[0]).z
+                  ), // a
+                  Ogre::Vector3(
+                    block.mesh[m].vertices.at(t.vertex_index[1]).x + wm_offset_x,
+                    block.mesh[m].vertices.at(t.vertex_index[1]).y + wm_offset_y,
+                    block.mesh[m].vertices.at(t.vertex_index[1]).z
+                  ), // b
+                  Ogre::Vector3(
+                    block.mesh[m].vertices.at(t.vertex_index[2]).x + wm_offset_x,
+                    block.mesh[m].vertices.at(t.vertex_index[2]).y + wm_offset_y,
+                    block.mesh[m].vertices.at(t.vertex_index[2]).z
+                  ), // c
+                  t.walkability,
+                  t.function_id
+                );
+            }
         }
         map.blocks.push_back(block);
     }
     // TODO: Generate material.
     // Generate manual meshes.
     for (int b = 0; b < map.blocks.size(); b ++){
+        int wm_offset_x = 32 * (b % 9);
+        int wm_offset_y = 32 * (b / 9);
         //std::cout << "[MAN] Block " << b << "/" << map.blocks.size() << std::endl;
 
         std::string name = map_name + "_" + std::to_string(b);
@@ -177,10 +221,60 @@ bool WorldInstaller::ProcessMap(){
         //std::cout << "[WI] Export mesh " << (output_dir_ + "wm/" + mesh->getName() + ".mesh")
         //  << std::endl;
         mesh_serializer.exportMesh(
-          mesh.getPointer(), output_dir_ + "wm/" + mesh->getName() + ".mesh"
+          mesh.getPointer(),
+          output_dir_ + "models/world/terrain/" + std::to_string(processed_maps_)
+            + "/" + mesh->getName() + ".mesh"
         );
+        // WM0 is te main worldmap, its shape changes according to the history progress, it has
+        // 68 blocks but only 63 are used at a time
+        if (processed_maps_ > 0 || b < 63){
+            std::unique_ptr<TiXmlElement> xml_block(new TiXmlElement("block"));
+            xml_block->SetAttribute("index", std::to_string(b));
+            xml_block->SetAttribute(
+              "file_name",
+              "terrain/" + std::to_string(processed_maps_) + "/" + mesh->getName() + ".mesh"
+            );
+            xml_block->SetAttribute(
+              "position", std::to_string(wm_offset_x) + " " + std::to_string(wm_offset_y) + " 0"
+            );
+            if (processed_maps_ == 0){
+                // TODO: Correct history points for each block.
+                switch (b){
+                    case 50: // Block 50 replaced by block 63
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_63.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                    case 41: // Block 41 replaced by block 64
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_64.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                    case 42: // Block 42 replaced by block 65
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_65.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                    case 60: // Block 60 replaced by block 66
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_66.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                    case 47: // Block 50 replaced by block 67
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_67.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                    case 48: // Block 50 replaced by block 68
+                        xml_block->SetAttribute("alt_file_name", "terrain/0/WM0_68.mesh");
+                        xml_block->SetAttribute("alt_story_change", "1000");
+                        break;
+                }
+            }
+            xml_terrain->LinkEndChild(xml_block.release());
+        }
     }
-    // Export mesh file.
+    // Add other data to the XML file.
+    // 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");
+    // Ready for next map or next step.
     processed_maps_ ++;
     if (processed_maps_ >= wm_map_.size()) return true;
     else return false;

+ 1 - 1
src/installer/WorldInstaller.h

@@ -127,7 +127,7 @@ class WorldInstaller{
         /**
          * Mesh vertex data.
          *
-         * Can also be used for nromal nada.
+         * Can also be used for normal data.
          */
         struct Vertex{
 

+ 79 - 0
src/installer/data/WorldMapWalkmesh.cpp

@@ -0,0 +1,79 @@
+/*
+ * 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 "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){
+    ab_.id = -1;
+    ab_.warp = false;
+    bc_.id = -1;
+    bc_.warp = false;
+    ca_.id = -1;
+    ca_.warp = false;
+}
+
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getA(){return a_;}
+
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getB(){return b_;}
+
+Ogre::Vector3 WorldMapWalkmesh::Triangle::getC(){return c_;}
+
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getAB(){return ab_;}
+
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getBC(){return bc_;}
+
+WorldMapWalkmesh::Triangle::Target WorldMapWalkmesh::Triangle::getCA(){return ca_;}
+
+void WorldMapWalkmesh::Triangle::setAB(const int id, const bool warp){
+    ab_.id = std::max(id, -1);
+    ab_.warp = warp;
+}
+
+void WorldMapWalkmesh::Triangle::setBC(const int id, const bool warp){
+    bc_.id = std::max(id, -1);
+    bc_.warp = warp;
+}
+
+void WorldMapWalkmesh::Triangle::setCA(const int id, const bool warp){
+    ca_.id = std::max(id, -1);
+    ca_.warp = warp;
+}
+
+WorldMapWalkmesh::WorldMapWalkmesh(const unsigned int id, const bool warp):
+  id_(id), warp_(warp), calculated_(false){
+    triangles_.clear();
+}
+
+void WorldMapWalkmesh::addTriangle(
+  const Ogre::Vector3 a, const Ogre::Vector3 b, const Ogre::Vector3 c,
+  const unsigned int walkability, const int event
+){
+    calculated_ = false;
+    Triangle tri(triangles_.size(), a, b, c, walkability, event);
+    triangles_.push_back(tri);
+}
+
+void WorldMapWalkmesh::generate(std::string path){\
+    if (!calculated_) calculate();
+}
+
+void WorldMapWalkmesh::calculate(){
+    // TODO
+    calculated_ = true;
+}

+ 249 - 0
src/installer/data/WorldMapWalkmesh.h

@@ -0,0 +1,249 @@
+/*
+ * V-Gears
+ * Copyright (C) 2022 V-Gears Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "OGRE/Ogre.h"
+#include "common/TypeDefine.h"
+
+/**
+ * Represents a walkmesh for a world map.
+ */
+class WorldMapWalkmesh{
+
+    public:
+
+        class Triangle{
+
+            public:
+
+                /**
+                 * Triangle reached when exiting this triangle from one of its borders.
+                 */
+                struct Target{
+
+                    /**
+                     * Target triangle ID.
+                     *
+                     * If -1, there is no way out by this side.
+                     */
+                    int id;
+
+                    /**
+                     * False if the target triangle is truly adjacent, true if it warps to other
+                     * side of the world.
+                     */
+                    bool warp;
+                };
+
+                /**
+                 * Creates a triangle
+                 *
+                 * @param[in] id Triangle ID.
+                 * @param[in] a X, Y, Z coordinates of the first vertex.
+                 * @param[in] b X, Y, Z coordinates of the second vertex.
+                 * @param[in] c X, Y, Z coordinates of the third vertex.
+                 * @param[in] walkability Walkability mode ID.
+                 * @param[in] event Event triggered upon entering the triangle. -1 for no event.
+                 */
+                Triangle(
+                  const unsigned int id, Ogre::Vector3 a, const Ogre::Vector3 b,
+                  const Ogre::Vector3 c, const unsigned int walkability, const int event
+                );
+
+                /**
+                 * Retrieves the coordinates of the first vertex
+                 *
+                 * @return Vertex coordinates (X, Y, Z).
+                 */
+                Ogre::Vector3 getA();
+
+                /**
+                 * Retrieves the coordinates of the second vertex
+                 *
+                 * @return Vertex coordinates (X, Y, Z).
+                 */
+                Ogre::Vector3 getB();
+
+                /**
+                 * Retrieves the coordinates of the third vertex
+                 *
+                 * @return Vertex coordinates (X, Y, Z).
+                 */
+                Ogre::Vector3 getC();
+
+                /**
+                 * Retrieves the triangle reached when exiting the triangle by the side between the
+                 * vertices A and B.
+                 *
+                 * @return Target triangle.
+                 */
+                Target getAB();
+
+                /**
+                 * Retrieves the triangle reached when exiting the triangle by the side between the
+                 * vertices B and C.
+                 *
+                 * @return Target triangle.
+                 */
+                Target getBC();
+
+                /**
+                 * Retrieves the triangle reached when exiting the triangle by the side between the
+                 * vertices C and A.
+                 *
+                 * @return Target triangle.
+                 */
+                Target getCA();
+
+                /**
+                 * Sets the triangle reached when exiting the triangle by the side between the
+                 * vertices A and B.
+                 *
+                 * @param[in] id Target triangle ID. -1 to indicate no way out by the edge.
+                 * @param[in] warp True to indicate world warping (ie, if walking into an edge of
+                 * the world gets you at the other end).
+                 */
+                void setAB(const int id, const bool warp);
+
+                /**
+                 * Sets the triangle reached when exiting the triangle by the side between the
+                 * vertices B and C.
+                 *
+                 * @param[in] id Target triangle ID. -1 to indicate no way out by the edge.
+                 * @param[in] warp True to indicate world warping (ie, if walking into an edge of
+                 * the world gets you at the other end).
+                 */
+                void setBC(const int id, const bool warp);
+
+                /**
+                 * Sets the triangle reached when exiting the triangle by the side between the
+                 * vertices C and A.
+                 *
+                 * @param[in] id Target triangle ID. -1 to indicate no way out by the edge.
+                 * @param[in] warp True to indicate world warping (ie, if walking into an edge of
+                 * the world gets you at the other end).
+                 */
+                void setCA(const int id, const bool warp);
+
+            private:
+
+                /**
+                 * Triangle ID.
+                 */
+                unsigned int id_;
+
+                /**
+                 * First vertex, X, Y, Z coordinates.
+                 */
+                Ogre::Vector3 a_;
+
+                /**
+                 * Second vertex, X, Y, Z coordinates.
+                 */
+                Ogre::Vector3 b_;
+
+                /**
+                 * Third vertex, X, Y, Z coordinates.
+                 */
+                Ogre::Vector3 c_;
+
+                /**
+                 * Target reached when exiting by the side formed by {@see a_} and {@see b_}.
+                 */
+                Target ab_;
+
+                /**
+                 * Target reached when exiting by the side formed by {@see b_} and {@see c_}.
+                 */
+                Target bc_;
+
+                /**
+                 * Target reached when exiting by the side formed by {@see c_} and {@see a_}.
+                 */
+                Target ca_;
+
+                /**
+                 * Walkability mode.
+                 */
+                unsigned int walkability_;
+
+                /**
+                 * Id of the event triggered upon entering the triangle.
+                 */
+                int event_;
+        };
+
+        /**
+         * Constructor.
+         *
+         * @param[in] id World map ID.
+         * @param[in] warp Indicates if the world map is warpable (ie, if walking into an edge of
+         * the world gets you at the other end).
+         */
+        WorldMapWalkmesh(const unsigned int id, const bool warp);
+
+        /**
+         * Adds a triangle.
+         *
+         * @param[in] a X, Y, Z coordinates of the first vertex.
+         * @param[in] b X, Y, Z coordinates of the second vertex.
+         * @param[in] c X, Y, Z coordinates of the third vertex.
+         * @param[in] walkability Walkability mode ID.
+         * @param[in] event Event triggered upon entering the triangle. -1 for no event.
+         */
+        void addTriangle(
+          const Ogre::Vector3 a, const Ogre::Vector3 b, const Ogre::Vector3 c,
+          const unsigned int walkability, const int event
+        );
+
+        /**
+         * Generates a world map walkmesh file.
+         *
+         * @param[in] path Path where the file will be saved (directory must exist).
+         */
+        void generate(const std::string path);
+
+    private:
+
+        /**
+         * Worldmap ID
+         */
+        unsigned int id_;
+
+        /**
+         * Indicates if the world is a warpable one.
+         */
+        bool warp_;
+
+        /**
+         * The list of triangles.
+         */
+        std::vector<Triangle> triangles_;
+
+        /**
+         * Indicates if the relations between triangles has been calculated since the last triangle
+         * was added.
+         */
+        bool calculated_;
+
+        /**
+         * Calculates the relations between triangles.
+         */
+        void calculate();
+};