855 lines
34 KiB
C++
855 lines
34 KiB
C++
#include <algorithm>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include "glad/glad.h"
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include "stb_image.h"
|
|
|
|
#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>
|
|
#include <vector>
|
|
#include <math.h>
|
|
|
|
#include "collisions.h"
|
|
#include "player.h"
|
|
#include "shader.h"
|
|
#include "ghost.h"
|
|
#include "text.h"
|
|
#include "config.h"
|
|
|
|
// Forward declarations for GLFW callbacks configured at start-up.
|
|
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;
|
|
|
|
// settings
|
|
unsigned int WINDOWWIDTH = 1600;
|
|
unsigned int WINDOWHEIGHT = 900;
|
|
|
|
// camera
|
|
bool first_frame = false;
|
|
float timed = 0.0f;
|
|
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";
|
|
const char *modelC = "model";
|
|
|
|
const char *objectcolorC = "objectcolor";
|
|
const char *viewposC = "viewPos";
|
|
const char *shininessC = "material.shininess";
|
|
const char *colorC = "material.color";
|
|
const char *lightposC = "light.position";
|
|
const char *directionC = "light.direction";
|
|
const char *largecutoffC = "light.largecutoff";
|
|
const char *smallcutoffC = "light.smallcutoff";
|
|
const char *ambientC = "light.ambient";
|
|
const char *diffuseC = "light.diffuse";
|
|
const char *specularC = "light.specular";
|
|
|
|
int num_walls;
|
|
float *wall_vertices;
|
|
|
|
int trailmax = 500;
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
int success;
|
|
glm::vec3 position;
|
|
glm::vec3 start_position;
|
|
float start_yaw;
|
|
|
|
// Load configuration knobs before spinning up the world.
|
|
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");
|
|
float yaw;
|
|
if (fscanf(fp, "%f %f %f %f\n", &position.x, &position.y, &position.z, &yaw) == EOF) {
|
|
printf("1st fscanf failed.\n");
|
|
return -1;
|
|
}
|
|
start_position = position;
|
|
start_yaw = yaw;
|
|
player = new Player(position, yaw);
|
|
if (fscanf(fp, "%d\n", &num_walls) == EOF) {
|
|
printf("2nd fscanf failed.\n");
|
|
return -1;
|
|
}
|
|
player->num_walls = num_walls;
|
|
// num surfaces * 6 (vertices per point) * 6 (floats per point)
|
|
int num_wall_vertices = num_walls * 6 * 6;
|
|
wall_vertices = (float *)calloc(sizeof(float), num_wall_vertices);
|
|
float xmin_wall, xmax_wall, zmin_wall, zmax_wall;
|
|
xmin_wall = 9999.9;
|
|
xmax_wall = -9999.9;
|
|
zmin_wall = 9999.9;
|
|
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(
|
|
fp,
|
|
"%f %f %f %f %f %f\n",
|
|
&wall_vertices[vix],
|
|
&wall_vertices[vix+1],
|
|
&wall_vertices[vix+2],
|
|
&wall_vertices[vix+3],
|
|
&wall_vertices[vix+4],
|
|
&wall_vertices[vix+5]
|
|
) == EOF) {
|
|
printf("3rd fscanf failed.\n");
|
|
return -1;
|
|
}
|
|
if (wall_vertices[vix] < xmin_wall) {
|
|
xmin_wall = wall_vertices[vix];
|
|
} else if (wall_vertices[vix] > xmax_wall) {
|
|
xmax_wall = wall_vertices[vix];
|
|
}
|
|
if (wall_vertices[vix + 2] < zmin_wall) {
|
|
zmin_wall = wall_vertices[vix + 2];
|
|
} 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");
|
|
return -1;
|
|
}
|
|
}
|
|
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);
|
|
for (int j = 0; j < 6; j++) {
|
|
int vix = j*6;
|
|
if (fscanf(
|
|
fp,
|
|
"%f %f %f %f %f %f\n",
|
|
&floor_vertices[vix],
|
|
&floor_vertices[vix+1],
|
|
&floor_vertices[vix+2],
|
|
&floor_vertices[vix+3],
|
|
&floor_vertices[vix+4],
|
|
&floor_vertices[vix+5]
|
|
) == EOF) {
|
|
printf("floor fscanf failed.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
// Initialize GLFW/GLAD and windowing.
|
|
glfwInit();
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
|
|
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
|
const GLFWvidmode *monitor_mode = glfwGetVideoMode(monitor);
|
|
|
|
WINDOWWIDTH = monitor_mode->width;
|
|
WINDOWHEIGHT = monitor_mode->height;
|
|
//WINDOWWIDTH = 1280;
|
|
//WINDOWHEIGHT = 960;
|
|
|
|
GLFWwindow* window = glfwCreateWindow(WINDOWWIDTH, WINDOWHEIGHT, "Ghostland!", monitor, NULL);
|
|
//GLFWwindow* window = glfwCreateWindow(WINDOWWIDTH, WINDOWHEIGHT, "Ghostland!", NULL, NULL);
|
|
if (window == NULL)
|
|
{
|
|
printf("Failed to create GLFW window.\n");
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
glfwMakeContextCurrent(window);
|
|
//glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
|
|
glfwSetCursorPosCallback(window, mouse_callback);
|
|
glfwSetScrollCallback(window, scroll_callback);
|
|
|
|
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
|
|
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
|
|
{
|
|
printf("Failed to initialize GLAD.\n");
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
//glEnable(GL_CULL_FACE);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
int wall_program = create_shader_program("vertexshader.glsl", "fragmentshader.glsl");
|
|
if (wall_program < 0) {
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
|
|
// Perhaps there's a better way of doing this, rather than re-compiling the same files?
|
|
int floor_program = create_shader_program("vertexshader.glsl", "fragmentshader.glsl");
|
|
if (wall_program < 0) {
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
|
|
// do stuff for walls
|
|
unsigned int wallsVBO, wallsVAO;
|
|
glGenVertexArrays(1, &wallsVAO);
|
|
glGenBuffers(1, &wallsVBO);
|
|
|
|
glBindVertexArray(wallsVAO);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, wallsVBO);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * num_wall_vertices, wall_vertices, GL_STATIC_DRAW);
|
|
|
|
// position attribute
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
// normal attribute
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
|
|
glEnableVertexAttribArray(1);
|
|
|
|
// do stuff for floor
|
|
unsigned int floorVBO, floorVAO;
|
|
glGenVertexArrays(1, &floorVAO);
|
|
glGenBuffers(1, &floorVBO);
|
|
|
|
glBindVertexArray(floorVAO);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, floorVBO);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 6 * 1, floor_vertices, GL_STATIC_DRAW);
|
|
|
|
// position attribute
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
// normal attribute
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
|
|
glEnableVertexAttribArray(1);
|
|
|
|
// do stuff for trail
|
|
float trail_vertices[] = {
|
|
0.25f, 0.0f, 0.0f,
|
|
-0.25f, 0.0f, 0.5f,
|
|
-0.25f, 0.0f, -0.5f,
|
|
|
|
-0.25f, 0.0f, 0.1f,
|
|
-0.25f, 0.0f, -0.1f,
|
|
-0.75f, 0.0f, 0.1f,
|
|
|
|
-0.75f, 0.0f, 0.1f,
|
|
-0.75f, 0.0f, -0.1f,
|
|
-0.25f, 0.0f, -0.1f,
|
|
};
|
|
|
|
unsigned int trailVBO, trailVAO;
|
|
glGenVertexArrays(1, &trailVAO);
|
|
glGenBuffers(1, &trailVBO);
|
|
|
|
glBindVertexArray(trailVAO);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, trailVBO);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * 3 * 3, trail_vertices, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
int trail_program = create_shader_program("trailshader.glsl", "trailfragshader.glsl");
|
|
if (trail_program < 0) {
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
|
|
glm::vec3 *trail_positions = (glm::vec3 *)calloc(sizeof(glm::vec3), trailmax);
|
|
float *trail_angles = (float *)calloc(sizeof(float), trailmax);
|
|
int trail_ix = 0;
|
|
int trail_sz = 0;
|
|
|
|
// do stuff for ghosts
|
|
// create program
|
|
int ghost_program = create_shader_program("ghostshader.glsl", "ghostfragshader.glsl");
|
|
if (ghost_program < 0) {
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
// ghost vertices
|
|
float ghost_vertices[] = {
|
|
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
|
|
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
|
|
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
|
|
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
|
|
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
|
|
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
|
|
};
|
|
unsigned int ghostVBO, ghostVAO;
|
|
glGenVertexArrays(1, &ghostVAO);
|
|
glBindVertexArray(ghostVAO);
|
|
glGenBuffers(1, &ghostVBO);
|
|
glBindBuffer(GL_ARRAY_BUFFER, ghostVBO);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(ghost_vertices), ghost_vertices, GL_STATIC_DRAW);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
|
|
glEnableVertexAttribArray(2);
|
|
// ghost texture
|
|
unsigned int ghost_texture;
|
|
glGenTextures(1, &ghost_texture);
|
|
glBindTexture(GL_TEXTURE_2D, ghost_texture);
|
|
// set the texture wrapping parameters
|
|
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_);
|
|
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
// set texture filtering parameters
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
int width, height, numberChannels;
|
|
//stbi_set_flip_vertically_on_load(true);
|
|
unsigned char *ghost_data = stbi_load("ghost_facing_right.png", &width, &height, &numberChannels, 0);
|
|
printf("Num channels: %d\n", numberChannels);
|
|
if (!ghost_data) {
|
|
printf("Failed to load texture!\n");
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ghost_data);
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
stbi_image_free(ghost_data);
|
|
|
|
// do stuff for text
|
|
int text_program;
|
|
if (init_text(&text_program) < 0) {
|
|
printf("Failed to load text module!\n");
|
|
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;
|
|
float time_sec;
|
|
glm::vec3 wall_color = glm::vec3(0.61f, 0.6f, 0.59f);
|
|
float wall_shininess = 6.5;
|
|
glm::vec3 floor_color = glm::vec3(0.11f, 0.1f, 0.09f);
|
|
float floor_shininess = 3.25;
|
|
glm::vec3 ambient = glm::vec3(0.007f, 0.006f, 0.005f);
|
|
glm::vec3 diffuse = glm::vec3(0.61f, 0.60f, 0.59f);
|
|
glm::vec3 specular = glm::vec3(0.11f, 0.10f, 0.09f);
|
|
|
|
float largecutoff = glm::cos(glm::radians(30.0f));
|
|
float smallcutoff = glm::cos(glm::radians(7.5f));
|
|
|
|
std::vector<Ghost *> ghosts;
|
|
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
|
|
while (glm::length(ghost->get_pos() - start_position) <= 30.0f) {
|
|
ghost->regenerate_position();
|
|
}
|
|
|
|
ghosts.push_back(ghost);
|
|
}
|
|
|
|
player->mouse_callback(window, WINDOWWIDTH/2, WINDOWHEIGHT/2);
|
|
|
|
int FPS = -1;
|
|
|
|
// Main render/game loop.
|
|
while (!glfwWindowShouldClose(window))
|
|
{
|
|
float current_frame = static_cast<float>(glfwGetTime());
|
|
int current_frameI = (int)current_frame;
|
|
if (current_frameI != time_secI) { // show stats every second
|
|
FPS = num_frames;
|
|
glm::vec3 player_pos = player->get_pos();
|
|
float yaw, pitch;
|
|
yaw = player->get_yaw();
|
|
pitch = player->get_pitch();
|
|
printf("FPS: %d\n", num_frames);
|
|
printf("Player is at: (%f, %f, %f) facing (%f, %f)\n", player_pos.x, player_pos.y, player_pos.z, yaw, pitch);
|
|
time_sec = current_frame;
|
|
time_secI = current_frameI;
|
|
num_frames = 1;
|
|
trail_positions[trail_ix].x = player_pos.x;
|
|
trail_positions[trail_ix].y = 6.5;
|
|
trail_positions[trail_ix].z = player_pos.z;
|
|
trail_angles[trail_ix] = yaw;
|
|
//printf("Recorded trail %d: (%f, %f, %f) %f\n", trail_ix, trail_positions[trail_ix].x, trail_positions[trail_ix].y, trail_positions[trail_ix].z, yaw);
|
|
trail_ix = (trail_ix + 1) % trailmax;
|
|
if (trail_sz < trailmax) {
|
|
trail_sz++;
|
|
}
|
|
} else { // otherwise increase number of frames
|
|
num_frames++;
|
|
}
|
|
timed = current_frame - last_frame;
|
|
last_frame = current_frame;
|
|
if (!first_frame) {
|
|
first_frame = true;
|
|
continue;
|
|
}
|
|
|
|
player->process_input(window);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glUseProgram(wall_program);
|
|
|
|
float fov = player->get_fov();
|
|
glm::mat4 projection = glm::perspective(glm::radians(fov), (float)WINDOWWIDTH / (float)WINDOWHEIGHT, 0.1f, 256.0f);
|
|
set_uniform(wall_program, projectionC, projection);
|
|
|
|
glm::vec3 camera_pos, camera_front, camera_up;
|
|
camera_pos = player->get_camera_pos();
|
|
camera_front = player->get_camera_front();
|
|
camera_up = player->get_camera_up();
|
|
|
|
glm::mat4 view = glm::lookAt(camera_pos, camera_pos + camera_front, camera_up);
|
|
set_uniform(wall_program, viewC, view);
|
|
|
|
glm::mat4 model = glm::mat4(1.0f);
|
|
set_uniform(wall_program, modelC, model);
|
|
|
|
// light position
|
|
glm::vec3 light_pos = player->get_light_pos();
|
|
//printf("light_pos: (%f, %f, %f)\n", light_pos.x, light_pos.y, light_pos.z);
|
|
glm::vec3 light_front = player->get_light_front();
|
|
//printf("light_pos: (%f, %f, %f)\n", light_front.x, light_front.y, light_front.z);
|
|
|
|
// Sort ghosts by distance to player for optimization.
|
|
glm::vec3 player_pos = player->get_pos();
|
|
struct {
|
|
bool operator()(const Ghost *a, const Ghost *b) const {
|
|
glm::vec3 a_diff, b_diff;
|
|
a_diff = player->get_pos() - a->get_pos();
|
|
b_diff = player->get_pos() - b->get_pos();
|
|
float a_dist, b_dist;
|
|
a_dist = glm::length(a_diff);
|
|
b_dist = glm::length(b_diff);
|
|
return a_dist < b_dist;
|
|
}
|
|
} ghost_compare;
|
|
// Only the closest 20% need perfect ordering for blending, so partial_sort is cheaper than full sort.
|
|
std::partial_sort(ghosts.begin(), ghosts.begin() + ghosts.size() / 5, ghosts.end(), ghost_compare);
|
|
|
|
// Calculate nearest ghost distance (first ghost is now closest)
|
|
float nearest_ghost_distance = glm::length(ghosts[0]->get_pos() - player_pos);
|
|
|
|
// Interpolate flashlight color based on nearest ghost distance
|
|
// At 30.0+ units: (0.61, 0.60, 0.59) - normal color
|
|
// At 10.0 units: (0.99, 0.60, 0.59) - red shift
|
|
float color_factor = 1.0f;
|
|
if (nearest_ghost_distance < 30.0f) {
|
|
color_factor = (nearest_ghost_distance - 10.0f) / (30.0f - 10.0f);
|
|
color_factor = glm::clamp(color_factor, 0.0f, 1.0f);
|
|
}
|
|
|
|
glm::vec3 normal_color = glm::vec3(0.61f, 0.60f, 0.59f);
|
|
glm::vec3 danger_color = glm::vec3(0.99f, 0.60f, 0.59f);
|
|
diffuse = glm::mix(danger_color, normal_color, color_factor);
|
|
|
|
// fragment requirements
|
|
set_uniform(wall_program, viewposC, camera_pos);
|
|
set_uniform(wall_program, shininessC, wall_shininess);
|
|
set_uniform(wall_program, colorC, wall_color);
|
|
set_uniform(wall_program, lightposC, light_pos);
|
|
set_uniform(wall_program, directionC, light_front);
|
|
set_uniform(wall_program, largecutoffC, largecutoff);
|
|
set_uniform(wall_program, smallcutoffC, smallcutoff);
|
|
set_uniform(wall_program, ambientC, ambient);
|
|
set_uniform(wall_program, diffuseC, diffuse);
|
|
set_uniform(wall_program, specularC, specular);
|
|
|
|
glBindVertexArray(wallsVAO);
|
|
glDrawArrays(GL_TRIANGLES, 0, num_wall_vertices);
|
|
|
|
glUseProgram(floor_program);
|
|
|
|
set_uniform(floor_program, projectionC, projection);
|
|
set_uniform(floor_program, viewC, view);
|
|
set_uniform(floor_program, modelC, model);
|
|
set_uniform(floor_program, viewposC, camera_pos);
|
|
|
|
// fragment requirements
|
|
set_uniform(floor_program, shininessC, floor_shininess);
|
|
set_uniform(floor_program, colorC, floor_color);
|
|
set_uniform(floor_program, lightposC, light_pos);
|
|
set_uniform(floor_program, directionC, light_front);
|
|
set_uniform(floor_program, largecutoffC, largecutoff);
|
|
set_uniform(floor_program, smallcutoffC, smallcutoff);
|
|
set_uniform(floor_program, ambientC, ambient);
|
|
set_uniform(floor_program, diffuseC, diffuse);
|
|
set_uniform(floor_program, specularC, specular);
|
|
|
|
glBindVertexArray(floorVAO);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6 * 6 * 1);
|
|
|
|
glUseProgram(trail_program);
|
|
|
|
set_uniform(trail_program, projectionC, projection);
|
|
set_uniform(trail_program, viewC, view);
|
|
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));
|
|
set_uniform(trail_program, modelC, model);
|
|
glBindVertexArray(trailVAO);
|
|
glDrawArrays(GL_TRIANGLES, 0, 3 * 3 * 3);
|
|
}
|
|
|
|
glUseProgram(ghost_program);
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, ghost_texture);
|
|
|
|
set_uniform(ghost_program, projectionC, projection);
|
|
set_uniform(ghost_program, viewC, view);
|
|
for (int i = ghosts.size()/5; i >= 0; i--) {
|
|
glm::mat4 ghost_model = ghosts[i]->get_model(camera_pos);
|
|
ghosts[i]->apply_movement(current_frame, timed);
|
|
|
|
// When ghosts close in, reset player back to the maze entrance.
|
|
glm::vec3 ghost_pos = ghosts[i]->get_pos();
|
|
glm::vec3 player_pos = player->get_pos();
|
|
float distance = glm::length(ghost_pos - player_pos);
|
|
if (distance <= ghost_reset_distance) {
|
|
player->reset_position(start_position, start_yaw);
|
|
}
|
|
|
|
// Nudge ghosts away from the spawn area so the player gets a breather.
|
|
float start_distance = glm::length(ghost_pos - start_position);
|
|
if (start_distance <= 30.0f) {
|
|
ghosts[i]->reverse_direction_from_position(start_position);
|
|
}
|
|
|
|
set_uniform(ghost_program, modelC, ghost_model);
|
|
glBindVertexArray(ghostVAO);
|
|
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));
|
|
set_uniform(text_program, projectionC, projection);
|
|
char fps_buff[256];
|
|
sprintf(fps_buff, "FPS: %d", FPS);
|
|
render_text(text_program, std::string(fps_buff), 25.0F, WINDOWHEIGHT - 35.0f, 0.5f, glm::vec3(1.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
player->apply_movement(timed);
|
|
|
|
glfwSwapBuffers(window);
|
|
glfwPollEvents();
|
|
|
|
}
|
|
|
|
glDeleteVertexArrays(1, &wallsVAO);
|
|
glDeleteBuffers(1, &wallsVBO);
|
|
|
|
glDeleteVertexArrays(1, &floorVAO);
|
|
glDeleteBuffers(1, &floorVBO);
|
|
|
|
if (minimap_config.enabled) {
|
|
glDeleteVertexArrays(1, &minimapVAO);
|
|
glDeleteBuffers(1, &minimapVBO);
|
|
glDeleteProgram(minimap_program);
|
|
}
|
|
|
|
glfwTerminate();
|
|
return 0;
|
|
}
|
|
|
|
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
|
|
glViewport(0, 0, width, height);
|
|
}
|
|
|
|
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
|
|
player->mouse_callback(window, xpos, 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);
|
|
}
|