saura quad map
This commit is contained in:
@@ -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" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
@@ -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
|
||||||
|
|||||||
10
src/Engine.h
10
src/Engine.h
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
132
src/Font.h
132
src/Font.h
@@ -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
184
src/Hud.h
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
2843
src/main.cpp
2843
src/main.cpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user