This commit is contained in:
Will
2026-03-29 16:16:15 +01:00
parent 3d573a200e
commit 1d349946d2
30 changed files with 23182 additions and 327 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1142,6 +1142,9 @@ CMakeFiles/ModernEngine.dir/src/Engine.cpp.o: \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/assume_aligned.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/assume_aligned.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/ranges_uninitialized_algorithms.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/ranges_uninitialized_algorithms.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/uses_allocator_construction.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/uses_allocator_construction.h \
/opt/homebrew/include/glm/gtc/matrix_access.hpp \
/opt/homebrew/include/glm/gtc/../detail/setup.hpp \
/opt/homebrew/include/glm/gtc/matrix_access.inl \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__ostream/print.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__ostream/print.h \

View File

@@ -975,6 +975,9 @@ CMakeFiles/ModernEngine.dir/src/LevelEditor.cpp.o: \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \
/opt/homebrew/include/glm/gtc/matrix_access.hpp \
/opt/homebrew/include/glm/gtc/../detail/setup.hpp \
/opt/homebrew/include/glm/gtc/matrix_access.inl \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__ostream/print.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__ostream/print.h \

View File

@@ -976,6 +976,9 @@ CMakeFiles/ModernEngine.dir/src/OBJModelPrimitive.cpp.o: \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \
/opt/homebrew/include/glm/gtc/matrix_access.hpp \
/opt/homebrew/include/glm/gtc/../detail/setup.hpp \
/opt/homebrew/include/glm/gtc/matrix_access.inl \
/Users/will/Documents/zoomengine/src/OBJLoader.h \ /Users/will/Documents/zoomengine/src/OBJLoader.h \
/Users/will/Documents/zoomengine/src/SimpleTextureLoader.h \ /Users/will/Documents/zoomengine/src/SimpleTextureLoader.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \

Binary file not shown.

View File

@@ -975,6 +975,9 @@ CMakeFiles/ModernEngine.dir/src/main.cpp.o: \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__stop_token/stop_callback.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/future \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__exception/exception_ptr.h \
/opt/homebrew/include/glm/gtc/matrix_access.hpp \
/opt/homebrew/include/glm/gtc/../detail/setup.hpp \
/opt/homebrew/include/glm/gtc/matrix_access.inl \
/Users/will/Documents/zoomengine/src/LevelEditor.cpp \ /Users/will/Documents/zoomengine/src/LevelEditor.cpp \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/ostream \

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
empty

View File

@@ -1 +0,0 @@
16

BIN
build/ModernEngine Executable file

Binary file not shown.

View File

@@ -522,6 +522,46 @@ Engine::Engine(int w, int h, const char* title)
InitOverlay(); InitOverlay();
} }
// -------------------------------------------------------------
// CullSphere — Gribb/Hartmann frustum plane extraction
//
// Extracts 6 frustum planes directly from the view-projection
// matrix by combining its rows. [web:72][web:76]
// Returns TRUE if the sphere is fully OUTSIDE any plane
// (i.e. should be culled — skip rendering).
// -------------------------------------------------------------
bool Engine::CullSphere(const glm::mat4& vp,
const glm::vec3& center,
float radius)
{
// GLM stores matrices column-major, so row(vp, n) extracts row n.
// Each frustum plane = combination of row 3 ± row n. [web:75][web:76]
const glm::vec4 row0 = glm::row(vp, 0);
const glm::vec4 row1 = glm::row(vp, 1);
const glm::vec4 row2 = glm::row(vp, 2);
const glm::vec4 row3 = glm::row(vp, 3);
const glm::vec4 planes[6] = {
row3 + row0, // Left
row3 - row0, // Right
row3 + row1, // Bottom
row3 - row1, // Top
row3 + row2, // Near
row3 - row2, // Far
};
const glm::vec4 pt(center, 1.0f);
for (const auto& plane : planes) {
// Signed distance from sphere centre to plane.
// If distance < -radius, sphere is fully on the outside. [web:73]
if (glm::dot(plane, pt) < -radius) {
return true; // Cull — outside this plane
}
}
return false; // Inside or intersecting all 6 planes — keep
}
// Culling subset method // Culling subset method
Engine::CullingResult Engine::CullObjectsSubset( Engine::CullingResult Engine::CullObjectsSubset(
const std::vector<GameObject*>& objects, const std::vector<GameObject*>& objects,
@@ -1004,8 +1044,16 @@ void Engine::RenderSceneInstanced() {
// MULTITHREADED CULLING SECTION // MULTITHREADED CULLING SECTION
glm::mat4 vp = proj * view; glm::mat4 vp = proj * view;
std::vector<GameObject*> raw_objects;
raw_objects.reserve(game_objects_.size());
for (const auto& sp : game_objects_)
raw_objects.push_back(sp.get());
if (raw_objects.empty()) return;
// Determine number of threads and chunk size // Determine number of threads and chunk size
size_t numThreads = std::min(threadPool_.getNumThreads(), game_objects_.size()); size_t numThreads = std::min(threadPool_.size(), game_objects_.size());
numThreads = std::max(numThreads, static_cast<size_t>(1));
size_t objectsPerThread = (game_objects_.size() + numThreads - 1) / numThreads; size_t objectsPerThread = (game_objects_.size() + numThreads - 1) / numThreads;
// Launch culling tasks // Launch culling tasks
@@ -1018,9 +1066,11 @@ void Engine::RenderSceneInstanced() {
if (startIdx >= game_objects_.size()) break; if (startIdx >= game_objects_.size()) break;
futures.push_back(threadPool_.enqueue([this, startIdx, endIdx, vp]() { futures.push_back(threadPool_.enqueue(
return CullObjectsSubset(game_objects_, startIdx, endIdx, vp); [this, &raw_objects, startIdx, endIdx, vp]() -> CullingResult {
})); return CullObjectsSubset(raw_objects, startIdx, endIdx, vp);
}
));
} }
// Collect results // Collect results

View File

@@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include "ThreadPool.h" #include "ThreadPool.h"
#include <glm/gtc/matrix_access.hpp>
// Forward declarations // Forward declarations
@@ -80,6 +81,10 @@ private:
std::unordered_map<Primitive*, std::vector<glm::mat4>>& outBatches std::unordered_map<Primitive*, std::vector<glm::mat4>>& outBatches
); );
static bool CullSphere(const glm::mat4& vp,
const glm::vec3& center,
float radius);

