diff --git a/AGENTS.md b/AGENTS.md index 21208a2..85eb71a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,3 +17,9 @@ There is no automated framework yet, so rely on focused manual passes. Before op ## Commit & Pull Request Guidelines Existing history favors short, descriptive subjects ("Player's position is reset when close to ghosts"). Follow that tone, keep subjects under 72 characters, and add body lines for rationale or performance numbers. Each PR should include: summary of gameplay-visible changes, configs or assets touched, reproduction steps or screenshots when visuals change, and confirmation that `make` succeeds on a clean tree. Link related issues and call out any follow-up tasks explicitly. + +## Minimap Implementation Plan +- Add minimap config keys in `ghostland.json` (enable flag, width/height, margin, colors). After loading `maze.txt`, cache 2D wall segments plus world bounds for quick reuse. +- Create a lightweight 2D orthographic shader/VAO for overlay drawing; use GL_LINES for walls, triangle-fan circles for breadcrumbs/ghosts, and a small triangle for the player arrow. Disable depth test while rendering the overlay near the end of the frame. +- Each frame convert wall endpoints and breadcrumb/ghost positions relative to the player’s X/Z so the minimap scrolls with the player centered. Clip or skip segments outside the visible minimap extents. +- Draw order: white background quad, wall lines, breadcrumb dots, ghost dots (border then fill), red arrow rotated by player yaw. Re-enable depth test and continue with HUD text. diff --git a/README.md b/README.md index 76c8042..7a92d65 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,59 @@ While the previous project was implemented in go, for this project I was not abl | --- | --- | --- | --- | | `camera_speed` | float | `20.0` | Scales how fast the free camera pans when you move the mouse to the screen edge or hold movement keys. Raise for faster fly-through, lower for precise adjustments. | | `ghost_reset_distance` | float | `10.0` | Minimum distance (units) between the player and the nearest ghost before the player is snapped back to the maze start. Increase for a safer buffer, decrease for higher tension. | +| `ghost_count` | int | `800` | Number of ghosts spawned across the maze at startup. | +| `minimap_enabled` | int | `1` | Set to `0` to disable the minimap overlay entirely. | +| `minimap_width` / `minimap_height` | int | `200` / `200` | Size in pixels of the minimap rectangle rendered in the bottom-left corner. | +| `minimap_margin` | int | `16` | Padding (pixels) between the minimap and the screen edges. | +| `minimap_wall_[rgb]` | float | `0.0 / 0.0 / 0.0` | Wall line color per channel (0–1). | +| `minimap_background_[rgb]` | float | `1.0 / 1.0 / 1.0` | Background fill color per channel (0–1). | +| `minimap_arrow_[rgb]` | float | `1.0 / 0.0 / 0.0` | Player arrow color per channel (0–1). | +| `minimap_zoom` | float | `2.0` | Zoom level (pixels per world unit) for the minimap. | +| `minimap_breadcrumb_[rgb]` | float | `1.0 / 0.3 / 0.3` | Breadcrumb dot color per channel (0–1). | +| `minimap_breadcrumb_radius` | float | `3.0` | Breadcrumb radius in pixels. | +| `breadcrumb_max_distance` | float | `200.0` | Bread crumbs beyond this world-space distance from the player are not drawn (both in-world and on the minimap). | +| `minimap_ghost_fill_[rgb]` | float | `0.9 / 0.9 / 1.0` | Ghost dot interior color on the minimap. | +| `minimap_ghost_border_[rgb]` | float | `0.2 / 0.2 / 0.4` | Ghost dot border color. | +| `minimap_ghost_radius` | float | `4.0` | Ghost indicator radius in pixels. | Example: ```json { "camera_speed": 30.0, - "ghost_reset_distance": 8.5 + "ghost_reset_distance": 8.5, + "ghost_count": 600, + "ghost_count": 600, + "minimap_enabled": 1, + "minimap_width": 220, + "minimap_height": 220, + "minimap_margin": 24, + "minimap_wall_r": 0.0, + "minimap_wall_g": 0.0, + "minimap_wall_b": 0.0, + "minimap_background_r": 1.0, + "minimap_background_g": 1.0, + "minimap_background_b": 1.0, + "minimap_arrow_r": 1.0, + "minimap_arrow_g": 0.0, + "minimap_arrow_b": 0.0, + "minimap_zoom": 2.0, + "minimap_breadcrumb_r": 1.0, + "minimap_breadcrumb_g": 0.3, + "minimap_breadcrumb_b": 0.3, + "minimap_breadcrumb_radius": 3.0, + "breadcrumb_max_distance": 200.0, + "minimap_ghost_fill_r": 0.9, + "minimap_ghost_fill_g": 0.9, + "minimap_ghost_fill_b": 1.0, + "minimap_ghost_border_r": 0.2, + "minimap_ghost_border_g": 0.2, + "minimap_ghost_border_b": 0.4, + "minimap_ghost_radius": 4.0 } ``` +## Minimap Overlay +The minimap is a HUD overlay that reuses data parsed from `maze.txt` to draw a scrolling view of the maze. Player breadcrumbs are recorded every second and rendered as solid discs so you can retrace recent movement. Ghosts are displayed as border + fill circles that remain unrotated while the player arrow rotates with camera yaw. Tweak the overlay’s sizing, colors, and zoom via `ghostland.json`—when `minimap_zoom` increases, the entire map scales up; breadcrumb/ghost radius and colors are controlled by the dedicated `minimap_breadcrumb_*` and `minimap_ghost_*` keys described above. + ![gameplay capture](https://stromsy.com/content/ghostland_6.gif) diff --git a/ghostland.cpp b/ghostland.cpp index 55732fe..f14837f 100644 --- a/ghostland.cpp +++ b/ghostland.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void set_light_front(int xoffset, int yoffset); +void render_minimap(const glm::vec3 &player_pos, float player_yaw, const glm::vec3 *trail_positions, int trail_sz, const std::vector &ghosts); // player Player *player; @@ -42,6 +44,44 @@ float last_frame = 0.0f; float camera_speed = 20.0f; float ghost_reset_distance = 10.0f; +int ghost_count = 800; + +// Runtime configuration and palette for the minimap overlay. +struct MinimapConfig { + bool enabled = true; + int width = 200; + int height = 200; + int margin = 16; + float zoom = 2.0f; + glm::vec3 wall_color = glm::vec3(0.0f, 0.0f, 0.0f); + glm::vec3 background_color = glm::vec3(1.0f, 1.0f, 1.0f); + glm::vec3 arrow_color = glm::vec3(1.0f, 0.0f, 0.0f); + float breadcrumb_radius = 3.0f; + glm::vec3 breadcrumb_color = glm::vec3(1.0f, 0.3f, 0.3f); + float breadcrumb_max_distance = 200.0f; + float ghost_radius = 4.0f; + glm::vec3 ghost_fill_color = glm::vec3(0.9f, 0.9f, 1.0f); + glm::vec3 ghost_border_color = glm::vec3(0.2f, 0.2f, 0.4f); +}; + +struct MinimapSegment { + glm::vec2 start; + glm::vec2 end; +}; + +struct MinimapBounds { + float xmin = 0.0f; + float xmax = 0.0f; + float zmin = 0.0f; + float zmax = 0.0f; +}; + +MinimapConfig minimap_config; +std::vector minimap_segments; +MinimapBounds minimap_bounds; +unsigned int minimapVAO = 0; +unsigned int minimapVBO = 0; +int minimap_program = -1; const char *projectionC = "projection"; const char *viewC = "view"; @@ -75,6 +115,33 @@ int main(int argc, char *argv[]) { Config::loadFromFile("ghostland.json"); camera_speed = Config::getFloat("camera_speed", camera_speed); ghost_reset_distance = Config::getFloat("ghost_reset_distance", ghost_reset_distance); + minimap_config.enabled = Config::getInt("minimap_enabled", minimap_config.enabled ? 1 : 0) != 0; + minimap_config.width = Config::getInt("minimap_width", minimap_config.width); + minimap_config.height = Config::getInt("minimap_height", minimap_config.height); + minimap_config.margin = Config::getInt("minimap_margin", minimap_config.margin); + minimap_config.zoom = Config::getFloat("minimap_zoom", minimap_config.zoom); + minimap_config.wall_color.r = Config::getFloat("minimap_wall_r", minimap_config.wall_color.r); + minimap_config.wall_color.g = Config::getFloat("minimap_wall_g", minimap_config.wall_color.g); + minimap_config.wall_color.b = Config::getFloat("minimap_wall_b", minimap_config.wall_color.b); + minimap_config.background_color.r = Config::getFloat("minimap_background_r", minimap_config.background_color.r); + minimap_config.background_color.g = Config::getFloat("minimap_background_g", minimap_config.background_color.g); + minimap_config.background_color.b = Config::getFloat("minimap_background_b", minimap_config.background_color.b); + minimap_config.arrow_color.r = Config::getFloat("minimap_arrow_r", minimap_config.arrow_color.r); + minimap_config.arrow_color.g = Config::getFloat("minimap_arrow_g", minimap_config.arrow_color.g); + minimap_config.arrow_color.b = Config::getFloat("minimap_arrow_b", minimap_config.arrow_color.b); + minimap_config.breadcrumb_color.r = Config::getFloat("minimap_breadcrumb_r", minimap_config.breadcrumb_color.r); + minimap_config.breadcrumb_color.g = Config::getFloat("minimap_breadcrumb_g", minimap_config.breadcrumb_color.g); + minimap_config.breadcrumb_color.b = Config::getFloat("minimap_breadcrumb_b", minimap_config.breadcrumb_color.b); + minimap_config.breadcrumb_radius = Config::getFloat("minimap_breadcrumb_radius", minimap_config.breadcrumb_radius); + minimap_config.breadcrumb_max_distance = Config::getFloat("breadcrumb_max_distance", minimap_config.breadcrumb_max_distance); + minimap_config.ghost_fill_color.r = Config::getFloat("minimap_ghost_fill_r", minimap_config.ghost_fill_color.r); + minimap_config.ghost_fill_color.g = Config::getFloat("minimap_ghost_fill_g", minimap_config.ghost_fill_color.g); + minimap_config.ghost_fill_color.b = Config::getFloat("minimap_ghost_fill_b", minimap_config.ghost_fill_color.b); + minimap_config.ghost_border_color.r = Config::getFloat("minimap_ghost_border_r", minimap_config.ghost_border_color.r); + minimap_config.ghost_border_color.g = Config::getFloat("minimap_ghost_border_g", minimap_config.ghost_border_color.g); + minimap_config.ghost_border_color.b = Config::getFloat("minimap_ghost_border_b", minimap_config.ghost_border_color.b); + minimap_config.ghost_radius = Config::getFloat("minimap_ghost_radius", minimap_config.ghost_radius); + ghost_count = Config::getInt("ghost_count", ghost_count); // Maze file contains serialized spawn and wall mesh data generated by mazeparser.py. FILE *fp = fopen("maze.txt", "r"); @@ -101,6 +168,7 @@ int main(int argc, char *argv[]) { zmax_wall = -9999.9; // read walls for (int i = 0; i < num_walls; i++) { + glm::vec2 wall_floor_pts[2]; for (int j = 0; j < 6; j++) { int vix = i*6*6 + j*6; if (fscanf( @@ -126,6 +194,13 @@ int main(int argc, char *argv[]) { } else if (wall_vertices[vix + 2] > zmax_wall) { zmax_wall = wall_vertices[vix + 2]; } + if (j < 2) { + wall_floor_pts[j].x = wall_vertices[vix]; + wall_floor_pts[j].y = wall_vertices[vix + 2]; + if (j == 1) { + minimap_segments.push_back({wall_floor_pts[0], wall_floor_pts[1]}); + } + } } if (fscanf(fp, "\n") == EOF) { printf("4th fscanf failed.\n"); @@ -133,6 +208,10 @@ int main(int argc, char *argv[]) { } } player->wall_vertices = wall_vertices; + minimap_bounds.xmin = xmin_wall; + minimap_bounds.xmax = xmax_wall; + minimap_bounds.zmin = zmin_wall; + minimap_bounds.zmax = zmax_wall; // read floor float *floor_vertices = (float *)calloc(sizeof(float), 6*6); @@ -340,6 +419,24 @@ int main(int argc, char *argv[]) { glfwTerminate(); return -1; } + + if (minimap_config.enabled) { + minimap_program = create_shader_program("minimapshader.glsl", "minimapfragshader.glsl"); + if (minimap_program < 0) { + glfwTerminate(); + return -1; + } + glGenVertexArrays(1, &minimapVAO); + glGenBuffers(1, &minimapVBO); + glBindVertexArray(minimapVAO); + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + size_t minimap_buffer_capacity = std::max(minimap_segments.size() * 4 + 12, 256); + glBufferData(GL_ARRAY_BUFFER, minimap_buffer_capacity * sizeof(float), NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } // TODO move these into config file?? int time_secI = 0, num_frames = 1; @@ -356,7 +453,7 @@ int main(int argc, char *argv[]) { float smallcutoff = glm::cos(glm::radians(7.5f)); std::vector ghosts; - for (int i = 0; i < 800; i++) { + for (int i = 0; i < std::max(1, ghost_count); i++) { Ghost *ghost = new Ghost(xmin_wall, xmax_wall, zmin_wall, zmax_wall); // Ensure ghost is not within 30.0 units of start_position @@ -367,7 +464,7 @@ int main(int argc, char *argv[]) { ghosts.push_back(ghost); } - player->mouse_callback(window, WINDOWWIDTH/2, WINDOWHEIGHT); + player->mouse_callback(window, WINDOWWIDTH/2, WINDOWHEIGHT/2); int FPS = -1; @@ -509,7 +606,16 @@ int main(int argc, char *argv[]) { glm::vec3 trail_color = glm::vec3(1.0f, 0.0f, 0.0f); set_uniform(trail_program, objectcolorC, trail_color); + float breadcrumb_draw_limit_sq = minimap_config.breadcrumb_max_distance > 0.0f + ? minimap_config.breadcrumb_max_distance * minimap_config.breadcrumb_max_distance + : -1.0f; for (int i = 0; i < trail_sz; i++) { + if (breadcrumb_draw_limit_sq > 0.0f) { + glm::vec3 delta = trail_positions[i] - player_pos; + if (glm::dot(delta, delta) > breadcrumb_draw_limit_sq) { + continue; + } + } glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, trail_positions[i]); model = glm::rotate(model, glm::radians(trail_angles[i]), glm::vec3(0.0f, -1.0f, 0.0f)); @@ -548,6 +654,10 @@ int main(int argc, char *argv[]) { glDrawArrays(GL_TRIANGLES, 0, (3 + 3 + 2) * 6); } + if (minimap_config.enabled) { + render_minimap(player->get_pos(), player->get_yaw(), trail_positions, trail_sz, ghosts); + } + if (FPS != -1) { glUseProgram(text_program); projection = glm::ortho(0.0f, static_cast(WINDOWWIDTH), 0.0f, static_cast(WINDOWHEIGHT)); @@ -570,6 +680,12 @@ int main(int argc, char *argv[]) { glDeleteVertexArrays(1, &floorVAO); glDeleteBuffers(1, &floorVBO); + if (minimap_config.enabled) { + glDeleteVertexArrays(1, &minimapVAO); + glDeleteBuffers(1, &minimapVBO); + glDeleteProgram(minimap_program); + } + glfwTerminate(); return 0; } @@ -585,3 +701,154 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) { void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { player->scroll_callback(window, xoffset, yoffset); } + +void render_minimap(const glm::vec3 &player_pos, float player_yaw, const glm::vec3 *trail_positions, int trail_sz, const std::vector &ghosts) { + // 2D overlay renders last so sorting/blending for 3D content is unaffected. + if (!minimap_config.enabled || minimap_program < 0) { + return; + } + + glDisable(GL_DEPTH_TEST); + glUseProgram(minimap_program); + + glm::mat4 ortho = glm::ortho(0.0f, static_cast(WINDOWWIDTH), 0.0f, static_cast(WINDOWHEIGHT)); + set_uniform(minimap_program, projectionC, ortho); + + glBindVertexArray(minimapVAO); + + float left = static_cast(minimap_config.margin); + float bottom = static_cast(minimap_config.margin); + float right = left + minimap_config.width; + float top = bottom + minimap_config.height; + + glEnable(GL_SCISSOR_TEST); + glScissor(static_cast(left), static_cast(bottom), minimap_config.width, minimap_config.height); + + float rect_vertices[] = { + left, bottom, + right, bottom, + right, top, + left, top, + left, bottom, + right, top + }; + + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(rect_vertices), rect_vertices); + set_uniform(minimap_program, "inColor", minimap_config.background_color); + glDrawArrays(GL_TRIANGLES, 0, 6); + + glm::vec2 player_flat(player_pos.x, player_pos.z); + glm::vec2 map_center(left + minimap_config.width * 0.5f, bottom + minimap_config.height * 0.5f); + float span_x = std::max(1.0f, minimap_bounds.xmax - minimap_bounds.xmin); + float span_z = std::max(1.0f, minimap_bounds.zmax - minimap_bounds.zmin); + float span_scale = std::min( + static_cast(minimap_config.width) / span_x, + static_cast(minimap_config.height) / span_z + ); + float scale = minimap_config.zoom > 0.0f ? minimap_config.zoom : span_scale; + + static std::vector line_vertices; + line_vertices.clear(); + line_vertices.reserve(minimap_segments.size() * 4); + for (const auto &segment : minimap_segments) { + glm::vec2 rs = segment.start - player_flat; + glm::vec2 re = segment.end - player_flat; + line_vertices.push_back(map_center.x + rs.x * scale); + line_vertices.push_back(map_center.y + rs.y * scale); + line_vertices.push_back(map_center.x + re.x * scale); + line_vertices.push_back(map_center.y + re.y * scale); + } + if (!line_vertices.empty()) { + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, line_vertices.size() * sizeof(float), line_vertices.data()); + set_uniform(minimap_program, "inColor", minimap_config.wall_color); + glDrawArrays(GL_LINES, 0, line_vertices.size() / 2); + } + + // Breadcrumbs mark recently recorded player positions. + if (trail_positions != nullptr && trail_sz > 0 && minimap_config.breadcrumb_radius > 0.0f) { + const int circle_segments = 12; + const float angle_step = glm::two_pi() / static_cast(circle_segments); + float radius = minimap_config.breadcrumb_radius; + float max_dist_sq = minimap_config.breadcrumb_max_distance > 0.0f ? minimap_config.breadcrumb_max_distance * minimap_config.breadcrumb_max_distance : -1.0f; + std::vector circle_vertices((circle_segments + 2) * 2); + for (int i = 0; i < trail_sz; i++) { + glm::vec2 rel = glm::vec2(trail_positions[i].x, trail_positions[i].z) - player_flat; + if (max_dist_sq > 0.0f && glm::dot(rel, rel) > max_dist_sq) { + continue; + } + glm::vec2 center = map_center + rel * scale; + circle_vertices[0] = center.x; + circle_vertices[1] = center.y; + for (int seg = 0; seg <= circle_segments; seg++) { + float angle = seg * angle_step; + circle_vertices[(seg + 1) * 2 + 0] = center.x + cos(angle) * radius; + circle_vertices[(seg + 1) * 2 + 1] = center.y + sin(angle) * radius; + } + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, circle_vertices.size() * sizeof(float), circle_vertices.data()); + set_uniform(minimap_program, "inColor", minimap_config.breadcrumb_color); + glDrawArrays(GL_TRIANGLE_FAN, 0, circle_vertices.size() / 2); + } + } + + // Ghosts are shown as border+fill circles; re-use a small triangle fan per ghost. + if (!ghosts.empty() && minimap_config.ghost_radius > 0.0f) { + const int ghost_segments = 16; + const float ghost_angle_step = glm::two_pi() / static_cast(ghost_segments); + std::vector ghost_vertices((ghost_segments + 2) * 2); + auto draw_circle = [&](const glm::vec2 ¢er, float radius, const glm::vec3 &color) { + if (radius <= 0.0f) return; + ghost_vertices[0] = center.x; + ghost_vertices[1] = center.y; + for (int seg = 0; seg <= ghost_segments; ++seg) { + float angle = seg * ghost_angle_step; + ghost_vertices[(seg + 1) * 2 + 0] = center.x + cos(angle) * radius; + ghost_vertices[(seg + 1) * 2 + 1] = center.y + sin(angle) * radius; + } + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, ghost_vertices.size() * sizeof(float), ghost_vertices.data()); + set_uniform(minimap_program, "inColor", color); + glDrawArrays(GL_TRIANGLE_FAN, 0, ghost_vertices.size() / 2); + }; + float border_radius = minimap_config.ghost_radius; + float fill_radius = std::max(0.0f, border_radius - 1.0f); + for (const Ghost *ghost : ghosts) { + glm::vec3 ghost_pos = ghost->get_pos(); + glm::vec2 rel = glm::vec2(ghost_pos.x, ghost_pos.z) - player_flat; + glm::vec2 center = map_center + rel * scale; + draw_circle(center, border_radius, minimap_config.ghost_border_color); + draw_circle(center, fill_radius, minimap_config.ghost_fill_color); + } + } + + float arrow_length = std::min(minimap_config.width, minimap_config.height) * 0.2f; + float arrow_half_width = arrow_length * 0.35f; + float yaw_radians = glm::radians(player_yaw - 90.0f); + float cos_yaw = cos(yaw_radians); + float sin_yaw = sin(yaw_radians); + auto rotate = [&](float x, float y) -> glm::vec2 { + return glm::vec2( + x * cos_yaw - y * sin_yaw, + x * sin_yaw + y * cos_yaw + ); + }; + glm::vec2 tip = map_center + rotate(0.0f, arrow_length * 0.5f); + glm::vec2 left_pt = map_center + rotate(-arrow_half_width, -arrow_length * 0.5f); + glm::vec2 right_pt = map_center + rotate(arrow_half_width, -arrow_length * 0.5f); + float arrow_vertices[] = { + tip.x, tip.y, + left_pt.x, left_pt.y, + right_pt.x, right_pt.y + }; + glBindBuffer(GL_ARRAY_BUFFER, minimapVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(arrow_vertices), arrow_vertices); + set_uniform(minimap_program, "inColor", minimap_config.arrow_color); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + glDisable(GL_SCISSOR_TEST); + glEnable(GL_DEPTH_TEST); +} diff --git a/ghostland.json b/ghostland.json index fb2715b..0fa82f9 100644 --- a/ghostland.json +++ b/ghostland.json @@ -1,4 +1,31 @@ { "camera_speed": 35.0, - "ghost_reset_distance": 5.0 + "ghost_reset_distance": 5.0, + "ghost_count": 800, + "minimap_enabled": 1, + "minimap_width": 200, + "minimap_height": 200, + "minimap_margin": 16, + "minimap_wall_r": 0.8, + "minimap_wall_g": 0.8, + "minimap_wall_b": 0.8, + "minimap_background_r": 0.2, + "minimap_background_g": 0.1, + "minimap_background_b": 0.1, + "minimap_arrow_r": 1.0, + "minimap_arrow_g": 0.0, + "minimap_arrow_b": 0.0, + "minimap_zoom": 2.0, + "minimap_breadcrumb_r": 1.0, + "minimap_breadcrumb_g": 0.3, + "minimap_breadcrumb_b": 0.3, + "minimap_breadcrumb_radius": 3.0, + "minimap_ghost_fill_r": 0.9, + "minimap_ghost_fill_g": 0.9, + "minimap_ghost_fill_b": 1.0, + "minimap_ghost_border_r": 0.2, + "minimap_ghost_border_g": 0.2, + "minimap_ghost_border_b": 0.4, + "minimap_ghost_radius": 4.0, + "breadcrumb_max_distance": 200.0 } diff --git a/minimapfragshader.glsl b/minimapfragshader.glsl new file mode 100644 index 0000000..ad646f1 --- /dev/null +++ b/minimapfragshader.glsl @@ -0,0 +1,10 @@ +#version 460 core +// Solid-color fragment shader for minimap overlay primitives. +out vec4 FragColor; + +uniform vec3 inColor; + +void main() +{ + FragColor = vec4(inColor, 1.0); +} diff --git a/minimapshader.glsl b/minimapshader.glsl new file mode 100644 index 0000000..fdcd896 --- /dev/null +++ b/minimapshader.glsl @@ -0,0 +1,10 @@ +#version 460 core +// Vertex shader for minimap overlay geometry (lines/quads) in screen space. +layout (location = 0) in vec2 aPos; + +uniform mat4 projection; + +void main() +{ + gl_Position = projection * vec4(aPos, 0.0, 1.0); +}