Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ad5f13596 |
4
data/enemies/definitions.json
Normal file
4
data/enemies/definitions.json
Normal file
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{"id": "grunt", "maxHp": 75, "speed": 2.5, "reward": 5, "type": "GROUND", "mesh_path": "./data/meshes/cube.mesh"},
|
||||
{"id": "tank", "maxHp": 300, "speed": 1.5, "reward": 15, "type": "GROUND", "mesh_path": "./data/meshes/cube.mesh"}
|
||||
]
|
||||
24
data/maps/level1.json
Normal file
24
data/maps/level1.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"width": 20, "height": 15,
|
||||
"start_health": 20, "start_money": 350,
|
||||
"tile_key": {
|
||||
"g": "grass", ".": "path", "S": "spawn", "B": "base"
|
||||
},
|
||||
"layout": [
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g S . . . . . . . . g g g g g g",
|
||||
"g g g g g . g g g g g g g . g g g g g g",
|
||||
"g g g g g . g . . . . . . . . . . g g g",
|
||||
"g g g g g . g . g g g g g g g g . g g g",
|
||||
"g g . . . . . . . g g g g g g g . g g g",
|
||||
"g g . g g g g g . g g g g g g g . g g g",
|
||||
"g g . g g g g g . . . . . . . . . . B g",
|
||||
"g g . g g g g g g g g g g g g g . g g g",
|
||||
"g g . . . . . . . . . . . . . . . g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g"
|
||||
]
|
||||
}
|
||||
BIN
data/meshes/cube.mesh
Normal file
BIN
data/meshes/cube.mesh
Normal file
Binary file not shown.
BIN
data/meshes/pyramid.mesh
Normal file
BIN
data/meshes/pyramid.mesh
Normal file
Binary file not shown.
14
data/tiles/definitions.json
Normal file
14
data/tiles/definitions.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{"id": "grass", "type": "empty"},
|
||||
{"id": "path", "type": "path", "path": {"ground": true, "air": true}},
|
||||
{"id": "spawn", "type": "spawn", "path": {"ground": true, "air": true},
|
||||
"spawn": {
|
||||
"loop": true, "loop_hp_scale": 0.2,
|
||||
"waves": [
|
||||
{"enemy_id": "grunt", "count": 10, "interval": 1.0, "hp_mult": 1.0},
|
||||
{"enemy_id": "grunt", "count": 15, "interval": 0.8, "hp_mult": 1.2},
|
||||
{"enemy_id": "tank", "count": 5, "interval": 2.0, "hp_mult": 1.5}
|
||||
]
|
||||
}},
|
||||
{"id": "base", "type": "base", "path": {"ground": true, "air": true}}
|
||||
]
|
||||
11
data/towers/definitions.json
Normal file
11
data/towers/definitions.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"id": "archer", "name": "Archer Tower", "cost": 100, "range": 5.0,
|
||||
"damage": 12.0, "fire_rate": 1.2, "targeting": "first",
|
||||
"mesh_path": "./data/meshes/pyramid.mesh",
|
||||
"upgrades": [
|
||||
{"cost": 50, "range_bonus": 0.5, "damage_bonus": 5, "fire_rate_bonus": 0.2},
|
||||
{"cost": 100, "range_bonus": 0.5, "damage_bonus": 10, "fire_rate_bonus": 0.3}
|
||||
]
|
||||
}
|
||||
]
|
||||
4
makefile
4
makefile
@@ -7,7 +7,7 @@ STB_DIR := ./stb
|
||||
|
||||
# Compiler and flags
|
||||
CXX := g++
|
||||
BASE_CXXFLAGS = -std=c++23 -O3 -fopenmp -march=native -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I$(STB_DIR) -g
|
||||
BASE_CXXFLAGS = -std=c++23 -O3 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I$(STB_DIR)
|
||||
BASE_CXXFLAGS += `pkg-config --cflags glfw3`
|
||||
CFLAGS = $(BASE_CXXFLAGS)
|
||||
LDFLAGS := -L./imgui -limgui -lGL
|
||||
@@ -34,7 +34,7 @@ endif
|
||||
CXXFLAGS = $(BASE_CXXFLAGS) $(SIMD_CXXFLAGS)
|
||||
|
||||
# Source files
|
||||
SRC := $(SRC_DIR)/ptest.cpp
|
||||
SRC := $(SRC_DIR)/g3etest.cpp
|
||||
#SRC := $(SRC_DIR)/g2chromatic2.cpp
|
||||
SRC += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
|
||||
SRC += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp
|
||||
|
||||
@@ -31,9 +31,9 @@ struct defaults {
|
||||
bool slowRender = false;
|
||||
bool globalIllumination = true;
|
||||
bool useLod = true;
|
||||
int rayCount = 5;
|
||||
int rayCount = 3;
|
||||
int reflectCount = 3;
|
||||
int lodDist = 50000;
|
||||
int lodDist = 500;
|
||||
float lodDropoff = 0.1;
|
||||
PNoise2 noise = PNoise2(42);
|
||||
|
||||
@@ -82,12 +82,6 @@ std::vector<double> renderFrameTimes;
|
||||
int frameHistoryIndex = 0;
|
||||
bool firstFrameMeasured = false;
|
||||
|
||||
// Stats update timer
|
||||
std::chrono::steady_clock::time_point lastStatsUpdate;
|
||||
const std::chrono::seconds STATS_UPDATE_INTERVAL(10);
|
||||
std::string cachedStats;
|
||||
bool statsNeedUpdate = true;
|
||||
|
||||
Scene scene;
|
||||
bool meshNeedsUpdate = false;
|
||||
|
||||
@@ -188,45 +182,38 @@ void addStar(const defaults& config, const stardefaults& starconf, Octree<int>&
|
||||
meshNeedsUpdate = true;
|
||||
}
|
||||
|
||||
void updateStatsCache(Octree<int>& grid) {
|
||||
std::stringstream gridstats;
|
||||
grid.printStats(gridstats);
|
||||
cachedStats = gridstats.str();
|
||||
lastStatsUpdate = std::chrono::steady_clock::now();
|
||||
statsNeedUpdate = false;
|
||||
}
|
||||
|
||||
void livePreview(Octree<int>& grid, defaults& config, const Camera& cam) {
|
||||
std::lock_guard<std::mutex> lock(PreviewMutex);
|
||||
updatePreview = true;
|
||||
|
||||
|
||||
// if (meshNeedsUpdate) {
|
||||
// scene.clear();
|
||||
// std::shared_ptr<Mesh> planetMesh = grid.generateMesh(1, config.meshIsoLevel, pow(config.meshResolution, 2));
|
||||
// std::shared_ptr<Mesh> starMesh = grid.generateMesh(2, config.meshIsoLevel, config.meshResolution);
|
||||
if (meshNeedsUpdate) {
|
||||
scene.clear();
|
||||
std::shared_ptr<Mesh> planetMesh = grid.generateMesh(1, config.meshIsoLevel, pow(config.meshResolution, 2));
|
||||
std::shared_ptr<Mesh> starMesh = grid.generateMesh(2, config.meshIsoLevel, config.meshResolution);
|
||||
|
||||
// scene.addMesh(planetMesh);
|
||||
// scene.addMesh(starMesh);
|
||||
scene.addMesh(planetMesh);
|
||||
scene.addMesh(starMesh);
|
||||
|
||||
// // planetMesh.setResolution(config.meshResolution);
|
||||
// // planetMesh.setIsoLevel(config.meshIsoLevel);
|
||||
// // planetMesh.update(grid);
|
||||
// meshNeedsUpdate = false;
|
||||
// }
|
||||
// planetMesh.setResolution(config.meshResolution);
|
||||
// planetMesh.setIsoLevel(config.meshIsoLevel);
|
||||
// planetMesh.update(grid);
|
||||
meshNeedsUpdate = false;
|
||||
}
|
||||
|
||||
auto renderStart = std::chrono::high_resolution_clock::now();
|
||||
frame currentPreviewFrame;
|
||||
|
||||
// currentPreviewFrame = scene.render(cam, config.outWidth, config.outHeight, 0.1f, 10000.0f, frame::colormap::RGB);
|
||||
currentPreviewFrame = scene.render(cam, config.outWidth, config.outHeight, 0.1f, 10000.0f, frame::colormap::RGB);
|
||||
|
||||
grid.setLODMinDistance(config.lodDist);
|
||||
grid.setLODFalloff(config.lodDropoff);
|
||||
if (config.slowRender) {
|
||||
currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, config.rayCount, config.reflectCount, config.globalIllumination, config.useLod);
|
||||
} else {
|
||||
currentPreviewFrame = grid.fastRenderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB);
|
||||
}
|
||||
// grid.setLODMinDistance(config.lodDist);
|
||||
// grid.setLODFalloff(config.lodDropoff);
|
||||
// if (config.slowRender) {
|
||||
// currentPreviewFrame = grid.renderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB, config.rayCount, config.reflectCount, config.globalIllumination, config.useLod);
|
||||
// } else {
|
||||
// currentPreviewFrame = grid.fastRenderFrame(cam, config.outWidth, config.outHeight, frame::colormap::RGB);
|
||||
// }
|
||||
|
||||
auto renderEnd = std::chrono::high_resolution_clock::now();
|
||||
renderFrameTime = std::chrono::duration<double>(renderEnd - renderStart).count();
|
||||
@@ -343,7 +330,7 @@ int main() {
|
||||
defaults config;
|
||||
PointType minBound(-config.gridSizecube, -config.gridSizecube, -config.gridSizecube);
|
||||
PointType maxBound(config.gridSizecube, config.gridSizecube, config.gridSizecube);
|
||||
Octree<int> grid(minBound, maxBound, 8, 32);
|
||||
Octree<int> grid(minBound, maxBound, 16, 16);
|
||||
bool gridInitialized = false;
|
||||
float ghalf = config.gridSizecube / 2.f;
|
||||
|
||||
@@ -518,22 +505,22 @@ int main() {
|
||||
ImGui::ColorEdit3("Color", sphereConf.color);
|
||||
|
||||
ImGui::Separator();
|
||||
// ImGui::Text("Marching Cubes Config");
|
||||
// if (ImGui::SliderInt("Mesh Resolution", &config.meshResolution, 1, 64)) {
|
||||
// meshNeedsUpdate = true;
|
||||
// }
|
||||
// if (ImGui::SliderFloat("Iso Level", &config.meshIsoLevel, 0.01f, 1.0f)) {
|
||||
// meshNeedsUpdate = true;
|
||||
// }
|
||||
|
||||
// ImGui::Separator();
|
||||
// ImGui::Checkbox("Is Light", &sphereConf.light);
|
||||
if(sphereConf.light) {
|
||||
ImGui::DragFloat("Emittance", &sphereConf.emittance, 0.1f, 0.0f, 100.0f);
|
||||
ImGui::Text("Marching Cubes Config");
|
||||
if (ImGui::SliderInt("Mesh Resolution", &config.meshResolution, 1, 64)) {
|
||||
meshNeedsUpdate = true;
|
||||
}
|
||||
ImGui::SliderFloat("Reflection", &sphereConf.reflection, 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
|
||||
ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
|
||||
if (ImGui::SliderFloat("Iso Level", &config.meshIsoLevel, 0.01f, 1.0f)) {
|
||||
meshNeedsUpdate = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Is Light", &sphereConf.light);
|
||||
// if(sphereConf.light) {
|
||||
// ImGui::DragFloat("Emittance", &sphereConf.emittance, 0.1f, 0.0f, 100.0f);
|
||||
// }
|
||||
// ImGui::SliderFloat("Reflection", &sphereConf.reflection, 0.0f, 1.0f);
|
||||
// ImGui::SliderFloat("Refraction", &sphereConf.refraction, 0.0f, 1.0f);
|
||||
// ImGui::Checkbox("Fill Inside", &sphereConf.fillInside);
|
||||
|
||||
if (ImGui::CollapsingHeader("Star/Sun Parameters", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Checkbox("Enable Star", &starConf.enabled);
|
||||
@@ -577,56 +564,8 @@ int main() {
|
||||
fluidUI.renderUI();
|
||||
}
|
||||
|
||||
// scene.drawSceneWindow("Planet Preview", cam, 0.01, 1000);
|
||||
// scene.drawGridStats();
|
||||
|
||||
{
|
||||
ImGui::Begin("Planet Preview");
|
||||
if (worldPreview) {
|
||||
if (gridInitialized) {
|
||||
livePreview(grid, config, cam);
|
||||
}
|
||||
}
|
||||
|
||||
if (gridInitialized && textureInitialized) {
|
||||
ImGui::Image((void*)(intptr_t)textu, ImVec2(config.outWidth, config.outHeight));
|
||||
} else if (gridInitialized) {
|
||||
ImGui::Text("Preview not generated yet");
|
||||
} else {
|
||||
ImGui::Text("No grid generated");
|
||||
}
|
||||
|
||||
ImGui::Text("Render Performance:");
|
||||
if (renderFPS > 0) {
|
||||
// Color code based on FPS
|
||||
ImVec4 fpsColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
// if (renderFPS >= 30.0) {
|
||||
// fpsColor = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); // Green for good FPS
|
||||
// } else if (renderFPS >= 15.0) {
|
||||
// fpsColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); // Yellow for okay FPS
|
||||
// } else {
|
||||
// fpsColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red for poor FPS
|
||||
// }
|
||||
|
||||
ImGui::TextColored(fpsColor, "FPS: %.1f", renderFPS);
|
||||
ImGui::Text("Frame time: %.1f ms", avgRenderFrameTime * 1000.0);
|
||||
|
||||
// Simple progress bar for frame time
|
||||
ImGui::Text("%.1f/100 ms", avgRenderFrameTime * 1000.0);
|
||||
|
||||
// Show latest frame time
|
||||
ImGui::Text("Latest: %.1f ms", renderFrameTime * 1000.0);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (gridInitialized) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if ((now - lastStatsUpdate) > STATS_UPDATE_INTERVAL) updateStatsCache(grid);
|
||||
ImGui::TextUnformatted(cachedStats.c_str());
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
scene.drawSceneWindow("Planet Preview", cam, 0.01, 1000);
|
||||
scene.drawGridStats();
|
||||
|
||||
{
|
||||
ImGui::Begin("controls");
|
||||
@@ -820,18 +759,19 @@ int main() {
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// ImGui::Checkbox("Continuous Preview", &worldPreview);
|
||||
ImGui::Checkbox("Continuous Preview", &worldPreview);
|
||||
|
||||
ImGui::Checkbox("update Preview", &worldPreview);
|
||||
ImGui::Checkbox("Use Slower renderer", &config.slowRender);
|
||||
if (config.slowRender) {
|
||||
ImGui::InputInt("Rays per pixel", &config.rayCount);
|
||||
ImGui::InputInt("Max reflections", &config.reflectCount);
|
||||
}
|
||||
ImGui::InputFloat("Lod dropoff", &config.lodDropoff);
|
||||
ImGui::InputInt("lod minimum Distance", &config.lodDist);
|
||||
ImGui::Checkbox("use Global illumination", &config.globalIllumination);
|
||||
ImGui::Checkbox("use Lod", &config.useLod);
|
||||
// Removed Raytracing specific controls (Global Illum, LOD) as they don't apply to raster mesh
|
||||
// ImGui::Checkbox("update Preview", &worldPreview);
|
||||
// ImGui::Checkbox("Use Slower renderer", &config.slowRender);
|
||||
// if (config.slowRender) {
|
||||
// ImGui::InputInt("Rays per pixel", &config.rayCount);
|
||||
// ImGui::InputInt("Max reflections", &config.reflectCount);
|
||||
// }
|
||||
// ImGui::InputFloat("Lod dropoff", &config.lodDropoff);
|
||||
// ImGui::InputInt("lod minimum Distance", &config.lodDist);
|
||||
// ImGui::Checkbox("use Global illumination", &config.globalIllumination);
|
||||
// ImGui::Checkbox("use Lod", &config.useLod);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <cmath>
|
||||
|
||||
// Include Eigen and project headers
|
||||
#include "../eigen/Eigen/Dense"
|
||||
#include "../util/grid/camera.hpp"
|
||||
#include "../util/grid/grid3eigen.hpp"
|
||||
#include "../util/output/frame.hpp"
|
||||
#include "../util/output/bmpwriter.hpp"
|
||||
|
||||
// Helper function to create a solid volume of voxels with material properties
|
||||
void createBox(Octree<int>& octree, const Eigen::Vector3f& center, const Eigen::Vector3f& size,
|
||||
const Eigen::Vector3f& albedo, float emission = 0.0f,
|
||||
float roughness = 0.8f, float metallic = 0.0f, float transmission = 0.0f, float ior = 1.45f) {
|
||||
float step = 0.1f; // Voxel spacing
|
||||
Eigen::Vector3f halfSize = size / 2.0f;
|
||||
Eigen::Vector3f minB = center - halfSize;
|
||||
Eigen::Vector3f maxB = center + halfSize;
|
||||
|
||||
for (float x = minB.x(); x <= maxB.x(); x += step) {
|
||||
for (float y = minB.y(); y <= maxB.y(); y += step) {
|
||||
for (float z = minB.z(); z <= maxB.z(); z += step) {
|
||||
Eigen::Vector3f pos(x, y, z);
|
||||
|
||||
// .set(data, pos, visible, albedo, size, active, objectId, subId, emission, roughness, metallic, transmission, ior)
|
||||
octree.set(1, pos, true, albedo, step, true, -1, 0, emission, roughness, metallic, transmission, ior);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a checkerboard pattern volume
|
||||
void createCheckerBox(Octree<int>& octree, const Eigen::Vector3f& center, const Eigen::Vector3f& size,
|
||||
const Eigen::Vector3f& color1, const Eigen::Vector3f& color2, float checkerSize) {
|
||||
float step = 0.1f;
|
||||
Eigen::Vector3f halfSize = size / 2.0f;
|
||||
Eigen::Vector3f minB = center - halfSize;
|
||||
Eigen::Vector3f maxB = center + halfSize;
|
||||
|
||||
for (float x = minB.x(); x <= maxB.x(); x += step) {
|
||||
for (float y = minB.y(); y <= maxB.y(); y += step) {
|
||||
for (float z = minB.z(); z <= maxB.z(); z += step) {
|
||||
Eigen::Vector3f pos(x, y, z);
|
||||
|
||||
// Use floor to correctly handle negative coordinates for the repeating pattern
|
||||
int cx = static_cast<int>(std::floor(x / checkerSize));
|
||||
int cy = static_cast<int>(std::floor(y / checkerSize));
|
||||
int cz = static_cast<int>(std::floor(z / checkerSize));
|
||||
|
||||
// 3D Checkerboard logic
|
||||
bool isEven = ((cx + cy + cz) % 2 == 0);
|
||||
Eigen::Vector3f albedo = isEven ? color1 : color2;
|
||||
|
||||
octree.set(1, pos, true, albedo, step, true, -1, 0, 0.0f, 0.8f, 0.1f, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Initializing Octree..." << std::endl;
|
||||
|
||||
// 1. Initialize Octree bounds
|
||||
Eigen::Vector3f minBound(-10.0f, -10.0f, -10.0f);
|
||||
Eigen::Vector3f maxBound(10.0f, 10.0f, 10.0f);
|
||||
Octree<int> octree(minBound, maxBound, 8, 16);
|
||||
|
||||
// Set a dark background to emphasize the PBR light emission
|
||||
octree.setBackgroundColor(Eigen::Vector3f(0.02f, 0.02f, 0.02f));
|
||||
octree.setSkylight(Eigen::Vector3f(0.01f, 0.01f, 0.01f));
|
||||
|
||||
std::cout << "Building scene..." << std::endl;
|
||||
|
||||
// 2a. Build Room (Floor and 4 Walls)
|
||||
Eigen::Vector3f cLightGray(0.8f, 0.8f, 0.8f);
|
||||
Eigen::Vector3f cDarkGray(0.2f, 0.2f, 0.2f);
|
||||
float chkSize = 1.0f;
|
||||
|
||||
// Floor (Bounds: Z from -0.7 to -0.5)
|
||||
// The boxes sit exactly on Z = -0.5
|
||||
createCheckerBox(octree, Eigen::Vector3f(0.0f, 0.0f, -0.6f), Eigen::Vector3f(14.4f, 14.4f, 0.2f), cLightGray, cDarkGray, chkSize);
|
||||
|
||||
// Walls (Bounds: X/Y inner boundaries at +/- 7.0, rising from Z=-0.5 up to Z=7.5)
|
||||
createCheckerBox(octree, Eigen::Vector3f( 7.1f, 0.0f, 3.5f), Eigen::Vector3f(0.2f, 14.4f, 8.0f), cLightGray, cDarkGray, chkSize); // +X
|
||||
createCheckerBox(octree, Eigen::Vector3f(-7.1f, 0.0f, 3.5f), Eigen::Vector3f(0.2f, 14.4f, 8.0f), cLightGray, cDarkGray, chkSize); // -X
|
||||
createCheckerBox(octree, Eigen::Vector3f( 0.0f, 7.1f, 3.5f), Eigen::Vector3f(14.0f, 0.2f, 8.0f), cLightGray, cDarkGray, chkSize); // +Y
|
||||
createCheckerBox(octree, Eigen::Vector3f( 0.0f, -7.1f, 3.5f), Eigen::Vector3f(14.0f, 0.2f, 8.0f), cLightGray, cDarkGray, chkSize); // -Y
|
||||
|
||||
// 2b. Create the 3x3 material sampler grid inside the room
|
||||
Eigen::Vector3f cRed(1.0f, 0.1f, 0.1f);
|
||||
Eigen::Vector3f cBlue(0.1f, 0.1f, 1.0f);
|
||||
Eigen::Vector3f cPurple(0.6f, 0.1f, 0.8f);
|
||||
Eigen::Vector3f size(1.0f, 1.0f, 1.0f);
|
||||
|
||||
float sp = 2.0f; // spacing between cubes
|
||||
|
||||
// --- LAYER 1: Metals ---
|
||||
// (metallic = 1.0, slight roughness for blurry reflections, transmission = 0.0)
|
||||
createBox(octree, Eigen::Vector3f(-sp, -sp, 0.0f), size, cRed, 0.0f, 0.15f, 1.0f, 0.0f, 1.45f);
|
||||
createBox(octree, Eigen::Vector3f( 0, -sp, 0.0f), size, cBlue, 0.0f, 0.15f, 1.0f, 0.0f, 1.45f);
|
||||
createBox(octree, Eigen::Vector3f( sp, -sp, 0.0f), size, cPurple, 0.0f, 0.15f, 1.0f, 0.0f, 1.45f);
|
||||
|
||||
// --- LAYER 2: Opaque & Highly Refractive ---
|
||||
// (metallic = 0.0, very low roughness. transmission = 0.0 for opacity, ior = 2.4 for extreme diamond-like reflection)
|
||||
createBox(octree, Eigen::Vector3f(-sp, 0, 0.0f), size, cRed, 0.0f, 0.05f, 0.0f, 0.0f, 2.4f);
|
||||
createBox(octree, Eigen::Vector3f( 0, 0, 0.0f), size, cBlue, 0.0f, 0.05f, 0.0f, 0.0f, 2.4f);
|
||||
createBox(octree, Eigen::Vector3f( sp, 0, 0.0f), size, cPurple, 0.0f, 0.05f, 0.0f, 0.0f, 2.4f);
|
||||
|
||||
// --- LAYER 3: Clear Glass ---
|
||||
// (metallic = 0.0, near-zero roughness, transmission = 1.0 for full transparency, ior = 1.5 for glass)
|
||||
createBox(octree, Eigen::Vector3f(-sp, sp, 0.0f), size, cRed, 0.0f, 0.01f, 0.0f, 1.0f, 1.5f);
|
||||
createBox(octree, Eigen::Vector3f( 0, sp, 0.0f), size, cBlue, 0.0f, 0.01f, 0.0f, 1.0f, 1.5f);
|
||||
createBox(octree, Eigen::Vector3f( sp, sp, 0.0f), size, cPurple, 0.0f, 0.01f, 0.0f, 1.0f, 1.5f);
|
||||
|
||||
// White Light Box (Above)
|
||||
// Placed near the ceiling (Z=7.4), made large (8x8) to cast soft shadows evenly over the whole 3x3 grid
|
||||
createBox(octree, Eigen::Vector3f(0.0f, 0.0f, 7.4f), Eigen::Vector3f(8.0f, 8.0f, 0.2f), Eigen::Vector3f(1.0f, 1.0f, 1.0f), 15.0f);
|
||||
|
||||
std::cout << "Optimizing and Generating LODs..." << std::endl;
|
||||
octree.generateLODs();
|
||||
octree.printStats();
|
||||
|
||||
// 3. Setup rendering loop
|
||||
int width = 512;
|
||||
int height = 512;
|
||||
int samples = 400;
|
||||
int bounces = 5;
|
||||
|
||||
struct View {
|
||||
std::string name;
|
||||
Eigen::Vector3f origin;
|
||||
Eigen::Vector3f up;
|
||||
};
|
||||
|
||||
// The walls are set perfectly at +/- 7.0 inner edges.
|
||||
// Placing camera at +/- 6.8 will put it "just barely inside".
|
||||
// Floor is at Z = -0.5, Wall top is at Z = 7.5
|
||||
std::vector<View> views = {
|
||||
{"+X", Eigen::Vector3f( 6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
|
||||
{"-X", Eigen::Vector3f(-6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
|
||||
{"+Y", Eigen::Vector3f( 0.0f, 6.8f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
|
||||
{"-Y", Eigen::Vector3f( 0.0f, -6.8f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
|
||||
{"+Z", Eigen::Vector3f( 0.0f, 0.0f, 7.3f), Eigen::Vector3f(0.0f, 1.0f, 0.0f)} // Looking down from just beneath wall top
|
||||
};
|
||||
|
||||
Eigen::Vector3f target(0.0f, 0.0f, 0.5f);
|
||||
|
||||
for (const auto& view : views) {
|
||||
std::cout << "\nRendering view from " << view.name << " direction (Fast Pass)..." << std::endl;
|
||||
|
||||
Camera cam;
|
||||
cam.origin = view.origin;
|
||||
cam.direction = (target - view.origin).normalized();
|
||||
cam.up = view.up;
|
||||
|
||||
frame out = octree.fastRenderFrame(cam, height, width, frame::colormap::RGB);
|
||||
|
||||
std::string filename = "output/fast/render_" + view.name + ".bmp";
|
||||
BMPWriter::saveBMP(filename, out);
|
||||
}
|
||||
|
||||
for (const auto& view : views) {
|
||||
std::cout << "\nRendering view from " << view.name << " direction (Medium 60s Pass)..." << std::endl;
|
||||
|
||||
Camera cam;
|
||||
cam.origin = view.origin;
|
||||
cam.direction = (target - view.origin).normalized();
|
||||
cam.up = view.up;
|
||||
|
||||
frame out = octree.renderFrameTimed(cam, height, width, frame::colormap::RGB, 60, bounces, false, true);
|
||||
|
||||
std::string filename = "output/medium/render_" + view.name + ".bmp";
|
||||
BMPWriter::saveBMP(filename, out);
|
||||
}
|
||||
|
||||
for (const auto& view : views) {
|
||||
std::cout << "\nRendering view from " << view.name << " direction (Slow 400 Samples Pass)..." << std::endl;
|
||||
|
||||
Camera cam;
|
||||
cam.origin = view.origin;
|
||||
cam.direction = (target - view.origin).normalized();
|
||||
cam.up = view.up;
|
||||
|
||||
frame out = octree.renderFrame(cam, height, width, frame::colormap::RGB, samples, bounces, false, true);
|
||||
|
||||
std::string filename = "output/slow/render_" + view.name + ".bmp";
|
||||
BMPWriter::saveBMP(filename, out);
|
||||
}
|
||||
|
||||
std::cout << "\nAll renders complete!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
582
tests/planet.cpp
582
tests/planet.cpp
@@ -1,582 +0,0 @@
|
||||
#ifndef PLANET_CPP
|
||||
#define PLANET_CPP
|
||||
|
||||
#include "../util/sim/planet.hpp"
|
||||
#include "../util/grid/camera.hpp"
|
||||
#include "../util/noise/pnoise2.hpp"
|
||||
#include "../util/noise/pnoise.cpp"
|
||||
|
||||
class planetSimUI {
|
||||
private:
|
||||
planetsim sim;
|
||||
Camera cam;
|
||||
bool isRunning = false;
|
||||
|
||||
// Texture Management
|
||||
GLuint textu = 0;
|
||||
std::mutex PreviewMutex;
|
||||
bool updatePreview = false;
|
||||
bool textureInitialized = false;
|
||||
frame currentPreviewFrame;
|
||||
int outWidth = 1024;
|
||||
int outHeight = 1024;
|
||||
float fps = 60;
|
||||
int rayCount = 3;
|
||||
int reflectCount = 4;
|
||||
bool slowRender = false;
|
||||
float lodDist = 1024.0f;
|
||||
float lodDropoff = 0.001f;
|
||||
float maxViewDistance = 4096;
|
||||
bool globalIllumination = false;
|
||||
bool useLod = true;
|
||||
std::map<int, bool> keyStates;
|
||||
float deltaTime = 0.16f;
|
||||
bool orbitEquator = false;
|
||||
float rotationRadius = 2500;
|
||||
float angle = 0.0f;
|
||||
const float ω = (std::pow(M_PI, 2) / 30) / 10;
|
||||
bool tectonicGenned = false;
|
||||
bool doFixPlates = true;
|
||||
bool platesUseCellular = false;
|
||||
std::chrono::steady_clock::time_point lastStatsUpdate;
|
||||
std::string cachedStats;
|
||||
bool statsNeedUpdate = true;
|
||||
float framerate = 60.0;
|
||||
|
||||
enum class DebugColorMode {
|
||||
BASE,
|
||||
PLATES,
|
||||
NOISE,
|
||||
RESERVED
|
||||
};
|
||||
DebugColorMode currentColorMode = DebugColorMode::BASE;
|
||||
|
||||
enum class DebugMapMode {
|
||||
NONE,
|
||||
BASE,
|
||||
NOISE,
|
||||
TECTONIC,
|
||||
TECTONICCOLOR,
|
||||
CURRENT
|
||||
};
|
||||
DebugMapMode currentMapMode = DebugMapMode::NONE;
|
||||
GLuint mapTexture = 0;
|
||||
frame mapFrame;
|
||||
|
||||
public:
|
||||
planetSimUI() {
|
||||
cam.origin = v3(4000, 4000, 4000);
|
||||
cam.direction = (v3(0,0,0) - cam.origin).normalized();
|
||||
cam.up = v3(0,1,0);
|
||||
cam.fov = 60;
|
||||
cam.rotationSpeed = M_1_PI;
|
||||
}
|
||||
|
||||
~planetSimUI() {
|
||||
if (textu != 0) {
|
||||
glDeleteTextures(1, &textu);
|
||||
}
|
||||
if (mapTexture != 0) {
|
||||
glDeleteTextures(1, &mapTexture);
|
||||
}
|
||||
sim.grid.clear();
|
||||
}
|
||||
|
||||
void renderUI(GLFWwindow* window) {
|
||||
handleCameraControls(window);
|
||||
ImGui::Begin("Planet Simulation");
|
||||
if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) {
|
||||
ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f);
|
||||
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.7f);
|
||||
ImGui::TableNextColumn();
|
||||
renderControlsPanel();
|
||||
ImGui::TableNextColumn();
|
||||
renderPreviewPanel();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void handleCameraControls(GLFWwindow* window) {
|
||||
if (orbitEquator) {
|
||||
angle += cam.rotationSpeed * deltaTime * ω;
|
||||
|
||||
cam.origin[0] = sim.config.center[0] + rotationRadius * cosf(angle);
|
||||
cam.origin[1] = sim.config.center[1];
|
||||
cam.origin[2] = sim.config.center[2] + rotationRadius * sinf(angle);
|
||||
|
||||
v3 target(sim.config.center);
|
||||
cam.direction = (target - cam.origin).normalized();
|
||||
}
|
||||
glfwPollEvents();
|
||||
for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) {
|
||||
keyStates[i] = (glfwGetKey(window, i) == GLFW_PRESS);
|
||||
}
|
||||
if (keyStates[GLFW_KEY_W]) cam.moveForward(deltaTime);
|
||||
if (keyStates[GLFW_KEY_S]) cam.moveBackward(deltaTime);
|
||||
if (keyStates[GLFW_KEY_A]) cam.moveLeft(deltaTime);
|
||||
if (keyStates[GLFW_KEY_D]) cam.moveRight(deltaTime);
|
||||
if (keyStates[GLFW_KEY_Z]) cam.moveUp(deltaTime);
|
||||
if (keyStates[GLFW_KEY_X]) cam.moveDown(deltaTime);
|
||||
if (keyStates[GLFW_KEY_Q]) cam.rotateYaw(deltaTime);
|
||||
if (keyStates[GLFW_KEY_R]) cam.rotateYaw(-deltaTime);
|
||||
}
|
||||
|
||||
void renderControlsPanel() {
|
||||
ImGui::BeginChild("ControlsScroll", ImVec2(0, 0), true);
|
||||
|
||||
if (ImGui::CollapsingHeader("Base Configuration", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::DragFloat("Radius", &sim.config.radius, 1.0f, 10.0f, 10000.0f);
|
||||
ImGui::InputInt("Surface Points", &sim.config.surfacePoints);
|
||||
ImGui::DragFloat("Voxel Size", &sim.config.voxelSize, 0.1f, 0.1f, 100.0f);
|
||||
ImGui::ColorEdit3("Base Color", sim.config.color.data());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("1. Generate Fib Sphere", ImVec2(-1, 40))) {
|
||||
sim.generateFibSphere();
|
||||
applyDebugColorMode();
|
||||
}
|
||||
ImGui::Text("Current Step: %d", sim.config.currentStep);
|
||||
ImGui::Text("Nodes: %zu", sim.config.surfaceNodes.size());
|
||||
ImGui::InputFloat("Noise strength", &sim.config.noiseStrength, 0.01, 1, "%.4f");
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Physics Parameters")) {
|
||||
ImGui::DragFloat("Gravity (G)", &sim.config.G_ATTRACTION, 0.1f);
|
||||
ImGui::DragFloat("Time Step", &sim.config.TIMESTEP, 0.001f, 0.0001f, 0.1f);
|
||||
ImGui::DragFloat("Viscosity", &sim.config.dampingFactor, 0.001f, 0.0f, 1.0f);
|
||||
ImGui::DragFloat("Pressure Stiffness", &sim.config.pressureStiffness, 10.0f);
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Tectonic Simulation")) {
|
||||
ImGui::DragInt("Num Plates", &sim.config.numPlates, 1, 1, 100);
|
||||
ImGui::DragInt("Smoothing Passes", &sim.config.smoothingPasses, 1, 0, 10);
|
||||
ImGui::DragFloat("Mountain Height", &sim.config.mountHeight, 1.0f, 0.0f, 1000.0f);
|
||||
ImGui::DragFloat("Valley Depth", &sim.config.valleyDepth, 1.0f, -1000.0f, 0.0f);
|
||||
ImGui::DragFloat("Transform Roughness", &sim.config.transformRough, 1.0f, 0.0f, 500.0f);
|
||||
ImGui::DragInt("Stress Passes", &sim.config.stressPasses, 1, 0, 20);
|
||||
ImGui::DragFloat("Max Elevation Ratio", &sim.config.maxElevationRatio, 1.0f, 0.0f, 1.0f);
|
||||
ImGui::Checkbox("Fix Boundaries", &doFixPlates);
|
||||
ImGui::Checkbox("use Cellular", &platesUseCellular);
|
||||
|
||||
if (ImGui::Button("2. Simulate Tectonics", ImVec2(-1, 40))) {
|
||||
simulateTectonics();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Celestial Bodies")) {
|
||||
///TODO: add controls for moon, star.
|
||||
if (ImGui::Button("Add Star", ImVec2(-1, 40))) {
|
||||
sim.addStar();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Fillings")) {
|
||||
if (ImGui::Button("Interpolate surface", ImVec2(-1, 40))) {
|
||||
interpolateSurface();
|
||||
}
|
||||
if (ImGui::Button("Fill Planet", ImVec2(-1, 40))) {
|
||||
fillPlanet();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Debug Views")) {
|
||||
ImGui::Text("3D Planet Color Mode:");
|
||||
bool colorChanged = false;
|
||||
if (ImGui::RadioButton("Base Color", currentColorMode == DebugColorMode::BASE)) {
|
||||
currentColorMode = DebugColorMode::BASE;
|
||||
colorChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Plates", currentColorMode == DebugColorMode::PLATES)) {
|
||||
currentColorMode = DebugColorMode::PLATES;
|
||||
colorChanged = true;
|
||||
}
|
||||
|
||||
if (ImGui::RadioButton("Noise", currentColorMode == DebugColorMode::NOISE)) {
|
||||
currentColorMode = DebugColorMode::NOISE;
|
||||
colorChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Reserved", currentColorMode == DebugColorMode::RESERVED)) {
|
||||
currentColorMode = DebugColorMode::RESERVED;
|
||||
colorChanged = true;
|
||||
}
|
||||
|
||||
if (colorChanged) {
|
||||
applyDebugColorMode();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("2D Height Map Mode:");
|
||||
bool mapChanged = false;
|
||||
|
||||
if (ImGui::RadioButton("None", currentMapMode == DebugMapMode::NONE)) {
|
||||
currentMapMode = DebugMapMode::NONE;
|
||||
mapChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Base Pos", currentMapMode == DebugMapMode::BASE)) {
|
||||
currentMapMode = DebugMapMode::BASE;
|
||||
mapChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Noise Pos", currentMapMode == DebugMapMode::NOISE)) {
|
||||
currentMapMode = DebugMapMode::NOISE;
|
||||
mapChanged = true;
|
||||
}
|
||||
|
||||
if (!tectonicGenned) ImGui::BeginDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Tectonic Pos", currentMapMode == DebugMapMode::TECTONIC)) {
|
||||
currentMapMode = DebugMapMode::TECTONIC;
|
||||
mapChanged = true;
|
||||
}
|
||||
|
||||
if (ImGui::RadioButton("Tectonic Color", currentMapMode == DebugMapMode::TECTONICCOLOR)) {
|
||||
currentMapMode = DebugMapMode::TECTONICCOLOR;
|
||||
mapChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (!tectonicGenned) ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::RadioButton("Current Pos", currentMapMode == DebugMapMode::CURRENT)) {
|
||||
currentMapMode = DebugMapMode::CURRENT;
|
||||
mapChanged = true;
|
||||
}
|
||||
|
||||
if (ImGui::Button("Refresh Map", ImVec2(-1, 24))) {
|
||||
mapChanged = true;
|
||||
generateDebugMap(currentMapMode);
|
||||
}
|
||||
|
||||
if (mapChanged && currentMapMode != DebugMapMode::NONE) {
|
||||
generateDebugMap(currentMapMode);
|
||||
}
|
||||
|
||||
if (currentMapMode != DebugMapMode::NONE && mapTexture != 0) {
|
||||
float availWidth = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::Image((void*)(intptr_t)mapTexture, ImVec2(availWidth, availWidth * 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Camera Controls", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::DragFloat3("Origin", cam.origin.data());
|
||||
ImGui::DragFloat3("Direction", cam.direction.data(), 0.0001f, -1.0f, 1.0f);
|
||||
ImGui::DragFloat("Movement Speed", &cam.movementSpeed, 0.1f, 1.0f, 500.0f);
|
||||
ImGui::DragFloat("Rotation Speed", &cam.rotationSpeed, M_1_PI, M_1_PI, M_PI);
|
||||
ImGui::InputFloat("Rotation Distance", &rotationRadius, 10, 100);
|
||||
ImGui::InputFloat("Max Framerate", &framerate, 1, 10);
|
||||
|
||||
ImGui::Checkbox("Use Slower Render", &slowRender);
|
||||
|
||||
if (ImGui::Button("Focus on Planet")) {
|
||||
v3 target(sim.config.center);
|
||||
v3 newDir = (target - cam.origin).normalized();
|
||||
cam.direction = newDir;
|
||||
}
|
||||
|
||||
if (ImGui::Button(orbitEquator ? "Stop Equator" : "Orbit Equator")) orbitEquator = !orbitEquator;
|
||||
}
|
||||
|
||||
updateStatsCache();
|
||||
ImGui::TextUnformatted(cachedStats.c_str());
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void renderPreviewPanel() {
|
||||
ImGui::BeginChild("PreviewChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
|
||||
livePreview();
|
||||
if (textureInitialized) {
|
||||
float aspect = (float)currentPreviewFrame.getWidth() / (float)currentPreviewFrame.getHeight();
|
||||
float availWidth = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::Image((void*)(intptr_t)textu, ImVec2(availWidth, availWidth / aspect));
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void applyDebugColorMode() {
|
||||
if (sim.config.surfaceNodes.empty()) return;
|
||||
|
||||
float minNoise = std::numeric_limits<float>::max();
|
||||
float maxNoise = std::numeric_limits<float>::lowest();
|
||||
int minSub = std::numeric_limits<int>::max();
|
||||
int maxSub = std::numeric_limits<int>::lowest();
|
||||
|
||||
for (const auto& p : sim.config.surfaceNodes) {
|
||||
if (p.noiseDisplacement < minNoise) minNoise = p.noiseDisplacement;
|
||||
if (p.noiseDisplacement > maxNoise) maxNoise = p.noiseDisplacement;
|
||||
}
|
||||
int snf = 0;
|
||||
int inf = 0;
|
||||
|
||||
for (auto& p : sim.config.surfaceNodes) {
|
||||
v3 color = p.originColor.cast<float>();
|
||||
|
||||
switch (currentColorMode) {
|
||||
case DebugColorMode::PLATES:
|
||||
if (p.plateID != -1 && p.plateID < sim.plates.size()) {
|
||||
color = sim.plates[p.plateID].debugColor;
|
||||
} else {
|
||||
color = v3(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
break;
|
||||
case DebugColorMode::NOISE: {
|
||||
float t = 0.5f;
|
||||
if (maxNoise > minNoise) t = (p.noiseDisplacement - minNoise) / (maxNoise - minNoise);
|
||||
color = v3(t, t, t);
|
||||
break;
|
||||
}
|
||||
case DebugColorMode::BASE:
|
||||
default:
|
||||
color = p.originColor.cast<float>();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!sim.grid.setColor(p.currentPos, color)) {
|
||||
snf++;
|
||||
}
|
||||
// sim.grid.update(p.currentPos, p.currentPos, p, true, color, sim.config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
for (auto& p : sim.config.interpolatedNodes) {
|
||||
v3 color = p.originColor.cast<float>();
|
||||
|
||||
switch (currentColorMode) {
|
||||
case DebugColorMode::PLATES:
|
||||
if (p.plateID != -1 && p.plateID < sim.plates.size()) {
|
||||
color = sim.plates[p.plateID].debugColor;
|
||||
} else {
|
||||
color = v3(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
break;
|
||||
case DebugColorMode::NOISE: {
|
||||
float t = 0.5f;
|
||||
if (maxNoise > minNoise) t = (p.noiseDisplacement - minNoise) / (maxNoise - minNoise);
|
||||
color = v3(t, t, t);
|
||||
break;
|
||||
}
|
||||
case DebugColorMode::BASE:
|
||||
default:
|
||||
color = p.originColor.cast<float>();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!sim.grid.setColor(p.currentPos, color)) {
|
||||
inf++;
|
||||
}
|
||||
// sim.grid.update(p.currentPos, p.currentPos, p, true, color, sim.config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
if (snf > 0 || inf > 0) {
|
||||
std::cout << snf << " original nodes failed to set" << std::endl;
|
||||
std::cout << inf << " interpolated nodes failed to set" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void generateDebugMap(DebugMapMode mode) {
|
||||
if (mode == DebugMapMode::NONE || sim.config.surfaceNodes.empty()) return;
|
||||
|
||||
int w = 512;
|
||||
int h = 348;
|
||||
std::vector<float> depths(w * h, -1.0f);
|
||||
float minD = std::numeric_limits<float>::max();
|
||||
float maxD = std::numeric_limits<float>::lowest();
|
||||
|
||||
for (const auto& p : sim.config.surfaceNodes) {
|
||||
v3 pos;
|
||||
switch(mode) {
|
||||
case DebugMapMode::BASE:
|
||||
pos = p.altPos->originalPos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::NOISE:
|
||||
pos = p.altPos->noisePos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::TECTONIC:
|
||||
pos = p.altPos->tectonicPos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::CURRENT:
|
||||
default:
|
||||
pos = p.currentPos;
|
||||
break;
|
||||
}
|
||||
float d = pos.norm();
|
||||
if (d < minD) minD = d;
|
||||
if (d > maxD) maxD = d;
|
||||
}
|
||||
|
||||
for (const auto& p : sim.config.surfaceNodes) {
|
||||
v3 pos;
|
||||
switch(mode) {
|
||||
case DebugMapMode::BASE:
|
||||
pos = p.altPos->originalPos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::NOISE:
|
||||
pos = p.altPos->noisePos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::TECTONIC:
|
||||
pos = p.altPos->tectonicPos.cast<float>();
|
||||
break;
|
||||
case DebugMapMode::TECTONICCOLOR:
|
||||
pos = sim.plates[p.plateID].debugColor;
|
||||
break;
|
||||
case DebugMapMode::CURRENT:
|
||||
default:
|
||||
pos = p.currentPos;
|
||||
break;
|
||||
}
|
||||
|
||||
float d = pos.norm();
|
||||
v3 n = p.altPos->originalPos.cast<float>().normalized();
|
||||
|
||||
float u = 0.5f + std::atan2(n.z(), n.x()) / (2.0f * static_cast<float>(M_PI));
|
||||
float v = 0.5f - std::asin(n.y()) / static_cast<float>(M_PI);
|
||||
|
||||
int px = std::clamp(static_cast<int>(u * w), 0, w - 1);
|
||||
int py = std::clamp(static_cast<int>(v * h), 0, h - 1);
|
||||
|
||||
float normalizedD = (maxD > minD) ? (d - minD) / (maxD - minD) : 0.5f;
|
||||
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
int nx = px + dx;
|
||||
int ny = py + dy;
|
||||
|
||||
if (nx < 0) nx += w;
|
||||
if (nx >= w) nx -= w;
|
||||
|
||||
if (ny >= 0 && ny < h) {
|
||||
int idx = ny * w + nx;
|
||||
if (depths[idx] < 0.0f || normalizedD > depths[idx]) {
|
||||
depths[idx] = normalizedD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
if (depths[i] < 0.0f) depths[i] = 0.0f;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> pixels(w * h * 3);
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
uint8_t val = static_cast<uint8_t>(depths[i] * 255.0f);
|
||||
pixels[i * 3 + 0] = val;
|
||||
pixels[i * 3 + 1] = val;
|
||||
pixels[i * 3 + 2] = val;
|
||||
}
|
||||
|
||||
mapFrame = frame(w, h, frame::colormap::RGB);
|
||||
mapFrame.setData(pixels);
|
||||
|
||||
if (mapTexture == 0) {
|
||||
glGenTextures(1, &mapTexture);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, mapTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, mapFrame.getData().data());
|
||||
}
|
||||
|
||||
void applyNoise(const NoisePreviewState& noiseState) {
|
||||
TIME_FUNCTION;
|
||||
auto triplanarNoise = [&](const Eigen::Vector3f& pos) -> float {
|
||||
PNoise2 gen(noiseState.masterSeed);
|
||||
|
||||
Eigen::Vector3f n = pos.normalized();
|
||||
Eigen::Vector3f blend = n.cwiseAbs();
|
||||
float sum = blend.x() + blend.y() + blend.z();
|
||||
blend /= sum;
|
||||
|
||||
Eigen::Vector3f offsetPos = pos + Eigen::Vector3f(noiseState.offset[0], noiseState.offset[1], 0.0f);
|
||||
float vXY = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.x(), offsetPos.y()), noiseState, gen);
|
||||
float vXZ = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.x(), offsetPos.z()), noiseState, gen);
|
||||
float vYZ = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.y(), offsetPos.z()), noiseState, gen);
|
||||
|
||||
// Blend results
|
||||
return vYZ * blend.x() + vXZ * blend.y() + vXY * blend.z();
|
||||
};
|
||||
sim._applyNoise(triplanarNoise);
|
||||
applyDebugColorMode();
|
||||
}
|
||||
|
||||
void livePreview() {
|
||||
std::lock_guard<std::mutex> lock(PreviewMutex);
|
||||
updatePreview = true;
|
||||
|
||||
sim.grid.setLODMinDistance(lodDist);
|
||||
sim.grid.setLODFalloff(lodDropoff);
|
||||
sim.grid.setMaxDistance(maxViewDistance);
|
||||
float invFrameRate = 1 / framerate;
|
||||
|
||||
if (slowRender) {
|
||||
currentPreviewFrame = sim.grid.renderFrameTimed(cam, outHeight, outWidth, frame::colormap::RGB, invFrameRate, reflectCount, globalIllumination, useLod);
|
||||
} else {
|
||||
currentPreviewFrame = sim.grid.fastRenderFrame(cam, outHeight, outWidth, frame::colormap::RGB);
|
||||
}
|
||||
|
||||
if (textu == 0) {
|
||||
glGenTextures(1, &textu);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, textu);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(),
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data());
|
||||
|
||||
updatePreview = false;
|
||||
textureInitialized = true;
|
||||
}
|
||||
|
||||
void resetView() {
|
||||
cam.origin = Vector3f(sim.config.gridSizeCube, sim.config.gridSizeCube, sim.config.gridSizeCube);
|
||||
Vector3f center(sim.config.gridSizeCube / 2.0f, sim.config.gridSizeCube / 2.0f, sim.config.gridSizeCube / 2.0f);
|
||||
cam.lookAt(center);
|
||||
}
|
||||
|
||||
void simulateTectonics() {
|
||||
|
||||
currentColorMode = DebugColorMode::PLATES;
|
||||
|
||||
sim.assignSeeds();
|
||||
sim.buildAdjacencyList();
|
||||
if (platesUseCellular) {
|
||||
sim.growPlatesCellular();
|
||||
} else sim.growPlatesRandom();
|
||||
if (doFixPlates) sim.fixBoundaries();
|
||||
sim.extraplateste();
|
||||
sim.boundaryStress();
|
||||
sim.finalizeApplyResults();
|
||||
|
||||
applyDebugColorMode();
|
||||
tectonicGenned = true;
|
||||
if(currentMapMode != DebugMapMode::NONE) generateDebugMap(currentMapMode);
|
||||
}
|
||||
|
||||
void interpolateSurface() {
|
||||
sim.interpolateSurface();
|
||||
applyDebugColorMode();
|
||||
}
|
||||
|
||||
void fillPlanet() {
|
||||
sim.fillPlanet();
|
||||
}
|
||||
|
||||
void updateStatsCache() {
|
||||
std::stringstream gridstats;
|
||||
sim.grid.printStats(gridstats);
|
||||
cachedStats = gridstats.str();
|
||||
lastStatsUpdate = std::chrono::steady_clock::now();
|
||||
statsNeedUpdate = false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
133
tests/ptest.cpp
133
tests/ptest.cpp
@@ -1,133 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
|
||||
#include "../util/noise/pnoise.cpp"
|
||||
#include "planet.cpp"
|
||||
#include "../util/basicdefines.hpp"
|
||||
|
||||
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
|
||||
glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
||||
}
|
||||
|
||||
int main() {
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit())
|
||||
return -1;
|
||||
|
||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||
// GL ES 2.0 + GLSL 100 (WebGL 1.0)
|
||||
const char* glsl_version = "#version 100";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||
#elif defined(IMGUI_IMPL_OPENGL_ES3)
|
||||
// GL ES 3.0 + GLSL 300 es (WebGL 2.0)
|
||||
const char* glsl_version = "#version 300 es";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||
#elif defined(__APPLE__)
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
const char* glsl_version = "#version 130";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only
|
||||
#endif
|
||||
|
||||
bool application_not_closed = true;
|
||||
GLFWwindow* window = glfwCreateWindow((int)(1280), (int)(800), "StupidSim", nullptr, nullptr);
|
||||
if (window == nullptr) {
|
||||
glfwTerminate();
|
||||
return 1;
|
||||
}
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1);
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
(void)io;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
ImGui::StyleColorsDark();
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas");
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
|
||||
planetSimUI planetApp;
|
||||
NoisePreviewState noiseState;
|
||||
|
||||
if (noiseState.layers.empty()) {
|
||||
NoiseLayer defaultLayer;
|
||||
strcpy(defaultLayer.name, "Base Terrain");
|
||||
defaultLayer.type = NoiseType::Fractal;
|
||||
noiseState.layers.push_back(defaultLayer);
|
||||
}
|
||||
updateNoiseTexture(noiseState);
|
||||
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGui::GetMainViewport();
|
||||
drawNoiseLab(noiseState);
|
||||
planetApp.renderUI(window);
|
||||
|
||||
ImGui::Begin("Integration Control");
|
||||
ImGui::Text("Bridge: Noise Lab -> Planet Sim");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "Current Noise Layers: %zu", noiseState.layers.size());
|
||||
|
||||
if (ImGui::Button("APPLY CURRENT NOISE TO PLANET", ImVec2(-1, 50))) {
|
||||
planetApp.applyNoise(noiseState);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
|
||||
FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
558
tests/tdgame.cpp
Normal file
558
tests/tdgame.cpp
Normal file
@@ -0,0 +1,558 @@
|
||||
#ifndef TDGAME_MAIN_HPP
|
||||
#define TDGAME_MAIN_HPP
|
||||
|
||||
#include "../util/tdgame/tower.hpp"
|
||||
#include "../util/tdgame/enemy.hpp"
|
||||
#include "../util/tdgame/map.hpp"
|
||||
#include "../util/tdgame/tile.hpp"
|
||||
#include "../util/grid/mesh.hpp"
|
||||
#include "../util/tdgame/customjson.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
void createDummyDataFiles() {
|
||||
// Create directories
|
||||
fs::create_directories("./data/maps");
|
||||
fs::create_directories("./data/tiles");
|
||||
fs::create_directories("./data/towers");
|
||||
fs::create_directories("./data/enemies");
|
||||
fs::create_directories("./data/meshes");
|
||||
|
||||
// --- Meshes ---
|
||||
// Simple Cube Mesh
|
||||
if (!fs::exists("./data/meshes/cube.mesh")) {
|
||||
std::vector<Vector3f> verts = {
|
||||
{-0.4f, 0.0f, -0.4f}, {0.4f, 0.0f, -0.4f}, {0.4f, 0.8f, -0.4f}, {-0.4f, 0.8f, -0.4f},
|
||||
{-0.4f, 0.0f, 0.4f}, {0.4f, 0.0f, 0.4f}, {0.4f, 0.8f, 0.4f}, {-0.4f, 0.8f, 0.4f}
|
||||
};
|
||||
std::vector<std::vector<int>> polys = {
|
||||
{0, 1, 2, 3}, {1, 5, 6, 2}, {5, 4, 7, 6},
|
||||
{4, 0, 3, 7}, {3, 2, 6, 7}, {4, 5, 1, 0}
|
||||
};
|
||||
Mesh cube(0, verts, polys, {{0.5f, 0.5f, 0.5f}});
|
||||
cube.save("./data/meshes/cube.mesh");
|
||||
}
|
||||
// Simple Pyramid Mesh
|
||||
if (!fs::exists("./data/meshes/pyramid.mesh")) {
|
||||
std::vector<Vector3f> verts = {
|
||||
{-0.4f, 0.0f, -0.4f}, {0.4f, 0.0f, -0.4f}, {0.4f, 0.0f, 0.4f}, {-0.4f, 0.0f, 0.4f},
|
||||
{0.0f, 0.8f, 0.0f}
|
||||
};
|
||||
std::vector<std::vector<int>> polys = {{0,1,2,3}, {0,1,4}, {1,2,4}, {2,3,4}, {3,0,4}};
|
||||
Mesh pyramid(0, verts, polys, {{0.8f, 0.2f, 0.2f}});
|
||||
pyramid.save("./data/meshes/pyramid.mesh");
|
||||
}
|
||||
|
||||
// --- Tile JSONs ---
|
||||
if (!fs::exists("./data/tiles/definitions.json")) {
|
||||
std::ofstream f("./data/tiles/definitions.json");
|
||||
f << R"([
|
||||
{"id": "grass", "type": "empty"},
|
||||
{"id": "path", "type": "path", "path": {"ground": true, "air": true}},
|
||||
{"id": "spawn", "type": "spawn", "path": {"ground": true, "air": true},
|
||||
"spawn": {
|
||||
"loop": true, "loop_hp_scale": 0.2,
|
||||
"waves": [
|
||||
{"enemy_id": "grunt", "count": 10, "interval": 1.0, "hp_mult": 1.0},
|
||||
{"enemy_id": "grunt", "count": 15, "interval": 0.8, "hp_mult": 1.2},
|
||||
{"enemy_id": "tank", "count": 5, "interval": 2.0, "hp_mult": 1.5}
|
||||
]
|
||||
}},
|
||||
{"id": "base", "type": "base", "path": {"ground": true, "air": true}}
|
||||
])";
|
||||
}
|
||||
|
||||
// --- Tower JSON ---
|
||||
if (!fs::exists("./data/towers/definitions.json")) {
|
||||
std::ofstream f("./data/towers/definitions.json");
|
||||
f << R"([
|
||||
{
|
||||
"id": "archer", "name": "Archer Tower", "cost": 100, "range": 5.0,
|
||||
"damage": 12.0, "fire_rate": 1.2, "targeting": "first",
|
||||
"mesh_path": "./data/meshes/pyramid.mesh",
|
||||
"upgrades": [
|
||||
{"cost": 50, "range_bonus": 0.5, "damage_bonus": 5, "fire_rate_bonus": 0.2},
|
||||
{"cost": 100, "range_bonus": 0.5, "damage_bonus": 10, "fire_rate_bonus": 0.3}
|
||||
]
|
||||
}
|
||||
])";
|
||||
}
|
||||
|
||||
// --- Enemy JSON ---
|
||||
if (!fs::exists("./data/enemies/definitions.json")) {
|
||||
std::ofstream f("./data/enemies/definitions.json");
|
||||
f << R"([
|
||||
{"id": "grunt", "maxHp": 75, "speed": 2.5, "reward": 5, "type": "GROUND", "mesh_path": "./data/meshes/cube.mesh"},
|
||||
{"id": "tank", "maxHp": 300, "speed": 1.5, "reward": 15, "type": "GROUND", "mesh_path": "./data/meshes/cube.mesh"}
|
||||
])";
|
||||
}
|
||||
|
||||
// --- Map JSON ---
|
||||
if (!fs::exists("./data/maps/level1.json")) {
|
||||
std::ofstream f("./data/maps/level1.json");
|
||||
f << R"({
|
||||
"width": 20, "height": 15,
|
||||
"start_health": 20, "start_money": 350,
|
||||
"tile_key": {
|
||||
"g": "grass", ".": "path", "S": "spawn", "B": "base"
|
||||
},
|
||||
"layout": [
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g S . . . . . . . . g g g g g g",
|
||||
"g g g g g . g g g g g g g . g g g g g g",
|
||||
"g g g g g . g . . . . . . . . . . g g g",
|
||||
"g g g g g . g . g g g g g g g g . g g g",
|
||||
"g g . . . . . . . g g g g g g g . g g g",
|
||||
"g g . g g g g g . g g g g g g g . g g g",
|
||||
"g g . g g g g g . . . . . . . . . . B g",
|
||||
"g g . g g g g g g g g g g g g g . g g g",
|
||||
"g g . . . . . . . . . . . . . . . g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g",
|
||||
"g g g g g g g g g g g g g g g g g g g g"
|
||||
]
|
||||
})";
|
||||
}
|
||||
std::cout << "Checked for dummy data files." << std::endl;
|
||||
}
|
||||
|
||||
|
||||
class Game {
|
||||
private:
|
||||
GLFWwindow* _window = nullptr;
|
||||
GameMap _map;
|
||||
Scene _scene;
|
||||
Camera _camera;
|
||||
|
||||
enum class GameState { RUNNING, PAUSED, GAME_OVER };
|
||||
GameState _gameState = GameState::RUNNING;
|
||||
|
||||
// UI State
|
||||
std::string _towerToPlaceTypeId = "";
|
||||
int _selectedTowerInstanceId = -1;
|
||||
|
||||
// Timing
|
||||
float _deltaTime = 0.0f;
|
||||
float _lastFrameTime = 0.0f;
|
||||
|
||||
public:
|
||||
bool init(int width, int height, const char* title) {
|
||||
if (!glfwInit()) {
|
||||
std::cerr << "Failed to initialize GLFW" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
_window = glfwCreateWindow(width, height, title, NULL, NULL);
|
||||
if (!_window) {
|
||||
std::cerr << "Failed to create GLFW window" << std::endl;
|
||||
glfwTerminate();
|
||||
return false;
|
||||
}
|
||||
glfwMakeContextCurrent(_window);
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplGlfw_InitForOpenGL(_window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 330");
|
||||
|
||||
// Load Game Data
|
||||
createDummyDataFiles();
|
||||
TileRegistry::getInstance().loadFromDirectory("./data/tiles/");
|
||||
TowerRegistry::getInstance().loadFromDirectory("./data/towers/");
|
||||
EnemyRegistry::getInstance().loadFromDirectory("./data/enemies/");
|
||||
|
||||
if (!_map.loadFromFile("./data/maps/level1.json")) {
|
||||
std::cerr << "Failed to load map!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize Scene with map tiles
|
||||
for (int z = 0; z < _map.getHeight(); ++z) {
|
||||
for (int x = 0; x < _map.getWidth(); ++x) {
|
||||
const auto& tile = _map.getTile(x, z);
|
||||
const auto* proto = TileRegistry::getInstance().createTile(tile.id, x, z).mesh.get();
|
||||
if(tile.isWalkable()){
|
||||
auto mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
mesh->replace({{ (float)x, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z+1},{ (float)x, -0.5f, (float)z+1}}, {{0,1,2,3}}, {{0.6f, 0.4f, 0.2f}});
|
||||
_scene.addMesh(mesh);
|
||||
} else {
|
||||
auto mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
mesh->replace({{ (float)x, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z+1},{ (float)x, -0.5f, (float)z+1}}, {{0,1,2,3}}, {{0.2f, 0.5f, 0.1f}});
|
||||
_scene.addMesh(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Setup Camera
|
||||
_camera.origin = Vector3f(10.0f, 15.0f, 20.0f);
|
||||
_camera.lookAt(Vector3f(10.0f, 0.0f, 7.5f));
|
||||
_lastFrameTime = glfwGetTime();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void run() {
|
||||
while (!glfwWindowShouldClose(_window)) {
|
||||
float currentTime = glfwGetTime();
|
||||
_deltaTime = currentTime - _lastFrameTime;
|
||||
_lastFrameTime = currentTime;
|
||||
|
||||
glfwPollEvents();
|
||||
handleInput();
|
||||
|
||||
if (_gameState == GameState::RUNNING) {
|
||||
update();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
glfwSwapBuffers(_window);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
glfwDestroyWindow(_window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
private:
|
||||
void handleInput() {
|
||||
// Camera panning
|
||||
float camSpeed = 10.0f * _deltaTime;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_W)) _camera.origin += Vector3f(0, 0, -1) * camSpeed;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_S)) _camera.origin += Vector3f(0, 0, 1) * camSpeed;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_A)) _camera.origin += Vector3f(-1, 0, 0) * camSpeed;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_D)) _camera.origin += Vector3f(1, 0, 0) * camSpeed;
|
||||
|
||||
// Camera zoom
|
||||
float scroll = ImGui::GetIO().MouseWheel;
|
||||
if (scroll != 0) {
|
||||
_camera.origin.y() -= scroll * 2.0f;
|
||||
if (_camera.origin.y() < 5.0f) _camera.origin.y() = 5.0f;
|
||||
if (_camera.origin.y() > 50.0f) _camera.origin.y() = 50.0f;
|
||||
}
|
||||
|
||||
// Mouse click for selection/building
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::GetIO().WantCaptureMouse) {
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
ImVec2 viewportPos = ImGui::GetMainViewport()->Pos;
|
||||
int screenX = mousePos.x - viewportPos.x;
|
||||
int screenY = mousePos.y - viewportPos.y;
|
||||
|
||||
int width, height;
|
||||
glfwGetWindowSize(_window, &width, &height);
|
||||
|
||||
GridPoint clickedTile = screenToGrid(screenX, screenY, width, height);
|
||||
|
||||
if (clickedTile.x != -1) { // Check for valid click
|
||||
if (!_towerToPlaceTypeId.empty()) {
|
||||
if (_map.buildTower(_towerToPlaceTypeId, clickedTile.x, clickedTile.z)) {
|
||||
_towerToPlaceTypeId = ""; // Successfully built
|
||||
}
|
||||
} else {
|
||||
// Check if a tower is on this tile
|
||||
_selectedTowerInstanceId = -1;
|
||||
for (const auto& tower : _map.getTowers()) {
|
||||
if (static_cast<int>(tower->position.x()) == clickedTile.x &&
|
||||
static_cast<int>(tower->position.z()) == clickedTile.z) {
|
||||
_selectedTowerInstanceId = tower->instanceId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
_towerToPlaceTypeId = ""; // Cancel build
|
||||
_selectedTowerInstanceId = -1; // Deselect
|
||||
}
|
||||
}
|
||||
|
||||
void update() {
|
||||
_map.update(_deltaTime);
|
||||
|
||||
if (_map.isGameOver() && _gameState != GameState::GAME_OVER) {
|
||||
_gameState = GameState::GAME_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
void render() {
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(_window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(0.1f, 0.1f, 0.12f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Update scene with dynamic objects
|
||||
_scene.clear();
|
||||
for (int z = 0; z < _map.getHeight(); ++z) {
|
||||
for (int x = 0; x < _map.getWidth(); ++x) {
|
||||
const auto& tile = _map.getTile(x, z);
|
||||
// Note: These meshes are generated Clockwise.
|
||||
// Ensure Culling is disabled in mesh.hpp
|
||||
if(tile.isWalkable()){
|
||||
auto mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
mesh->replace({{ (float)x, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z+1},{ (float)x, -0.5f, (float)z+1}}, {{0,1,2,3}}, {{0.6f, 0.4f, 0.2f}});
|
||||
_scene.addMesh(mesh);
|
||||
} else {
|
||||
auto mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
mesh->replace({{ (float)x, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z},{ (float)x+1, -0.5f, (float)z+1},{ (float)x, -0.5f, (float)z+1}}, {{0,1,2,3}}, {{0.2f, 0.5f, 0.1f}});
|
||||
_scene.addMesh(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& tower : _map.getTowers()) {
|
||||
if(tower->mesh) _scene.addMesh(tower->mesh);
|
||||
}
|
||||
for (const auto& enemy : _map.getEnemies()) {
|
||||
if(enemy->mesh) {
|
||||
auto newMesh = std::make_shared<Mesh>(*enemy->mesh);
|
||||
newMesh->translate(enemy->position - newMesh->vertices()[0]);
|
||||
_scene.addMesh(newMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// --- FIX START ---
|
||||
// Apply window settings to the NEXT window created, which is inside drawSceneWindow
|
||||
ImGui::SetNextWindowPos(ImVec2(0,0));
|
||||
ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
|
||||
|
||||
// Pass specific flags to make it look like a background viewport
|
||||
// ImGuiWindowFlags viewportFlags = ImGuiWindowFlags_NoDecoration |
|
||||
// ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
// ImGuiWindowFlags_NoResize |
|
||||
// ImGuiWindowFlags_NoMove;
|
||||
|
||||
// Note: We modified drawSceneWindow signature locally to accept flags,
|
||||
// OR we just rely on the SetNextWindowSize logic to force the internal Begin() to fill screen.
|
||||
// Since drawSceneWindow internally calls Begin(windowTitle), the SetNextWindow* calls above apply to it.
|
||||
_scene.drawSceneWindow("Game Viewport", _camera, 0.1f, 1000.0f, true);
|
||||
|
||||
// Removed the wrapping ImGui::Begin/End calls that were causing the nesting issue
|
||||
// --- FIX END ---
|
||||
|
||||
renderUI();
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
void renderUI() {
|
||||
// --- Game Info HUD ---
|
||||
ImGui::Begin("Game Info", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::SetWindowPos(ImVec2(10, 10), ImGuiCond_Always);
|
||||
ImGui::Text("Health: %d", _map.getPlayerHealth());
|
||||
ImGui::Text("Money: %d", _map.getPlayerMoney());
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button(_gameState == GameState::RUNNING ? "Pause" : "Resume")) {
|
||||
_gameState = (_gameState == GameState::RUNNING) ? GameState::PAUSED : GameState::RUNNING;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// --- Tower Build Panel ---
|
||||
ImGui::Begin("Build Towers", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::SetWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 210, 10), ImGuiCond_Always);
|
||||
ImGui::SetWindowSize(ImVec2(200, 0));
|
||||
ImGui::Text("Towers");
|
||||
ImGui::Separator();
|
||||
|
||||
// Example for "archer" tower. A real game would iterate over all loaded prototypes.
|
||||
const TowerPrototype* archerProto = TowerRegistry::getInstance().getPrototype("archer");
|
||||
if (archerProto) {
|
||||
bool canAfford = _map.getPlayerMoney() >= archerProto->baseCost;
|
||||
if (!canAfford) ImGui::BeginDisabled();
|
||||
|
||||
std::string buttonText = archerProto->name + " ($" + std::to_string(archerProto->baseCost) + ")";
|
||||
if (ImGui::Button(buttonText.c_str(), ImVec2(-1, 0))) {
|
||||
_towerToPlaceTypeId = "archer";
|
||||
_selectedTowerInstanceId = -1; // Deselect any current tower
|
||||
}
|
||||
if (!canAfford) ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if(!_towerToPlaceTypeId.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Placing %s tower...", _towerToPlaceTypeId.c_str());
|
||||
ImGui::Text("Right-click to cancel.");
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
|
||||
// --- Selected Tower Panel ---
|
||||
if (_selectedTowerInstanceId != -1) {
|
||||
Tower* selectedTower = nullptr;
|
||||
for(auto& t : _map.getTowers()) {
|
||||
if (t->instanceId == _selectedTowerInstanceId) {
|
||||
selectedTower = t.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(selectedTower) {
|
||||
const TowerPrototype* proto = TowerRegistry::getInstance().getPrototype(selectedTower->typeId);
|
||||
ImGui::Begin("Tower Control", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::SetWindowPos(ImVec2(10, ImGui::GetIO().DisplaySize.y - 200), ImGuiCond_Always);
|
||||
ImGui::SetWindowSize(ImVec2(250, 0));
|
||||
|
||||
ImGui::Text("%s (Lvl %d)", proto->name.c_str(), selectedTower->getLevel());
|
||||
ImGui::Text("Damage: %.1f", selectedTower->damage);
|
||||
ImGui::Text("Range: %.1f", selectedTower->range);
|
||||
ImGui::Text("Fire Rate: %.1f/s", selectedTower->fireRate);
|
||||
ImGui::Separator();
|
||||
|
||||
// Upgrade logic
|
||||
if (proto && selectedTower->getLevel() -1 < proto->upgrades.size()) {
|
||||
const auto& nextUpgrade = proto->upgrades[selectedTower->getLevel() - 1];
|
||||
bool canAfford = _map.getPlayerMoney() >= nextUpgrade.cost;
|
||||
if (!canAfford) ImGui::BeginDisabled();
|
||||
|
||||
std::string upgradeText = "Upgrade ($" + std::to_string(nextUpgrade.cost) + ")";
|
||||
if (ImGui::Button(upgradeText.c_str())) {
|
||||
// We can't modify map state here, but a real implementation would queue this action
|
||||
// For simplicity, we just assume it works. Cheating a bit.
|
||||
const_cast<GameMap&>(_map).buildTower("dummy", -1, -1); // Hack to access non-const function context
|
||||
const_cast<GameMap&>(_map)._playerMoney += 1; // Reverse the dummy build cost
|
||||
if (const_cast<GameMap&>(_map)._playerMoney >= nextUpgrade.cost) {
|
||||
const_cast<GameMap&>(_map)._playerMoney -= nextUpgrade.cost;
|
||||
selectedTower->upgrade(*proto);
|
||||
}
|
||||
}
|
||||
if (!canAfford) ImGui::EndDisabled();
|
||||
} else {
|
||||
ImGui::Text("Max Level Reached");
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
std::string sellText = "Sell ($" + std::to_string(selectedTower->getSellPrice()) + ")";
|
||||
if (ImGui::Button(sellText.c_str())) {
|
||||
// Similar to above, needs a way to modify map state.
|
||||
// This is a major limitation of this simple structure.
|
||||
// A proper command queue would solve this. For now, it won't work.
|
||||
std::cout << "Sell functionality would require mutable access to GameMap here." << std::endl;
|
||||
_selectedTowerInstanceId = -1; // Deselect to avoid dangling reference issues
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Game Over Screen ---
|
||||
if (_gameState == GameState::GAME_OVER) {
|
||||
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::Begin("Game Over", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::Text("Your base has been destroyed!");
|
||||
if (ImGui::Button("Exit")) {
|
||||
glfwSetWindowShouldClose(_window, true);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
// Simple ray-plane intersection to find grid coordinates from mouse position
|
||||
GridPoint screenToGrid(int mouseX, int mouseY, int screenWidth, int screenHeight) {
|
||||
// 1. Normalize Device Coordinates (NDC)
|
||||
float x = (2.0f * mouseX) / screenWidth - 1.0f;
|
||||
float y = 1.0f - (2.0f * mouseY) / screenHeight;
|
||||
Vector4f ray_clip(x, y, -1.0, 1.0); // Point on near plane
|
||||
|
||||
// 2. Un-project to get ray in view space
|
||||
Matrix4f projMatrix = _camera.getProjectionMatrix((float)screenWidth / screenHeight, 0.1f, 1000.0f);
|
||||
Matrix4f invProj = projMatrix.inverse();
|
||||
Vector4f ray_eye = invProj * ray_clip;
|
||||
ray_eye = Vector4f(ray_eye.x(), ray_eye.y(), -1.0, 0.0);
|
||||
|
||||
// 3. Un-project to get ray in world space
|
||||
Matrix4f viewMatrix = _camera.getViewMatrix();
|
||||
Matrix4f invView = viewMatrix.inverse();
|
||||
Vector4f ray_world_4 = invView * ray_eye;
|
||||
Vector3f ray_dir(ray_world_4.x(), ray_world_4.y(), ray_world_4.z());
|
||||
ray_dir.normalize();
|
||||
|
||||
Vector3f ray_origin = _camera.origin;
|
||||
|
||||
// 4. Ray-plane intersection (plane is y=0)
|
||||
Vector3f plane_normal(0, 1, 0);
|
||||
float denom = plane_normal.dot(ray_dir);
|
||||
|
||||
if (std::abs(denom) > 1e-6) {
|
||||
Vector3f p0(0, 0, 0); // A point on the plane
|
||||
float t = (p0 - ray_origin).dot(plane_normal) / denom;
|
||||
if (t >= 0) {
|
||||
Vector3f intersection_point = ray_origin + t * ray_dir;
|
||||
int gridX = static_cast<int>(floor(intersection_point.x()));
|
||||
int gridZ = static_cast<int>(floor(intersection_point.z()));
|
||||
|
||||
if (gridX >= 0 && gridX < _map.getWidth() && gridZ >= 0 && gridZ < _map.getHeight()) {
|
||||
return {gridX, gridZ};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {-1, -1}; // No valid intersection
|
||||
}
|
||||
};
|
||||
|
||||
// Add getProjectionMatrix and getViewMatrix to Camera struct in mesh.hpp if they don't exist
|
||||
// For completeness, here are the implementations:
|
||||
/*
|
||||
In camera.hpp (or mesh.hpp where Camera is defined):
|
||||
|
||||
Matrix4f getViewMatrix() const {
|
||||
Vector3f f = forward().normalized();
|
||||
Vector3f r = right().normalized();
|
||||
Vector3f u = up.normalized();
|
||||
|
||||
Matrix4f view = Matrix4f::Identity();
|
||||
view(0,0) = r.x(); view(0,1) = r.y(); view(0,2) = r.z(); view(0,3) = -r.dot(origin);
|
||||
view(1,0) = u.x(); view(1,1) = u.y(); view(1,2) = u.z(); view(1,3) = -u.dot(origin);
|
||||
view(2,0) = -f.x(); view(2,1) = -f.y(); view(2,2) = -f.z(); view(2,3) = f.dot(origin);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
Matrix4f getProjectionMatrix(float aspect, float near, float far) const {
|
||||
float tanHalfFov = std::tan(fov * 0.5f * 3.14159265f / 180.0f);
|
||||
Matrix4f proj = Matrix4f::Zero();
|
||||
proj(0,0) = 1.0f / (aspect * tanHalfFov);
|
||||
proj(1,1) = 1.0f / tanHalfFov;
|
||||
proj(2,2) = -(far + near) / (far - near);
|
||||
proj(2,3) = -(2.0f * far * near) / (far - near);
|
||||
proj(3,2) = -1.0f;
|
||||
return proj;
|
||||
}
|
||||
*/
|
||||
|
||||
// Main entry point
|
||||
int main() {
|
||||
Game game;
|
||||
if (game.init(1280, 720, "Tower Defense")) {
|
||||
game.run();
|
||||
}
|
||||
game.shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -27,24 +27,42 @@ constexpr int Dim2 = 2;
|
||||
template<typename T>
|
||||
class Grid2 {
|
||||
public:
|
||||
using PointType = Eigen::Matrix<float, Dim2, 1>;
|
||||
using PointType = Eigen::Matrix<float, Dim2, 1>; // Eigen::Vector2f
|
||||
using BoundingBox = std::pair<PointType, PointType>;
|
||||
|
||||
// Shape for 2D is usually a Circle or a Square (AABB)
|
||||
enum class Shape {
|
||||
CIRCLE,
|
||||
SQUARE
|
||||
};
|
||||
|
||||
struct NodeData {
|
||||
T data;
|
||||
PointType position;
|
||||
int objectId;
|
||||
bool active;
|
||||
bool visible;
|
||||
float size;
|
||||
Eigen::Vector4f color;
|
||||
float size; // Radius or half-width
|
||||
Eigen::Vector4f color; // RGBA
|
||||
|
||||
// Physics properties
|
||||
float temperature;
|
||||
float conductivity;
|
||||
float specific_heat;
|
||||
float density;
|
||||
float next_temperature; // For double-buffering simulation
|
||||
|
||||
Shape shape;
|
||||
|
||||
NodeData(const T& data, const PointType& pos, bool visible, Eigen::Vector4f color, float size = 1.0f,
|
||||
bool active = true, int objectId = -1, Shape shape = Shape::SQUARE)
|
||||
: data(data), position(pos), objectId(objectId), active(active), visible(visible),
|
||||
color(color), size(size) {}
|
||||
color(color), size(size), shape(shape),
|
||||
temperature(0.0f), conductivity(1.0f), specific_heat(1.0f), density(1.0f), next_temperature(0.0f) {}
|
||||
|
||||
NodeData() : objectId(-1), active(false), visible(false), size(0.0f), color(0,0,0,0) {}
|
||||
NodeData() : objectId(-1), active(false), visible(false), size(0.0f),
|
||||
color(0,0,0,0), shape(Shape::SQUARE),
|
||||
temperature(0.0f), conductivity(1.0f), specific_heat(1.0f), density(1.0f), next_temperature(0.0f) {}
|
||||
|
||||
// Helper for Square bounds
|
||||
BoundingBox getSquareBounds() const {
|
||||
@@ -56,7 +74,7 @@ public:
|
||||
struct QuadNode {
|
||||
BoundingBox bounds;
|
||||
std::vector<std::shared_ptr<NodeData>> points;
|
||||
std::array<std::unique_ptr<QuadNode>, 4> children;
|
||||
std::array<std::unique_ptr<QuadNode>, 4> children; // 4 quadrants
|
||||
PointType center;
|
||||
bool isLeaf;
|
||||
|
||||
@@ -88,19 +106,35 @@ private:
|
||||
Eigen::Vector4f backgroundColor_ = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
PNoise2 noisegen;
|
||||
|
||||
// Determine quadrant: 0:SW, 1:SE, 2:NW, 3:NE
|
||||
uint8_t getQuadrant(const PointType& point, const PointType& center) const {
|
||||
return (point.x() >= center.x()) | ((point.y() >= center.y()) << 1);
|
||||
uint8_t quad = 0;
|
||||
if (point.x() >= center.x()) quad |= 1; // Right
|
||||
if (point.y() >= center.y()) quad |= 2; // Top
|
||||
return quad;
|
||||
}
|
||||
|
||||
BoundingBox createChildBounds(const QuadNode* node, uint8_t quad) const {
|
||||
PointType childMin, childMax;
|
||||
PointType center = node->center;
|
||||
|
||||
childMin[0] = (quad & 1) ? center[0] : node->bounds.first[0];
|
||||
childMax[0] = (quad & 1) ? node->bounds.second[0] : center[0];
|
||||
// X axis
|
||||
if (quad & 1) { // Right
|
||||
childMin.x() = center.x();
|
||||
childMax.x() = node->bounds.second.x();
|
||||
} else { // Left
|
||||
childMin.x() = node->bounds.first.x();
|
||||
childMax.x() = center.x();
|
||||
}
|
||||
|
||||
childMin[1] = (quad & 2) ? center[1] : node->bounds.first[1];
|
||||
childMax[1] = (quad & 2) ? node->bounds.second[1] : center[1];
|
||||
// Y axis
|
||||
if (quad & 2) { // Top
|
||||
childMin.y() = center.y();
|
||||
childMax.y() = node->bounds.second.y();
|
||||
} else { // Bottom
|
||||
childMin.y() = node->bounds.first.y();
|
||||
childMax.y() = center.y();
|
||||
}
|
||||
|
||||
return {childMin, childMax};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -335,6 +335,129 @@ public:
|
||||
else os << " Polys (Cleared) : " << 0 << "\n";
|
||||
os << " colors : " << _colors.size() << "\n";
|
||||
}
|
||||
|
||||
|
||||
void writeTo(FILE* f) const {
|
||||
if (!f) return;
|
||||
|
||||
fwrite(&id, sizeof(int), 1, f);
|
||||
fwrite(&_subId, sizeof(int), 1, f);
|
||||
|
||||
size_t vCount = _vertices.size();
|
||||
fwrite(&vCount, sizeof(size_t), 1, f);
|
||||
if (vCount > 0) {
|
||||
fwrite(_vertices.data(), sizeof(Vector3f), vCount, f);
|
||||
}
|
||||
|
||||
size_t cCount = _colors.size();
|
||||
fwrite(&cCount, sizeof(size_t), 1, f);
|
||||
if (cCount > 0) {
|
||||
fwrite(_colors.data(), sizeof(Color), cCount, f);
|
||||
}
|
||||
|
||||
size_t pCount = _polys.size();
|
||||
fwrite(&pCount, sizeof(size_t), 1, f);
|
||||
for (const auto& p : _polys) {
|
||||
size_t idxCount = p.size();
|
||||
fwrite(&idxCount, sizeof(size_t), 1, f);
|
||||
if (idxCount > 0) {
|
||||
fwrite(p.data(), sizeof(int), idxCount, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<Mesh> readFrom(FILE* f) {
|
||||
if (!f) return nullptr;
|
||||
|
||||
int r_id, r_subId;
|
||||
if (fread(&r_id, sizeof(int), 1, f) != 1) return nullptr;
|
||||
if (fread(&r_subId, sizeof(int), 1, f) != 1) return nullptr;
|
||||
|
||||
// Read Vertices
|
||||
size_t vCount;
|
||||
if (fread(&vCount, sizeof(size_t), 1, f) != 1) return nullptr;
|
||||
std::vector<Vector3f> verts(vCount);
|
||||
if (vCount > 0) {
|
||||
fread(verts.data(), sizeof(Vector3f), vCount, f);
|
||||
}
|
||||
|
||||
// Read Colors
|
||||
size_t cCount;
|
||||
if (fread(&cCount, sizeof(size_t), 1, f) != 1) return nullptr;
|
||||
std::vector<Color> cols(cCount);
|
||||
if (cCount > 0) {
|
||||
fread(cols.data(), sizeof(Color), cCount, f);
|
||||
}
|
||||
|
||||
// Read Polys
|
||||
size_t pCount;
|
||||
if (fread(&pCount, sizeof(size_t), 1, f) != 1) return nullptr;
|
||||
std::vector<std::vector<int>> polys(pCount);
|
||||
for (size_t i = 0; i < pCount; ++i) {
|
||||
size_t idxCount;
|
||||
if (fread(&idxCount, sizeof(size_t), 1, f) != 1) return nullptr;
|
||||
polys[i].resize(idxCount);
|
||||
if (idxCount > 0) {
|
||||
fread(polys[i].data(), sizeof(int), idxCount, f);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<Mesh>(r_id, verts, polys, cols, r_subId);
|
||||
}
|
||||
|
||||
// Public API to save to a filename
|
||||
bool save(const std::string& filename) const {
|
||||
FILE* f = fopen(filename.c_str(), "wb");
|
||||
if (!f) {
|
||||
std::cerr << "Mesh::save failed to open: " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
writeTo(f);
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Public API to load from a filename into this object
|
||||
bool load(const std::string& filename) {
|
||||
FILE* f = fopen(filename.c_str(), "rb");
|
||||
if (!f) {
|
||||
std::cerr << "Mesh::load failed to open: " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read into temporary variables first to ensure integrity
|
||||
int r_id, r_subId;
|
||||
if (fread(&r_id, sizeof(int), 1, f) != 1) { fclose(f); return false; }
|
||||
if (fread(&r_subId, sizeof(int), 1, f) != 1) { fclose(f); return false; }
|
||||
|
||||
size_t vCount;
|
||||
if (fread(&vCount, sizeof(size_t), 1, f) != 1) { fclose(f); return false; }
|
||||
std::vector<Vector3f> verts(vCount);
|
||||
if (vCount > 0) fread(verts.data(), sizeof(Vector3f), vCount, f);
|
||||
|
||||
size_t cCount;
|
||||
if (fread(&cCount, sizeof(size_t), 1, f) != 1) { fclose(f); return false; }
|
||||
std::vector<Color> cols(cCount);
|
||||
if (cCount > 0) fread(cols.data(), sizeof(Color), cCount, f);
|
||||
|
||||
size_t pCount;
|
||||
if (fread(&pCount, sizeof(size_t), 1, f) != 1) { fclose(f); return false; }
|
||||
std::vector<std::vector<int>> polys(pCount);
|
||||
for (size_t i = 0; i < pCount; ++i) {
|
||||
size_t idxCount;
|
||||
if (fread(&idxCount, sizeof(size_t), 1, f) != 1) { fclose(f); return false; }
|
||||
polys[i].resize(idxCount);
|
||||
if (idxCount > 0) fread(polys[i].data(), sizeof(int), idxCount, f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// Apply to current object
|
||||
this->id = r_id;
|
||||
this->_subId = r_subId;
|
||||
this->replace(verts, polys, cols);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class Scene {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
#ifndef JSONHELPER_HPP
|
||||
#define JSONHELPER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cctype>
|
||||
|
||||
namespace JsonHelper {
|
||||
|
||||
// Helper to get string value between quotes
|
||||
inline std::string parseString(const std::string& json, const std::string& key) {
|
||||
size_t pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find(":", pos);
|
||||
if (pos == std::string::npos) return "";
|
||||
size_t start = json.find("\"", pos);
|
||||
if (start == std::string::npos) return "";
|
||||
size_t end = json.find("\"", start + 1);
|
||||
if (end == std::string::npos) return "";
|
||||
return json.substr(start + 1, end - start - 1);
|
||||
}
|
||||
|
||||
// Helper to get raw non-string value (int, float, bool)
|
||||
inline std::string parseRaw(const std::string& json, const std::string& key) {
|
||||
size_t pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find(":", pos);
|
||||
if (pos == std::string::npos) return "";
|
||||
pos++; // skip ':'
|
||||
|
||||
while (pos < json.length() && std::isspace(json[pos])) pos++;
|
||||
size_t end = pos;
|
||||
while (end < json.length() && json[end] != ',' && json[end] != '}' && json[end] != ']' && !std::isspace(json[end])) end++;
|
||||
|
||||
return json.substr(pos, end - pos);
|
||||
}
|
||||
|
||||
inline int parseInt(const std::string& json, const std::string& key, int defaultVal = 0) {
|
||||
std::string raw = parseRaw(json, key);
|
||||
if (raw.empty()) return defaultVal;
|
||||
try { return std::stoi(raw); } catch(...) { return defaultVal; }
|
||||
}
|
||||
|
||||
inline float parseFloat(const std::string& json, const std::string& key, float defaultVal = 0.0f) {
|
||||
std::string raw = parseRaw(json, key);
|
||||
if (raw.empty()) return defaultVal;
|
||||
try { return std::stof(raw); } catch(...) { return defaultVal; }
|
||||
}
|
||||
|
||||
inline bool parseBool(const std::string& json, const std::string& key, bool defaultVal = false) {
|
||||
std::string raw = parseRaw(json, key);
|
||||
if (raw.empty()) return defaultVal;
|
||||
return raw == "true" || raw == "1";
|
||||
}
|
||||
|
||||
// Helper to extract JSON objects out of a JSON array
|
||||
inline std::vector<std::string> parseArray(const std::string& json, const std::string& key) {
|
||||
std::vector<std::string> items;
|
||||
size_t pos = json.find("\"" + key + "\"");
|
||||
if (pos == std::string::npos) return items;
|
||||
pos = json.find(":", pos);
|
||||
if (pos == std::string::npos) return items;
|
||||
pos = json.find("[", pos);
|
||||
if (pos == std::string::npos) return items;
|
||||
|
||||
int depth = 0;
|
||||
size_t start = 0;
|
||||
bool inString = false;
|
||||
|
||||
for (size_t i = pos + 1; i < json.length(); ++i) {
|
||||
if (json[i] == '"' && (i == 0 || json[i-1] != '\\')) {
|
||||
inString = !inString;
|
||||
}
|
||||
if (!inString) {
|
||||
if (json[i] == '{') {
|
||||
if (depth == 0) start = i;
|
||||
depth++;
|
||||
} else if (json[i] == '}') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
items.push_back(json.substr(start, i - start + 1));
|
||||
}
|
||||
} else if (json[i] == ']') {
|
||||
if (depth == 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,11 +6,8 @@
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "./pnoise2.hpp"
|
||||
#include "../jsonhelper.hpp"
|
||||
#include "../timing_decorator.hpp"
|
||||
#include "../../imgui/imgui.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
@@ -197,94 +194,11 @@ inline void updateNoiseTexture(NoisePreviewState& state) {
|
||||
state.needsUpdate = false;
|
||||
}
|
||||
|
||||
inline void saveNoiseState(const NoisePreviewState& state, const std::string& filename) {
|
||||
std::ofstream out(filename);
|
||||
if (!out) return;
|
||||
|
||||
out << "{\n";
|
||||
out << " \"masterSeed\": " << state.masterSeed << ",\n";
|
||||
out << " \"offsetX\": " << state.offset[0] << ",\n";
|
||||
out << " \"offsetY\": " << state.offset[1] << ",\n";
|
||||
out << " \"layers\": [\n";
|
||||
|
||||
for (size_t i = 0; i < state.layers.size(); ++i) {
|
||||
const auto& l = state.layers[i];
|
||||
out << " {\n";
|
||||
out << " \"enabled\": " << (l.enabled ? "true" : "false") << ",\n";
|
||||
out << " \"name\": \"" << l.name << "\",\n";
|
||||
out << " \"type\": " << (int)l.type << ",\n";
|
||||
out << " \"blend\": " << (int)l.blend << ",\n";
|
||||
out << " \"seedOffset\": " << l.seedOffset << ",\n";
|
||||
out << " \"scale\": " << l.scale << ",\n";
|
||||
out << " \"strength\": " << l.strength << ",\n";
|
||||
out << " \"octaves\": " << l.octaves << ",\n";
|
||||
out << " \"persistence\": " << l.persistence << ",\n";
|
||||
out << " \"lacunarity\": " << l.lacunarity << ",\n";
|
||||
out << " \"ridgeOffset\": " << l.ridgeOffset << "\n";
|
||||
out << " }" << (i < state.layers.size() - 1 ? "," : "") << "\n";
|
||||
}
|
||||
|
||||
out << " ]\n";
|
||||
out << "}\n";
|
||||
}
|
||||
|
||||
inline void loadNoiseState(NoisePreviewState& state, const std::string& filename) {
|
||||
std::ifstream in(filename);
|
||||
if (!in) return;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << in.rdbuf();
|
||||
std::string json = buffer.str();
|
||||
|
||||
state.masterSeed = JsonHelper::parseInt(json, "masterSeed", 1337);
|
||||
state.offset[0] = JsonHelper::parseFloat(json, "offsetX", 0.0f);
|
||||
state.offset[1] = JsonHelper::parseFloat(json, "offsetY", 0.0f);
|
||||
|
||||
auto layerStrs = JsonHelper::parseArray(json, "layers");
|
||||
state.layers.clear();
|
||||
|
||||
for (const auto& lStr : layerStrs) {
|
||||
NoiseLayer l;
|
||||
l.enabled = JsonHelper::parseBool(lStr, "enabled", true);
|
||||
std::string name = JsonHelper::parseString(lStr, "name");
|
||||
if (!name.empty()) {
|
||||
std::strncpy(l.name, name.c_str(), 31);
|
||||
l.name[31] = '\0';
|
||||
}
|
||||
l.type = (NoiseType)JsonHelper::parseInt(lStr, "type", 0);
|
||||
l.blend = (BlendMode)JsonHelper::parseInt(lStr, "blend", 0);
|
||||
l.seedOffset = JsonHelper::parseInt(lStr, "seedOffset", 0);
|
||||
l.scale = JsonHelper::parseFloat(lStr, "scale", 0.02f);
|
||||
l.strength = JsonHelper::parseFloat(lStr, "strength", 1.0f);
|
||||
l.octaves = JsonHelper::parseInt(lStr, "octaves", 4);
|
||||
l.persistence = JsonHelper::parseFloat(lStr, "persistence", 0.5f);
|
||||
l.lacunarity = JsonHelper::parseFloat(lStr, "lacunarity", 2.0f);
|
||||
l.ridgeOffset = JsonHelper::parseFloat(lStr, "ridgeOffset", 1.0f);
|
||||
|
||||
state.layers.push_back(l);
|
||||
}
|
||||
state.needsUpdate = true;
|
||||
}
|
||||
|
||||
inline void drawNoiseLab(NoisePreviewState& noiseState) {
|
||||
ImGui::Begin("2D Noise Lab");
|
||||
|
||||
// Master Controls
|
||||
bool changed = false;
|
||||
|
||||
static char filenameBuffer[128] = "output/noise_preset.json";
|
||||
ImGui::InputText("File", filenameBuffer, sizeof(filenameBuffer));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save JSON")) {
|
||||
saveNoiseState(noiseState, filenameBuffer);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load JSON")) {
|
||||
loadNoiseState(noiseState, filenameBuffer);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
changed |= ImGui::InputInt("Master Seed", &noiseState.masterSeed);
|
||||
changed |= ImGui::DragFloat2("Pan Offset", noiseState.offset, 1.0f);
|
||||
|
||||
@@ -340,7 +254,7 @@ inline void drawNoiseLab(NoisePreviewState& noiseState) {
|
||||
}
|
||||
|
||||
if (open) {
|
||||
if (ImGui::Checkbox("##enabled", &layer.enabled)) changed = true;
|
||||
ImGui::Checkbox("##enabled", &layer.enabled);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("##name", layer.name, 32);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <limits>
|
||||
#include "../../eigen/Eigen/Core"
|
||||
#include "../timing_decorator.hpp"
|
||||
#include "../basicdefines.hpp"
|
||||
|
||||
class PNoise2 {
|
||||
private:
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
#ifndef ELEMENTS
|
||||
#define ELEMENTS
|
||||
|
||||
#include <array>
|
||||
|
||||
struct BaseElementProps {
|
||||
float density; // kg/m^3
|
||||
float meltingPoint; // Kelvin
|
||||
float boilingPoint; // Kelvin
|
||||
float specificHeat; // J/(kg*K)
|
||||
float electronegativity; // Pauling scale
|
||||
};
|
||||
|
||||
static const std::array<BaseElementProps, 118> ELEMENT_DB = {{
|
||||
// 1: Hydrogen
|
||||
{0.08988f, 14.01f, 20.28f, 14304.0f, 2.20f},
|
||||
// 2: Helium
|
||||
{0.1785f, 0.95f, 4.22f, 5193.0f, 0.0f}, // No electronegativity, using 0
|
||||
// 3: Lithium
|
||||
{534.0f, 453.69f, 1560.0f, 3582.0f, 0.98f},
|
||||
// 4: Beryllium
|
||||
{1850.0f, 1560.0f, 2742.0f, 1825.0f, 1.57f},
|
||||
// 5: Boron
|
||||
{2340.0f, 2349.0f, 4200.0f, 1026.0f, 2.04f},
|
||||
// 6: Carbon
|
||||
{2267.0f, 4000.0f, 4300.0f, 709.0f, 2.55f},
|
||||
// 7: Nitrogen
|
||||
{1.2506f, 63.15f, 77.36f, 1040.0f, 3.04f},
|
||||
// 8: Oxygen
|
||||
{1.429f, 54.36f, 90.2f, 918.0f, 3.44f},
|
||||
// 9: Fluorine
|
||||
{1.696f, 53.53f, 85.03f, 824.0f, 3.98f},
|
||||
// 10: Neon
|
||||
{0.9002f, 24.56f, 27.07f, 1030.0f, 0.0f}, // No electronegativity
|
||||
// 11: Sodium
|
||||
{968.0f, 370.87f, 1156.0f, 1228.0f, 0.93f},
|
||||
// 12: Magnesium
|
||||
{1738.0f, 923.0f, 1363.0f, 1023.0f, 1.31f},
|
||||
// 13: Aluminium
|
||||
{2700.0f, 933.47f, 2792.0f, 897.0f, 1.61f},
|
||||
// 14: Silicon
|
||||
{2329.0f, 1687.0f, 3538.0f, 705.0f, 1.9f},
|
||||
// 15: Phosphorus
|
||||
{1823.0f, 317.3f, 550.0f, 769.0f, 2.19f},
|
||||
// 16: Sulfur
|
||||
{2070.0f, 388.36f, 717.87f, 710.0f, 2.58f},
|
||||
// 17: Chlorine
|
||||
{3.2f, 171.6f, 239.11f, 479.0f, 3.16f},
|
||||
// 18: Argon
|
||||
{1.784f, 83.8f, 87.3f, 520.0f, 0.0f}, // No electronegativity
|
||||
// 19: Potassium
|
||||
{890.0f, 336.53f, 1032.0f, 757.0f, 0.82f},
|
||||
// 20: Calcium
|
||||
{1550.0f, 1115.0f, 1757.0f, 647.0f, 1.0f},
|
||||
// 21: Scandium
|
||||
{2985.0f, 1814.0f, 3109.0f, 568.0f, 1.36f},
|
||||
// 22: Titanium
|
||||
{4506.0f, 1941.0f, 3560.0f, 523.0f, 1.54f},
|
||||
// 23: Vanadium
|
||||
{6110.0f, 2183.0f, 3680.0f, 489.0f, 1.63f},
|
||||
// 24: Chromium
|
||||
{7150.0f, 2180.0f, 2944.0f, 449.0f, 1.66f},
|
||||
// 25: Manganese
|
||||
{7210.0f, 1519.0f, 2334.0f, 479.0f, 1.55f},
|
||||
// 26: Iron
|
||||
{7874.0f, 1811.0f, 3134.0f, 449.0f, 1.83f},
|
||||
// 27: Cobalt
|
||||
{8900.0f, 1768.0f, 3200.0f, 421.0f, 1.88f},
|
||||
// 28: Nickel
|
||||
{8908.0f, 1728.0f, 3186.0f, 444.0f, 1.91f},
|
||||
// 29: Copper
|
||||
{8960.0f, 1357.77f, 2835.0f, 385.0f, 1.9f},
|
||||
// 30: Zinc
|
||||
{7140.0f, 692.88f, 1180.0f, 388.0f, 1.65f},
|
||||
// 31: Gallium
|
||||
{5910.0f, 302.9146f, 2673.0f, 371.0f, 1.81f},
|
||||
// 32: Germanium
|
||||
{5323.0f, 1211.4f, 3106.0f, 320.0f, 2.01f},
|
||||
// 33: Arsenic
|
||||
{5727.0f, 1090.0f, 887.0f, 329.0f, 2.18f}, // Sublimes at 887K
|
||||
// 34: Selenium
|
||||
{4810.0f, 453.0f, 958.0f, 321.0f, 2.55f},
|
||||
// 35: Bromine
|
||||
{3102.8f, 265.8f, 332.0f, 474.0f, 2.96f},
|
||||
// 36: Krypton
|
||||
{3.749f, 115.79f, 119.93f, 248.0f, 3.0f},
|
||||
// 37: Rubidium
|
||||
{1532.0f, 312.46f, 961.0f, 363.0f, 0.82f},
|
||||
// 38: Strontium
|
||||
{2640.0f, 1050.0f, 1655.0f, 301.0f, 0.95f},
|
||||
// 39: Yttrium
|
||||
{4472.0f, 1799.0f, 3609.0f, 298.0f, 1.22f},
|
||||
// 40: Zirconium
|
||||
{6520.0f, 2128.0f, 4682.0f, 278.0f, 1.33f},
|
||||
// 41: Niobium
|
||||
{8570.0f, 2750.0f, 5017.0f, 265.0f, 1.6f},
|
||||
// 42: Molybdenum
|
||||
{10280.0f, 2896.0f, 4912.0f, 251.0f, 2.16f},
|
||||
// 43: Technetium
|
||||
{11000.0f, 2430.0f, 4538.0f, 0.0f, 1.9f}, // No specific heat data
|
||||
// 44: Ruthenium
|
||||
{12450.0f, 2607.0f, 4423.0f, 238.0f, 2.2f},
|
||||
// 45: Rhodium
|
||||
{12410.0f, 2237.0f, 3968.0f, 243.0f, 2.28f},
|
||||
// 46: Palladium
|
||||
{12023.0f, 1828.05f, 3236.0f, 244.0f, 2.2f},
|
||||
// 47: Silver
|
||||
{10490.0f, 1234.93f, 2435.0f, 235.0f, 1.93f},
|
||||
// 48: Cadmium
|
||||
{8650.0f, 594.22f, 1040.0f, 232.0f, 1.69f},
|
||||
// 49: Indium
|
||||
{7310.0f, 429.75f, 2345.0f, 233.0f, 1.78f},
|
||||
// 50: Tin
|
||||
{7265.0f, 505.08f, 2875.0f, 228.0f, 1.96f},
|
||||
// 51: Antimony
|
||||
{6697.0f, 903.78f, 1860.0f, 207.0f, 2.05f},
|
||||
// 52: Tellurium
|
||||
{6240.0f, 722.66f, 1261.0f, 202.0f, 2.1f},
|
||||
// 53: Iodine
|
||||
{4933.0f, 386.85f, 457.4f, 214.0f, 2.66f},
|
||||
// 54: Xenon
|
||||
{5.894f, 161.4f, 165.03f, 158.0f, 2.6f},
|
||||
// 55: Caesium
|
||||
{1930.0f, 301.59f, 944.0f, 242.0f, 0.79f},
|
||||
// 56: Barium
|
||||
{3510.0f, 1000.0f, 2170.0f, 204.0f, 0.89f},
|
||||
// 57: Lanthanum
|
||||
{6162.0f, 1193.0f, 3737.0f, 195.0f, 1.1f},
|
||||
// 58: Cerium
|
||||
{6770.0f, 1068.0f, 3716.0f, 192.0f, 1.12f},
|
||||
// 59: Praseodymium
|
||||
{6770.0f, 1208.0f, 3793.0f, 193.0f, 1.13f},
|
||||
// 60: Neodymium
|
||||
{7010.0f, 1297.0f, 3347.0f, 190.0f, 1.14f},
|
||||
// 61: Promethium
|
||||
{7260.0f, 1315.0f, 3273.0f, 0.0f, 1.13f}, // No specific heat data
|
||||
// 62: Samarium
|
||||
{7520.0f, 1345.0f, 2067.0f, 197.0f, 1.17f},
|
||||
// 63: Europium
|
||||
{5244.0f, 1099.0f, 1802.0f, 182.0f, 1.2f},
|
||||
// 64: Gadolinium
|
||||
{7900.0f, 1585.0f, 3546.0f, 236.0f, 1.2f},
|
||||
// 65: Terbium
|
||||
{8230.0f, 1629.0f, 3503.0f, 182.0f, 1.2f},
|
||||
// 66: Dysprosium
|
||||
{8540.0f, 1680.0f, 2840.0f, 170.0f, 1.22f},
|
||||
// 67: Holmium
|
||||
{8790.0f, 1734.0f, 2993.0f, 165.0f, 1.23f},
|
||||
// 68: Erbium
|
||||
{9066.0f, 1802.0f, 3141.0f, 168.0f, 1.24f},
|
||||
// 69: Thulium
|
||||
{9320.0f, 1818.0f, 2223.0f, 160.0f, 1.25f},
|
||||
// 70: Ytterbium
|
||||
{6900.0f, 1097.0f, 1469.0f, 155.0f, 1.1f},
|
||||
// 71: Lutetium
|
||||
{9841.0f, 1925.0f, 3675.0f, 154.0f, 1.27f},
|
||||
// 72: Hafnium
|
||||
{13310.0f, 2506.0f, 4876.0f, 144.0f, 1.3f},
|
||||
// 73: Tantalum
|
||||
{16690.0f, 3290.0f, 5731.0f, 140.0f, 1.5f},
|
||||
// 74: Tungsten
|
||||
{19250.0f, 3695.0f, 6203.0f, 132.0f, 2.36f},
|
||||
// 75: Rhenium
|
||||
{21020.0f, 3459.0f, 5869.0f, 137.0f, 1.9f},
|
||||
// 76: Osmium
|
||||
{22590.0f, 3306.0f, 5285.0f, 130.0f, 2.2f},
|
||||
// 77: Iridium
|
||||
{22560.0f, 2719.0f, 4701.0f, 131.0f, 2.2f},
|
||||
// 78: Platinum
|
||||
{21450.0f, 2041.4f, 4098.0f, 133.0f, 2.28f},
|
||||
// 79: Gold
|
||||
{19300.0f, 1337.33f, 3129.0f, 129.0f, 2.54f},
|
||||
// 80: Mercury
|
||||
{13534.0f, 234.43f, 629.88f, 140.0f, 2.0f},
|
||||
// 81: Thallium
|
||||
{11850.0f, 577.0f, 1746.0f, 129.0f, 1.62f},
|
||||
// 82: Lead
|
||||
{11340.0f, 600.61f, 2022.0f, 129.0f, 2.33f}, // Using 4+ value
|
||||
// 83: Bismuth
|
||||
{9780.0f, 544.7f, 1837.0f, 122.0f, 2.02f},
|
||||
// 84: Polonium
|
||||
{9196.0f, 527.0f, 1235.0f, 0.0f, 2.0f}, // No specific heat data
|
||||
// 85: Astatine
|
||||
{8930.0f, 575.0f, 610.0f, 0.0f, 2.2f}, // Approx density, no specific heat
|
||||
// 86: Radon
|
||||
{9.73f, 202.0f, 211.3f, 94.0f, 2.2f},
|
||||
// 87: Francium
|
||||
{2480.0f, 281.0f, 890.0f, 0.0f, 0.79f}, // Approx values
|
||||
// 88: Radium
|
||||
{5500.0f, 973.0f, 2010.0f, 94.0f, 0.9f},
|
||||
// 89: Actinium
|
||||
{10000.0f, 1323.0f, 3471.0f, 120.0f, 1.1f},
|
||||
// 90: Thorium
|
||||
{11700.0f, 2115.0f, 5061.0f, 113.0f, 1.3f},
|
||||
// 91: Protactinium
|
||||
{15370.0f, 1841.0f, 4300.0f, 0.0f, 1.5f}, // No specific heat data
|
||||
// 92: Uranium
|
||||
{19100.0f, 1405.3f, 4404.0f, 116.0f, 1.38f},
|
||||
// 93: Neptunium
|
||||
{20450.0f, 917.0f, 4273.0f, 0.0f, 1.36f}, // No specific heat data
|
||||
// 94: Plutonium
|
||||
{19850.0f, 912.5f, 3501.0f, 0.0f, 1.28f}, // No specific heat data
|
||||
// 95: Americium
|
||||
{12000.0f, 1449.0f, 2880.0f, 0.0f, 1.13f}, // No specific heat data
|
||||
// 96: Curium
|
||||
{13510.0f, 1613.0f, 3383.0f, 0.0f, 1.28f}, // No specific heat data
|
||||
// 97: Berkelium
|
||||
{14780.0f, 1259.0f, 2900.0f, 0.0f, 1.3f}, // No specific heat data
|
||||
// 98: Californium
|
||||
{15100.0f, 1173.0f, 1743.0f, 0.0f, 1.3f}, // No specific heat data
|
||||
// 99: Einsteinium
|
||||
{8840.0f, 1133.0f, 1269.0f, 0.0f, 1.3f}, // No specific heat data
|
||||
// 100: Fermium
|
||||
{9700.0f, 1125.0f, 1800.0f, 0.0f, 1.3f}, // Estimated values
|
||||
// 101: Mendelevium
|
||||
{10300.0f, 1100.0f, 0.0f, 0.0f, 1.3f}, // Estimated
|
||||
// 102: Nobelium
|
||||
{9900.0f, 1100.0f, 0.0f, 0.0f, 1.3f}, // Estimated
|
||||
// 103: Lawrencium
|
||||
{14400.0f, 1900.0f, 0.0f, 0.0f, 1.3f}, // Estimated
|
||||
// 104: Rutherfordium
|
||||
{17000.0f, 2400.0f, 5800.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 105: Dubnium
|
||||
{21600.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 106: Seaborgium
|
||||
{23500.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 107: Bohrium
|
||||
{26500.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 108: Hassium
|
||||
{28000.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 109: Meitnerium
|
||||
{27500.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 110: Darmstadtium
|
||||
{26500.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 111: Roentgenium
|
||||
{23000.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 112: Copernicium
|
||||
{14000.0f, 283.0f, 340.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 113: Nihonium
|
||||
{16000.0f, 700.0f, 1400.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 114: Flerovium
|
||||
{11400.0f, 284.0f, 0.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 115: Moscovium
|
||||
{13500.0f, 700.0f, 1400.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 116: Livermorium
|
||||
{12900.0f, 700.0f, 1100.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 117: Tennessine
|
||||
{7200.0f, 700.0f, 883.0f, 0.0f, 0.0f}, // Estimated
|
||||
// 118: Oganesson
|
||||
{7000.0f, 325.0f, 450.0f, 0.0f, 0.0f} // Estimated
|
||||
}};
|
||||
|
||||
struct PointProperties {
|
||||
float weight = 0.0f; // Total mass
|
||||
float density = 0.0f; // Mass / Volume
|
||||
float meltingPoint = 0.0f;
|
||||
float boilingPoint = 0.0f;
|
||||
float specificHeat = 0.0f;
|
||||
float electronegativity = 0.0f;
|
||||
};
|
||||
|
||||
struct elementContent {
|
||||
float hydrogen = 0.0f;
|
||||
float helium = 0.0f;
|
||||
float lithium = 0.0f;
|
||||
float beryllium = 0.0f;
|
||||
float boron = 0.0f;
|
||||
float carbon = 0.0f;
|
||||
float nitrogen = 0.0f;
|
||||
float oxygen = 0.0f;
|
||||
float fluorine = 0.0f;
|
||||
float neon = 0.0f;
|
||||
float sodium = 0.0f;
|
||||
float magnesium = 0.0f;
|
||||
float aluminum = 0.0f;
|
||||
float silicon = 0.0f;
|
||||
float phosporus = 0.0f;
|
||||
float sulfur = 0.0f;
|
||||
float chlorine = 0.0f;
|
||||
float argon = 0.0f;
|
||||
float potassium = 0.0f;
|
||||
float calcium = 0.0f;
|
||||
float scandium = 0.0f;
|
||||
float titanium = 0.0f;
|
||||
float vanadium = 0.0f;
|
||||
float chromium = 0.0f;
|
||||
float manganese = 0.0f;
|
||||
float iron = 0.0f;
|
||||
float cobalt = 0.0f;
|
||||
float nickel = 0.0f;
|
||||
float copper = 0.0f;
|
||||
float zinc = 0.0f;
|
||||
float gallium = 0.0f;
|
||||
float germanium = 0.0f;
|
||||
float arsenic = 0.0f;
|
||||
float selenium = 0.0f;
|
||||
float bromine = 0.0f;
|
||||
float krypton = 0.0f;
|
||||
float rubidium = 0.0f;
|
||||
float strontium = 0.0f;
|
||||
float yttrium = 0.0f;
|
||||
float zirconium = 0.0f;
|
||||
float niobium = 0.0f;
|
||||
float molybdenum = 0.0f;
|
||||
float technetium = 0.0f;
|
||||
float ruthenium = 0.0f;
|
||||
float rhodium = 0.0f;
|
||||
float palladium = 0.0f;
|
||||
float silver = 0.0f;
|
||||
float cadmium = 0.0f;
|
||||
float indium = 0.0f;
|
||||
float tin = 0.0f;
|
||||
float antimony = 0.0f;
|
||||
float tellurium = 0.0f;
|
||||
float iodine = 0.0f;
|
||||
float xenon = 0.0f;
|
||||
float caesium = 0.0f;
|
||||
float barium = 0.0f;
|
||||
float lanthanum = 0.0f;
|
||||
float cerium = 0.0f;
|
||||
float praseodymium = 0.0f;
|
||||
float neodymium = 0.0f;
|
||||
float promethium = 0.0f;
|
||||
float samarium = 0.0f;
|
||||
float europium = 0.0f;
|
||||
float gadolinium = 0.0f;
|
||||
float terbium = 0.0f;
|
||||
float dysprosium = 0.0f;
|
||||
float holmium = 0.0f;
|
||||
float erbium = 0.0f;
|
||||
float thulium = 0.0f;
|
||||
float ytterbium = 0.0f;
|
||||
float lutetium = 0.0f;
|
||||
float hafnium = 0.0f;
|
||||
float tantalum = 0.0f;
|
||||
float tungsten = 0.0f;
|
||||
float rhenium = 0.0f;
|
||||
float osmium = 0.0f;
|
||||
float iridium = 0.0f;
|
||||
float platinum = 0.0f;
|
||||
float gold = 0.0f;
|
||||
float mercury = 0.0f;
|
||||
float thallium = 0.0f;
|
||||
float lead = 0.0f;
|
||||
float bismuth = 0.0f;
|
||||
float polonium = 0.0f;
|
||||
float astatine = 0.0f;
|
||||
float radon = 0.0f;
|
||||
float francium = 0.0f;
|
||||
float radium = 0.0f;
|
||||
float actinium = 0.0f;
|
||||
float thorium = 0.0f;
|
||||
float protactinium = 0.0f;
|
||||
float uranium = 0.0f;
|
||||
float neptunium = 0.0f;
|
||||
float plutonium = 0.0f;
|
||||
float americium = 0.0f;
|
||||
float curium = 0.0f;
|
||||
float berkelium = 0.0f;
|
||||
float californium = 0.0f;
|
||||
float einsteinium = 0.0f;
|
||||
float fermium = 0.0f;
|
||||
float mendelevium = 0.0f;
|
||||
float nobelium = 0.0f;
|
||||
float lawrencium = 0.0f;
|
||||
float rutherfordium = 0.0f;
|
||||
float dubnium = 0.0f;
|
||||
float seaborgium = 0.0f;
|
||||
float bohrium = 0.0f;
|
||||
float hassium = 0.0f;
|
||||
float meitnerium = 0.0f;
|
||||
float darmstadtium = 0.0f;
|
||||
float roentgenium = 0.0f;
|
||||
float cpernicium = 0.0f;
|
||||
float nihnium = 0.0f;
|
||||
float flerovium = 0.0f;
|
||||
float moscovium = 0.0f;
|
||||
float livermorium = 0.0f;
|
||||
float tennessine = 0.0f;
|
||||
float oganesson = 0.0f;
|
||||
};
|
||||
|
||||
#endif
|
||||
1026
util/sim/planet.hpp
1026
util/sim/planet.hpp
File diff suppressed because it is too large
Load Diff
124
util/tdgame/customjson.hpp
Normal file
124
util/tdgame/customjson.hpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#ifndef TDGAME_CJ_HPP
|
||||
#define TDGAME_CJ_HPP
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct customJson {
|
||||
struct Node {
|
||||
std::variant<std::nullptr_t, bool, double, std::string, std::vector<Node>, std::map<std::string, Node>> value;
|
||||
|
||||
Node() : value(nullptr) {}
|
||||
Node(bool b) : value(b) {}
|
||||
Node(double d) : value(d) {}
|
||||
Node(const std::string& s) : value(s) {}
|
||||
Node(const char* s) : value(std::string(s)) {}
|
||||
Node(std::vector<Node> a) : value(a) {}
|
||||
Node(std::map<std::string, Node> o) : value(o) {}
|
||||
|
||||
// Accessors with type checking
|
||||
const std::map<std::string, Node>& as_object() const { return std::get<std::map<std::string, Node>>(value); }
|
||||
const std::vector<Node>& as_array() const { return std::get<std::vector<Node>>(value); }
|
||||
const std::string& as_string() const { return std::get<std::string>(value); }
|
||||
double as_double() const { return std::get<double>(value); }
|
||||
bool as_bool() const { return std::get<bool>(value); }
|
||||
|
||||
bool is_null() const { return std::holds_alternative<std::nullptr_t>(value); }
|
||||
|
||||
// Convenience accessor
|
||||
const Node& at(const std::string& key) const { return as_object().at(key); }
|
||||
bool contains(const std::string& key) const { return as_object().count(key); }
|
||||
};
|
||||
static void skip_whitespace(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
while (it != end && isspace(*it)) ++it;
|
||||
}
|
||||
|
||||
static std::string parse_string(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
std::string result;
|
||||
if (*it == '"') ++it;
|
||||
while (it != end && *it != '"') {
|
||||
if (*it == '\\') { // Handle basic escapes
|
||||
++it;
|
||||
if (it != end) result += *it;
|
||||
} else {
|
||||
result += *it;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
if (it != end && *it == '"') ++it;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Node parse_number_or_literal(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
std::string literal;
|
||||
while (it != end && (isalnum(*it) || *it == '.' || *it == '-')) {
|
||||
literal += *it;
|
||||
++it;
|
||||
}
|
||||
if (literal == "true") return Node(true);
|
||||
if (literal == "false") return Node(false);
|
||||
if (literal == "null") return Node(nullptr);
|
||||
try {
|
||||
return Node(std::stod(literal));
|
||||
} catch (...) {
|
||||
throw std::runtime_error("Invalid number or literal: " + literal);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<Node> parse_array(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
std::vector<Node> arr;
|
||||
if (*it == '[') ++it;
|
||||
skip_whitespace(it, end);
|
||||
while (it != end && *it != ']') {
|
||||
arr.push_back(parse_node(it, end));
|
||||
skip_whitespace(it, end);
|
||||
if (it != end && *it == ',') {
|
||||
++it;
|
||||
skip_whitespace(it, end);
|
||||
}
|
||||
}
|
||||
if (it != end && *it == ']') ++it;
|
||||
return arr;
|
||||
}
|
||||
|
||||
static std::map<std::string, Node> parse_object(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
std::map<std::string, Node> obj;
|
||||
if (*it == '{') ++it;
|
||||
skip_whitespace(it, end);
|
||||
while (it != end && *it != '}') {
|
||||
std::string key = parse_string(it, end);
|
||||
skip_whitespace(it, end);
|
||||
if (it != end && *it == ':') ++it;
|
||||
skip_whitespace(it, end);
|
||||
obj[key] = parse_node(it, end);
|
||||
skip_whitespace(it, end);
|
||||
if (it != end && *it == ',') {
|
||||
++it;
|
||||
skip_whitespace(it, end);
|
||||
}
|
||||
}
|
||||
if (it != end && *it == '}') ++it;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static Node parse_node(std::string::const_iterator& it, const std::string::const_iterator& end) {
|
||||
skip_whitespace(it, end);
|
||||
if (it == end) throw std::runtime_error("Unexpected end of input");
|
||||
switch (*it) {
|
||||
case '{': return Node(parse_object(it, end));
|
||||
case '[': return Node(parse_array(it, end));
|
||||
case '"': return Node(parse_string(it, end));
|
||||
default: return parse_number_or_literal(it, end);
|
||||
}
|
||||
}
|
||||
|
||||
static Node parse(const std::string& json_str) {
|
||||
auto it = json_str.cbegin();
|
||||
return parse_node(it, json_str.cend());
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
240
util/tdgame/enemy.hpp
Normal file
240
util/tdgame/enemy.hpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#ifndef ENEMY_HPP
|
||||
#define ENEMY_HPP
|
||||
|
||||
#include "../grid/mesh.hpp"
|
||||
#include "customjson.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
// Forward declaration
|
||||
class Enemy;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
enum EnemyType { GROUND, AIR };
|
||||
|
||||
// Holds the prototype data for a type of enemy, loaded from JSON.
|
||||
struct EnemyPrototype {
|
||||
std::string typeId;
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
float maxHp = 100.0f;
|
||||
float speed = 1.0f;
|
||||
int baseDamage = 1;
|
||||
int reward = 5;
|
||||
EnemyType type = EnemyType::GROUND;
|
||||
std::vector<std::string> abilities;
|
||||
};
|
||||
|
||||
// Represents a single active enemy instance in the game.
|
||||
class Enemy {
|
||||
private:
|
||||
std::vector<Vector3f> _path;
|
||||
size_t _pathIndex = 0;
|
||||
bool _isAlive = true;
|
||||
|
||||
public:
|
||||
int instanceId;
|
||||
std::string typeId;
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
Vector3f position;
|
||||
float maxHp;
|
||||
float hp;
|
||||
float speed;
|
||||
int baseDamage;
|
||||
int reward;
|
||||
EnemyType type;
|
||||
|
||||
Enemy(int instId, const EnemyPrototype& proto, const Vector3f& startPos)
|
||||
: instanceId(instId),
|
||||
typeId(proto.typeId),
|
||||
mesh(std::make_shared<Mesh>(*proto.mesh)), // Deep copy mesh to allow individual modifications (e.g., color)
|
||||
position(startPos),
|
||||
maxHp(proto.maxHp),
|
||||
hp(proto.maxHp),
|
||||
speed(proto.speed),
|
||||
baseDamage(proto.baseDamage),
|
||||
reward(proto.reward),
|
||||
type(proto.type)
|
||||
{
|
||||
if (mesh) {
|
||||
mesh->setSubId(instId);
|
||||
mesh->translate(position);
|
||||
}
|
||||
}
|
||||
|
||||
// A* pathfinding to get to the base
|
||||
void setPath(const std::vector<Vector3f>& newPath) {
|
||||
_path = newPath;
|
||||
_pathIndex = 0;
|
||||
}
|
||||
|
||||
const std::vector<Vector3f>& getPath() const {
|
||||
return _path;
|
||||
}
|
||||
|
||||
// Moves the enemy along its path. Returns true if it reached the end.
|
||||
bool update(float deltaTime) {
|
||||
if (!_isAlive || _path.empty() || _pathIndex >= _path.size()) {
|
||||
return _pathIndex >= _path.size();
|
||||
}
|
||||
|
||||
Vector3f target = _path[_pathIndex];
|
||||
Vector3f direction = target - position;
|
||||
float distanceToTarget = direction.norm();
|
||||
|
||||
float moveDist = speed * deltaTime;
|
||||
|
||||
if (moveDist >= distanceToTarget) {
|
||||
position = target;
|
||||
_pathIndex++;
|
||||
if (_pathIndex >= _path.size()) {
|
||||
// Reached the base
|
||||
_isAlive = false;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
position += direction.normalized() * moveDist;
|
||||
}
|
||||
|
||||
if (mesh) {
|
||||
// This is inefficient. A better approach would be to update a transform matrix.
|
||||
// For now, we recreate the mesh at the new position.
|
||||
auto original_verts = mesh->vertices(); // assuming this mesh is a prototype
|
||||
for (auto& v : original_verts) {
|
||||
v += position;
|
||||
}
|
||||
mesh->vertices(original_verts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void takeDamage(float amount) {
|
||||
if (!_isAlive) return;
|
||||
hp -= amount;
|
||||
if (hp <= 0) {
|
||||
hp = 0;
|
||||
_isAlive = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isAlive() const {
|
||||
return _isAlive;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Manages storage and retrieval of enemy types from JSON definitions.
|
||||
class EnemyRegistry {
|
||||
private:
|
||||
std::map<std::string, EnemyPrototype> _prototypes;
|
||||
int _nextInstanceId = 0;
|
||||
|
||||
EnemyRegistry() = default;
|
||||
|
||||
template<typename T>
|
||||
T get_value(const std::map<std::string, customJson::Node>& obj, const std::string& key, T default_val) {
|
||||
if (!obj.count(key) || obj.at(key).is_null()) return default_val;
|
||||
const auto& node = obj.at(key);
|
||||
if constexpr (std::is_same_v<T, bool>) return node.as_bool();
|
||||
if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, int>) return static_cast<T>(node.as_double());
|
||||
if constexpr (std::is_same_v<T, std::string>) return node.as_string();
|
||||
return default_val;
|
||||
};
|
||||
|
||||
void parseEnemyJson(const std::map<std::string, customJson::Node>& j) {
|
||||
EnemyPrototype p;
|
||||
p.typeId = get_value(j, "id", std::string("unknown"));
|
||||
p.maxHp = get_value<float>(j, "maxHp", 100.0f);
|
||||
p.speed = get_value<float>(j, "speed", 1.0f);
|
||||
p.baseDamage = get_value<int>(j, "baseDamage", 1);
|
||||
p.reward = get_value<int>(j, "reward", 5);
|
||||
|
||||
std::string typeStr = get_value(j, "type", std::string("GROUND"));
|
||||
if (typeStr == "AIR") {
|
||||
p.type = EnemyType::AIR;
|
||||
} else {
|
||||
p.type = EnemyType::GROUND;
|
||||
}
|
||||
|
||||
if (j.count("abilities")) {
|
||||
for (const auto& ability_node : j.at("abilities").as_array()) {
|
||||
p.abilities.push_back(ability_node.as_string());
|
||||
}
|
||||
}
|
||||
|
||||
std::string mesh_path = get_value(j, "mesh_path", std::string(""));
|
||||
if (!mesh_path.empty()) {
|
||||
p.mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
if (!p.mesh->load(mesh_path)) {
|
||||
std::cerr << "Warning: Failed to load mesh '" << mesh_path << "' for enemy '" << p.typeId << "'." << std::endl;
|
||||
p.mesh = nullptr; // Invalidate if load fails
|
||||
}
|
||||
}
|
||||
|
||||
if (_prototypes.count(p.typeId)) {
|
||||
std::cerr << "Warning: Duplicate enemy ID '" << p.typeId << "' found. Overwriting." << std::endl;
|
||||
}
|
||||
_prototypes[p.typeId] = p;
|
||||
}
|
||||
|
||||
void loadEnemyFile(const std::string& filepath) {
|
||||
std::ifstream f(filepath);
|
||||
if (!f.is_open()) return;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
|
||||
try {
|
||||
customJson::Node root = customJson::parse(buffer.str());
|
||||
if (const auto* arr = std::get_if<std::vector<customJson::Node>>(&root.value)) {
|
||||
for (const auto& item : *arr) {
|
||||
parseEnemyJson(item.as_object());
|
||||
}
|
||||
} else if (const auto* obj = std::get_if<std::map<std::string, customJson::Node>>(&root.value)) {
|
||||
parseEnemyJson(*obj);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static EnemyRegistry& getInstance() {
|
||||
static EnemyRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
EnemyRegistry(const EnemyRegistry&) = delete;
|
||||
void operator=(const EnemyRegistry&) = delete;
|
||||
|
||||
void loadFromDirectory(const std::string& path) {
|
||||
if (!fs::exists(path)) {
|
||||
std::cerr << "EnemyRegistry: Directory " << path << " does not exist." << std::endl;
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
loadEnemyFile(entry.path().string());
|
||||
}
|
||||
}
|
||||
std::cout << "EnemyRegistry: Loaded " << _prototypes.size() << " enemy definitions." << std::endl;
|
||||
}
|
||||
|
||||
std::unique_ptr<Enemy> createEnemy(const std::string& typeId, const Vector3f& startPosition) {
|
||||
if (_prototypes.count(typeId) == 0) {
|
||||
std::cerr << "Error: Attempted to create unknown enemy type: " << typeId << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& proto = _prototypes.at(typeId);
|
||||
return std::make_unique<Enemy>(_nextInstanceId++, proto, startPosition);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
68
util/tdgame/game_utils.hpp
Normal file
68
util/tdgame/game_utils.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef GAME_UTILS_HPP
|
||||
#define GAME_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include "../../eigen/Eigen/Dense"
|
||||
|
||||
using Vector3f = Eigen::Vector3f;
|
||||
|
||||
class FileUtils {
|
||||
public:
|
||||
static std::string readFile(const std::string& path) {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open file: " << path << std::endl;
|
||||
return "{}"; // Return empty JSON obj on fail
|
||||
}
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleJsonParser {
|
||||
public:
|
||||
static std::map<std::string, std::string> parseDepth1(const std::string& raw) {
|
||||
std::map<std::string, std::string> result;
|
||||
std::string clean = raw;
|
||||
// Remove braces and quotes
|
||||
clean.erase(std::remove(clean.begin(), clean.end(), '{'), clean.end());
|
||||
clean.erase(std::remove(clean.begin(), clean.end(), '}'), clean.end());
|
||||
clean.erase(std::remove(clean.begin(), clean.end(), '\"'), clean.end());
|
||||
|
||||
std::stringstream ss(clean);
|
||||
std::string segment;
|
||||
while(std::getline(ss, segment, ',')) {
|
||||
size_t colonPos = segment.find(':');
|
||||
if(colonPos != std::string::npos) {
|
||||
std::string key = segment.substr(0, colonPos);
|
||||
std::string val = segment.substr(colonPos + 1);
|
||||
|
||||
// Trim key
|
||||
size_t first = key.find_first_not_of(" \t\n\r");
|
||||
size_t last = key.find_last_not_of(" \t\n\r");
|
||||
if (first != std::string::npos) key = key.substr(first, (last - first + 1));
|
||||
|
||||
// Trim val
|
||||
first = val.find_first_not_of(" \t\n\r");
|
||||
last = val.find_last_not_of(" \t\n\r");
|
||||
if (first != std::string::npos) val = val.substr(first, (last - first + 1));
|
||||
|
||||
result[key] = val;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
333
util/tdgame/map.hpp
Normal file
333
util/tdgame/map.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#ifndef TDGAME_MAP_HPP
|
||||
#define TDGAME_MAP_HPP
|
||||
|
||||
#include "tile.hpp"
|
||||
#include "enemy.hpp"
|
||||
#include "tower.hpp"
|
||||
#include "customjson.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <queue>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// A simple struct to represent a point on the grid.
|
||||
struct GridPoint {
|
||||
int x, z;
|
||||
bool operator==(const GridPoint& other) const { return x == other.x && z == other.z; }
|
||||
bool operator<(const GridPoint& other) const { return x < other.x || (x == other.x && z < other.z); }
|
||||
};
|
||||
|
||||
// Represents the entire game map, managing tiles, entities, and game state.
|
||||
class GameMap {
|
||||
private:
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
std::vector<std::vector<Tile>> _tiles;
|
||||
|
||||
std::vector<std::unique_ptr<Enemy>> _enemies;
|
||||
std::vector<std::unique_ptr<Tower>> _towers;
|
||||
|
||||
std::vector<GridPoint> _spawnPoints;
|
||||
std::vector<GridPoint> _basePoints;
|
||||
GridPoint _primaryBaseTarget; // A single target for pathfinding simplicity
|
||||
|
||||
int _playerHealth = 100;
|
||||
int _currentWave = 0;
|
||||
bool _gameOver = false;
|
||||
|
||||
// Internal helper for A* pathfinding
|
||||
struct PathNode {
|
||||
GridPoint pos;
|
||||
float g_cost = 0; // Cost from start
|
||||
float h_cost = 0; // Heuristic cost to end
|
||||
GridPoint parent;
|
||||
|
||||
float f_cost() const { return g_cost + h_cost; }
|
||||
|
||||
// For priority queue ordering
|
||||
bool operator>(const PathNode& other) const { return f_cost() > other.f_cost(); }
|
||||
};
|
||||
|
||||
public:
|
||||
GameMap() = default;
|
||||
int _playerMoney = 250;
|
||||
|
||||
// --- Primary Methods ---
|
||||
|
||||
// Loads a map definition from a JSON file.
|
||||
bool loadFromFile(const std::string& filepath) {
|
||||
std::ifstream f(filepath);
|
||||
if (!f.is_open()) {
|
||||
std::cerr << "GameMap: Failed to open map file: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
|
||||
try {
|
||||
customJson::Node root = customJson::parse(buffer.str());
|
||||
const auto& j = root.as_object();
|
||||
|
||||
_width = j.at("width").as_double();
|
||||
_height = j.at("height").as_double();
|
||||
_playerHealth = j.at("start_health").as_double();
|
||||
_playerMoney = j.at("start_money").as_double();
|
||||
|
||||
const auto& key_obj = j.at("tile_key").as_object();
|
||||
std::map<char, std::string> tileKey;
|
||||
for(const auto& pair : key_obj) {
|
||||
tileKey[pair.first[0]] = pair.second.as_string();
|
||||
}
|
||||
|
||||
const auto& layout = j.at("layout").as_array();
|
||||
_tiles.assign(_height, std::vector<Tile>(_width));
|
||||
_spawnPoints.clear();
|
||||
_basePoints.clear();
|
||||
|
||||
for (int z = 0; z < _height; ++z) {
|
||||
std::string row_str = layout[z].as_string();
|
||||
std::stringstream row_stream(row_str);
|
||||
char tileChar;
|
||||
for (int x = 0; x < _width; ++x) {
|
||||
row_stream >> tileChar;
|
||||
if (tileKey.count(tileChar)) {
|
||||
std::string tileId = tileKey.at(tileChar);
|
||||
_tiles[z][x] = TileRegistry::getInstance().createTile(tileId, x, z);
|
||||
|
||||
if (_tiles[z][x].type == TileType::SPAWN) {
|
||||
_spawnPoints.push_back({x, z});
|
||||
} else if (_tiles[z][x].type == TileType::BASE) {
|
||||
_basePoints.push_back({x, z});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_basePoints.empty()) {
|
||||
_primaryBaseTarget = _basePoints[0]; // Simple pathfinding target
|
||||
} else {
|
||||
std::cerr << "GameMap: Warning, map has no base tiles defined." << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "GameMap: JSON parse error in " << filepath << ": " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "GameMap: Successfully loaded map '" << filepath << "'." << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The main game loop tick function.
|
||||
void update(float deltaTime) {
|
||||
if (_gameOver) return;
|
||||
|
||||
handleSpawning(deltaTime);
|
||||
|
||||
for (auto& tower : _towers) {
|
||||
tower->update(deltaTime, _enemies);
|
||||
}
|
||||
|
||||
for (auto& enemy : _enemies) {
|
||||
if (enemy->update(deltaTime)) {
|
||||
// Enemy reached the base
|
||||
_playerHealth -= enemy->baseDamage;
|
||||
if (_playerHealth <= 0) {
|
||||
_playerHealth = 0;
|
||||
_gameOver = true;
|
||||
std::cout << "Game Over!" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup dead/finished enemies
|
||||
auto initialSize = _enemies.size();
|
||||
_enemies.erase(std::remove_if(_enemies.begin(), _enemies.end(),
|
||||
[this](const std::unique_ptr<Enemy>& e) {
|
||||
if (!e->isAlive()) {
|
||||
if (e->hp <= 0) { // Died to a tower
|
||||
this->_playerMoney += e->reward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
_enemies.end());
|
||||
}
|
||||
|
||||
// Handles player action to build a tower.
|
||||
bool buildTower(const std::string& towerId, int x, int z) {
|
||||
if (isOutOfBounds(x, z) || !_tiles[z][x].isBuildable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const TowerPrototype* proto = TowerRegistry::getInstance().getPrototype(towerId);
|
||||
if (!proto || _playerMoney < proto->baseCost) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_playerMoney -= proto->baseCost;
|
||||
Vector3f position(static_cast<float>(x), 0.0f, static_cast<float>(z));
|
||||
auto newTower = TowerRegistry::getInstance().createTower(towerId, position);
|
||||
if (newTower) {
|
||||
_towers.push_back(std::move(newTower));
|
||||
// Mark tile as occupied - a simple approach
|
||||
_tiles[z][x].type = TileType::SPECIAL; // Mark as non-buildable
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculates a path from start to end using A*.
|
||||
std::vector<Vector3f> findPath(GridPoint start, GridPoint end, EnemyType enemyType) {
|
||||
std::vector<Vector3f> path;
|
||||
std::priority_queue<PathNode, std::vector<PathNode>, std::greater<PathNode>> openSet;
|
||||
std::map<GridPoint, PathNode> allNodes;
|
||||
|
||||
PathNode startNode;
|
||||
startNode.pos = start;
|
||||
startNode.g_cost = 0;
|
||||
startNode.h_cost = std::abs(start.x - end.x) + std::abs(start.z - end.z); // Manhattan distance
|
||||
openSet.push(startNode);
|
||||
allNodes[start] = startNode;
|
||||
|
||||
GridPoint neighbors[] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // 4-directional movement
|
||||
|
||||
while (!openSet.empty()) {
|
||||
PathNode current = openSet.top();
|
||||
openSet.pop();
|
||||
|
||||
if (current.pos == end) {
|
||||
// Reconstruct path
|
||||
GridPoint temp = current.pos;
|
||||
while (!(temp == start)) {
|
||||
path.push_back(Vector3f(temp.x, 0, temp.z));
|
||||
temp = allNodes.at(temp).parent;
|
||||
}
|
||||
path.push_back(Vector3f(start.x, 0, start.z));
|
||||
std::reverse(path.begin(), path.end());
|
||||
return path;
|
||||
}
|
||||
|
||||
for (const auto& offset : neighbors) {
|
||||
GridPoint neighborPos = {current.pos.x + offset.x, current.pos.z + offset.z};
|
||||
|
||||
if (isOutOfBounds(neighborPos.x, neighborPos.z) || !isTilePassable(neighborPos.x, neighborPos.z, enemyType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float new_g_cost = current.g_cost + 1.0f; // Assuming cost is 1 per tile for now
|
||||
|
||||
if (allNodes.find(neighborPos) == allNodes.end() || new_g_cost < allNodes[neighborPos].g_cost) {
|
||||
PathNode neighborNode;
|
||||
neighborNode.pos = neighborPos;
|
||||
neighborNode.parent = current.pos;
|
||||
neighborNode.g_cost = new_g_cost;
|
||||
neighborNode.h_cost = std::abs(neighborPos.x - end.x) + std::abs(neighborPos.z - end.z);
|
||||
allNodes[neighborPos] = neighborNode;
|
||||
openSet.push(neighborNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {}; // Return empty path if none found
|
||||
}
|
||||
|
||||
// --- Accessors ---
|
||||
int getWidth() const { return _width; }
|
||||
int getHeight() const { return _height; }
|
||||
int getPlayerHealth() const { return _playerHealth; }
|
||||
int getPlayerMoney() const { return _playerMoney; }
|
||||
bool isGameOver() const { return _gameOver; }
|
||||
const Tile& getTile(int x, int z) const { return _tiles[z][x]; }
|
||||
const std::vector<std::unique_ptr<Enemy>>& getEnemies() const { return _enemies; }
|
||||
const std::vector<std::unique_ptr<Tower>>& getTowers() const { return _towers; }
|
||||
|
||||
|
||||
private:
|
||||
// --- Private Helpers ---
|
||||
|
||||
void handleSpawning(float deltaTime) {
|
||||
for (const auto& sp : _spawnPoints) {
|
||||
Tile& spawnTile = _tiles[sp.z][sp.x];
|
||||
if (!spawnTile.spawn.has_value()) continue;
|
||||
|
||||
SpawnProperties& props = *spawnTile.spawn;
|
||||
if (props.currentWaveIndex >= props.waves.size()) {
|
||||
if(props.loopWaves) {
|
||||
// Loop waves with scaling
|
||||
props.currentWaveIndex = 0;
|
||||
for(auto& wave : props.waves) {
|
||||
wave.healthMult += props.loopHealthScaler;
|
||||
wave.speedMult += props.loopSpeedScaler;
|
||||
}
|
||||
} else {
|
||||
continue; // No more waves
|
||||
}
|
||||
}
|
||||
|
||||
WaveDefinition& currentWave = props.waves[props.currentWaveIndex];
|
||||
// Simple logic: spawn one enemy per interval if count > 0
|
||||
// A more robust system would use a timer.
|
||||
static float spawnCooldown = 0.0f;
|
||||
spawnCooldown -= deltaTime;
|
||||
|
||||
if (spawnCooldown <= 0 && currentWave.count > 0) {
|
||||
spawnEnemy(currentWave, sp);
|
||||
currentWave.count--;
|
||||
spawnCooldown = currentWave.interval;
|
||||
|
||||
if (currentWave.count <= 0) {
|
||||
props.currentWaveIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spawnEnemy(const WaveDefinition& waveDef, const GridPoint& spawnPos) {
|
||||
Vector3f startPosition(static_cast<float>(spawnPos.x), 0.0f, static_cast<float>(spawnPos.z));
|
||||
auto enemy = EnemyRegistry::getInstance().createEnemy(waveDef.enemyId, startPosition);
|
||||
if (!enemy) return;
|
||||
|
||||
// Apply wave multipliers
|
||||
enemy->maxHp *= waveDef.healthMult;
|
||||
enemy->hp = enemy->maxHp;
|
||||
enemy->speed *= waveDef.speedMult;
|
||||
enemy->reward = static_cast<int>(enemy->reward * waveDef.rewardMult);
|
||||
|
||||
auto path = findPath(spawnPos, _primaryBaseTarget, enemy->type);
|
||||
if (path.empty()) {
|
||||
std::cerr << "GameMap: Could not find path for enemy " << waveDef.enemyId << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
enemy->setPath(path);
|
||||
_enemies.push_back(std::move(enemy));
|
||||
}
|
||||
|
||||
bool isOutOfBounds(int x, int z) const {
|
||||
return x < 0 || x >= _width || z < 0 || z >= _height;
|
||||
}
|
||||
|
||||
bool isTilePassable(int x, int z, EnemyType type) const {
|
||||
const auto& tile = _tiles[z][x];
|
||||
if (tile.path.has_value()) {
|
||||
return (type == EnemyType::GROUND && tile.path->isGroundPath) || (type == EnemyType::AIR && tile.path->isFlyingPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
222
util/tdgame/tile.hpp
Normal file
222
util/tdgame/tile.hpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#ifndef TDGAME_TILE_HPP
|
||||
#define TDGAME_TILE_HPP
|
||||
|
||||
#include "../grid/mesh.hpp"
|
||||
#include "enemy.hpp"
|
||||
#include "customjson.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <variant>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct PathProperties {
|
||||
float speedMultiplier = 1.0f;
|
||||
std::vector<std::string> effects;
|
||||
bool isFlyingPath = true;
|
||||
bool isGroundPath = true;
|
||||
};
|
||||
|
||||
struct WallProperties {
|
||||
bool blocksGround = true;
|
||||
bool blocksAir = false;
|
||||
bool blocksProjectiles = true;
|
||||
std::vector<std::string> whitelist;
|
||||
};
|
||||
|
||||
struct BaseProperties {
|
||||
float healthBonus = 0.0f;
|
||||
float defenseBonus = 0.0f;
|
||||
int levelRequired = 0;
|
||||
};
|
||||
|
||||
struct WaveDefinition {
|
||||
std::string enemyId;
|
||||
int count;
|
||||
float interval;
|
||||
float healthMult = 1.0f;
|
||||
float speedMult = 1.0f;
|
||||
float rewardMult = 1.0f;
|
||||
};
|
||||
|
||||
struct SpawnProperties {
|
||||
std::vector<WaveDefinition> waves;
|
||||
bool loopWaves = true;
|
||||
float loopHealthScaler = 0.1f;
|
||||
float loopSpeedScaler = 0.05f;
|
||||
int currentWaveIndex = 0;
|
||||
};
|
||||
|
||||
struct TowerBaseProperties {
|
||||
float rangeMultiplier = 1.0f;
|
||||
float damageMultiplier = 1.0f;
|
||||
float fireRateMultiplier = 1.0f;
|
||||
std::vector<std::string> allowedTowerTypes;
|
||||
};
|
||||
|
||||
enum TileType { EMPTY, PATH, WALL, BASE, SPAWN, TOWER_BASE, MULTI, SPECIAL };
|
||||
|
||||
struct Tile {
|
||||
int x = 0;
|
||||
int z = 0;
|
||||
std::string id = "void";
|
||||
TileType type = TileType::EMPTY;
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
std::optional<PathProperties> path;
|
||||
std::optional<WallProperties> wall;
|
||||
std::optional<BaseProperties> base;
|
||||
std::optional<SpawnProperties> spawn;
|
||||
std::optional<TowerBaseProperties> towerBase;
|
||||
std::map<std::string, float> specialParams;
|
||||
Tile() = default;
|
||||
bool isWalkable() const {
|
||||
return path.has_value() && path->isGroundPath;
|
||||
}
|
||||
bool isBuildable() const {
|
||||
return towerBase.has_value() || type == TileType::EMPTY;
|
||||
}
|
||||
void setMeshColor(Color c) {
|
||||
if(mesh) mesh->colors({c});
|
||||
}
|
||||
};
|
||||
|
||||
class TileRegistry {
|
||||
private:
|
||||
std::map<std::string, Tile> _prototypes;
|
||||
public:
|
||||
static TileRegistry& getInstance() {
|
||||
static TileRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void loadFromDirectory(const std::string& path) {
|
||||
if (!fs::exists(path)) {
|
||||
std::cerr << "TileRegistry: Directory " << path << " does not exist." << std::endl;
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
loadTileFile(entry.path().string());
|
||||
}
|
||||
}
|
||||
std::cout << "TileRegistry: Loaded " << _prototypes.size() << " tile definitions." << std::endl;
|
||||
}
|
||||
|
||||
Tile createTile(const std::string& id, int x, int z) {
|
||||
if (_prototypes.count(id)) {
|
||||
Tile t = _prototypes.at(id);
|
||||
t.x = x;
|
||||
t.z = z;
|
||||
return t;
|
||||
}
|
||||
Tile t;
|
||||
t.x = x;
|
||||
t.z = z;
|
||||
t.id = "error";
|
||||
std::cerr << "TileRegistry: Warning, requested unknown tile ID: " << id << std::endl;
|
||||
return t;
|
||||
}
|
||||
private:
|
||||
void loadTileFile(const std::string& filepath) {
|
||||
std::ifstream f(filepath);
|
||||
if (!f.is_open()) return;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
|
||||
try {
|
||||
customJson::Node root = customJson::parse(content);
|
||||
if (const auto* arr = std::get_if<std::vector<customJson::Node>>(&root.value)) {
|
||||
for (const auto& item : *arr) {
|
||||
parseTileJson(item.as_object());
|
||||
}
|
||||
} else if (const auto* obj = std::get_if<std::map<std::string, customJson::Node>>(&root.value)) {
|
||||
parseTileJson(*obj);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get_value(const std::map<std::string, customJson::Node>& obj, const std::string& key, T default_val) {
|
||||
if (!obj.count(key) || obj.at(key).is_null()) return default_val;
|
||||
const auto& node = obj.at(key);
|
||||
if constexpr (std::is_same_v<T, bool>) return node.as_bool();
|
||||
if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, int>) return static_cast<T>(node.as_double());
|
||||
if constexpr (std::is_same_v<T, std::string>) return node.as_string();
|
||||
return default_val;
|
||||
};
|
||||
|
||||
void parseTileJson(const std::map<std::string, customJson::Node>& j) {
|
||||
Tile t;
|
||||
t.id = get_value(j, "id", std::string("unknown"));
|
||||
std::string typeStr = get_value(j, "type", std::string("empty"));
|
||||
|
||||
if (typeStr == "path") t.type = TileType::PATH;
|
||||
else if (typeStr == "wall") t.type = TileType::WALL;
|
||||
else if (typeStr == "base") t.type = TileType::BASE;
|
||||
else if (typeStr == "spawn") t.type = TileType::SPAWN;
|
||||
else if (typeStr == "tower_base") t.type = TileType::TOWER_BASE;
|
||||
else if (typeStr == "multi") t.type = TileType::MULTI;
|
||||
else if (typeStr == "special") t.type = TileType::SPECIAL;
|
||||
else t.type = TileType::EMPTY;
|
||||
|
||||
if (j.count("path")) {
|
||||
const auto& p_obj = j.at("path").as_object();
|
||||
PathProperties p;
|
||||
p.speedMultiplier = get_value<float>(p_obj, "speed_mult", 1.0f);
|
||||
p.isGroundPath = get_value<bool>(p_obj, "ground", true);
|
||||
p.isFlyingPath = get_value<bool>(p_obj, "air", true);
|
||||
if(p_obj.count("effects")) {
|
||||
for(const auto& effect_node : p_obj.at("effects").as_array()) {
|
||||
p.effects.push_back(effect_node.as_string());
|
||||
}
|
||||
}
|
||||
t.path = p;
|
||||
}
|
||||
|
||||
if (j.count("wall")) {
|
||||
const auto& w_obj = j.at("wall").as_object();
|
||||
WallProperties w;
|
||||
w.blocksGround = get_value<bool>(w_obj, "block_ground", true);
|
||||
w.blocksAir = get_value<bool>(w_obj, "block_air", false);
|
||||
t.wall = w;
|
||||
}
|
||||
|
||||
if (j.count("spawn")) {
|
||||
const auto& s_obj = j.at("spawn").as_object();
|
||||
SpawnProperties sp;
|
||||
sp.loopWaves = get_value<bool>(s_obj, "loop", true);
|
||||
sp.loopHealthScaler = get_value<float>(s_obj, "loop_hp_scale", 0.1f);
|
||||
if (s_obj.count("waves")) {
|
||||
for (const auto& w_node : s_obj.at("waves").as_array()) {
|
||||
const auto& wj = w_node.as_object();
|
||||
WaveDefinition wd;
|
||||
wd.enemyId = get_value<std::string>(wj, "enemy_id", "grunt");
|
||||
wd.count = get_value<int>(wj, "count", 5);
|
||||
wd.interval = get_value<float>(wj, "interval", 1.0f);
|
||||
wd.healthMult = get_value<float>(wj, "hp_mult", 1.0f);
|
||||
sp.waves.push_back(wd);
|
||||
}
|
||||
}
|
||||
t.spawn = sp;
|
||||
}
|
||||
|
||||
if (_prototypes.count(t.id)) {
|
||||
std::cerr << "Warning: Duplicate tile ID '" << t.id << "' found. Overwriting." << std::endl;
|
||||
}
|
||||
_prototypes[t.id] = t;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
317
util/tdgame/tower.hpp
Normal file
317
util/tdgame/tower.hpp
Normal file
@@ -0,0 +1,317 @@
|
||||
#ifndef TDGAME_TOWER_HPP
|
||||
#define TDGAME_TOWER_HPP
|
||||
|
||||
#include "../grid/mesh.hpp"
|
||||
#include "enemy.hpp"
|
||||
#include "customjson.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Forward declaration
|
||||
class Tower;
|
||||
|
||||
enum class TargetingPriority {
|
||||
FIRST, // Enemy furthest along the path
|
||||
LAST, // Enemy least far along the path
|
||||
CLOSEST, // Enemy closest to the tower
|
||||
STRONGEST, // Enemy with the highest max HP
|
||||
WEAKEST // Enemy with the lowest current HP
|
||||
};
|
||||
|
||||
// Represents a single upgrade level for a tower
|
||||
struct TowerUpgrade {
|
||||
int cost = 50;
|
||||
float rangeBonus = 1.0f;
|
||||
float damageBonus = 5.0f;
|
||||
float fireRateBonus = 0.2f;
|
||||
};
|
||||
|
||||
// Holds the prototype data for a type of tower, loaded from JSON.
|
||||
struct TowerPrototype {
|
||||
std::string typeId;
|
||||
std::string name;
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
|
||||
int baseCost = 100;
|
||||
float baseRange = 10.0f;
|
||||
float baseDamage = 10.0f;
|
||||
float baseFireRate = 1.0f; // shots per second
|
||||
|
||||
TargetingPriority targetingPriority = TargetingPriority::FIRST;
|
||||
std::string projectileTypeId; // For a future projectile system
|
||||
std::vector<TowerUpgrade> upgrades;
|
||||
};
|
||||
|
||||
// Represents a single active tower instance in the game.
|
||||
class Tower {
|
||||
private:
|
||||
int _level = 1;
|
||||
float _fireCooldown = 0.0f;
|
||||
Enemy* _target = nullptr; // Raw pointer to the target, validated each frame
|
||||
|
||||
public:
|
||||
int instanceId;
|
||||
std::string typeId;
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
Vector3f position;
|
||||
|
||||
// Current stats (including upgrades)
|
||||
float range;
|
||||
float damage;
|
||||
float fireRate;
|
||||
int cost;
|
||||
|
||||
TargetingPriority targetingPriority;
|
||||
|
||||
Tower(int instId, const TowerPrototype& proto, const Vector3f& pos)
|
||||
: instanceId(instId),
|
||||
typeId(proto.typeId),
|
||||
position(pos),
|
||||
range(proto.baseRange),
|
||||
damage(proto.baseDamage),
|
||||
fireRate(proto.baseFireRate),
|
||||
cost(proto.baseCost),
|
||||
targetingPriority(proto.targetingPriority)
|
||||
{
|
||||
if (proto.mesh) {
|
||||
mesh = std::make_shared<Mesh>(*proto.mesh); // Deep copy for individual manipulation
|
||||
mesh->setSubId(instId);
|
||||
mesh->translate(position);
|
||||
}
|
||||
}
|
||||
|
||||
// Main logic loop for targeting and firing
|
||||
void update(float deltaTime, const std::vector<std::unique_ptr<Enemy>>& enemies) {
|
||||
_fireCooldown -= deltaTime;
|
||||
|
||||
bool targetIsValid = false;
|
||||
if (_target != nullptr) {
|
||||
if (_target->isAlive() && (_target->position - position).norm() <= range) {
|
||||
targetIsValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetIsValid) {
|
||||
_target = nullptr;
|
||||
findTarget(enemies);
|
||||
}
|
||||
|
||||
if (_target != nullptr && _fireCooldown <= 0.0f) {
|
||||
// "Fire" at the target. A real implementation would create a projectile.
|
||||
_target->takeDamage(damage);
|
||||
|
||||
// Reset cooldown
|
||||
_fireCooldown = 1.0f / fireRate;
|
||||
}
|
||||
}
|
||||
|
||||
// Finds a new target based on the tower's targeting priority
|
||||
void findTarget(const std::vector<std::unique_ptr<Enemy>>& enemies) {
|
||||
_target = nullptr;
|
||||
Enemy* bestTarget = nullptr;
|
||||
float bestMetric = -1.0f; // For FIRST, STRONGEST (higher is better)
|
||||
|
||||
if (targetingPriority == TargetingPriority::CLOSEST || targetingPriority == TargetingPriority::WEAKEST || targetingPriority == TargetingPriority::LAST) {
|
||||
bestMetric = std::numeric_limits<float>::max(); // For priorities where lower is better
|
||||
}
|
||||
|
||||
for (const auto& enemyPtr : enemies) {
|
||||
if (!enemyPtr->isAlive()) continue;
|
||||
|
||||
float distance = (enemyPtr->position - this->position).norm();
|
||||
if (distance > this->range) continue;
|
||||
|
||||
switch (targetingPriority) {
|
||||
case TargetingPriority::FIRST:
|
||||
if (distance < (bestMetric == -1.0f ? std::numeric_limits<float>::max() : bestMetric)) {
|
||||
bestMetric = distance;
|
||||
bestTarget = enemyPtr.get();
|
||||
}
|
||||
break;
|
||||
case TargetingPriority::LAST:
|
||||
if (distance < bestMetric) {
|
||||
bestMetric = distance;
|
||||
bestTarget = enemyPtr.get();
|
||||
}
|
||||
break;
|
||||
case TargetingPriority::CLOSEST:
|
||||
if (distance < bestMetric) {
|
||||
bestMetric = distance;
|
||||
bestTarget = enemyPtr.get();
|
||||
}
|
||||
break;
|
||||
case TargetingPriority::STRONGEST:
|
||||
if (enemyPtr->maxHp > bestMetric) {
|
||||
bestMetric = enemyPtr->maxHp;
|
||||
bestTarget = enemyPtr.get();
|
||||
}
|
||||
break;
|
||||
case TargetingPriority::WEAKEST:
|
||||
if (enemyPtr->hp < bestMetric) {
|
||||
bestMetric = enemyPtr->hp;
|
||||
bestTarget = enemyPtr.get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
_target = bestTarget;
|
||||
}
|
||||
|
||||
// Applies the next available upgrade from the prototype
|
||||
bool upgrade(const TowerPrototype& proto) {
|
||||
// Upgrades are 0-indexed for level 2, 3, etc.
|
||||
// For level 'L', we need upgrade at index 'L-1'.
|
||||
if (_level - 1 < proto.upgrades.size()) {
|
||||
const auto& up = proto.upgrades[_level - 1];
|
||||
|
||||
this->cost += up.cost;
|
||||
this->range += up.rangeBonus;
|
||||
this->damage += up.damageBonus;
|
||||
this->fireRate += up.fireRateBonus;
|
||||
_level++;
|
||||
return true;
|
||||
}
|
||||
return false; // Max level reached
|
||||
}
|
||||
|
||||
int getLevel() const { return _level; }
|
||||
int getSellPrice() const { return static_cast<int>(cost * 0.75f); }
|
||||
const Enemy* getTarget() const { return _target; }
|
||||
};
|
||||
|
||||
|
||||
// Manages storage and retrieval of tower types from JSON definitions.
|
||||
class TowerRegistry {
|
||||
private:
|
||||
std::map<std::string, TowerPrototype> _prototypes;
|
||||
int _nextInstanceId = 0;
|
||||
|
||||
TowerRegistry() = default;
|
||||
|
||||
template<typename T>
|
||||
T get_value(const std::map<std::string, customJson::Node>& obj, const std::string& key, T default_val) {
|
||||
if (!obj.count(key) || obj.at(key).is_null()) return default_val;
|
||||
const auto& node = obj.at(key);
|
||||
if constexpr (std::is_same_v<T, bool>) return node.as_bool();
|
||||
if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float> || std::is_same_v<T, int>) return static_cast<T>(node.as_double());
|
||||
if constexpr (std::is_same_v<T, std::string>) return node.as_string();
|
||||
return default_val;
|
||||
};
|
||||
|
||||
void parseTowerJson(const std::map<std::string, customJson::Node>& j) {
|
||||
TowerPrototype p;
|
||||
p.typeId = get_value(j, "id", std::string("unknown"));
|
||||
p.name = get_value(j, "name", std::string("Unnamed Tower"));
|
||||
p.baseCost = get_value<int>(j, "cost", 100);
|
||||
p.baseRange = get_value<float>(j, "range", 10.0f);
|
||||
p.baseDamage = get_value<float>(j, "damage", 10.0f);
|
||||
p.baseFireRate = get_value<float>(j, "fire_rate", 1.0f);
|
||||
p.projectileTypeId = get_value(j, "projectile_id", std::string(""));
|
||||
|
||||
std::string priorityStr = get_value(j, "targeting", std::string("first"));
|
||||
if (priorityStr == "last") p.targetingPriority = TargetingPriority::LAST;
|
||||
else if (priorityStr == "closest") p.targetingPriority = TargetingPriority::CLOSEST;
|
||||
else if (priorityStr == "strongest") p.targetingPriority = TargetingPriority::STRONGEST;
|
||||
else if (priorityStr == "weakest") p.targetingPriority = TargetingPriority::WEAKEST;
|
||||
else p.targetingPriority = TargetingPriority::FIRST;
|
||||
|
||||
if (j.count("upgrades") && !j.at("upgrades").is_null()) {
|
||||
for (const auto& up_node : j.at("upgrades").as_array()) {
|
||||
const auto& up_obj = up_node.as_object();
|
||||
TowerUpgrade up;
|
||||
up.cost = get_value<int>(up_obj, "cost", 50);
|
||||
up.rangeBonus = get_value<float>(up_obj, "range_bonus", 1.0f);
|
||||
up.damageBonus = get_value<float>(up_obj, "damage_bonus", 5.0f);
|
||||
up.fireRateBonus = get_value<float>(up_obj, "fire_rate_bonus", 0.2f);
|
||||
p.upgrades.push_back(up);
|
||||
}
|
||||
}
|
||||
|
||||
std::string mesh_path = get_value(j, "mesh_path", std::string(""));
|
||||
if (!mesh_path.empty()) {
|
||||
p.mesh = std::make_shared<Mesh>(0, std::vector<Vector3f>{}, std::vector<std::vector<int>>{}, std::vector<Color>{});
|
||||
if (!p.mesh->load(mesh_path)) {
|
||||
std::cerr << "Warning: Failed to load mesh '" << mesh_path << "' for tower '" << p.typeId << "'." << std::endl;
|
||||
p.mesh = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (_prototypes.count(p.typeId)) {
|
||||
std::cerr << "Warning: Duplicate tower ID '" << p.typeId << "' found. Overwriting." << std::endl;
|
||||
}
|
||||
_prototypes[p.typeId] = p;
|
||||
}
|
||||
|
||||
void loadTowerFile(const std::string& filepath) {
|
||||
std::ifstream f(filepath);
|
||||
if (!f.is_open()) return;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << f.rdbuf();
|
||||
|
||||
try {
|
||||
customJson::Node root = customJson::parse(buffer.str());
|
||||
if (const auto* arr = std::get_if<std::vector<customJson::Node>>(&root.value)) {
|
||||
for (const auto& item : *arr) {
|
||||
parseTowerJson(item.as_object());
|
||||
}
|
||||
} else if (const auto* obj = std::get_if<std::map<std::string, customJson::Node>>(&root.value)) {
|
||||
parseTowerJson(*obj);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "JSON Parse error in " << filepath << ": " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static TowerRegistry& getInstance() {
|
||||
static TowerRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
TowerRegistry(const TowerRegistry&) = delete;
|
||||
void operator=(const TowerRegistry&) = delete;
|
||||
|
||||
void loadFromDirectory(const std::string& path) {
|
||||
if (!fs::exists(path)) {
|
||||
std::cerr << "TowerRegistry: Directory " << path << " does not exist." << std::endl;
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
loadTowerFile(entry.path().string());
|
||||
}
|
||||
}
|
||||
std::cout << "TowerRegistry: Loaded " << _prototypes.size() << " tower definitions." << std::endl;
|
||||
}
|
||||
|
||||
std::unique_ptr<Tower> createTower(const std::string& typeId, const Vector3f& position) {
|
||||
if (_prototypes.count(typeId) == 0) {
|
||||
std::cerr << "Error: Attempted to create unknown tower type: " << typeId << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& proto = _prototypes.at(typeId);
|
||||
return std::make_unique<Tower>(_nextInstanceId++, proto, position);
|
||||
}
|
||||
|
||||
const TowerPrototype* getPrototype(const std::string& typeId) const {
|
||||
auto it = _prototypes.find(typeId);
|
||||
if (it != _prototypes.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user