saura quad map

This commit is contained in:
will
2026-04-23 20:35:26 +01:00
parent 2411571ad0
commit eae0bb7f8d
15 changed files with 3216 additions and 43 deletions

View File

@@ -16,6 +16,10 @@
{ "label": "Sanctuary (Full)", "action": "sanctuary" }, { "label": "Sanctuary (Full)", "action": "sanctuary" },
{ "label": "Dam (Multi-water)", "action": "dam" }, { "label": "Dam (Multi-water)", "action": "dam" },
{ "label": "Waterfall (Flow)", "action": "waterfall" }, { "label": "Waterfall (Flow)", "action": "waterfall" },
{ "label": "Physics Playground","action": "playground" },
{ "label": "Four-Quadrant Map", "action": "fourmap" },
{ "label": "Builders (top-down)", "action": "builders" },
{ "label": "Odyssey (Explore)", "action": "odyssey" },
{ "label": "Level Editor", "action": "edit" }, { "label": "Level Editor", "action": "edit" },
{ "label": "Quit", "action": "quit" } { "label": "Quit", "action": "quit" }
] ]

View File

@@ -17161,6 +17161,7 @@ CMakeFiles/ModernEngine.dir/src/main.cpp.o
/Users/will/Documents/zoomengine/src/Audio.h /Users/will/Documents/zoomengine/src/Audio.h
/Users/will/Documents/zoomengine/src/Engine.h /Users/will/Documents/zoomengine/src/Engine.h
/Users/will/Documents/zoomengine/src/Font.h /Users/will/Documents/zoomengine/src/Font.h
/Users/will/Documents/zoomengine/src/Hud.h
/Users/will/Documents/zoomengine/src/Input.h /Users/will/Documents/zoomengine/src/Input.h
/Users/will/Documents/zoomengine/src/Json.h /Users/will/Documents/zoomengine/src/Json.h
/Users/will/Documents/zoomengine/src/LevelEditor.cpp /Users/will/Documents/zoomengine/src/LevelEditor.cpp

View File

@@ -17144,6 +17144,7 @@ CMakeFiles/ModernEngine.dir/src/main.cpp.o: /Users/will/Documents/zoomengine/src
/Users/will/Documents/zoomengine/src/Audio.h \ /Users/will/Documents/zoomengine/src/Audio.h \
/Users/will/Documents/zoomengine/src/Engine.h \ /Users/will/Documents/zoomengine/src/Engine.h \
/Users/will/Documents/zoomengine/src/Font.h \ /Users/will/Documents/zoomengine/src/Font.h \
/Users/will/Documents/zoomengine/src/Hud.h \
/Users/will/Documents/zoomengine/src/Input.h \ /Users/will/Documents/zoomengine/src/Input.h \
/Users/will/Documents/zoomengine/src/Json.h \ /Users/will/Documents/zoomengine/src/Json.h \
/Users/will/Documents/zoomengine/src/LevelEditor.cpp \ /Users/will/Documents/zoomengine/src/LevelEditor.cpp \
@@ -20153,6 +20154,8 @@ CMakeFiles/ModernEngine.dir/src/main.cpp.o: /Users/will/Documents/zoomengine/src
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__type_traits/is_callable.h: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__type_traits/is_callable.h:
/Users/will/Documents/zoomengine/src/Hud.h:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__algorithm/iter_swap.h: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__algorithm/iter_swap.h:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__cstddef/nullptr_t.h: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__cstddef/nullptr_t.h:

View File

@@ -1451,6 +1451,7 @@ CMakeFiles/ModernEngine.dir/src/main.cpp.o: \
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/unordered_set \ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/unordered_set \
/Users/will/Documents/zoomengine/src/MainMenu.h \ /Users/will/Documents/zoomengine/src/MainMenu.h \
/Users/will/Documents/zoomengine/src/TextureCache.h \ /Users/will/Documents/zoomengine/src/TextureCache.h \
/Users/will/Documents/zoomengine/src/Hud.h \
/Users/will/Documents/zoomengine/src/LevelEditor.cpp \ /Users/will/Documents/zoomengine/src/LevelEditor.cpp \
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/random \ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/random \
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__random/bernoulli_distribution.h \ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__random/bernoulli_distribution.h \

