Add minimap. Some perforarmance tweaks especially around bread crumbs.
This commit is contained in:
271
ghostland.cpp
271
ghostland.cpp
@@ -9,6 +9,7 @@
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
@@ -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<Ghost *> &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<MinimapSegment> 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<size_t>(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<Ghost *> 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<float>(WINDOWWIDTH), 0.0f, static_cast<float>(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<Ghost *> &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<float>(WINDOWWIDTH), 0.0f, static_cast<float>(WINDOWHEIGHT));
|
||||
set_uniform(minimap_program, projectionC, ortho);
|
||||
|
||||
glBindVertexArray(minimapVAO);
|
||||
|
||||
float left = static_cast<float>(minimap_config.margin);
|
||||
float bottom = static_cast<float>(minimap_config.margin);
|
||||
float right = left + minimap_config.width;
|
||||
float top = bottom + minimap_config.height;
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(static_cast<int>(left), static_cast<int>(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<float>(minimap_config.width) / span_x,
|
||||
static_cast<float>(minimap_config.height) / span_z
|
||||
);
|
||||
float scale = minimap_config.zoom > 0.0f ? minimap_config.zoom : span_scale;
|
||||
|
||||
static std::vector<float> 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<float>() / static_cast<float>(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<float> 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<float>() / static_cast<float>(ghost_segments);
|
||||
std::vector<float> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user