From bb6698e37322568617bc1899706b5b93e6b17a1c Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Tue, 17 Jan 2017 23:57:20 +0000 Subject: [PATCH] Implement Hierarchy of game ZoneData This allows querying of the most specific ZoneData for a given point --- rwengine/src/data/ZoneData.hpp | 84 ++++++++++++++++--- rwengine/src/engine/GameData.cpp | 30 ++++--- rwengine/src/engine/GameData.hpp | 21 ++++- rwengine/src/loaders/LoaderIPL.cpp | 2 +- rwengine/src/loaders/LoaderIPL.hpp | 2 +- rwengine/src/script/ScriptFunctions.hpp | 10 +-- .../src/script/modules/GTA3ModuleImpl.inl | 37 ++++---- rwgame/states/DebugState.cpp | 4 +- tests/CMakeLists.txt | 2 + tests/test_LoaderIPL.cpp | 21 +++++ tests/test_ZoneData.cpp | 33 ++++++++ 11 files changed, 190 insertions(+), 56 deletions(-) create mode 100644 tests/test_LoaderIPL.cpp create mode 100644 tests/test_ZoneData.cpp diff --git a/rwengine/src/data/ZoneData.hpp b/rwengine/src/data/ZoneData.hpp index dcf1affc..bce87a6f 100644 --- a/rwengine/src/data/ZoneData.hpp +++ b/rwengine/src/data/ZoneData.hpp @@ -1,15 +1,16 @@ -#pragma once -#ifndef _ZONEDATA_HPP_ -#define _ZONEDATA_HPP_ +#ifndef RWENGINE_DATA_ZONEDATA_HPP +#define RWENGINE_DATA_ZONEDATA_HPP +#include #include +#include #include +#include #define ZONE_GANG_COUNT 13 /** - * \class Zone - * A Zone entry + * Zone Data loaded from IPL/zon files */ struct ZoneData { /** @@ -37,30 +38,87 @@ struct ZoneData { /** * Text of the zone? */ - std::string Text; + std::string text = {}; /** * Gang spawn density for daytime (8:00-19:00) */ - unsigned int gangDensityDay[ZONE_GANG_COUNT]; + unsigned int gangDensityDay[ZONE_GANG_COUNT] = {}; /** * Gang spawn density for nighttime (19:00-8:00) */ - unsigned int gangDensityNight[ZONE_GANG_COUNT]; + unsigned int gangDensityNight[ZONE_GANG_COUNT] = {}; /** * Gang car spawn density for daytime (8:00-19:00) */ - unsigned int gangCarDensityDay[ZONE_GANG_COUNT]; + unsigned int gangCarDensityDay[ZONE_GANG_COUNT] = {}; /** * Gang car spawn density for nighttime (19:00-8:00) */ - unsigned int gangCarDensityNight[ZONE_GANG_COUNT]; + unsigned int gangCarDensityNight[ZONE_GANG_COUNT] = {}; - unsigned int pedGroupDay; - unsigned int pedGroupNight; + unsigned int pedGroupDay = 0; + unsigned int pedGroupNight = 0; + + /** + * Pointer to parent zone in zone array + */ + ZoneData* parent_ = nullptr; + + /** + * Totally contained zones + */ + std::vector children_ = {}; + + static bool isZoneContained(const ZoneData& inner, const ZoneData& outer) { + return glm::all(glm::greaterThanEqual(inner.min, outer.min)) && + glm::all(glm::lessThanEqual(inner.max, outer.max)); + } + + bool containsPoint(const glm::vec3& point) const { + return glm::all(glm::greaterThanEqual(point, min)) && + glm::all(glm::lessThanEqual(point, max)); + } + + ZoneData* findLeafAtPoint(const glm::vec3& point) { + for (ZoneData* child : children_) { + auto descendent = child->findLeafAtPoint(point); + if (descendent) { + return descendent; + } + } + return containsPoint(point) ? this : nullptr; + } + + bool insertZone(ZoneData& inner) { + if (!isZoneContained(inner, *this)) { + return false; + } + + for (ZoneData* child : children_) { + if (child->insertZone(inner)) { + return true; + } + } + + // inner is a child of outer + + // Move any zones that are really within inner to inner + auto it = std::stable_partition( + children_.begin(), children_.end(), + [&](ZoneData* a) { return !inner.insertZone(*a); }); + children_.erase(it, children_.end()); + + children_.push_back(&inner); + inner.parent_ = this; + + return true; + } }; -#endif \ No newline at end of file +using ZoneDataList = std::vector; + +#endif diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 19e45cbd..151cf5e8 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -56,6 +56,10 @@ void GameData::load() { loadIFP("ped.ifp"); + // Clear existing zones + gamezones = ZoneDataList{ + {"CITYZON", 0, {-4000.f, -4000.f, -500.f}, {4000.f, 4000.f, 500.f}, 0}}; + loadLevelFile("data/default.dat"); loadLevelFile("data/gta3.dat"); } @@ -164,20 +168,24 @@ void GameData::loadIPL(const std::string& path) { bool GameData::loadZone(const std::string& path) { LoaderIPL ipll; - if (ipll.load(path)) { - if (ipll.zones.size() > 0) { - for (auto& z : ipll.zones) { - zones.insert({z.name, z}); - } - logger->info("Data", "Loaded " + std::to_string(ipll.zones.size()) + - " zones from " + path); - return true; - } - } else { + // Load the zones + if (!ipll.load(path)) { logger->error("Data", "Failed to load zones from " + path); + return false; } - return false; + gamezones.insert(gamezones.end(), ipll.zones.begin(), ipll.zones.end()); + + // Build zone hierarchy + for (ZoneData& zone : gamezones) { + zone.children_.clear(); + if (&zone == &gamezones.front()) { + continue; + } + gamezones[0].insertZone(zone); + } + + return true; } enum ColSection { diff --git a/rwengine/src/engine/GameData.hpp b/rwengine/src/engine/GameData.hpp index bd3a17a4..cdc0dd47 100644 --- a/rwengine/src/engine/GameData.hpp +++ b/rwengine/src/engine/GameData.hpp @@ -43,6 +43,7 @@ private: Logger* logger; LoaderDFF dffLoader; + public: /** * ctor @@ -193,10 +194,22 @@ public: */ std::map archives; - /** - * Map Zones - */ - std::map zones; + ZoneDataList gamezones; + + ZoneDataList mapzones; + + ZoneData* findZone(const std::string& name) { + auto it = + std::find_if(gamezones.begin(), gamezones.end(), + [&](const ZoneData& a) { return a.name == name; }); + return it != gamezones.end() ? &(*it) : nullptr; + } + + ZoneData* findZoneAt(const glm::vec3& pos) { + RW_CHECK(!gamezones.empty(), "No game zones loaded"); + ZoneData* zone = gamezones[0].findLeafAtPoint(pos); + return zone; + } std::unordered_map> modelinfo; diff --git a/rwengine/src/loaders/LoaderIPL.cpp b/rwengine/src/loaders/LoaderIPL.cpp index 38d6ee5b..c74b227b 100644 --- a/rwengine/src/loaders/LoaderIPL.cpp +++ b/rwengine/src/loaders/LoaderIPL.cpp @@ -124,4 +124,4 @@ bool LoaderIPL::load(const std::string& filename) { } return true; -} \ No newline at end of file +} diff --git a/rwengine/src/loaders/LoaderIPL.hpp b/rwengine/src/loaders/LoaderIPL.hpp index 31eba170..702286ff 100644 --- a/rwengine/src/loaders/LoaderIPL.hpp +++ b/rwengine/src/loaders/LoaderIPL.hpp @@ -20,7 +20,7 @@ public: std::vector> m_instances; /// List of Zones - std::vector zones; + ZoneDataList zones; }; #endif // LoaderIPL_h__ diff --git a/rwengine/src/script/ScriptFunctions.hpp b/rwengine/src/script/ScriptFunctions.hpp index 536fac40..8a345c8b 100644 --- a/rwengine/src/script/ScriptFunctions.hpp +++ b/rwengine/src/script/ScriptFunctions.hpp @@ -108,11 +108,11 @@ inline bool objectInRadiusNear(const ScriptArguments& args, GameObject* object, template inline bool objectInZone(const ScriptArguments& args, Tobj object, - const ScriptString zone) { - auto zfind = args.getWorld()->data->zones.find(zone); - if (zfind != args.getWorld()->data->zones.end()) { - auto& min = zfind->second.min; - auto& max = zfind->second.max; + const ScriptString name) { + auto zone = args.getWorld()->data->findZone(name); + if (zone) { + auto& min = zone->min; + auto& max = zone->max; return objectInBounds(object, min, max); } return false; diff --git a/rwengine/src/script/modules/GTA3ModuleImpl.inl b/rwengine/src/script/modules/GTA3ModuleImpl.inl index cb7576b4..762842df 100644 --- a/rwengine/src/script/modules/GTA3ModuleImpl.inl +++ b/rwengine/src/script/modules/GTA3ModuleImpl.inl @@ -3694,12 +3694,11 @@ void opcode_0152(const ScriptArguments& args, const ScriptString arg1, const Scr RW_UNUSED(arg15); RW_UNUSED(arg16); RW_UNUSED(arg17); - auto& zones = args.getWorld()->data->zones; - auto it = zones.find(arg1); - if (it != zones.end()) { - auto density = (it->second.gangCarDensityNight); - if (arg1) { - density = it->second.gangCarDensityDay; + auto zone = args.getWorld()->data->findZone(arg1); + if (zone) { + auto density = (zone->gangCarDensityNight); + if (arg2) { + density = zone->gangCarDensityDay; } auto count = args.getParameters().size(); for (auto g = 0u; g < count - 2; ++g) { @@ -3824,12 +3823,11 @@ void opcode_015c(const ScriptArguments& args, const ScriptString areaName, const RW_UNUSED(arg9); RW_UNUSED(arg10); RW_UNUSED(arg11); - auto& zones = args.getWorld()->data->zones; - auto it = zones.find(areaName); - if (it != zones.end()) { - auto density = (it->second.gangCarDensityNight); + auto zone = args.getWorld()->data->findZone(areaName); + if (zone) { + auto density = (zone->gangCarDensityNight); if (arg2) { - density = it->second.gangCarDensityDay; + density = zone->gangCarDensityDay; } auto count = args.getParameters().size(); for (auto g = 0u; g < count - 2; ++g) { @@ -8101,13 +8099,12 @@ void opcode_02dd(const ScriptArguments& args, const ScriptString areaName, Scrip RW_UNIMPLEMENTED_OPCODE(0x02dd); RW_UNUSED(areaName); RW_UNUSED(character); - const auto& zones = args.getWorld()->data->zones; std::string zname(args[0].string); // Only try to find a character if this is a known zone - auto zfind = zones.find(zname); - if(zfind != zones.end()) { + auto zone = args.getWorld()->data->findZone(areaName); + if(zone) { // Create a list of candidate characters by iterating and checking if the char is in this zone std::vector> candidates; @@ -8122,8 +8119,8 @@ void opcode_02dd(const ScriptArguments& args, const ScriptString areaName, Scrip // Check if character is in this zone auto cp = character->getPosition(); - auto& min = zfind->second.min; - auto& max = zfind->second.max; + auto& min = zone->min; + auto& max = zone->max; if (cp.x > min.x && cp.y > min.y && cp.z > min.z && cp.x < max.x && cp.y < max.y && cp.z < max.z) { candidates.push_back(p); @@ -9246,17 +9243,17 @@ void opcode_0324(const ScriptArguments& args, const ScriptString arg1, const Scr RW_UNUSED(arg1); RW_UNUSED(arg2); RW_UNUSED(arg3); - auto it = args.getWorld()->data->zones.find(args[0].string); - if( it != args.getWorld()->data->zones.end() ) + auto zone = args.getWorld()->data->findZone(arg1); + if (zone) { auto day = args[1].integer == 1; if( day ) { - it->second.pedGroupDay = args[2].integer; + zone->pedGroupDay = args[2].integer; } else { - it->second.pedGroupNight = args[2].integer; + zone->pedGroupNight = args[2].integer; } } } diff --git a/rwgame/states/DebugState.cpp b/rwgame/states/DebugState.cpp index 94006df4..759f56a0 100644 --- a/rwgame/states/DebugState.cpp +++ b/rwgame/states/DebugState.cpp @@ -232,7 +232,9 @@ void DebugState::tick(float dt) { void DebugState::draw(GameRenderer* r) { // Draw useful information like camera position. std::stringstream ss; - ss << "Camera Position: " << glm::to_string(_debugCam.position); + ss << "Camera Position: " << glm::to_string(_debugCam.position) << "\n"; + auto zone = getWorld()->data->findZoneAt(_debugCam.position); + ss << (zone ? zone->name : "No Zone") << "\n"; TextRenderer::TextInfo ti; ti.text = GameStringUtil::fromString(ss.str()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21a3eba3..e663ed70 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ set(TEST_SOURCES "test_Input.cpp" "test_lifetime.cpp" "test_loaderdff.cpp" + "test_LoaderIPL.cpp" "test_Logger.cpp" "test_menu.cpp" "test_object.cpp" @@ -45,6 +46,7 @@ set(TEST_SOURCES "test_VisualFX.cpp" "test_weapon.cpp" "test_world.cpp" + "test_ZoneData.cpp" # Hack in rwgame sources until there's a per-target test suite "${CMAKE_SOURCE_DIR}/rwgame/GameConfig.cpp" diff --git a/tests/test_LoaderIPL.cpp b/tests/test_LoaderIPL.cpp new file mode 100644 index 00000000..45685a65 --- /dev/null +++ b/tests/test_LoaderIPL.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "test_globals.hpp" + +BOOST_AUTO_TEST_SUITE(LoaderIPLTests) + +#if RW_TEST_WITH_DATA +BOOST_AUTO_TEST_CASE(test_load_zones) { + LoaderIPL loader; + const auto& gdpath = Global::get().getGamePath(); + BOOST_REQUIRE(loader.load(gdpath + "/data/gta3.zon")); + + BOOST_REQUIRE(loader.zones.size() > 2); + + auto& zone1 = loader.zones[0]; + BOOST_CHECK_EQUAL(zone1.name, "ROADBR1"); +} + +#endif + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/test_ZoneData.cpp b/tests/test_ZoneData.cpp new file mode 100644 index 00000000..8a2d9cfa --- /dev/null +++ b/tests/test_ZoneData.cpp @@ -0,0 +1,33 @@ +#include +#include +#include "test_globals.hpp" + +BOOST_AUTO_TEST_SUITE(ZoneDataTests) + +BOOST_AUTO_TEST_CASE(test_contains_point) { + ZoneData zone; + zone.min = glm::vec3(10.f, 10.f, -5.f); + zone.max = glm::vec3(30.f, 40.f, 5.f); + + BOOST_CHECK(zone.containsPoint({15.f, 15.f, 0.f})); + BOOST_CHECK(zone.containsPoint({10.f, 10.f, 0.f})); + BOOST_CHECK(zone.containsPoint({30.f, 35.f, 0.f})); + BOOST_CHECK(!zone.containsPoint({35.f, 30.f, 0.f})); + BOOST_CHECK(!zone.containsPoint({0.f, 0.f, 0.f})); + BOOST_CHECK(!zone.containsPoint({-15.f, -15.f, 0.f})); +} + +BOOST_AUTO_TEST_CASE(test_hierarchy) { + ZoneData zone; + zone.min = glm::vec3(-10.f,-10.f,-5.f); + zone.max = glm::vec3( 10.f, 10.f, 5.f); + ZoneData leaf; + leaf.min = glm::vec3(0.f, 0.f,-5.f); + leaf.max = glm::vec3(10.f,10.f, 5.f); + BOOST_CHECK(zone.insertZone(leaf)); + + BOOST_CHECK_EQUAL(zone.findLeafAtPoint({-5.f, 0.f, 0.f}), &zone); + BOOST_CHECK_EQUAL(zone.findLeafAtPoint({ 5.f, 5.f, 0.f}), &leaf); + +} +BOOST_AUTO_TEST_SUITE_END()