Added editor functionality to edit bound boxes
This commit is contained in:
parent
22b0e1992f
commit
4b868f47c2
@ -99,6 +99,8 @@ set(SOURCES
|
|||||||
../src/MenuManager.cpp
|
../src/MenuManager.cpp
|
||||||
../src/Location.h
|
../src/Location.h
|
||||||
../src/Location.cpp
|
../src/Location.cpp
|
||||||
|
../src/LocationEditor.h
|
||||||
|
../src/LocationEditor.cpp
|
||||||
../src/GameConstants.h
|
../src/GameConstants.h
|
||||||
../src/GameConstants.cpp
|
../src/GameConstants.cpp
|
||||||
../src/ScriptEngine.h
|
../src/ScriptEngine.h
|
||||||
|
|||||||
@ -56,6 +56,8 @@ add_executable(witcher001
|
|||||||
../src/MenuManager.cpp
|
../src/MenuManager.cpp
|
||||||
../src/Location.h
|
../src/Location.h
|
||||||
../src/Location.cpp
|
../src/Location.cpp
|
||||||
|
../src/LocationEditor.h
|
||||||
|
../src/LocationEditor.cpp
|
||||||
../src/GameConstants.h
|
../src/GameConstants.h
|
||||||
../src/GameConstants.cpp
|
../src/GameConstants.cpp
|
||||||
../src/ScriptEngine.h
|
../src/ScriptEngine.h
|
||||||
|
|||||||
@ -10,8 +10,18 @@
|
|||||||
"positionX": 6.6644,
|
"positionX": 6.6644,
|
||||||
"positionY": 0.9,
|
"positionY": 0.9,
|
||||||
"positionZ": -12.5262,
|
"positionZ": -12.5262,
|
||||||
|
"approachRadius": 0.6,
|
||||||
|
"boundsMaxX": 0.32435035705566406,
|
||||||
|
"boundsMaxY": 0.5,
|
||||||
|
"boundsMaxZ": 0.5178079605102539,
|
||||||
|
"boundsMinX": -0.19553518295288086,
|
||||||
|
"boundsMinY": -0.5,
|
||||||
|
"boundsMinZ": -0.2698993682861328,
|
||||||
|
"interactionPositionX": 6.694502353668213,
|
||||||
|
"interactionPositionY": 0.0,
|
||||||
|
"interactionPositionZ": -12.80740737915039,
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
"interactionRadius": 0.3,
|
"interactionRadius": 0.0,
|
||||||
"activateFunction": "on_phone_pickup"
|
"activateFunction": "on_phone_pickup"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -24,6 +34,16 @@
|
|||||||
"positionX": 5.84296,
|
"positionX": 5.84296,
|
||||||
"positionY": 0.9,
|
"positionY": 0.9,
|
||||||
"positionZ": -12.4661,
|
"positionZ": -12.4661,
|
||||||
|
"approachRadius": 0.6,
|
||||||
|
"boundsMaxX": 0.17081403732299805,
|
||||||
|
"boundsMaxY": 0.5,
|
||||||
|
"boundsMaxZ": 0.35796260833740234,
|
||||||
|
"boundsMinX": -0.22043895721435547,
|
||||||
|
"boundsMinY": -0.5,
|
||||||
|
"boundsMinZ": -0.25891780853271484,
|
||||||
|
"interactionPositionX": 5.819052696228027,
|
||||||
|
"interactionPositionY": 0.0,
|
||||||
|
"interactionPositionZ": -12.76319408416748,
|
||||||
"scale": 1.0,
|
"scale": 1.0,
|
||||||
"interactionRadius": 0.3,
|
"interactionRadius": 0.3,
|
||||||
"activateFunction": "on_journal_pickup"
|
"activateFunction": "on_journal_pickup"
|
||||||
|
|||||||
57
src/Game.cpp
57
src/Game.cpp
@ -694,9 +694,9 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
else if (event.button.button == SDL_BUTTON_RIGHT
|
else if (event.button.button == SDL_BUTTON_RIGHT
|
||||||
&& event.type == SDL_MOUSEBUTTONUP
|
&& event.type == SDL_MOUSEBUTTONUP
|
||||||
&& navigationEditorMode
|
&& editorMode == EditorMode::Navigation
|
||||||
&& currentLocation) {
|
&& currentLocation) {
|
||||||
currentLocation->navigationEditorHandleRightClick();
|
currentLocation->editor.handleRightClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (event.type == SDL_MOUSEMOTION) {
|
else if (event.type == SDL_MOUSEMOTION) {
|
||||||
@ -727,16 +727,20 @@ namespace ZL
|
|||||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_0:
|
case SDLK_0:
|
||||||
startDarklandsTransition();
|
|
||||||
break;
|
|
||||||
case SDLK_1:
|
case SDLK_1:
|
||||||
//if (audioPlayer) audioPlayer->playSoundAsync("audio/background.wav");
|
|
||||||
break;
|
|
||||||
case SDLK_2:
|
case SDLK_2:
|
||||||
//if (audioPlayer) audioPlayer->playMusicAsync("audio/lullaby-music-vol20-186394--online-audio-convert.com.ogg");
|
|
||||||
break;
|
|
||||||
case SDLK_3:
|
case SDLK_3:
|
||||||
//if (audioPlayer) audioPlayer->stopMusicAsync();
|
case SDLK_4:
|
||||||
|
case SDLK_5:
|
||||||
|
case SDLK_6:
|
||||||
|
case SDLK_7:
|
||||||
|
case SDLK_8:
|
||||||
|
case SDLK_9:
|
||||||
|
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
|
||||||
|
currentLocation->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0);
|
||||||
|
} else if (event.key.keysym.sym == SDLK_0) {
|
||||||
|
startDarklandsTransition();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
currentLocation->dialogueSystem.startDialogue("dialog_start001");
|
currentLocation->dialogueSystem.startDialogue("dialog_start001");
|
||||||
@ -747,14 +751,25 @@ namespace ZL
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
navigationEditorMode = !navigationEditorMode;
|
if (editorMode == EditorMode::None)
|
||||||
|
editorMode = EditorMode::Navigation;
|
||||||
|
else if (editorMode == EditorMode::Navigation)
|
||||||
|
editorMode = EditorMode::InteractiveObjects;
|
||||||
|
else
|
||||||
|
editorMode = EditorMode::None;
|
||||||
if (currentLocation) {
|
if (currentLocation) {
|
||||||
currentLocation->navigationEditorMode = navigationEditorMode;
|
currentLocation->editorMode = editorMode;
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation)
|
||||||
currentLocation->navigationEditorBuildNavMeshes();
|
currentLocation->editor.buildNavMeshes();
|
||||||
}
|
else if (editorMode == EditorMode::InteractiveObjects)
|
||||||
|
currentLocation->editor.buildInteractiveObjectBoundsMeshes();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation"
|
||||||
|
: (editorMode == EditorMode::InteractiveObjects) ? "InteractiveObjects"
|
||||||
|
: "None";
|
||||||
|
std::cout << "[EDITOR] Mode: " << modeName << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << "[NAV_EDITOR] Mode: " << (navigationEditorMode ? "ON" : "OFF") << std::endl;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
@ -802,22 +817,22 @@ namespace ZL
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_b:
|
case SDLK_b:
|
||||||
if (navigationEditorMode && currentLocation) {
|
if (editorMode != EditorMode::None && currentLocation) {
|
||||||
currentLocation->navigationEditorSave();
|
currentLocation->editor.saveAll();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_j:
|
case SDLK_j:
|
||||||
if (navigationEditorMode && currentLocation) {
|
if (editorMode != EditorMode::None && currentLocation) {
|
||||||
currentLocation->gameObjectEditorPlaceTree();
|
currentLocation->editor.placeTree();
|
||||||
} else {
|
} else {
|
||||||
menuManager.toggleQuestJournal();
|
menuManager.toggleQuestJournal();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (navigationEditorMode && currentLocation) {
|
if (editorMode != EditorMode::None && currentLocation) {
|
||||||
currentLocation->gameObjectEditorSave();
|
currentLocation->editor.saveObjects();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ namespace ZL {
|
|||||||
std::unordered_map<std::string, std::shared_ptr<Location>> locations;
|
std::unordered_map<std::string, std::shared_ptr<Location>> locations;
|
||||||
std::shared_ptr<Location> currentLocation;
|
std::shared_ptr<Location> currentLocation;
|
||||||
|
|
||||||
bool navigationEditorMode = false;
|
EditorMode editorMode = EditorMode::None;
|
||||||
|
|
||||||
// Global darklands state — persists across location transitions.
|
// Global darklands state — persists across location transitions.
|
||||||
bool isDarklands = false;
|
bool isDarklands = false;
|
||||||
|
|||||||
345
src/Location.cpp
345
src/Location.cpp
@ -4,11 +4,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "render/TextureManager.h"
|
#include "render/TextureManager.h"
|
||||||
#include "TextModel.h"
|
#include "TextModel.h"
|
||||||
#include <random>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
@ -47,8 +44,8 @@ namespace ZL
|
|||||||
Location::Location(Renderer& iRenderer, Inventory& iInventory)
|
Location::Location(Renderer& iRenderer, Inventory& iInventory)
|
||||||
: renderer(iRenderer)
|
: renderer(iRenderer)
|
||||||
, inventory(iInventory)
|
, inventory(iInventory)
|
||||||
|
, editor(*this)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Location::setup(const LocationSetup& params)
|
void Location::setup(const LocationSetup& params)
|
||||||
@ -303,8 +300,8 @@ namespace ZL
|
|||||||
activeNavigationIndex = 0;
|
activeNavigationIndex = 0;
|
||||||
navigation = navigationMaps.empty() ? nullptr : &navigationMaps[0];
|
navigation = navigationMaps.empty() ? nullptr : &navigationMaps[0];
|
||||||
|
|
||||||
if (navigationEditorMode && navigation) {
|
if (editorMode == EditorMode::Navigation && navigation) {
|
||||||
navigationEditorBuildNavMeshes();
|
editor.buildNavMeshes();
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr float kDynamicObstacleInfluenceDist = 6.0f;
|
static constexpr float kDynamicObstacleInfluenceDist = 6.0f;
|
||||||
@ -365,237 +362,13 @@ namespace ZL
|
|||||||
if (npc) npc->forceReplan();
|
if (npc) npc->forceReplan();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation) {
|
||||||
navigationEditorBuildNavMeshes();
|
editor.buildNavMeshes();
|
||||||
}
|
}
|
||||||
std::cout << "[NAV] Switched to navigation map " << index << "\n";
|
std::cout << "[NAV] Switched to navigation map " << index << "\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Location::navigationEditorBuildNavMeshes()
|
|
||||||
{
|
|
||||||
navigationEditorNavMeshes.clear();
|
|
||||||
if (!navigation) return;
|
|
||||||
const float y = navigation->getFloorY() + 0.02f;
|
|
||||||
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
|
||||||
for (const auto& obs : navigation->getObstaclePolygons()) {
|
|
||||||
if (obs.polygon.size() < 3) continue;
|
|
||||||
VertexRenderStruct mesh;
|
|
||||||
mesh.data = CreatePolygonFloor(obs.polygon, y, red);
|
|
||||||
mesh.RefreshVBO();
|
|
||||||
navigationEditorNavMeshes.push_back(std::move(mesh));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorDrawNavigation()
|
|
||||||
{
|
|
||||||
renderer.shaderManager.PushShader("defaultColor");
|
|
||||||
renderer.SetMatrix();
|
|
||||||
for (const auto& mesh : navigationEditorNavMeshes) {
|
|
||||||
renderer.DrawVertexRenderStruct(mesh);
|
|
||||||
}
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
renderer.SetMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorRebuildPointsMesh()
|
|
||||||
{
|
|
||||||
VertexDataStruct data;
|
|
||||||
const Eigen::Vector3f yellow(1.0f, 1.0f, 0.0f);
|
|
||||||
const float y = navigation ? navigation->getFloorY() + 0.05f : 0.05f;
|
|
||||||
const float s = 0.2f;
|
|
||||||
|
|
||||||
for (const auto& pt : navigationEditorPoints) {
|
|
||||||
// Small upward-pointing equilateral triangle in the XZ plane
|
|
||||||
Eigen::Vector3f v0(pt.x(), y, pt.z() - s * 1.15f);
|
|
||||||
Eigen::Vector3f v1(pt.x() - s, y, pt.z() + s * 0.58f);
|
|
||||||
Eigen::Vector3f v2(pt.x() + s, y, pt.z() + s * 0.58f);
|
|
||||||
|
|
||||||
data.PositionData.push_back(v0);
|
|
||||||
data.PositionData.push_back(v1);
|
|
||||||
data.PositionData.push_back(v2);
|
|
||||||
data.ColorData.push_back(yellow);
|
|
||||||
data.ColorData.push_back(yellow);
|
|
||||||
data.ColorData.push_back(yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigationEditorPointsMesh.data = std::move(data);
|
|
||||||
navigationEditorPointsMesh.RefreshVBO();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorDrawPoints()
|
|
||||||
{
|
|
||||||
if (navigationEditorPoints.empty()) return;
|
|
||||||
renderer.shaderManager.PushShader("defaultColor");
|
|
||||||
renderer.SetMatrix();
|
|
||||||
renderer.DrawVertexRenderStruct(navigationEditorPointsMesh);
|
|
||||||
renderer.shaderManager.PopShader();
|
|
||||||
renderer.SetMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorHandleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld)
|
|
||||||
{
|
|
||||||
if (ctrlHeld) {
|
|
||||||
const float removeRadius = 1.0f;
|
|
||||||
for (auto it = navigationEditorPoints.begin(); it != navigationEditorPoints.end(); ++it) {
|
|
||||||
const float dx = it->x() - hit.x();
|
|
||||||
const float dz = it->z() - hit.z();
|
|
||||||
if (dx * dx + dz * dz <= removeRadius * removeRadius) {
|
|
||||||
navigationEditorPoints.erase(it);
|
|
||||||
navigationEditorRebuildPointsMesh();
|
|
||||||
std::cout << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::cout << "[NAV_EDITOR] No point found within " << removeRadius << " units of click\n";
|
|
||||||
} else {
|
|
||||||
navigationEditorPoints.push_back(hit);
|
|
||||||
navigationEditorRebuildPointsMesh();
|
|
||||||
std::cout << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z()
|
|
||||||
<< "), total: " << navigationEditorPoints.size() << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorHandleRightClick()
|
|
||||||
{
|
|
||||||
if (navigationEditorPoints.size() < 3) {
|
|
||||||
std::cout << "[NAV_EDITOR] Need at least 3 points to form a polygon (have "
|
|
||||||
<< navigationEditorPoints.size() << "); clearing\n";
|
|
||||||
navigationEditorPoints.clear();
|
|
||||||
navigationEditorRebuildPointsMesh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PathFinder::ObstaclePolygon poly;
|
|
||||||
poly.name = "editor_obstacle_" + std::to_string(++navigationEditorObstacleCounter);
|
|
||||||
for (const auto& pt : navigationEditorPoints) {
|
|
||||||
poly.polygon.emplace_back(pt.x(), pt.z());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navigation) navigation->addObstaclePolygon(poly);
|
|
||||||
std::cout << "[NAV_EDITOR] Added obstacle '" << poly.name << "' with "
|
|
||||||
<< poly.polygon.size() << " vertices\n";
|
|
||||||
|
|
||||||
// Add a red mesh for the new obstacle polygon so it's visible immediately.
|
|
||||||
{
|
|
||||||
const float y = navigation ? navigation->getFloorY() + 0.02f : 0.02f;
|
|
||||||
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
|
||||||
VertexRenderStruct mesh;
|
|
||||||
mesh.data = CreatePolygonFloor(poly.polygon, y, red);
|
|
||||||
mesh.RefreshVBO();
|
|
||||||
navigationEditorNavMeshes.push_back(std::move(mesh));
|
|
||||||
}
|
|
||||||
|
|
||||||
navigationEditorPoints.clear();
|
|
||||||
navigationEditorRebuildPointsMesh();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorSave()
|
|
||||||
{
|
|
||||||
std::string baseName;
|
|
||||||
for (int i = 1; ; ++i) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof(buf), "saved_mesh%03d", i);
|
|
||||||
baseName = buf;
|
|
||||||
if (!std::filesystem::exists(baseName + ".json")) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigation) return;
|
|
||||||
|
|
||||||
if (navigation->saveConfig(baseName + ".json"))
|
|
||||||
std::cout << "[NAV_EDITOR] Saved config to: " << baseName << ".json\n";
|
|
||||||
else
|
|
||||||
std::cerr << "[NAV_EDITOR] Failed to save config to: " << baseName << ".json\n";
|
|
||||||
|
|
||||||
if (navigation->saveGrid(baseName + ".txt"))
|
|
||||||
std::cout << "[NAV_EDITOR] Saved grid to: " << baseName << ".txt\n";
|
|
||||||
else
|
|
||||||
std::cerr << "[NAV_EDITOR] Failed to save grid to: " << baseName << ".txt\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::navigationEditorReload()
|
|
||||||
{
|
|
||||||
setupNavigation(navigationMapPaths);
|
|
||||||
std::cout << "[NAV_EDITOR] Reloaded navigation maps (" << navigationMapPaths.size() << " maps)\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::gameObjectEditorPlaceTree()
|
|
||||||
{
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
static std::mt19937 rng(std::random_device{}());
|
|
||||||
std::uniform_real_distribution<float> scaleDist(0.8f, 1.2f);
|
|
||||||
std::uniform_real_distribution<float> rotDist(0.0f, 360.0f);
|
|
||||||
|
|
||||||
GameObjectData data;
|
|
||||||
data.name = "editor_tree_" + std::to_string(++editorPlacedObjectCounter);
|
|
||||||
data.texturePath = "resources/w/exterior/tree001.png";
|
|
||||||
data.meshPath = "resources/w/exterior/tree003.txt";
|
|
||||||
data.rotationX = 0.0f;
|
|
||||||
data.rotationY = rotDist(rng);
|
|
||||||
data.rotationZ = 0.0f;
|
|
||||||
data.positionX = player->position.x();
|
|
||||||
data.positionY = player->position.y();
|
|
||||||
data.positionZ = player->position.z();
|
|
||||||
data.scale = scaleDist(rng);
|
|
||||||
|
|
||||||
LoadedGameObject obj = GameObjectLoader::buildLoadedObject(data, renderer, CONST_ZIP_FILE);
|
|
||||||
obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });
|
|
||||||
obj.mesh.RefreshVBO();
|
|
||||||
|
|
||||||
gameObjects[data.name] = std::move(obj);
|
|
||||||
editorPlacedObjects.push_back(data);
|
|
||||||
|
|
||||||
std::cout << "[GAME_EDITOR] Placed '" << data.name << "' at ("
|
|
||||||
<< data.positionX << ", " << data.positionZ
|
|
||||||
<< ") scale=" << data.scale << " rotY=" << data.rotationY << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Location::gameObjectEditorSave()
|
|
||||||
{
|
|
||||||
if (editorPlacedObjects.empty()) {
|
|
||||||
std::cout << "[GAME_EDITOR] No editor-placed objects to save\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string baseName;
|
|
||||||
for (int i = 1; ; ++i) {
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof(buf), "saved_objects%03d", i);
|
|
||||||
baseName = buf;
|
|
||||||
if (!std::filesystem::exists(baseName + ".json")) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
json j;
|
|
||||||
j["objects"] = json::array();
|
|
||||||
for (const auto& d : editorPlacedObjects) {
|
|
||||||
json obj;
|
|
||||||
obj["name"] = d.name;
|
|
||||||
obj["texturePath"] = d.texturePath;
|
|
||||||
obj["meshPath"] = d.meshPath;
|
|
||||||
obj["rotationX"] = d.rotationX;
|
|
||||||
obj["rotationY"] = d.rotationY;
|
|
||||||
obj["rotationZ"] = d.rotationZ;
|
|
||||||
obj["positionX"] = d.positionX;
|
|
||||||
obj["positionY"] = d.positionY;
|
|
||||||
obj["positionZ"] = d.positionZ;
|
|
||||||
obj["scale"] = d.scale;
|
|
||||||
j["objects"].push_back(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string filename = baseName + ".json";
|
|
||||||
std::ofstream out(filename);
|
|
||||||
if (out.is_open()) {
|
|
||||||
out << j.dump(4);
|
|
||||||
std::cout << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
|
|
||||||
<< " object(s) to " << filename << "\n";
|
|
||||||
} else {
|
|
||||||
std::cerr << "[GAME_EDITOR] Failed to open " << filename << " for writing\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
||||||
if (interactiveObjects.empty()) {
|
if (interactiveObjects.empty()) {
|
||||||
//std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
//std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
||||||
@ -626,27 +399,49 @@ namespace ZL
|
|||||||
//std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
//std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
||||||
// << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
// << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
||||||
|
|
||||||
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
const bool hasBox = !(intObj.boundsMin.isZero() && intObj.boundsMax.isZero());
|
||||||
//std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
|
if (hasBox) {
|
||||||
|
const Eigen::Vector3f worldMin = intObj.position + intObj.boundsMin;
|
||||||
|
const Eigen::Vector3f worldMax = intObj.position + intObj.boundsMax;
|
||||||
|
float tMin = 0.1f;
|
||||||
|
float tMax = FLT_MAX;
|
||||||
|
bool miss = false;
|
||||||
|
for (int axis = 0; axis < 3; ++axis) {
|
||||||
|
if (std::abs(rayDir[axis]) < 1e-8f) {
|
||||||
|
if (rayOrigin[axis] < worldMin[axis] || rayOrigin[axis] > worldMax[axis]) {
|
||||||
|
miss = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const float invD = 1.0f / rayDir[axis];
|
||||||
|
float t0 = (worldMin[axis] - rayOrigin[axis]) * invD;
|
||||||
|
float t1 = (worldMax[axis] - rayOrigin[axis]) * invD;
|
||||||
|
if (invD < 0.0f) std::swap(t0, t1);
|
||||||
|
tMin = max(tMin, t0);
|
||||||
|
tMax = min(tMax, t1);
|
||||||
|
if (tMax < tMin) { miss = true; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!miss && tMin < closestDistance) {
|
||||||
|
closestDistance = tMin;
|
||||||
|
closestObject = &intObj;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
||||||
|
|
||||||
float distanceAlongRay = toObject.dot(rayDir);
|
float distanceAlongRay = toObject.dot(rayDir);
|
||||||
//std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
|
|
||||||
|
|
||||||
if (distanceAlongRay < 0.1f) {
|
if (distanceAlongRay < 0.1f) {
|
||||||
//std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
||||||
float distToObject = (closestPointOnRay - intObj.position).norm();
|
float distToObject = (closestPointOnRay - intObj.position).norm();
|
||||||
|
|
||||||
//std::cout << "[RAYCAST] Distance to object: " << distToObject
|
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
||||||
// << " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
|
closestDistance = distanceAlongRay;
|
||||||
|
closestObject = &intObj;
|
||||||
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
}
|
||||||
//std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
|
|
||||||
closestDistance = distanceAlongRay;
|
|
||||||
closestObject = &intObj;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -780,9 +575,12 @@ namespace ZL
|
|||||||
|
|
||||||
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
|
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation) {
|
||||||
navigationEditorDrawNavigation();
|
editor.drawNavigation();
|
||||||
navigationEditorDrawPoints();
|
editor.drawPoints();
|
||||||
|
}
|
||||||
|
if (editorMode == EditorMode::InteractiveObjects) {
|
||||||
|
editor.drawInteractiveObjectBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
@ -979,9 +777,12 @@ namespace ZL
|
|||||||
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation) {
|
||||||
navigationEditorDrawNavigation();
|
editor.drawNavigation();
|
||||||
navigationEditorDrawPoints();
|
editor.drawPoints();
|
||||||
|
}
|
||||||
|
if (editorMode == EditorMode::InteractiveObjects) {
|
||||||
|
editor.drawInteractiveObjectBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckGlError(__FILE__, __LINE__);
|
CheckGlError(__FILE__, __LINE__);
|
||||||
@ -1050,9 +851,12 @@ namespace ZL
|
|||||||
|
|
||||||
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
|
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation) {
|
||||||
navigationEditorDrawNavigation();
|
editor.drawNavigation();
|
||||||
navigationEditorDrawPoints();
|
editor.drawPoints();
|
||||||
|
}
|
||||||
|
if (editorMode == EditorMode::InteractiveObjects) {
|
||||||
|
editor.drawInteractiveObjectBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
@ -1323,10 +1127,13 @@ namespace ZL
|
|||||||
|
|
||||||
// Check if player reached target interactive object
|
// Check if player reached target interactive object
|
||||||
if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) {
|
if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) {
|
||||||
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
const Eigen::Vector3f& approachTarget = targetInteractiveObject->hasInteractionPosition
|
||||||
|
? targetInteractiveObject->interactionPosition
|
||||||
|
: targetInteractiveObject->position;
|
||||||
|
float distToObject = (player->position - approachTarget).norm();
|
||||||
|
|
||||||
// If player is close enough to pick up the item
|
// If player is close enough to pick up the item
|
||||||
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
|
if (distToObject <= targetInteractiveObject->approachRadius) {
|
||||||
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
||||||
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << std::endl;
|
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << std::endl;
|
||||||
|
|
||||||
@ -1409,12 +1216,22 @@ namespace ZL
|
|||||||
|
|
||||||
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
||||||
|
|
||||||
if (navigationEditorMode) {
|
if (editorMode == EditorMode::Navigation) {
|
||||||
if (rayDir.y() < -0.001f) {
|
if (rayDir.y() < -0.001f) {
|
||||||
const float t = -camPos.y() / rayDir.y();
|
const float t = -camPos.y() / rayDir.y();
|
||||||
const Eigen::Vector3f hit = camPos + rayDir * t;
|
const Eigen::Vector3f hit = camPos + rayDir * t;
|
||||||
const bool ctrlHeld = (SDL_GetModState() & KMOD_CTRL) != 0;
|
const bool ctrlHeld = (SDL_GetModState() & KMOD_CTRL) != 0;
|
||||||
navigationEditorHandleLeftClick(hit, ctrlHeld);
|
editor.handleLeftClick(hit, ctrlHeld);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorMode == EditorMode::InteractiveObjects) {
|
||||||
|
if (rayDir.y() < -0.001f) {
|
||||||
|
const float t = -camPos.y() / rayDir.y();
|
||||||
|
const Eigen::Vector3f hit = camPos + rayDir * t;
|
||||||
|
const bool ctrlHeld = (SDL_GetModState() & KMOD_CTRL) != 0;
|
||||||
|
editor.handleInteractiveObjectClick(hit, ctrlHeld);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1435,7 +1252,9 @@ namespace ZL
|
|||||||
targetInteractNpc = nullptr;
|
targetInteractNpc = nullptr;
|
||||||
targetInteractNpcIndex = -1;
|
targetInteractNpcIndex = -1;
|
||||||
targetTeleportZone = nullptr;
|
targetTeleportZone = nullptr;
|
||||||
player->setTarget(clickedObject->position);
|
player->setTarget(clickedObject->hasInteractionPosition
|
||||||
|
? clickedObject->interactionPosition
|
||||||
|
: clickedObject->position);
|
||||||
player->attackTarget = nullptr;
|
player->attackTarget = nullptr;
|
||||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#include "dialogue/DialogueSystem.h"
|
#include "dialogue/DialogueSystem.h"
|
||||||
#include "SparkEmitter.h"
|
#include "SparkEmitter.h"
|
||||||
#include "TeleportZone.h"
|
#include "TeleportZone.h"
|
||||||
|
#include "LocationEditor.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -99,28 +100,10 @@ namespace ZL
|
|||||||
std::function<void()> requestAdvanceDarklandsHud;
|
std::function<void()> requestAdvanceDarklandsHud;
|
||||||
|
|
||||||
// Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon
|
// Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon
|
||||||
bool navigationEditorMode = false;
|
EditorMode editorMode = EditorMode::None;
|
||||||
|
LocationEditor editor;
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> navigationEditorPoints;
|
|
||||||
VertexRenderStruct navigationEditorPointsMesh;
|
|
||||||
std::vector<VertexRenderStruct> navigationEditorNavMeshes;
|
|
||||||
std::vector<std::string> navigationMapPaths;
|
std::vector<std::string> navigationMapPaths;
|
||||||
int navigationEditorObstacleCounter = 0;
|
|
||||||
|
|
||||||
void navigationEditorBuildNavMeshes();
|
|
||||||
void navigationEditorDrawNavigation();
|
|
||||||
void navigationEditorRebuildPointsMesh();
|
|
||||||
void navigationEditorDrawPoints();
|
|
||||||
void navigationEditorHandleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld);
|
|
||||||
void navigationEditorHandleRightClick();
|
|
||||||
void navigationEditorSave();
|
|
||||||
void navigationEditorReload();
|
|
||||||
|
|
||||||
void gameObjectEditorPlaceTree();
|
|
||||||
void gameObjectEditorSave();
|
|
||||||
|
|
||||||
std::vector<GameObjectData> editorPlacedObjects;
|
|
||||||
int editorPlacedObjectCounter = 0;
|
|
||||||
|
|
||||||
// Set by Game when the user's primary pointer (left mouse / single touch)
|
// Set by Game when the user's primary pointer (left mouse / single touch)
|
||||||
// has crossed the tap-vs-drag threshold and is now rotating the camera.
|
// has crossed the tap-vs-drag threshold and is now rotating the camera.
|
||||||
@ -155,6 +138,7 @@ namespace ZL
|
|||||||
int getDialogueFlag(const std::string& flag) const;
|
int getDialogueFlag(const std::string& flag) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
friend class LocationEditor;
|
||||||
Renderer& renderer;
|
Renderer& renderer;
|
||||||
Inventory& inventory;
|
Inventory& inventory;
|
||||||
|
|
||||||
|
|||||||
510
src/LocationEditor.cpp
Normal file
510
src/LocationEditor.cpp
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
#include "LocationEditor.h"
|
||||||
|
#include "Location.h"
|
||||||
|
#include "Character.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
#include "render/OpenGlExtensions.h"
|
||||||
|
#include "TextModel.h"
|
||||||
|
#include "external/nlohmann/json.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace ZL
|
||||||
|
{
|
||||||
|
extern const char* CONST_ZIP_FILE;
|
||||||
|
|
||||||
|
LocationEditor::LocationEditor(Location& location)
|
||||||
|
: loc(location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::buildNavMeshes()
|
||||||
|
{
|
||||||
|
navigationEditorNavMeshes.clear();
|
||||||
|
if (!loc.navigation) return;
|
||||||
|
const float y = loc.navigation->getFloorY() + 0.02f;
|
||||||
|
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
||||||
|
for (const auto& obs : loc.navigation->getObstaclePolygons()) {
|
||||||
|
if (obs.polygon.size() < 3) continue;
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
mesh.data = CreatePolygonFloor(obs.polygon, y, red);
|
||||||
|
mesh.RefreshVBO();
|
||||||
|
navigationEditorNavMeshes.push_back(std::move(mesh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::drawNavigation()
|
||||||
|
{
|
||||||
|
loc.renderer.shaderManager.PushShader("defaultColor");
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
for (const auto& mesh : navigationEditorNavMeshes) {
|
||||||
|
loc.renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
}
|
||||||
|
loc.renderer.shaderManager.PopShader();
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::rebuildPointsMesh()
|
||||||
|
{
|
||||||
|
VertexDataStruct data;
|
||||||
|
const Eigen::Vector3f yellow(1.0f, 1.0f, 0.0f);
|
||||||
|
const float y = loc.navigation ? loc.navigation->getFloorY() + 0.05f : 0.05f;
|
||||||
|
const float s = 0.2f;
|
||||||
|
|
||||||
|
for (const auto& pt : navigationEditorPoints) {
|
||||||
|
Eigen::Vector3f v0(pt.x(), y, pt.z() - s * 1.15f);
|
||||||
|
Eigen::Vector3f v1(pt.x() - s, y, pt.z() + s * 0.58f);
|
||||||
|
Eigen::Vector3f v2(pt.x() + s, y, pt.z() + s * 0.58f);
|
||||||
|
|
||||||
|
data.PositionData.push_back(v0);
|
||||||
|
data.PositionData.push_back(v1);
|
||||||
|
data.PositionData.push_back(v2);
|
||||||
|
data.ColorData.push_back(yellow);
|
||||||
|
data.ColorData.push_back(yellow);
|
||||||
|
data.ColorData.push_back(yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationEditorPointsMesh.data = std::move(data);
|
||||||
|
navigationEditorPointsMesh.RefreshVBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::drawPoints()
|
||||||
|
{
|
||||||
|
if (navigationEditorPoints.empty()) return;
|
||||||
|
loc.renderer.shaderManager.PushShader("defaultColor");
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
loc.renderer.DrawVertexRenderStruct(navigationEditorPointsMesh);
|
||||||
|
loc.renderer.shaderManager.PopShader();
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::handleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld)
|
||||||
|
{
|
||||||
|
if (ctrlHeld) {
|
||||||
|
const float removeRadius = 1.0f;
|
||||||
|
for (auto it = navigationEditorPoints.begin(); it != navigationEditorPoints.end(); ++it) {
|
||||||
|
const float dx = it->x() - hit.x();
|
||||||
|
const float dz = it->z() - hit.z();
|
||||||
|
if (dx * dx + dz * dz <= removeRadius * removeRadius) {
|
||||||
|
navigationEditorPoints.erase(it);
|
||||||
|
rebuildPointsMesh();
|
||||||
|
std::cout << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "[NAV_EDITOR] No point found within " << removeRadius << " units of click\n";
|
||||||
|
} else {
|
||||||
|
navigationEditorPoints.push_back(hit);
|
||||||
|
rebuildPointsMesh();
|
||||||
|
std::cout << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z()
|
||||||
|
<< "), total: " << navigationEditorPoints.size() << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::handleRightClick()
|
||||||
|
{
|
||||||
|
if (navigationEditorPoints.size() < 3) {
|
||||||
|
std::cout << "[NAV_EDITOR] Need at least 3 points to form a polygon (have "
|
||||||
|
<< navigationEditorPoints.size() << "); clearing\n";
|
||||||
|
navigationEditorPoints.clear();
|
||||||
|
rebuildPointsMesh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathFinder::ObstaclePolygon poly;
|
||||||
|
poly.name = "editor_obstacle_" + std::to_string(++navigationEditorObstacleCounter);
|
||||||
|
for (const auto& pt : navigationEditorPoints) {
|
||||||
|
poly.polygon.emplace_back(pt.x(), pt.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loc.navigation) loc.navigation->addObstaclePolygon(poly);
|
||||||
|
std::cout << "[NAV_EDITOR] Added obstacle '" << poly.name << "' with "
|
||||||
|
<< poly.polygon.size() << " vertices\n";
|
||||||
|
|
||||||
|
{
|
||||||
|
const float y = loc.navigation ? loc.navigation->getFloorY() + 0.02f : 0.02f;
|
||||||
|
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
mesh.data = CreatePolygonFloor(poly.polygon, y, red);
|
||||||
|
mesh.RefreshVBO();
|
||||||
|
navigationEditorNavMeshes.push_back(std::move(mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationEditorPoints.clear();
|
||||||
|
rebuildPointsMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::save()
|
||||||
|
{
|
||||||
|
std::string baseName;
|
||||||
|
for (int i = 1; ; ++i) {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "saved_mesh%03d", i);
|
||||||
|
baseName = buf;
|
||||||
|
if (!std::filesystem::exists(baseName + ".json")) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loc.navigation) return;
|
||||||
|
|
||||||
|
if (loc.navigation->saveConfig(baseName + ".json"))
|
||||||
|
std::cout << "[NAV_EDITOR] Saved config to: " << baseName << ".json\n";
|
||||||
|
else
|
||||||
|
std::cerr << "[NAV_EDITOR] Failed to save config to: " << baseName << ".json\n";
|
||||||
|
|
||||||
|
if (loc.navigation->saveGrid(baseName + ".txt"))
|
||||||
|
std::cout << "[NAV_EDITOR] Saved grid to: " << baseName << ".txt\n";
|
||||||
|
else
|
||||||
|
std::cerr << "[NAV_EDITOR] Failed to save grid to: " << baseName << ".txt\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::reload()
|
||||||
|
{
|
||||||
|
loc.setupNavigation(loc.navigationMapPaths);
|
||||||
|
std::cout << "[NAV_EDITOR] Reloaded navigation maps (" << loc.navigationMapPaths.size() << " maps)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static VertexDataStruct CreateBoundsBoxMesh(const Eigen::Vector3f corners[8], const Eigen::Vector3f& color)
|
||||||
|
{
|
||||||
|
VertexDataStruct data;
|
||||||
|
|
||||||
|
auto addTriangle = [&](const Eigen::Vector3f& a, const Eigen::Vector3f& b, const Eigen::Vector3f& c) {
|
||||||
|
data.PositionData.push_back(a);
|
||||||
|
data.PositionData.push_back(b);
|
||||||
|
data.PositionData.push_back(c);
|
||||||
|
data.ColorData.push_back(color);
|
||||||
|
data.ColorData.push_back(color);
|
||||||
|
data.ColorData.push_back(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto addFace = [&](int a, int b, int c, int d) {
|
||||||
|
addTriangle(corners[a], corners[b], corners[c]);
|
||||||
|
addTriangle(corners[a], corners[c], corners[d]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// corners: 0-3 = bottom ring (y=min), 4-7 = top ring (y=max)
|
||||||
|
// 0=(x0,y0,z0) 1=(x1,y0,z0) 2=(x1,y0,z1) 3=(x0,y0,z1)
|
||||||
|
// 4=(x0,y1,z0) 5=(x1,y1,z0) 6=(x1,y1,z1) 7=(x0,y1,z1)
|
||||||
|
addFace(0, 1, 2, 3); // bottom
|
||||||
|
addFace(7, 6, 5, 4); // top
|
||||||
|
addFace(0, 4, 5, 1); // front (z=min)
|
||||||
|
addFace(3, 2, 6, 7); // back (z=max)
|
||||||
|
addFace(0, 3, 7, 4); // left (x=min)
|
||||||
|
addFace(1, 5, 6, 2); // right (x=max)
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::buildInteractiveObjectBoundsMeshes()
|
||||||
|
{
|
||||||
|
interactiveObjectBoundsMeshes.clear();
|
||||||
|
interactionPositionMeshes.clear();
|
||||||
|
|
||||||
|
const Eigen::Vector3f zero = Eigen::Vector3f::Zero();
|
||||||
|
const Eigen::Vector3f colorDefault(0.1f, 0.9f, 0.6f); // teal
|
||||||
|
const Eigen::Vector3f colorSelected(1.0f, 1.0f, 0.0f); // yellow
|
||||||
|
const Eigen::Vector3f colorPole(1.0f, 0.55f, 0.0f); // orange
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (const auto& obj : loc.interactiveObjects) {
|
||||||
|
const bool isSelected = (idx == selectedInteractiveObjectIndex);
|
||||||
|
|
||||||
|
// --- bounds box ---
|
||||||
|
if (obj.boundsMin != zero || obj.boundsMax != zero) {
|
||||||
|
const Eigen::Vector3f& color = isSelected ? colorSelected : colorDefault;
|
||||||
|
|
||||||
|
const float x0 = obj.boundsMin.x(), x1 = obj.boundsMax.x();
|
||||||
|
const float y0 = obj.boundsMin.y(), y1 = obj.boundsMax.y();
|
||||||
|
const float z0 = obj.boundsMin.z(), z1 = obj.boundsMax.z();
|
||||||
|
|
||||||
|
Eigen::Vector3f corners[8] = {
|
||||||
|
{ x0, y0, z0 }, { x1, y0, z0 },
|
||||||
|
{ x1, y0, z1 }, { x0, y0, z1 },
|
||||||
|
{ x0, y1, z0 }, { x1, y1, z0 },
|
||||||
|
{ x1, y1, z1 }, { x0, y1, z1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply the same TRS the draw function applies: scale → rotateY → translate
|
||||||
|
if (obj.scale != 1.f) {
|
||||||
|
for (auto& c : corners) c *= obj.scale;
|
||||||
|
}
|
||||||
|
if (obj.rotationY != 0.f) {
|
||||||
|
const float cosR = std::cos(obj.rotationY);
|
||||||
|
const float sinR = std::sin(obj.rotationY);
|
||||||
|
for (auto& c : corners) {
|
||||||
|
const float nx = c.x() * cosR - c.z() * sinR;
|
||||||
|
const float nz = c.x() * sinR + c.z() * cosR;
|
||||||
|
c.x() = nx;
|
||||||
|
c.z() = nz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& c : corners) c += obj.position;
|
||||||
|
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
mesh.data = CreateBoundsBoxMesh(corners, color);
|
||||||
|
mesh.RefreshVBO();
|
||||||
|
interactiveObjectBoundsMeshes.push_back(std::move(mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- interaction position pole ---
|
||||||
|
if (obj.hasInteractionPosition) {
|
||||||
|
const Eigen::Vector3f& p = obj.interactionPosition;
|
||||||
|
static constexpr float hw = 0.05f; // half width/depth
|
||||||
|
static constexpr float hh = 1.5f; // half height (pole = 3.0 tall)
|
||||||
|
|
||||||
|
Eigen::Vector3f corners[8] = {
|
||||||
|
{ p.x()-hw, p.y()-hh, p.z()-hw }, { p.x()+hw, p.y()-hh, p.z()-hw },
|
||||||
|
{ p.x()+hw, p.y()-hh, p.z()+hw }, { p.x()-hw, p.y()-hh, p.z()+hw },
|
||||||
|
{ p.x()-hw, p.y()+hh, p.z()-hw }, { p.x()+hw, p.y()+hh, p.z()-hw },
|
||||||
|
{ p.x()+hw, p.y()+hh, p.z()+hw }, { p.x()-hw, p.y()+hh, p.z()+hw },
|
||||||
|
};
|
||||||
|
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
mesh.data = CreateBoundsBoxMesh(corners, colorPole);
|
||||||
|
mesh.RefreshVBO();
|
||||||
|
interactionPositionMeshes.push_back(std::move(mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::drawInteractiveObjectBounds()
|
||||||
|
{
|
||||||
|
if (interactiveObjectBoundsMeshes.empty() && interactionPositionMeshes.empty()) return;
|
||||||
|
|
||||||
|
loc.renderer.shaderManager.PushShader("defaultColor");
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
for (const auto& mesh : interactiveObjectBoundsMeshes) {
|
||||||
|
loc.renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
}
|
||||||
|
for (const auto& mesh : interactionPositionMeshes) {
|
||||||
|
loc.renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
}
|
||||||
|
loc.renderer.shaderManager.PopShader();
|
||||||
|
loc.renderer.SetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::selectInteractiveObject(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= static_cast<int>(loc.interactiveObjects.size())) {
|
||||||
|
std::cout << "[IO_EDITOR] Index " << index << " out of range ("
|
||||||
|
<< loc.interactiveObjects.size() << " objects)\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedInteractiveObjectIndex = index;
|
||||||
|
boundsClickCount = 0;
|
||||||
|
buildInteractiveObjectBoundsMeshes();
|
||||||
|
std::cout << "[IO_EDITOR] Selected object " << index
|
||||||
|
<< " (" << loc.interactiveObjects[index].loadedObject.name << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::handleInteractiveObjectClick(const Eigen::Vector3f& worldHit, bool ctrlHeld)
|
||||||
|
{
|
||||||
|
if (selectedInteractiveObjectIndex < 0 ||
|
||||||
|
selectedInteractiveObjectIndex >= static_cast<int>(loc.interactiveObjects.size())) {
|
||||||
|
std::cout << "[IO_EDITOR] No valid object selected\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrlHeld) {
|
||||||
|
// Set the interaction position for the selected object (world XZ, Y=0).
|
||||||
|
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
||||||
|
target.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z());
|
||||||
|
target.hasInteractionPosition = true;
|
||||||
|
boundsClickCount = 0; // cancel any in-progress bounds placement
|
||||||
|
buildInteractiveObjectBoundsMeshes();
|
||||||
|
std::cout << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex
|
||||||
|
<< " (" << target.loadedObject.name << ")"
|
||||||
|
<< " at (" << worldHit.x() << ", " << worldHit.z() << ")\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& obj = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
||||||
|
|
||||||
|
// Convert world-space hit to object local space (inverse of scale → rotateY → translate)
|
||||||
|
auto worldToLocal = [&](const Eigen::Vector3f& w) -> Eigen::Vector3f {
|
||||||
|
Eigen::Vector3f local = w - obj.position;
|
||||||
|
if (obj.rotationY != 0.f) {
|
||||||
|
const float c = std::cos(-obj.rotationY);
|
||||||
|
const float s = std::sin(-obj.rotationY);
|
||||||
|
const float nx = local.x() * c - local.z() * s;
|
||||||
|
const float nz = local.x() * s + local.z() * c;
|
||||||
|
local.x() = nx;
|
||||||
|
local.z() = nz;
|
||||||
|
}
|
||||||
|
if (obj.scale > 1e-6f && obj.scale != 1.f)
|
||||||
|
local /= obj.scale;
|
||||||
|
return local;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (boundsClickCount == 0) {
|
||||||
|
boundsClickA = worldHit;
|
||||||
|
boundsClickCount = 1;
|
||||||
|
std::cout << "[IO_EDITOR] First corner placed at world ("
|
||||||
|
<< worldHit.x() << ", " << worldHit.z() << ") — click again for second corner\n";
|
||||||
|
} else {
|
||||||
|
const Eigen::Vector3f localA = worldToLocal(boundsClickA);
|
||||||
|
const Eigen::Vector3f localB = worldToLocal(worldHit);
|
||||||
|
|
||||||
|
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
||||||
|
target.boundsMin = Eigen::Vector3f(
|
||||||
|
min(localA.x(), localB.x()), 0.f, min(localA.z(), localB.z()));
|
||||||
|
target.boundsMax = Eigen::Vector3f(
|
||||||
|
max(localA.x(), localB.x()), 1.f, max(localA.z(), localB.z()));
|
||||||
|
|
||||||
|
boundsClickCount = 0;
|
||||||
|
buildInteractiveObjectBoundsMeshes();
|
||||||
|
|
||||||
|
std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
|
||||||
|
<< " (" << target.loadedObject.name << ")"
|
||||||
|
<< " min=(" << target.boundsMin.x() << ", " << target.boundsMin.z() << ")"
|
||||||
|
<< " max=(" << target.boundsMax.x() << ", " << target.boundsMax.z() << ")\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::saveInteractiveObjects()
|
||||||
|
{
|
||||||
|
std::string baseName;
|
||||||
|
for (int i = 1; ; ++i) {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "saved_interactive%03d", i);
|
||||||
|
baseName = buf;
|
||||||
|
if (!std::filesystem::exists(baseName + ".json")) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json j;
|
||||||
|
j["objects"] = json::array();
|
||||||
|
|
||||||
|
for (const auto& obj : loc.interactiveObjects) {
|
||||||
|
json item;
|
||||||
|
item["name"] = obj.loadedObject.name;
|
||||||
|
item["texturePath"] = obj.loadedObject.texturePath;
|
||||||
|
if (!obj.loadedObject.textureDarkandsPath.empty())
|
||||||
|
item["textureDarkandsPath"] = obj.loadedObject.textureDarkandsPath;
|
||||||
|
item["meshPath"] = obj.loadedObject.meshPath;
|
||||||
|
item["rotationX"] = obj.loadedObject.meshRotationX;
|
||||||
|
item["rotationY"] = obj.loadedObject.meshRotationY;
|
||||||
|
item["rotationZ"] = obj.loadedObject.meshRotationZ;
|
||||||
|
item["positionX"] = obj.jsonPositionX;
|
||||||
|
item["positionY"] = obj.jsonPositionY;
|
||||||
|
item["positionZ"] = obj.jsonPositionZ;
|
||||||
|
item["scale"] = obj.loadedObject.meshScale;
|
||||||
|
item["interactionRadius"] = obj.interactionRadius;
|
||||||
|
item["approachRadius"] = obj.approachRadius;
|
||||||
|
if (!obj.activateFunctionName.empty())
|
||||||
|
item["activateFunction"] = obj.activateFunctionName;
|
||||||
|
item["pivotX"] = obj.pivot.x();
|
||||||
|
item["pivotY"] = obj.pivot.y();
|
||||||
|
item["pivotZ"] = obj.pivot.z();
|
||||||
|
item["boundsMinX"] = obj.boundsMin.x();
|
||||||
|
item["boundsMinY"] = obj.boundsMin.y();
|
||||||
|
item["boundsMinZ"] = obj.boundsMin.z();
|
||||||
|
item["boundsMaxX"] = obj.boundsMax.x();
|
||||||
|
item["boundsMaxY"] = obj.boundsMax.y();
|
||||||
|
item["boundsMaxZ"] = obj.boundsMax.z();
|
||||||
|
if (obj.hasInteractionPosition) {
|
||||||
|
item["interactionPositionX"] = obj.interactionPosition.x();
|
||||||
|
item["interactionPositionY"] = obj.interactionPosition.y();
|
||||||
|
item["interactionPositionZ"] = obj.interactionPosition.z();
|
||||||
|
}
|
||||||
|
j["objects"].push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filename = baseName + ".json";
|
||||||
|
std::ofstream out(filename);
|
||||||
|
if (out.is_open()) {
|
||||||
|
out << j.dump(4);
|
||||||
|
std::cout << "[IO_EDITOR] Saved " << loc.interactiveObjects.size()
|
||||||
|
<< " interactive object(s) to " << filename << "\n";
|
||||||
|
} else {
|
||||||
|
std::cerr << "[IO_EDITOR] Failed to open " << filename << " for writing\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::saveAll()
|
||||||
|
{
|
||||||
|
save();
|
||||||
|
saveInteractiveObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::placeTree()
|
||||||
|
{
|
||||||
|
if (!loc.player) return;
|
||||||
|
|
||||||
|
static std::mt19937 rng(std::random_device{}());
|
||||||
|
std::uniform_real_distribution<float> scaleDist(0.8f, 1.2f);
|
||||||
|
std::uniform_real_distribution<float> rotDist(0.0f, 360.0f);
|
||||||
|
|
||||||
|
GameObjectData data;
|
||||||
|
data.name = "editor_tree_" + std::to_string(++editorPlacedObjectCounter);
|
||||||
|
data.texturePath = "resources/w/exterior/tree001.png";
|
||||||
|
data.meshPath = "resources/w/exterior/tree003.txt";
|
||||||
|
data.rotationX = 0.0f;
|
||||||
|
data.rotationY = rotDist(rng);
|
||||||
|
data.rotationZ = 0.0f;
|
||||||
|
data.positionX = loc.player->position.x();
|
||||||
|
data.positionY = loc.player->position.y();
|
||||||
|
data.positionZ = loc.player->position.z();
|
||||||
|
data.scale = scaleDist(rng);
|
||||||
|
|
||||||
|
LoadedGameObject obj = GameObjectLoader::buildLoadedObject(data, loc.renderer, CONST_ZIP_FILE);
|
||||||
|
obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });
|
||||||
|
obj.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
loc.gameObjects[data.name] = std::move(obj);
|
||||||
|
editorPlacedObjects.push_back(data);
|
||||||
|
|
||||||
|
std::cout << "[GAME_EDITOR] Placed '" << data.name << "' at ("
|
||||||
|
<< data.positionX << ", " << data.positionZ
|
||||||
|
<< ") scale=" << data.scale << " rotY=" << data.rotationY << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationEditor::saveObjects()
|
||||||
|
{
|
||||||
|
if (editorPlacedObjects.empty()) {
|
||||||
|
std::cout << "[GAME_EDITOR] No editor-placed objects to save\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string baseName;
|
||||||
|
for (int i = 1; ; ++i) {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "saved_objects%03d", i);
|
||||||
|
baseName = buf;
|
||||||
|
if (!std::filesystem::exists(baseName + ".json")) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json j;
|
||||||
|
j["objects"] = json::array();
|
||||||
|
for (const auto& d : editorPlacedObjects) {
|
||||||
|
json obj;
|
||||||
|
obj["name"] = d.name;
|
||||||
|
obj["texturePath"] = d.texturePath;
|
||||||
|
obj["meshPath"] = d.meshPath;
|
||||||
|
obj["rotationX"] = d.rotationX;
|
||||||
|
obj["rotationY"] = d.rotationY;
|
||||||
|
obj["rotationZ"] = d.rotationZ;
|
||||||
|
obj["positionX"] = d.positionX;
|
||||||
|
obj["positionY"] = d.positionY;
|
||||||
|
obj["positionZ"] = d.positionZ;
|
||||||
|
obj["scale"] = d.scale;
|
||||||
|
j["objects"].push_back(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filename = baseName + ".json";
|
||||||
|
std::ofstream out(filename);
|
||||||
|
if (out.is_open()) {
|
||||||
|
out << j.dump(4);
|
||||||
|
std::cout << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
|
||||||
|
<< " object(s) to " << filename << "\n";
|
||||||
|
} else {
|
||||||
|
std::cerr << "[GAME_EDITOR] Failed to open " << filename << " for writing\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
70
src/LocationEditor.h
Normal file
70
src/LocationEditor.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
#include "navigation/PathFinder.h"
|
||||||
|
#include "items/GameObjectLoader.h"
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class Location; // forward declaration — LocationEditor.cpp includes Location.h
|
||||||
|
|
||||||
|
enum class EditorMode {
|
||||||
|
None,
|
||||||
|
Navigation,
|
||||||
|
InteractiveObjects
|
||||||
|
// GameObjects // future
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocationEditor {
|
||||||
|
public:
|
||||||
|
explicit LocationEditor(Location& location);
|
||||||
|
|
||||||
|
// Navigation editor state
|
||||||
|
std::vector<Eigen::Vector3f> navigationEditorPoints;
|
||||||
|
VertexRenderStruct navigationEditorPointsMesh;
|
||||||
|
std::vector<VertexRenderStruct> navigationEditorNavMeshes;
|
||||||
|
int navigationEditorObstacleCounter = 0;
|
||||||
|
|
||||||
|
// Interactive object editor state
|
||||||
|
std::vector<VertexRenderStruct> interactiveObjectBoundsMeshes;
|
||||||
|
std::vector<VertexRenderStruct> interactionPositionMeshes;
|
||||||
|
int selectedInteractiveObjectIndex = 0;
|
||||||
|
int boundsClickCount = 0; // 0 = waiting for first corner, 1 = waiting for second
|
||||||
|
Eigen::Vector3f boundsClickA = Eigen::Vector3f::Zero();
|
||||||
|
|
||||||
|
// Game object editor state
|
||||||
|
std::vector<GameObjectData> editorPlacedObjects;
|
||||||
|
int editorPlacedObjectCounter = 0;
|
||||||
|
|
||||||
|
// Navigation editor methods
|
||||||
|
void buildNavMeshes();
|
||||||
|
void drawNavigation();
|
||||||
|
void rebuildPointsMesh();
|
||||||
|
void drawPoints();
|
||||||
|
void handleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld);
|
||||||
|
void handleRightClick();
|
||||||
|
void save();
|
||||||
|
void reload();
|
||||||
|
|
||||||
|
// Interactive object editor methods
|
||||||
|
void buildInteractiveObjectBoundsMeshes();
|
||||||
|
void drawInteractiveObjectBounds();
|
||||||
|
void selectInteractiveObject(int index);
|
||||||
|
void handleInteractiveObjectClick(const Eigen::Vector3f& worldHit, bool ctrlHeld);
|
||||||
|
void saveInteractiveObjects();
|
||||||
|
|
||||||
|
// Save all editor data at once (nav mesh + interactive objects)
|
||||||
|
void saveAll();
|
||||||
|
|
||||||
|
// Game object editor methods
|
||||||
|
void placeTree();
|
||||||
|
void saveObjects();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Location& loc;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
@ -93,10 +93,23 @@ namespace ZL {
|
|||||||
InteractiveObjectData data;
|
InteractiveObjectData data;
|
||||||
data.base = parseGameObjectData(item);
|
data.base = parseGameObjectData(item);
|
||||||
data.interactionRadius = item.value("interactionRadius", 2.0f);
|
data.interactionRadius = item.value("interactionRadius", 2.0f);
|
||||||
|
data.approachRadius = item.value("approachRadius", data.interactionRadius);
|
||||||
data.activateFunctionName = item.value("activateFunction", "");
|
data.activateFunctionName = item.value("activateFunction", "");
|
||||||
data.pivotX = item.value("pivotX", 0.0f);
|
data.pivotX = item.value("pivotX", 0.0f);
|
||||||
data.pivotY = item.value("pivotY", 0.0f);
|
data.pivotY = item.value("pivotY", 0.0f);
|
||||||
data.pivotZ = item.value("pivotZ", 0.0f);
|
data.pivotZ = item.value("pivotZ", 0.0f);
|
||||||
|
data.boundsMinX = item.value("boundsMinX", 0.0f);
|
||||||
|
data.boundsMinY = item.value("boundsMinY", 0.0f);
|
||||||
|
data.boundsMinZ = item.value("boundsMinZ", 0.0f);
|
||||||
|
data.boundsMaxX = item.value("boundsMaxX", 0.0f);
|
||||||
|
data.boundsMaxY = item.value("boundsMaxY", 0.0f);
|
||||||
|
data.boundsMaxZ = item.value("boundsMaxZ", 0.0f);
|
||||||
|
if (item.contains("interactionPositionX")) {
|
||||||
|
data.hasInteractionPosition = true;
|
||||||
|
data.interactionPositionX = item.value("interactionPositionX", 0.0f);
|
||||||
|
data.interactionPositionY = item.value("interactionPositionY", 0.0f);
|
||||||
|
data.interactionPositionZ = item.value("interactionPositionZ", 0.0f);
|
||||||
|
}
|
||||||
if (!data.base.meshPath.empty())
|
if (!data.base.meshPath.empty())
|
||||||
objects.push_back(std::move(data));
|
objects.push_back(std::move(data));
|
||||||
}
|
}
|
||||||
@ -182,8 +195,26 @@ namespace ZL {
|
|||||||
InteractiveObject intObj;
|
InteractiveObject intObj;
|
||||||
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
|
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
|
||||||
intObj.interactionRadius = data.interactionRadius;
|
intObj.interactionRadius = data.interactionRadius;
|
||||||
|
intObj.approachRadius = data.approachRadius;
|
||||||
intObj.activateFunctionName = data.activateFunctionName;
|
intObj.activateFunctionName = data.activateFunctionName;
|
||||||
intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ);
|
intObj.pivot = Eigen::Vector3f(data.pivotX, data.pivotY, data.pivotZ);
|
||||||
|
intObj.boundsMin = Eigen::Vector3f(data.boundsMinX, data.boundsMinY, data.boundsMinZ);
|
||||||
|
intObj.boundsMax = Eigen::Vector3f(data.boundsMaxX, data.boundsMaxY, data.boundsMaxZ);
|
||||||
|
intObj.hasInteractionPosition = data.hasInteractionPosition;
|
||||||
|
if (data.hasInteractionPosition)
|
||||||
|
intObj.interactionPosition = Eigen::Vector3f(data.interactionPositionX, data.interactionPositionY, data.interactionPositionZ);
|
||||||
|
|
||||||
|
// Store source data needed for serialization.
|
||||||
|
intObj.loadedObject.texturePath = data.base.texturePath;
|
||||||
|
intObj.loadedObject.textureDarkandsPath = data.base.textureDarkandsPath;
|
||||||
|
intObj.loadedObject.meshPath = data.base.meshPath;
|
||||||
|
intObj.loadedObject.meshRotationX = data.base.rotationX;
|
||||||
|
intObj.loadedObject.meshRotationY = data.base.rotationY;
|
||||||
|
intObj.loadedObject.meshRotationZ = data.base.rotationZ;
|
||||||
|
intObj.loadedObject.meshScale = data.base.scale;
|
||||||
|
intObj.jsonPositionX = data.base.positionX;
|
||||||
|
intObj.jsonPositionY = data.base.positionY;
|
||||||
|
intObj.jsonPositionZ = data.base.positionZ;
|
||||||
|
|
||||||
intObj.loadedObject.mesh.RefreshVBO();
|
intObj.loadedObject.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
|||||||
@ -29,10 +29,15 @@ namespace ZL {
|
|||||||
struct InteractiveObjectData {
|
struct InteractiveObjectData {
|
||||||
GameObjectData base;
|
GameObjectData base;
|
||||||
float interactionRadius = 2.0f;
|
float interactionRadius = 2.0f;
|
||||||
|
float approachRadius = 2.0f;
|
||||||
std::string activateFunctionName;
|
std::string activateFunctionName;
|
||||||
float pivotX = 0.0f;
|
float pivotX = 0.0f;
|
||||||
float pivotY = 0.0f;
|
float pivotY = 0.0f;
|
||||||
float pivotZ = 0.0f;
|
float pivotZ = 0.0f;
|
||||||
|
float boundsMinX = 0.0f, boundsMinY = 0.0f, boundsMinZ = 0.0f;
|
||||||
|
float boundsMaxX = 0.0f, boundsMaxY = 0.0f, boundsMaxZ = 0.0f;
|
||||||
|
bool hasInteractionPosition = false;
|
||||||
|
float interactionPositionX = 0.0f, interactionPositionY = 0.0f, interactionPositionZ = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NpcData {
|
struct NpcData {
|
||||||
|
|||||||
@ -16,6 +16,12 @@ namespace ZL {
|
|||||||
std::shared_ptr<Texture> textureDarklands;
|
std::shared_ptr<Texture> textureDarklands;
|
||||||
VertexRenderStruct mesh;
|
VertexRenderStruct mesh;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
// Source paths and baked transform — populated by GameObjectLoader, used for serialization.
|
||||||
|
std::string texturePath;
|
||||||
|
std::string textureDarkandsPath;
|
||||||
|
std::string meshPath;
|
||||||
|
float meshRotationX = 0.f, meshRotationY = 0.f, meshRotationZ = 0.f;
|
||||||
|
float meshScale = 1.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InteractiveObject {
|
struct InteractiveObject {
|
||||||
@ -26,6 +32,14 @@ namespace ZL {
|
|||||||
float scale = 1.0f; // uniform scale
|
float scale = 1.0f; // uniform scale
|
||||||
float alpha = 1.0f; // opacity: 1=opaque, 0=fully transparent
|
float alpha = 1.0f; // opacity: 1=opaque, 0=fully transparent
|
||||||
float interactionRadius;
|
float interactionRadius;
|
||||||
|
float approachRadius = 2.0f;
|
||||||
|
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
|
||||||
|
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
|
||||||
|
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero();
|
||||||
|
// Original JSON position before mesh-centering offset applied at load time — needed for serialization.
|
||||||
|
float jsonPositionX = 0.f, jsonPositionY = 0.f, jsonPositionZ = 0.f;
|
||||||
|
|
||||||
|
bool hasInteractionPosition = false;
|
||||||
bool isActive = true;
|
bool isActive = true;
|
||||||
bool isAnimating = false; // true while a timed animation is running
|
bool isAnimating = false; // true while a timed animation is running
|
||||||
std::string activateFunctionName;
|
std::string activateFunctionName;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user