Binary file not shown.

View File

@@ -255,21 +255,25 @@ void Engine::InitializeShader() {
#version 410 core #version 410 core
layout(location=0) in vec2 aPos; layout(location=0) in vec2 aPos;
layout(location=1) in vec3 aColor; layout(location=1) in vec3 aColor;
layout(location=2) in float aAlpha;
uniform mat4 uOrtho; uniform mat4 uOrtho;
out vec3 vColor; out vec3 vColor;
out float vAlpha;
void main() { void main() {
vec4 pos = uOrtho * vec4(aPos, 0.0, 1.0); vec4 pos = uOrtho * vec4(aPos, 0.0, 1.0);
gl_Position = pos; gl_Position = pos;
vColor = aColor; vColor = aColor;
vAlpha = aAlpha;
} }
)"; )";
const char* fs_overlay = R"( const char* fs_overlay = R"(
#version 410 core #version 410 core
in vec3 vColor; in vec3 vColor;
in float vAlpha;
out vec4 FragColor; out vec4 FragColor;
void main() { void main() {
FragColor = vec4(vColor, 1.0); FragColor = vec4(vColor, vAlpha);
} }
)"; )";
overlay_program = CreateProgram(vs_overlay, fs_overlay); overlay_program = CreateProgram(vs_overlay, fs_overlay);
@@ -730,7 +734,8 @@ public:
// TerrainPrimitive - heightmap terrain with procedural noise for landscapes // TerrainPrimitive - heightmap terrain with procedural noise for landscapes
class TerrainPrimitive : public Primitive { class TerrainPrimitive : public Primitive {
public: public:
TerrainPrimitive(int resolution, float size, float maxHeight, const glm::vec3& baseColor) { TerrainPrimitive(int resolution, float size, float maxHeight, const glm::vec3& baseColor,
float edgeFalloff = 0.0f, bool tintByBase = false) {
color_ = baseColor; color_ = baseColor;
std::vector<float> vertices; std::vector<float> vertices;
std::vector<unsigned int> indices; std::vector<unsigned int> indices;
@@ -747,10 +752,22 @@ public:
h += std::sin(px * 0.1f) * std::cos(pz * 0.1f) * maxHeight * 0.5f; h += std::sin(px * 0.1f) * std::cos(pz * 0.1f) * maxHeight * 0.5f;
h += std::sin(px * 0.25f + 1.3f) * std::cos(pz * 0.3f + 0.7f) * maxHeight * 0.25f; h += std::sin(px * 0.25f + 1.3f) * std::cos(pz * 0.3f + 0.7f) * maxHeight * 0.25f;
h += std::sin(px * 0.6f + 2.1f) * std::cos(pz * 0.5f + 1.5f) * maxHeight * 0.125f; h += std::sin(px * 0.6f + 2.1f) * std::cos(pz * 0.5f + 1.5f) * maxHeight * 0.125f;
// Color by height: green low, brown mid, grey/white high // Edge falloff: taper heights to zero within edgeFalloff units of mesh edge
if (edgeFalloff > 0.0f) {
float dx_edge = halfSize - std::fabs(px);
float dz_edge = halfSize - std::fabs(pz);
float d = std::min(dx_edge, dz_edge);
float f = std::clamp(d / edgeFalloff, 0.0f, 1.0f);
f = f * f * (3.0f - 2.0f * f); // smoothstep
h *= f;
}
// Color: either height-banded palette (default) or baseColor tinted by height
float t = (h + maxHeight) / (2.0f * maxHeight); float t = (h + maxHeight) / (2.0f * maxHeight);
glm::vec3 col; glm::vec3 col;
if (t < 0.3f) col = glm::mix(glm::vec3(0.2f, 0.35f, 0.1f), glm::vec3(0.3f, 0.5f, 0.15f), t / 0.3f); if (tintByBase) {
float k = 0.65f + 0.55f * t; // 0.65..1.2 brightness
col = glm::clamp(baseColor * k, glm::vec3(0.0f), glm::vec3(1.5f));
} else if (t < 0.3f) col = glm::mix(glm::vec3(0.2f, 0.35f, 0.1f), glm::vec3(0.3f, 0.5f, 0.15f), t / 0.3f);
else if (t < 0.6f) col = glm::mix(glm::vec3(0.3f, 0.5f, 0.15f), glm::vec3(0.45f, 0.35f, 0.2f), (t - 0.3f) / 0.3f); else if (t < 0.6f) col = glm::mix(glm::vec3(0.3f, 0.5f, 0.15f), glm::vec3(0.45f, 0.35f, 0.2f), (t - 0.3f) / 0.3f);
else if (t < 0.85f)col = glm::mix(glm::vec3(0.45f, 0.35f, 0.2f), glm::vec3(0.5f, 0.5f, 0.5f), (t - 0.6f) / 0.25f); else if (t < 0.85f)col = glm::mix(glm::vec3(0.45f, 0.35f, 0.2f), glm::vec3(0.5f, 0.5f, 0.5f), (t - 0.6f) / 0.25f);
else col = glm::mix(glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(0.95f, 0.95f, 0.98f), (t - 0.85f) / 0.15f); else col = glm::mix(glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(0.95f, 0.95f, 0.98f), (t - 0.85f) / 0.15f);
@@ -1241,7 +1258,10 @@ void Engine::RenderOverlay() {
{0.3f, 0.8f, 0.3f}, 1.5f); {0.3f, 0.8f, 0.3f}, 1.5f);
} }
// 9) Restore GL state // 9) Subclass overlay content (scene-specific HUD, message logs, etc.)
OnRenderOverlay();
// 10) Restore GL state
font_.end(); font_.end();
} }
@@ -1727,6 +1747,19 @@ std::string Engine::processAgentCommand(const json::Value& cmd) {
if (cmd.has("flow_speed")) if (cmd.has("flow_speed"))
physics_.water_flow_speed = std::max(0.0f, cmd.get_float("flow_speed", 0.0f)); physics_.water_flow_speed = std::max(0.0f, cmd.get_float("flow_speed", 0.0f));
// Pool-bounds gating: only apply buoyancy inside a circle (for pools).
if (cmd.has("bounds_center")) {
glm::vec3 c = readVec3(cmd, "bounds_center", glm::vec3(0));
physics_.water_bounds_center = glm::vec2(c.x, c.z);
physics_.water_bounds_enabled = true;
}
if (cmd.has("bounds_radius")) {
physics_.water_bounds_radius = std::max(0.1f, cmd.get_float("bounds_radius", 100.0f));
physics_.water_bounds_enabled = true;
}
if (cmd.has("bounds_enabled"))
physics_.water_bounds_enabled = cmd.get_bool("bounds_enabled", true);
RebuildWaterMesh(cmd.get_int("resolution", 128)); RebuildWaterMesh(cmd.get_int("resolution", 128));
return R"({"ok":true})"; return R"({"ok":true})";
} }
@@ -2004,8 +2037,8 @@ std::shared_ptr<Primitive> Engine::CreateCone(float radius, float height, int se
return std::make_shared<ConePrimitive>(radius, height, segments, color); return std::make_shared<ConePrimitive>(radius, height, segments, color);
} }
std::shared_ptr<Primitive> Engine::CreateTerrain(int resolution, float size, float maxHeight, const glm::vec3& baseColor) { std::shared_ptr<Primitive> Engine::CreateTerrain(int resolution, float size, float maxHeight, const glm::vec3& baseColor, float edgeFalloff, bool tintByBase) {
return std::make_shared<TerrainPrimitive>(resolution, size, maxHeight, baseColor); return std::make_shared<TerrainPrimitive>(resolution, size, maxHeight, baseColor, edgeFalloff, tintByBase);
} }
// GameObject management // GameObject management
@@ -2038,6 +2071,25 @@ void Engine::SetCameraOrbit(float distance, float angle, float height) {
); );
} }
glm::vec3 Engine::ScreenToGround() const {
glm::vec2 m = Input::GetMousePosition();
float ndc_x = 2.0f * m.x / float(width_) - 1.0f;
float ndc_y = 1.0f - 2.0f * m.y / float(height_);
glm::mat4 view = glm::lookAt(camera_position_, camera_target_, camera_up_);
glm::mat4 proj = glm::perspective(glm::radians(settings_.fov),
float(width_) / height_, 0.1f, settings_.drawDistance);
glm::mat4 inv = glm::inverse(proj * view);
glm::vec4 a = inv * glm::vec4(ndc_x, ndc_y, -1.0f, 1.0f);
glm::vec4 b = inv * glm::vec4(ndc_x, ndc_y, 1.0f, 1.0f);
glm::vec3 p0 = glm::vec3(a) / a.w;
glm::vec3 p1 = glm::vec3(b) / b.w;
glm::vec3 d = glm::normalize(p1 - p0);
if (std::abs(d.y) < 1e-5f) return glm::vec3(0);
float t = -p0.y / d.y;
if (t < 0) return glm::vec3(0);
return p0 + d * t;
}
// ---- Sky ---- // ---- Sky ----
void Engine::InitSky() { void Engine::InitSky() {
const char* vs = R"(#version 410 core const char* vs = R"(#version 410 core

View File

@@ -31,6 +31,9 @@ public:
virtual void OnShutdown(); virtual void OnShutdown();
virtual void OnUpdate(float delta_time); virtual void OnUpdate(float delta_time);
virtual void OnRender(); virtual void OnRender();
// Extensibility hook for subclasses to add text overlay content. Called
// at the end of the engine's RenderOverlay pass with font_ already set up.
virtual void OnRenderOverlay() {}
void Run(); void Run();
@@ -44,7 +47,7 @@ public:
std::shared_ptr<Primitive> CreateOBJModel(const std::string& objPath, const std::string& texturePath = ""); std::shared_ptr<Primitive> CreateOBJModel(const std::string& objPath, const std::string& texturePath = "");
std::shared_ptr<Primitive> CreateCylinder(float radius = 0.5f, float height = 2.0f, int segments = 16, const glm::vec3& color = glm::vec3(1.0f)); std::shared_ptr<Primitive> CreateCylinder(float radius = 0.5f, float height = 2.0f, int segments = 16, const glm::vec3& color = glm::vec3(1.0f));
std::shared_ptr<Primitive> CreateCone(float radius = 1.0f, float height = 2.0f, int segments = 16, const glm::vec3& color = glm::vec3(1.0f)); std::shared_ptr<Primitive> CreateCone(float radius = 1.0f, float height = 2.0f, int segments = 16, const glm::vec3& color = glm::vec3(1.0f));
std::shared_ptr<Primitive> CreateTerrain(int resolution = 64, float size = 100.0f, float maxHeight = 8.0f, const glm::vec3& baseColor = glm::vec3(0.3f, 0.5f, 0.2f)); std::shared_ptr<Primitive> CreateTerrain(int resolution = 64, float size = 100.0f, float maxHeight = 8.0f, const glm::vec3& baseColor = glm::vec3(0.3f, 0.5f, 0.2f), float edgeFalloff = 0.0f, bool tintByBase = false);
// GameObject management // GameObject management
@@ -60,6 +63,11 @@ public:
const glm::vec3& GetCameraPosition() const { return camera_position_; } const glm::vec3& GetCameraPosition() const { return camera_position_; }
const glm::vec3& GetCameraTarget() const { return camera_target_; } const glm::vec3& GetCameraTarget() const { return camera_target_; }
// Unproject the current mouse cursor to the y=0 ground plane using the
// active camera + default FOV. Returns (0,0,0) if the ray points away
// from the plane.
glm::vec3 ScreenToGround() const;
GLuint text_vao = 0, text_vbo = 0; GLuint text_vao = 0, text_vbo = 0;

View File

@@ -60,47 +60,59 @@ public:
float getSpacing() const { return spacing_; } float getSpacing() const { return spacing_; }
float getWeight() const { return weight_; } float getWeight() const { return weight_; }
// Draw a string. lineWidth < 0 means use the default weight. // Draw a string as filled triangle quads (each stroke thickened into a
// Returns the total advance width in pixels. // ribbon). Returns total advance width in pixels.
float drawText(const std::string& text, float x, float y, float size, float drawText(const std::string& text, float x, float y, float size,
const glm::vec3& color = glm::vec3(1.0f), float lineWidth = -1.0f) { const glm::vec3& color = glm::vec3(1.0f), float lineWidth = -1.0f) {
if (text.empty()) return 0.0f; if (text.empty()) return 0.0f;
float lw = (lineWidth < 0) ? weight_ : lineWidth; float stroke = ((lineWidth < 0) ? weight_ : lineWidth) * 0.5f;
verts_.clear(); verts_.clear();
float cx = x; float cx = x;
float advance = size * spacing_;
for (char ch : text) { for (char ch : text) {
const Glyph& g = getGlyph(ch); const Glyph& g = getGlyph(ch);
for (size_t i = 0; i + 1 < g.points.size(); i += 2) { for (size_t i = 0; i + 1 < g.points.size(); i += 2) {
auto& p0 = g.points[i]; glm::vec2 a(cx + g.points[i].x * size, y + g.points[i].y * size);
auto& p1 = g.points[i + 1]; glm::vec2 b(cx + g.points[i+1].x * size, y + g.points[i+1].y * size);
verts_.push_back({cx + p0.x * size, y + p0.y * size, color.r, color.g, color.b}); emitQuad(a, b, stroke, color);
verts_.push_back({cx + p1.x * size, y + p1.y * size, color.r, color.g, color.b});
} }
cx += advance; cx += advanceFor(ch, size);
} }
flushTriangles();
if (!verts_.empty()) {
glBufferData(GL_ARRAY_BUFFER, verts_.size() * sizeof(Vertex),
verts_.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)(2 * sizeof(float)));
if (smooth_) {
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}
glLineWidth(lw);
glDrawArrays(GL_LINES, 0, (GLsizei)verts_.size());
glLineWidth(1.0f);
if (smooth_) glDisable(GL_LINE_SMOOTH);
}
return cx - x; return cx - x;
} }
// Outlined text: draws a dark halo behind the main stroke for legibility.
// outlineExtra is extra pixels added on each side (not a multiplier), so
// the main glyph shape stays legible.
float drawOutlinedText(const std::string& text, float x, float y, float size,
const glm::vec3& color = glm::vec3(1.0f),
const glm::vec3& outline = glm::vec3(0.0f, 0.0f, 0.0f),
float outlineExtra = 1.5f) {
float base = (weight_ < 0 ? 2.0f : weight_);
drawText(text, x, y, size, outline, base + outlineExtra * 2.0f);
return drawText(text, x, y, size, color, base);
}
// Filled rectangle — useful for HUD panel backgrounds.
void fillRect(float x, float y, float w, float h,
const glm::vec4& rgba) {
verts_.clear();
pushTri(x, y, x + w, y, x + w, y + h, glm::vec3(rgba), rgba.a);
pushTri(x, y, x + w, y + h, x, y + h, glm::vec3(rgba), rgba.a);
flushTriangles();
}
// Rectangle outline as 4 thin ribbons.
void strokeRect(float x, float y, float w, float h,
const glm::vec4& rgba, float stroke = 1.5f) {
float s = stroke * 0.5f;
fillRect(x - s, y - s, w + 2*s, 2*s, rgba); // bottom
fillRect(x - s, y + h - s, w + 2*s, 2*s, rgba); // top
fillRect(x - s, y - s, 2*s, h + 2*s, rgba); // left
fillRect(x + w - s, y - s, 2*s, h + 2*s, rgba); // right
}
// Draw centered text // Draw centered text
float drawTextCentered(const std::string& text, float centerX, float y, float size, float drawTextCentered(const std::string& text, float centerX, float y, float size,
const glm::vec3& color = glm::vec3(1.0f), float lineWidth = -1.0f) { const glm::vec3& color = glm::vec3(1.0f), float lineWidth = -1.0f) {
@@ -131,24 +143,65 @@ public:
return y - cy; return y - cy;
} }
// Measure text width in pixels without drawing // Measure text width in pixels — respects per-glyph widths
float measureText(const std::string& text, float size) const { float measureText(const std::string& text, float size) const {
return text.size() * size * spacing_; float w = 0;
for (char c : text) w += advanceFor(c, size);
return w;
} }
private: private:
struct Vertex { float x, y, r, g, b; }; struct Vertex { float x, y, r, g, b, a; };
struct Glyph { std::vector<glm::vec2> points; }; // pairs of line endpoints struct Glyph { std::vector<glm::vec2> points; }; // pairs of line endpoints
GLuint vao_ = 0, vbo_ = 0, program_ = 0; GLuint vao_ = 0, vbo_ = 0, program_ = 0;
float spacing_ = 0.75f; // character advance as fraction of size float spacing_ = 0.95f; // character advance as fraction of size
float weight_ = 2.0f; // default line width in pixels float weight_ = 1.8f; // default stroke thickness
bool smooth_ = true; // anti-aliased line rendering bool smooth_ = true;
std::array<Glyph, 128> glyphs_; std::array<Glyph, 128> glyphs_;
std::array<float, 128> widths_{}; // per-glyph advance width (fraction)
std::vector<Vertex> verts_; std::vector<Vertex> verts_;
const Glyph& getGlyph(char c) const { float advanceFor(char c, float size) const {
unsigned char uc = (unsigned char)c; unsigned char uc = (unsigned char)c;
float w = (uc < 128 && widths_[uc] > 0) ? widths_[uc] : spacing_;
return size * w;
}
void pushTri(float x0, float y0, float x1, float y1, float x2, float y2,
const glm::vec3& rgb, float a) {
verts_.push_back({x0, y0, rgb.r, rgb.g, rgb.b, a});
verts_.push_back({x1, y1, rgb.r, rgb.g, rgb.b, a});
verts_.push_back({x2, y2, rgb.r, rgb.g, rgb.b, a});
}
void emitQuad(const glm::vec2& a, const glm::vec2& b, float half,
const glm::vec3& color) {
glm::vec2 d = b - a;
float len = std::sqrt(d.x * d.x + d.y * d.y);
if (len < 1e-5f) return;
glm::vec2 n(-d.y / len * half, d.x / len * half); // perpendicular
glm::vec2 p0 = a - n, p1 = a + n, p2 = b + n, p3 = b - n;
pushTri(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, color, 1.0f);
pushTri(p0.x, p0.y, p2.x, p2.y, p3.x, p3.y, color, 1.0f);
}
void flushTriangles() {
if (verts_.empty()) return;
glBufferData(GL_ARRAY_BUFFER, verts_.size() * sizeof(Vertex),
verts_.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)(2 * sizeof(float)));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)(5 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)verts_.size());
}
const Glyph& getGlyph(char c) const {
static const Glyph empty; // shared whitespace sentinel
unsigned char uc = (unsigned char)c;
if (uc == ' ' || uc == '\t') return empty;
if (uc < 128 && !glyphs_[uc].points.empty()) return glyphs_[uc]; if (uc < 128 && !glyphs_[uc].points.empty()) return glyphs_[uc];
return glyphs_[(unsigned char)'?']; return glyphs_[(unsigned char)'?'];
} }
@@ -160,6 +213,17 @@ private:
} }
void buildGlyphs() { void buildGlyphs() {
// Per-glyph advance widths (fraction of font size). Scale relative
// to the active spacing so changing spacing_ preserves rhythm.
for (int i = 0; i < 128; ++i) widths_[i] = spacing_;
const float s = spacing_;
widths_['i'] = widths_['I'] = widths_['!'] = widths_['|'] = widths_['.'] =
widths_[','] = widths_[';'] = widths_[':'] = widths_['\''] = 0.45f * s;
widths_['l'] = widths_['j'] = widths_['t'] = widths_['f'] = widths_['r'] = 0.60f * s;
widths_['M'] = widths_['W'] = widths_['m'] = widths_['w'] = 1.40f * s;
widths_[' '] = 0.70f * s;
widths_['-'] = widths_['_'] = 0.85f * s;
// ---- Uppercase Letters ---- // ---- Uppercase Letters ----
{ auto& g = glyphs_['A']; { auto& g = glyphs_['A'];
L(g, 0,0, 0.5f,1); L(g, 0.5f,1, 1,0); L(g, 0.2f,0.45f, 0.8f,0.45f); } L(g, 0,0, 0.5f,1); L(g, 0.5f,1, 1,0); L(g, 0.2f,0.45f, 0.8f,0.45f); }

184
src/Hud.h Normal file
View File

@@ -0,0 +1,184 @@
#pragma once
#include "Font.h"
#include <glm/glm.hpp>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
// Immediate-mode HUD builder.
//
// Hud hud(font, width, height);
// hud.beginPanel(10, 10, 320, 120, {0,0,0,0.55f});
// hud.label("Title", 16, {1,1,1});
// hud.bar(200, 14, 0.72f, {0.1f,0.6f,0.2f,1}, {1,1,1,0.2f});
// hud.label("72% throughput", 12, {0.9f,0.95f,0.9f});
// hud.endPanel();
// hud.flush();
//
// Panels are stacked rectangles with translucent backgrounds + optional
// borders. Widgets inside flow top-down from the panel's top, padded by
// `padding`. The font handles text rendering; the Hud draws its own rects
// via the font's fillRect/strokeRect helpers.
class Hud {
public:
Hud(Font& font, int screen_w, int screen_h)
: font_(font), w_(screen_w), h_(screen_h) {}
// --- Panel stacking ---------------------------------------------------
void beginPanel(float x, float y, float w, float h,
const glm::vec4& bg = glm::vec4(0, 0, 0, 0.55f),
const glm::vec4& border = glm::vec4(1, 1, 1, 0.0f),
float border_thickness = 1.0f) {
Panel p{x, y, w, h, x + padding_, y + h - padding_ - 16.0f, bg, border, border_thickness};
panels_.push_back(p);
}
void endPanel() {
if (!panels_.empty()) {
// Defer the actual draw so text is drawn on top of backgrounds in
// the right order. For simplicity, draw bg now, widgets are already
// placed, border last.
panels_.pop_back();
}
}
// --- Widgets ----------------------------------------------------------
void label(const std::string& text, float size = 14.0f,
const glm::vec3& color = glm::vec3(1.0f),
bool outlined = false) {
if (panels_.empty()) return;
Panel& p = panels_.back();
float y = p.cursor_y;
draws_.push_back([this, text, p_x = p.cursor_x, y, size, color, outlined]() {
if (outlined) font_.drawOutlinedText(text, p_x, y, size, color);
else font_.drawText(text, p_x, y, size, color);
});
p.cursor_y -= (size + 4.0f);
}
void labelRight(const std::string& text, float size,
const glm::vec3& color = glm::vec3(1.0f)) {
if (panels_.empty()) return;
Panel& p = panels_.back();
float right = p.x + p.w - padding_;
float y = p.cursor_y;
draws_.push_back([this, text, right, y, size, color]() {
font_.drawTextRight(text, right, y, size, color);
});
p.cursor_y -= (size + 4.0f);
}
// Progress bar: value in [0,1]. Width spans the panel minus padding.
void bar(float height, float value,
const glm::vec4& fill = glm::vec4(0.2f, 0.7f, 0.3f, 1.0f),
const glm::vec4& bg = glm::vec4(1.0f, 1.0f, 1.0f, 0.15f),
const glm::vec4& border_ = glm::vec4(1.0f, 1.0f, 1.0f, 0.35f)) {
if (panels_.empty()) return;
Panel& p = panels_.back();
float x = p.cursor_x;
float y = p.cursor_y - height + 10.0f;
float w = p.w - 2 * padding_;
float v = std::clamp(value, 0.0f, 1.0f);
draws_.push_back([this, x, y, w, height, v, fill, bg, border_]() {
font_.fillRect(x, y, w, height, bg);
font_.fillRect(x, y, w * v, height, fill);
font_.strokeRect(x, y, w, height, border_, 1.0f);
});
p.cursor_y -= (height + 6.0f);
}
// Colored icon: solid rounded rect
void icon(float w_icon, float h_icon, const glm::vec4& col) {
if (panels_.empty()) return;
Panel& p = panels_.back();
float x = p.cursor_x;
float y = p.cursor_y - h_icon + 10.0f;
draws_.push_back([this, x, y, w_icon, h_icon, col]() {
font_.fillRect(x, y, w_icon, h_icon, col);
});
p.cursor_y -= (h_icon + 4.0f);
}
// Horizontal separator line
void separator(float thickness = 1.0f,
const glm::vec4& col = glm::vec4(1, 1, 1, 0.25f)) {
if (panels_.empty()) return;
Panel& p = panels_.back();
float x = p.cursor_x;
float y = p.cursor_y + 6.0f;
float w = p.w - 2 * padding_;
draws_.push_back([this, x, y, w, thickness, col]() {
font_.fillRect(x, y, w, thickness, col);
});
p.cursor_y -= (thickness + 6.0f);
}
void spacer(float h = 4.0f) {
if (panels_.empty()) return;
panels_.back().cursor_y -= h;
}
// --- Free-form (outside panels) ---------------------------------------
void rect(float x, float y, float w, float h, const glm::vec4& col) {
draws_.push_back([this, x, y, w, h, col]() { font_.fillRect(x, y, w, h, col); });
}
void text(float x, float y, const std::string& t, float size,
const glm::vec3& col, bool outlined = false) {
draws_.push_back([this, x, y, t, size, col, outlined]() {
if (outlined) font_.drawOutlinedText(t, x, y, size, col);
else font_.drawText(t, x, y, size, col);
});
}
// Draw anchored panel background frames then queued widgets in order
void flush() {
for (auto& f : bg_draws_) f();
for (auto& f : draws_) f();
for (auto& f : border_draws_) f();
bg_draws_.clear();
draws_.clear();
border_draws_.clear();
panels_.clear();
}
void setPadding(float p) { padding_ = p; }
private:
struct Panel {
float x, y, w, h;
float cursor_x, cursor_y;
glm::vec4 bg;
glm::vec4 border;
float border_t;
};
Font& font_;
int w_, h_;
float padding_ = 12.0f;
std::vector<Panel> panels_;
std::vector<std::function<void()>> bg_draws_;
std::vector<std::function<void()>> draws_;
std::vector<std::function<void()>> border_draws_;
public:
// Called instead of endPanel() when you want the panel bg drawn into the
// background layer (so widget draws appear above it).
void drawPanelBackgroundNow() {
if (panels_.empty()) return;
const Panel& p = panels_.back();
glm::vec4 bg = p.bg, br = p.border;
float x = p.x, y = p.y, w = p.w, h = p.h;
float bt = p.border_t;
if (bg.a > 0.0f) {
bg_draws_.push_back([this, x, y, w, h, bg]() {
font_.fillRect(x, y, w, h, bg);
});
}
if (br.a > 0.0f) {
border_draws_.push_back([this, x, y, w, h, br, bt]() {
font_.strokeRect(x, y, w, h, br, bt);
});
}
}
};

View File

@@ -146,7 +146,14 @@ void World::IntegrateForces(float dt) {
glm::vec3 total_torque = b.torque_accum; glm::vec3 total_torque = b.torque_accum;
float submerged_frac_agg = 0.0f; float submerged_frac_agg = 0.0f;
if (water_enabled && b.shape != Shape::Plane) { bool inside_pool = true;
if (water_enabled && water_bounds_enabled) {
glm::vec2 xz(b.position.x, b.position.z);
glm::vec2 d = xz - water_bounds_center;
if (glm::dot(d, d) > water_bounds_radius * water_bounds_radius)
inside_pool = false;
}
if (water_enabled && inside_pool && b.shape != Shape::Plane) {
if (b.shape == Shape::Sphere) { if (b.shape == Shape::Sphere) {
float r = b.half_extents.x; float r = b.half_extents.x;
float wy = SampleWaterY(b.position.x, b.position.z); float wy = SampleWaterY(b.position.x, b.position.z);

View File

@@ -93,6 +93,12 @@ public:
glm::vec2 water_flow_dir = glm::vec2(0.0f, 0.0f); glm::vec2 water_flow_dir = glm::vec2(0.0f, 0.0f);
float water_flow_speed = 0.0f; float water_flow_speed = 0.0f;
// Optional circular bounds on the buoyancy region (for pools). When
// enabled, only bodies inside (center, radius) get buoyancy forces.
bool water_bounds_enabled = false;
glm::vec2 water_bounds_center = glm::vec2(0.0f);
float water_bounds_radius = 0.0f;
// Gerstner sampler — mirrors the water shader exactly. // Gerstner sampler — mirrors the water shader exactly.
float SampleWaterY(float x, float z) const; float SampleWaterY(float x, float z) const;

File diff suppressed because it is too large Load Diff