Added editor functionality to edit bound boxes
This commit is contained in:
1 parent
22b0e1992f
commit
4b868f47c2
12 files changed
+779
-307
No files matched your search
+82
-263
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user