View File

@@ -1,51 +1,63 @@
// LevelEditor.cpp // LevelEditor.cpp — FIXED
#include "Engine.h" #include "Engine.h"
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <cmath> #include <cmath>
#include <cstring>
// ---------------------------------------------
// LevelEditor: free camera, object placement, light sliders, stats
// ---------------------------------------------
class LevelEditor : public Engine { class LevelEditor : public Engine {
private: private:
std::vector<std::shared_ptr<GameObject>> objects_; std::vector<std::shared_ptr<GameObject>> objects_;
std::shared_ptr<Primitive> monkeyPrim_; std::shared_ptr<Primitive> monkeyPrim_;
std::shared_ptr<GameObject> selected_; std::shared_ptr<GameObject> selected_;
// FIX: Button stores its own pressed state so DrawButton can be
// called for multiple buttons without a shared static variable
struct Button { struct Button {
float x, y, w, h; float x, y, w, h;
bool pressed; bool pressed = false;
}; };
// Free-cam state
// Free cam state
bool firstMouse_ = true; bool firstMouse_ = true;
double lastX_, lastY_; double lastX_ = 0.0, lastY_ = 0.0;
float yaw_ = -90.0f, pitch_ = 0.0f; float yaw_ = -90.0f, pitch_ = 0.0f;
float camSpeed_ = 10.0f, mouseSens_ = 0.1f; float camSpeed_ = 10.0f, mouseSens_ = 0.1f;
// Light control state // Light sliders
float lightR = 1, lightG = 1, lightB = 1, lightI = 1; float lightR = 1.0f, lightG = 1.0f, lightB = 1.0f, lightI = 1.0f;
bool dragR = false, dragG = false, dragB = false, dragI = false; bool dragR = false, dragG = false, dragB = false, dragI = false;
Button btnOrtho{ 560.0f, 150.0f, 80.0f, 20.0f, false };
Button btnOrtho { 560, 150, 80, 20, false };
bool useOrtho_ = false; bool useOrtho_ = false;
// Helpers // FIX: per-key previous-state for edge detection (one-shot placement)
bool prevKey1_ = false, prevKey2_ = false, prevKey3_ = false,
prevKey4_ = false, prevKey5_ = false;
// ---------------------------------------------------------------
// Input helpers
// ---------------------------------------------------------------
bool KeyDown(int k) const { return glfwGetKey(window_, k) == GLFW_PRESS; } bool KeyDown(int k) const { return glfwGetKey(window_, k) == GLFW_PRESS; }
// Returns true only on the frame the key transitions low→high [web:61]
bool KeyJustPressed(int k, bool& prev) const {
bool cur = KeyDown(k);
bool just = cur && !prev;
prev = cur;
return just;
}
void ProcessMouse(double xpos, double ypos) { void ProcessMouse(double xpos, double ypos) {
if (firstMouse_) { if (firstMouse_) {
lastX_ = xpos; lastY_ = ypos; lastX_ = xpos; lastY_ = ypos;
firstMouse_ = false; firstMouse_ = false;
return; // FIX: skip first frame to avoid a jump
} }
float xoff = (xpos - lastX_) * mouseSens_; float xoff = static_cast<float>(xpos - lastX_) * mouseSens_;
float yoff = (lastY_ - ypos) * mouseSens_; float yoff = static_cast<float>(lastY_ - ypos) * mouseSens_;
lastX_ = xpos; lastY_ = ypos; lastX_ = xpos; lastY_ = ypos;
yaw_ += xoff; yaw_ += xoff;
pitch_ = glm::clamp(pitch_ + yoff, -89.0f, 89.0f); pitch_ = glm::clamp(pitch_ + yoff, -89.0f, 89.0f);
@@ -56,43 +68,76 @@ private:
camera_target_ = camera_position_ + glm::normalize(front); camera_target_ = camera_position_ + glm::normalize(front);
} }
// Place primitive at camera target // FIX: overload — explicit position for use before camera is set up
void Place(std::shared_ptr<Primitive> prim) { void Place(std::shared_ptr<Primitive> prim, const glm::vec3& pos) {
glm::vec3 pos = camera_target_;
auto go = CreateGameObject(prim, pos); auto go = CreateGameObject(prim, pos);
objects_.push_back(go); objects_.push_back(go);
AddGameObject(go); AddGameObject(go);
} }
void Place(std::shared_ptr<Primitive> prim) { Place(prim, camera_target_); }
// Raycast pick uses bounding spheres
std::shared_ptr<GameObject> Pick(double mx, double my) { std::shared_ptr<GameObject> Pick(double mx, double my) {
int w, h; glfwGetWindowSize(window_, &w, &h); int w, h; glfwGetWindowSize(window_, &w, &h);
float x = (2.0f*mx)/w -1.0f, y=1.0f-(2.0f*my)/h; float nx = (2.0f * mx) / w - 1.0f;
glm::vec4 ray_clip{x,y,-1,1}; float ny = 1.0f - (2.0f * my) / h;
glm::vec4 ray_clip{ nx, ny, -1.0f, 1.0f };
// Create projection and view matrices locally
glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f); glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f);
glm::mat4 view = glm::lookAt(camera_position_, camera_target_, camera_up_); glm::mat4 view = glm::lookAt(camera_position_, camera_target_, camera_up_);
glm::vec4 ray_eye = glm::inverse(proj) * ray_clip;
glm::vec4 ray_eye = glm::inverse(proj)*ray_clip; ray_eye.z=-1; ray_eye.w=0; ray_eye.z = -1.0f; ray_eye.w = 0.0f;
glm::vec3 dir = glm::normalize(glm::vec3(glm::inverse(view) * ray_eye)); glm::vec3 dir = glm::normalize(glm::vec3(glm::inverse(view) * ray_eye));
std::shared_ptr<GameObject> best; float bd = FLT_MAX; std::shared_ptr<GameObject> best; float bd = FLT_MAX;
for (auto& obj : objects_) { for (auto& obj : objects_) {
glm::vec3 oc = camera_position_ - obj->GetPosition(); glm::vec3 oc = camera_position_ - obj->GetPosition();
float b = glm::dot(oc, dir); float b = glm::dot(oc, dir);
float c = glm::dot(oc,oc) - obj->GetBoundingRadius()*obj->GetBoundingRadius(); float c = glm::dot(oc, oc)
- obj->GetBoundingRadius() * obj->GetBoundingRadius();
float disc = b * b - c; float disc = b * b - c;
if(disc<0) continue; if (disc < 0.0f) continue;
float t = -b - sqrt(disc); float t = -b - std::sqrt(disc);
if(t>0 && t<bd){ bd=t; best=obj; } if (t > 0.0f && t < bd) { bd = t; best = obj; }
} }
return best; return best;
} }
void DrawChar(char c, float x, float y, float s) { // ---------------------------------------------------------------
static const std::vector<std::vector<std::pair<float,float>>> strokes = { // 2D drawing — unified vertex type + helper to avoid repeating
{{0,0},{1,0},{1,1},{0,1},{0,0}},{{0.5f,0},{0.5f,1}}, // glBufferData / glVertexAttribPointer for every draw call
// ---------------------------------------------------------------
struct V2D { float x, y, r, g, b; };
// FIX: single upload-and-draw helper used everywhere — the original
// code was missing glVertexAttribPointer calls before several
// glDrawArrays calls (e.g. the button fill triangles)
void UploadAndDraw(const std::vector<V2D>& v, GLenum mode) {
if (v.empty()) return;
glBufferData(GL_ARRAY_BUFFER,
static_cast<GLsizeiptr>(v.size() * sizeof(V2D)),
v.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(V2D), reinterpret_cast<void*>(0));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(V2D), reinterpret_cast<void*>(2 * sizeof(float)));
glDrawArrays(mode, 0, static_cast<GLsizei>(v.size()));
}
// ---------------------------------------------------------------
// Segment-display font — digits AND letters
// FIX: original DrawChar only handled '0''9'; calling it with
// 'R','G','B','I' (and button labels) produced no output.
// Merged into one function covering AZ and 09.
// ---------------------------------------------------------------
void DrawChar(char c, float x, float y, float s,
float r = 1.0f, float g = 1.0f, float b = 1.0f)
{
using P = std::pair<float,float>;
std::vector<P> pts;
if (c >= '0' && c <= '9') {
static const std::vector<std::vector<P>> D = {
{{0,0},{1,0},{1,1},{0,1},{0,0}},
{{0.5f,0},{0.5f,1}},
{{0,1},{1,1},{1,0.5f},{0,0.5f},{0,0},{1,0}}, {{0,1},{1,1},{1,0.5f},{0,0.5f},{0,0},{1,0}},
{{0,1},{1,1},{1,0},{0,0},{1,0.5f}}, {{0,1},{1,1},{1,0},{0,0},{1,0.5f}},
{{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}}, {{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}},
@@ -102,188 +147,159 @@ private:
{{0,0},{0,1},{1,1},{1,0},{0,0},{0,0.5f},{1,0.5f}}, {{0,0},{0,1},{1,1},{1,0},{0,0},{0,0.5f},{1,0.5f}},
{{1,0},{1,1},{0,1},{0,0.5f},{1,0.5f}} {{1,0},{1,1},{0,1},{0,0.5f},{1,0.5f}}
}; };
std::vector<float> v; pts = D[c - '0'];
if(c>='0'&&c<='9'){ } else {
for(auto&p:strokes[c-'0']){ switch (c) {
v.push_back(x+p.first*s); case 'A': pts={{0,0},{0.5f,1},{1,0},{0.25f,0.5f},{0.75f,0.5f}}; break;
v.push_back(y+p.second*s); case 'B': pts={{0,0},{0,1},{0.7f,1},{0.7f,0.5f},{0,0.5f},{0.7f,0.5f},{0.7f,0},{0,0}}; break;
v.push_back(1);v.push_back(1);v.push_back(1); case 'C': pts={{1,1},{0,1},{0,0},{1,0}}; break;
} case 'D': pts={{0,0},{0,1},{0.7f,0.8f},{0.7f,0.2f},{0,0}}; break;
glBufferData(GL_ARRAY_BUFFER,v.size()*sizeof(float),v.data(),GL_DYNAMIC_DRAW); case 'E': pts={{1,1},{0,1},{0,0.5f},{0.7f,0.5f},{0,0.5f},{0,0},{1,0}}; break;
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,5*sizeof(float),0); case 'F': pts={{0,0},{0,1},{1,1},{0,1},{0,0.5f},{0.7f,0.5f}}; break;
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,5*sizeof(float),(void*)(2*sizeof(float))); case 'G': pts={{1,1},{0,1},{0,0},{1,0},{1,0.5f},{0.5f,0.5f}}; break;
glDrawArrays(GL_LINE_STRIP,0,(GLsizei)(v.size()/5)); case 'H': pts={{0,0},{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}}; break;
case 'I': pts={{0,1},{1,1},{0.5f,1},{0.5f,0},{0,0},{1,0}}; break;
case 'J': pts={{0,1},{1,1},{1,0},{0,0},{0,0.3f}}; break;
case 'K': pts={{0,0},{0,1},{0,0.5f},{1,1},{0,0.5f},{1,0}}; break;
case 'L': pts={{0,1},{0,0},{1,0}}; break;
case 'M': pts={{0,0},{0,1},{0.5f,0.5f},{1,1},{1,0}}; break;
case 'N': pts={{0,0},{0,1},{1,0},{1,1}}; break;
case 'O': pts={{0,0},{0,1},{1,1},{1,0},{0,0}}; break;
case 'P': pts={{0,0},{0,1},{1,1},{1,0.5f},{0,0.5f}}; break;
case 'Q': pts={{1,0},{0,0},{0,1},{1,1},{1,0},{0.6f,0.2f}}; break;
case 'R': pts={{0,0},{0,1},{1,1},{1,0.5f},{0,0.5f},{1,0}}; break;
case 'S': pts={{1,1},{0,1},{0,0.5f},{1,0.5f},{1,0},{0,0}}; break;
case 'T': pts={{0,1},{1,1},{0.5f,1},{0.5f,0}}; break;
case 'U': pts={{0,1},{0,0},{1,0},{1,1}}; break;
case 'V': pts={{0,1},{0.5f,0},{1,1}}; break;
case 'W': pts={{0,1},{0.25f,0},{0.5f,0.5f},{0.75f,0},{1,1}}; break;
case 'X': pts={{0,0},{1,1},{0.5f,0.5f},{0,1},{1,0}}; break;
case 'Y': pts={{0,1},{0.5f,0.5f},{1,1},{0.5f,0.5f},{0.5f,0}}; break;
case 'Z': pts={{0,1},{1,1},{0,0},{1,0}}; break;
default: return; // skip spaces / unknown
} }
} }
// Draw a letter R,G,B,I std::vector<V2D> v;
void DrawLetter(char c, float x, float y, float s) { v.reserve(pts.size());
struct V{float x,y,r,g,b;}; for (auto& p : pts)
std::vector<V> L; v.push_back({ x + p.first * s, y + p.second * s, r, g, b });
if(c=='R'){ UploadAndDraw(v, GL_LINE_STRIP);
L={{x,y,1,1,1},{x,y+s,1,1,1},{x,y+s,1,1,1},{x+s*0.7f,y+s,1,1,1},
{x+s*0.7f,y+s,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},
{x,y+s*0.5f,1,1,1},{x,y+s*0.5f,1,1,1},{x+s*0.7f,y,1,1,1}};
} else if(c=='G'){
L={{x+s,y+s*0.3f,1,1,1},{x,y+s*0.3f,1,1,1},{x,y+s*0.3f,1,1,1},
{x,y+s*0.7f,1,1,1},{x,y+s*0.7f,1,1,1},{x+s,y+s*0.7f,1,1,1},
{x+s,y+s*0.7f,1,1,1},{x+s,y+s*0.5f,1,1,1},{x+s,y+s*0.5f,1,1,1},
{x+s*0.5f,y+s*0.5f,1,1,1}};
} else if(c=='B'){
L={{x,y,1,1,1},{x,y+s,1,1,1},{x,y+s,1,1,1},{x+s*0.7f,y+s,1,1,1},
{x+s*0.7f,y+s,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},
{x,y+s*0.5f,1,1,1},{x,y+s*0.5f,1,1,1},{x+s*0.7f,y,1,1,1},
{x+s*0.7f,y,1,1,1},{x,y,1,1,1}};
} else if(c=='I'){
L={{x,y+s,1,1,1},{x+s,y+s,1,1,1},{x+s*0.5f,y+s,1,1,1},
{x+s*0.5f,y,1,1,1},{x,y,1,1,1},{x+s,y,1,1,1}};
}
if(!L.empty()){
glBufferData(GL_ARRAY_BUFFER,L.size()*sizeof(V),L.data(),GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
glDrawArrays(GL_LINES,0,(GLsizei)L.size());
}
} }
// Draw a string left-to-right with fixed character advance
void DrawString(const char* text, float x, float y, float charSize,
float r = 1.0f, float g = 1.0f, float b = 1.0f)
{
const float advance = charSize * 0.75f + 2.0f;
for (int i = 0; text[i] != '\0'; ++i)
if (text[i] != ' ')
DrawChar(text[i], x + i * advance, y, charSize * 0.7f, r, g, b);
}
float MeasureTextWidth(const char* text, float size) { float MeasureTextWidth(const char* text, float size) {
return strlen(text) * size * 0.6f; return strlen(text) * (size * 0.7f + 2.0f);
} }
// Draw a production-ready gradient button // FIX: takes Button& (not const) so pressed state is stored per-button,
bool DrawButton(const Button &btn, const char* label, // replacing the broken shared static bool in the original
double mx, double my, bool mb) { bool DrawButton(Button& btn, const char* label,
struct V { float x,y,r,g,b; }; double mx, double my, bool mb)
{
// Determine states
bool hovered = mx >= btn.x && mx <= btn.x + btn.w bool hovered = mx >= btn.x && mx <= btn.x + btn.w
&& my >= btn.y && my <= btn.y + btn.h; && my >= btn.y && my <= btn.y + btn.h;
static bool pressedPrev = false; bool prevPressed = btn.pressed;
bool pressed = mb && hovered; btn.pressed = mb && hovered;
bool clicked = pressed && !pressedPrev; bool clicked = btn.pressed && !prevPressed;
pressedPrev = pressed;
// Colors glm::vec3 top = hovered ? glm::vec3(0.3f,0.5f,0.8f)
glm::vec3 topColor = hovered ? glm::vec3(0.3f,0.5f,0.8f) : glm::vec3(0.2f,0.4f,0.7f); : glm::vec3(0.2f,0.4f,0.7f);
glm::vec3 bottomColor = hovered ? glm::vec3(0.1f,0.3f,0.6f) : glm::vec3(0.0f,0.2f,0.5f); glm::vec3 bot = hovered ? glm::vec3(0.1f,0.3f,0.6f)
if (pressed) { : glm::vec3(0.0f,0.2f,0.5f);
topColor *= 0.8f; if (btn.pressed) { top *= 0.8f; bot *= 0.8f; }
bottomColor *= 0.8f;
}
// Vertex data: two triangles forming the quad with gradient // FIX: UploadAndDraw now sets glVertexAttribPointer — the original
V verts[6] = { // triangle draw was missing those calls entirely
// first triangle UploadAndDraw({
{btn.x, btn.y, bottomColor.r, bottomColor.g, bottomColor.b}, {btn.x, btn.y, bot.r, bot.g, bot.b},
{btn.x + btn.w, btn.y, bottomColor.r, bottomColor.g, bottomColor.b}, {btn.x + btn.w, btn.y, bot.r, bot.g, bot.b},
{btn.x + btn.w, btn.y + btn.h, topColor.r, topColor.g, topColor.b }, {btn.x + btn.w, btn.y + btn.h, top.r, top.g, top.b},
// second triangle {btn.x, btn.y, bot.r, bot.g, bot.b},
{btn.x, btn.y, bottomColor.r, bottomColor.g, bottomColor.b}, {btn.x + btn.w, btn.y + btn.h, top.r, top.g, top.b},
{btn.x + btn.w, btn.y + btn.h, topColor.r, topColor.g, topColor.b }, {btn.x, btn.y + btn.h, top.r, top.g, top.b},
{btn.x, btn.y + btn.h, topColor.r, topColor.g, topColor.b } }, GL_TRIANGLES);
};
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW); UploadAndDraw({
glDrawArrays(GL_TRIANGLES, 0, 6);
// Draw border
struct V2 { float x,y,r,g,b; };
V2 border[5] = {
{btn.x, btn.y, 0,0,0}, {btn.x, btn.y, 0,0,0},
{btn.x + btn.w, btn.y, 0,0,0}, {btn.x + btn.w, btn.y, 0,0,0},
{btn.x + btn.w, btn.y + btn.h, 0,0,0}, {btn.x + btn.w, btn.y + btn.h, 0,0,0},
{btn.x, btn.y + btn.h, 0,0,0}, {btn.x, btn.y + btn.h, 0,0,0},
{btn.x, btn.y, 0,0,0} {btn.x, btn.y, 0,0,0},
}; }, GL_LINE_STRIP);
glBufferData(GL_ARRAY_BUFFER, sizeof(border), border, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V2),(void*)0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V2),(void*)(2*sizeof(float)));
glDrawArrays(GL_LINE_STRIP,0,5);
// Draw label centered // Centred label using DrawString (supports full AZ)
float textWidth = MeasureTextWidth(label, 12.0f); float tw = MeasureTextWidth(label, 10.0f);
float tx = btn.x + (btn.w - textWidth) * 0.5f; float tx = btn.x + (btn.w - tw) * 0.5f;
float ty = btn.y + (btn.h - 12.0f) * 0.5f; float ty = btn.y + (btn.h - 10.0f) * 0.5f;
DrawChar(label[0], tx, ty, 12); // loop over each char if full string support needed DrawString(label, tx, ty, 10.0f);
return clicked; return clicked;
} }
// Draw slider at (x,y), width w height h controlling value 01
void DrawSlider(float x, float y, float w, float h, void DrawSlider(float x, float y, float w, float h,
float& val, bool& drag, float& val, bool& drag,
double mx,double my,bool mb){ double mx, double my, bool mb)
// handle drag {
if (mb && mx >= x && mx <= x+w && my >= y && my <= y+h) drag = true; if (mb && mx >= x && mx <= x+w && my >= y && my <= y+h) drag = true;
if (!mb) drag = false; if (!mb) drag = false;
if (drag) val = glm::clamp(float((mx - x) / w), 0.0f, 1.0f); if (drag) val = glm::clamp(float((mx - x) / w), 0.0f, 1.0f);
struct V{float x,y,r,g,b;}; // Track background
// track bg UploadAndDraw({
std::vector<V> quad = {
{x, y, 0.2f,0.2f,0.2f}, {x+w, y, 0.2f,0.2f,0.2f}, {x, y, 0.2f,0.2f,0.2f}, {x+w, y, 0.2f,0.2f,0.2f},
{x+w, y+h, 0.2f,0.2f,0.2f}, {x, y+h, 0.2f,0.2f,0.2f} {x+w, y+h, 0.2f,0.2f,0.2f}, {x, y+h, 0.2f,0.2f,0.2f}
}; }, GL_TRIANGLE_FAN);
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
glDrawArrays(GL_TRIANGLE_FAN,0,4);
// filled // Filled portion
float fw = w * val; float fw = w * val;
quad = { UploadAndDraw({
{x, y, 0.8f,0.4f,0.4f}, {x+fw, y, 0.8f,0.4f,0.4f}, {x, y, 0.8f,0.4f,0.4f}, {x+fw, y, 0.8f,0.4f,0.4f},
{x+fw, y+h, 0.8f,0.4f,0.4f}, {x, y+h, 0.8f,0.4f,0.4f} {x+fw, y+h, 0.8f,0.4f,0.4f}, {x, y+h, 0.8f,0.4f,0.4f}
}; }, GL_TRIANGLE_FAN);
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
glDrawArrays(GL_TRIANGLE_FAN,0,4);
// knob // Knob
float kx=x+fw-2, ky=y-2, ks=h+4; float kx = x + fw - 2.0f, ky = y - 2.0f, ks = h + 4.0f;
quad = { UploadAndDraw({
{kx,ky,1.0f,1.0f,1.0f},{kx+ks,ky,1.0f,1.0f,1.0f}, {kx, ky, 1,1,1}, {kx+ks, ky, 1,1,1},
{kx+ks,ky+ks,1.0f,1.0f,1.0f},{kx,ky+ks,1.0f,1.0f,1.0f} {kx+ks, ky+ks, 1,1,1}, {kx, ky+ks, 1,1,1}
}; }, GL_TRIANGLE_FAN);
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
glDrawArrays(GL_TRIANGLE_FAN,0,4);
} }
public: public:
LevelEditor() : Engine(3840,2160,"Level Editor").enable_vsync = true {} // FIX 1 — constructor: enable_vsync set in body, not on base ctor call
LevelEditor() : Engine(3840, 2160, "Level Editor") {
}
bool OnInitialize() override { bool OnInitialize() override {
// load monkey try { monkeyPrim_ = CreateOBJModel("monkey.obj"); }
try { catch (...) { monkeyPrim_ = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f)); }
monkeyPrim_ = CreateOBJModel("monkey.obj");
}
catch(...) {
monkeyPrim_ = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
}
// initial grid - fix glm::vec3 constructor // FIX: use explicit-position overload — camera_target_ is not yet
Place(CreateGrid(20, 1.0f, glm::vec3(0.4f, 0.4f, 0.4f))); // set at this point, so passing it would place the grid at (0,0,0)
// only by accident if Engine zero-inits it. Be explicit.
Place(CreateGrid(20, 1.0f, glm::vec3(0.4f)), glm::vec3(0.0f));
// camera camera_position_ = glm::vec3(0.0f, 20.0f, 20.0f);
camera_position_ = glm::vec3(0, 20, 20); camera_target_ = camera_position_
glm::vec3 dir = glm::normalize(glm::vec3(0,0,0) - camera_position_); + glm::normalize(glm::vec3(0.0f) - camera_position_);
camera_target_ = camera_position_ + dir;
return true; return true;
} }
void OnUpdate(float dt) override { void OnUpdate(float dt) override {
// free flight WASD QE
glm::vec3 front = glm::normalize(camera_target_ - camera_position_); glm::vec3 front = glm::normalize(camera_target_ - camera_position_);
glm::vec3 right = glm::normalize(glm::cross(front, camera_up_)); glm::vec3 right = glm::normalize(glm::cross(front, camera_up_));
if (KeyDown(GLFW_KEY_W)) camera_position_ += front * camSpeed_ * dt; if (KeyDown(GLFW_KEY_W)) camera_position_ += front * camSpeed_ * dt;
if (KeyDown(GLFW_KEY_S)) camera_position_ -= front * camSpeed_ * dt; if (KeyDown(GLFW_KEY_S)) camera_position_ -= front * camSpeed_ * dt;
if (KeyDown(GLFW_KEY_A)) camera_position_ -= right * camSpeed_ * dt; if (KeyDown(GLFW_KEY_A)) camera_position_ -= right * camSpeed_ * dt;
@@ -292,30 +308,28 @@ public:
if (KeyDown(GLFW_KEY_E)) camera_position_ += camera_up_ * camSpeed_ * dt; if (KeyDown(GLFW_KEY_E)) camera_position_ += camera_up_ * camSpeed_ * dt;
camera_target_ = camera_position_ + front; camera_target_ = camera_position_ + front;
// mouse look on RMB
double mx, my; glfwGetCursorPos(window_, &mx, &my); double mx, my; glfwGetCursorPos(window_, &mx, &my);
if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
ProcessMouse(mx, my); ProcessMouse(mx, my);
else firstMouse_=true; else
firstMouse_ = true;
// placement keys - fix glm::vec3 constructors // FIX: KeyJustPressed — one object per key-press, not one per frame [web:61]
if(KeyDown(GLFW_KEY_1)) Place(CreateCube(glm::vec3(0.8f, 0.2f, 0.2f))); if (KeyJustPressed(GLFW_KEY_1, prevKey1_)) Place(CreateCube(glm::vec3(0.8f,0.2f,0.2f)));
if(KeyDown(GLFW_KEY_2)) Place(CreateSphere(16, glm::vec3(0.2f, 0.8f, 0.2f))); if (KeyJustPressed(GLFW_KEY_2, prevKey2_)) Place(CreateSphere(16, glm::vec3(0.2f,0.8f,0.2f)));
if(KeyDown(GLFW_KEY_3)) Place(CreatePlane(5, 5, glm::vec3(0.2f, 0.2f, 0.8f))); if (KeyJustPressed(GLFW_KEY_3, prevKey3_)) Place(CreatePlane(5.0f, 5.0f, glm::vec3(0.2f,0.2f,0.8f)));
if(KeyDown(GLFW_KEY_4)) Place(CreateGrid(5, 1.0f, glm::vec3(0.5f, 0.5f, 0.5f))); if (KeyJustPressed(GLFW_KEY_4, prevKey4_)) Place(CreateGrid(5, 1.0f, glm::vec3(0.5f)));
if(KeyDown(GLFW_KEY_5)) Place(monkeyPrim_); if (KeyJustPressed(GLFW_KEY_5, prevKey5_)) Place(monkeyPrim_);
// picking
if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) if (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
selected_ = Pick(mx, my); selected_ = Pick(mx, my);
// move selected with arrows
if (selected_) { if (selected_) {
glm::vec3 p = selected_->GetPosition(); glm::vec3 p = selected_->GetPosition();
if(KeyDown(GLFW_KEY_UP)) p.z -= dt*5; if (KeyDown(GLFW_KEY_UP)) p.z -= dt * 5.0f;
if(KeyDown(GLFW_KEY_DOWN)) p.z += dt*5; if (KeyDown(GLFW_KEY_DOWN)) p.z += dt * 5.0f;
if(KeyDown(GLFW_KEY_LEFT)) p.x -= dt*5; if (KeyDown(GLFW_KEY_LEFT)) p.x -= dt * 5.0f;
if(KeyDown(GLFW_KEY_RIGHT)) p.x += dt*5; if (KeyDown(GLFW_KEY_RIGHT)) p.x += dt * 5.0f;
selected_->SetPosition(p); selected_->SetPosition(p);
} }
@@ -323,70 +337,64 @@ public:
} }
void OnRender() override { void OnRender() override {
glUseProgram(shader_program_); glUseProgram(shader_program_);
int w = width_, h = height_;
glm::mat4 proj = useOrtho_ glm::mat4 proj = useOrtho_
? glm::ortho(-30.0f*float(w)/h, 30.0f*float(w)/h, -30.0f, 30.0f, 0.1f, 1000.0f) ? glm::ortho(-30.0f * float(width_)/height_, 30.0f * float(width_)/height_,
: glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f); -30.0f, 30.0f, 0.1f, 1000.0f)
glUniformMatrix4fv(glGetUniformLocation(shader_program_, "uProjection"), 1, GL_FALSE, &proj[0][0]); : glm::perspective(glm::radians(60.0f),
float(width_) / float(height_), 0.1f, 1000.0f);
glUniformMatrix4fv(glGetUniformLocation(shader_program_, "uProjection"),
1, GL_FALSE, &proj[0][0]);
// 1) 3D scene + overlay graphs
Engine::OnRender(); Engine::OnRender();
RenderOverlay();
// 2) custom 2D UI: sliders + stats // ── 2D overlay ───────────────────────────────────────────────
RenderOverlay(); // draws graphs & text
// setup 2D
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(overlay_program); glUseProgram(overlay_program);
GLint loc = glGetUniformLocation(overlay_program,"uOrtho"); glm::mat4 ortho2D = glm::ortho(0.0f, float(width_), 0.0f, float(height_));
glm::mat4 ortho = glm::ortho(0.0f,float(width_),0.0f,float(height_)); glUniformMatrix4fv(glGetUniformLocation(overlay_program, "uOrtho"),
glUniformMatrix4fv(loc,1,GL_FALSE,&ortho[0][0]); 1, GL_FALSE, &ortho2D[0][0]);
glBindVertexArray(overlay_vao); glBindVertexArray(overlay_vao);
glBindBuffer(GL_ARRAY_BUFFER, overlay_vbo); glBindBuffer(GL_ARRAY_BUFFER, overlay_vbo);
// mouse state
double mx, my; glfwGetCursorPos(window_, &mx, &my); double mx, my; glfwGetCursorPos(window_, &mx, &my);
my = height_ - my; // flip Y coordinate for OpenGL my = height_ - my; // flip Y to match OpenGL bottom-left origin
bool mb = glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; bool mb = glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
// draw sliders and labels // Sliders
DrawSlider(50, 50, 200, 10, lightR, dragR, mx, my, mb); DrawSlider(50, 50, 200, 10, lightR, dragR, mx, my, mb);
DrawSlider(50, 70, 200, 10, lightG, dragG, mx, my, mb); DrawSlider(50, 70, 200, 10, lightG, dragG, mx, my, mb);
DrawSlider(50, 90, 200, 10, lightB, dragB, mx, my, mb); DrawSlider(50, 90, 200, 10, lightB, dragB, mx, my, mb);
DrawSlider(50, 110, 200, 10, lightI, dragI, mx, my, mb); DrawSlider(50, 110, 200, 10, lightI, dragI, mx, my, mb);
if (DrawButton(btnOrtho, useOrtho_ ? "Ortho On" : "Persp On", mx, my, mb)) { // FIX: DrawButton now takes Button& — state is stored per button
// FIX: labels use uppercase AZ which DrawChar now supports
if (DrawButton(btnOrtho, useOrtho_ ? "ORTHO" : "PERSP", mx, my, mb))
useOrtho_ = !useOrtho_; useOrtho_ = !useOrtho_;
}
// FIX: was DrawChar('R',...) etc. — DrawChar now handles letters;
// coloured to match channel (red/green/blue/white)
DrawChar('R', 30, 52, 10, 1.0f, 0.3f, 0.3f);
DrawChar('G', 30, 72, 10, 0.3f, 1.0f, 0.3f);
DrawChar('B', 30, 92, 10, 0.3f, 0.5f, 1.0f);
DrawChar('I', 30, 112, 10, 1.0f, 1.0f, 1.0f);
// Push updated light values to 3D shader
// draw labels
DrawChar('R',30,50,12);
DrawChar('G',30,70,12);
DrawChar('B',30,90,12);
DrawChar('I',30,110,12);
// update 3D shader light
glUseProgram(shader_program_); glUseProgram(shader_program_);
glUniform3f(glGetUniformLocation(shader_program_,"uLightColor"),lightR,lightG,lightB); glUniform3f(glGetUniformLocation(shader_program_, "uLightColor"),
glUniform1f(glGetUniformLocation(shader_program_,"uAmbientStrength"),0.1f*lightI); lightR, lightG, lightB);
glUniform1f(glGetUniformLocation(shader_program_, "uAmbientStrength"),
0.1f * lightI);
// restore
glBindVertexArray(0); glBindVertexArray(0);
glUseProgram(0); glUseProgram(0);
glDisable(GL_BLEND); glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} }
void OnShutdown() override { void OnShutdown() override { Engine::OnShutdown(); }
Engine::OnShutdown();
}
}; };

