Added editor functionality to edit bound boxes

This commit is contained in:
Vladislav Khorev committed 2026-06-02 12:11:37 +03:00
1 parent 22b0e1992f
commit 4b868f47c2
12 files changed
+779 -307

No files matched your search

+82 -263
View File
@@ -4,11 +4,8 @@
#include <iostream>
#include "render/TextureManager.h"
#include "TextModel.h"
#include <random>
#include <cmath>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <cfloat>
@@ -47,8 +44,8 @@ namespace ZL
Location::Location(Renderer& iRenderer, Inventory& iInventory)
: renderer(iRenderer)
, inventory(iInventory)
, editor(*this)
{
}
void Location::setup(const LocationSetup& params)
@@ -303,8 +300,8 @@ namespace ZL
activeNavigationIndex = 0;
navigation = navigationMaps.empty() ? nullptr : &navigationMaps[0];
if (navigationEditorMode && navigation) {
navigationEditorBuildNavMeshes();
if (editorMode == EditorMode::Navigation && navigation) {
editor.buildNavMeshes();
}
static constexpr float kDynamicObstacleInfluenceDist = 6.0f;
@@ -365,237 +362,13 @@ namespace ZL
if (npc) npc->forceReplan();
}
if (navigationEditorMode) {
navigationEditorBuildNavMeshes();
if (editorMode == EditorMode::Navigation) {
editor.buildNavMeshes();
}
std::cout << "[NAV] Switched to navigation map " << index << "\n";
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) {
if (interactiveObjects.empty()) {
//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() << ", "
// << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
Eigen::Vector3f toObject = intObj.position - rayOrigin;
//std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
const bool hasBox = !(intObj.boundsMin.isZero() && intObj.boundsMax.isZero());
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);
//std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
float distanceAlongRay = toObject.dot(rayDir);
if (distanceAlongRay < 0.1f) {
//std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
continue;
}
if (distanceAlongRay < 0.1f) {
continue;
}
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
float distToObject = (closestPointOnRay - intObj.position).norm();
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
float distToObject = (closestPointOnRay - intObj.position).norm();
//std::cout << "[RAYCAST] Distance to object: " << distToObject
// << " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
//std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
closestDistance = distanceAlongRay;
closestObject = &intObj;
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
closestDistance = distanceAlongRay;
closestObject = &intObj;
}
}
}
/*
@@ -780,9 +575,12 @@ namespace ZL
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (navigationEditorMode) {
navigationEditorDrawNavigation();
navigationEditorDrawPoints();
if (editorMode == EditorMode::Navigation) {
editor.drawNavigation();
editor.drawPoints();
}
if (editorMode == EditorMode::InteractiveObjects) {
editor.drawInteractiveObjectBounds();
}
renderer.PopMatrix();
@@ -979,9 +777,12 @@ namespace ZL
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
#endif
if (navigationEditorMode) {
navigationEditorDrawNavigation();
navigationEditorDrawPoints();
if (editorMode == EditorMode::Navigation) {
editor.drawNavigation();
editor.drawPoints();
}
if (editorMode == EditorMode::InteractiveObjects) {
editor.drawInteractiveObjectBounds();
}
CheckGlError(__FILE__, __LINE__);
@@ -1050,9 +851,12 @@ namespace ZL
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
if (navigationEditorMode) {
navigationEditorDrawNavigation();
navigationEditorDrawPoints();
if (editorMode == EditorMode::Navigation) {
editor.drawNavigation();
editor.drawPoints();
}
if (editorMode == EditorMode::InteractiveObjects) {
editor.drawInteractiveObjectBounds();
}
renderer.PopMatrix();
@@ -1323,10 +1127,13 @@ namespace ZL
// Check if player reached target interactive object
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 (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
if (distToObject <= targetInteractiveObject->approachRadius) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << 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();
if (navigationEditorMode) {
if (editorMode == EditorMode::Navigation) {
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;
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;
}
@@ -1435,7 +1252,9 @@ namespace ZL
targetInteractNpc = nullptr;
targetInteractNpcIndex = -1;
targetTeleportZone = nullptr;
player->setTarget(clickedObject->position);
player->setTarget(clickedObject->hasInteractionPosition
? clickedObject->interactionPosition
: clickedObject->position);
player->attackTarget = nullptr;
std::cout << "[CLICK] Player moving to object..." << std::endl;
}