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
@@ -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
|
||||
Reference in new issue
Block a user