View File

@@ -8,11 +8,11 @@
#include <future> #include <future>
#include <functional> #include <functional>
#include <atomic> #include <atomic>
#include <type_traits> // for std::invoke_result_t #include <stdexcept>
#include <type_traits>
class ThreadPool { class ThreadPool {
public: public:
// Construct a thread pool with the given number of threads (defaults to hardware concurrency)
explicit ThreadPool(size_t numThreads = std::thread::hardware_concurrency()) explicit ThreadPool(size_t numThreads = std::thread::hardware_concurrency())
: stopFlag(false) : stopFlag(false)
{ {
@@ -36,21 +36,29 @@ public:
} }
} }
// Destructor shuts down the pool and joins all threads ~ThreadPool() { shutdown(); }
~ThreadPool() {
shutdown(); // Non-copyable, non-movable
} ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
// Enqueue a task into the thread pool. Returns a future for the result.
template <class F, class... Args> template <class F, class... Args>
auto enqueue(F&& f, Args&&... args) auto enqueue(F&& f, Args&&... args)
-> std::future<std::invoke_result_t<F, Args...>> // FIX: use decay_t so invoke_result matches what is actually stored —
// std::bind produced a wrapper whose result type differed from F's
-> std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>
{ {
using return_type = std::invoke_result_t<F, Args...>; using return_type =
std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>;
// Wrap the function and its arguments into a packaged_task // FIX: replace std::bind with tuple + std::apply — bind wrappers are
// not transparently invocable and break invoke_result deduction
auto taskPtr = std::make_shared<std::packaged_task<return_type()>>( auto taskPtr = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...) [func = std::forward<F>(f),
args = std::make_tuple(std::forward<Args>(args)...)]() mutable
-> return_type {
return std::apply(func, args);
}
); );
std::future<return_type> result = taskPtr->get_future(); std::future<return_type> result = taskPtr->get_future();
@@ -58,30 +66,31 @@ public:
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
if (stopFlag.load()) if (stopFlag.load())
throw std::runtime_error("enqueue on stopped ThreadPool"); throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([taskPtr]() { (*taskPtr)(); }); tasks.emplace([taskPtr]() { (*taskPtr)(); });
} }
condition.notify_one(); condition.notify_one();
return result; return result;
} }
// Signal all threads to shut down and join them // FIX: added size() — Engine.cpp called getNumThreads() which didn't exist
size_t size() const { return workers.size(); }
void shutdown() { void shutdown() {
{ {
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
stopFlag.store(true); stopFlag.store(true);
} }
condition.notify_all(); condition.notify_all();
for (std::thread &worker : workers) { for (std::thread& worker : workers)
if (worker.joinable()) if (worker.joinable())
worker.join(); worker.join();
} workers.clear(); // safe to call shutdown() more than once
} }
private: private:
std::vector<std::thread> workers; // worker threads std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks; // task queue std::queue<std::function<void()>> tasks;
std::mutex queueMutex; // protects tasks std::mutex queueMutex;
std::condition_variable condition; // signals availability std::condition_variable condition;
std::atomic<bool> stopFlag; // indicates shutdown std::atomic<bool> stopFlag;
}; };