You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

656 lines
18 KiB

#include <vector>
#include <cmath> // trig functions
#include <cstdlib> // calloc
// TODO: decide on extension library
//#include <GL/glew.h>
#include <GL/gl3w.h>
#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include <SDL_image.h>
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "aixlog.hpp"
#include "hexlib.h"
#include "hexgame.h"
#include "renderer.h"
#include "render_group.h"
#define MOVE_SPEED 5.f
#define ROTATE_SPEED 0.005f
#define CAMERA_Z_CLAMP_ANGLE 85.f
//#define PROJ_TYPE ORTHOGRAPHIC
#define PROJ_TYPE PERSPECTIVE
#define DEFAULT_VERTEX_SHADER_FILE "../data/default.vs"
#define DEFAULT_FRAGMENT_SHADER_FILE "../data/default.fs"
const char * LINE_FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = vec3(0,0,0);\n"
"}";
const char * DEBUG_FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = vec3(1,0,0);\n"
"}";
typedef struct clear_col
{
real32 R;
real32 G;
real32 B;
real32 A;
} clear_col;
clear_col g_clear_col { 75.f / 255.f, 135.f / 255.f, 135.f / 255.f, 1.f };
typedef struct gl_matrix_info
{
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
glm::mat4 MVP;
} gl_matrix_info;
enum projection_type
{
PERSPECTIVE,
ORTHOGRAPHIC,
};
struct camera
{
glm::vec3 position;
float hAngle;
float vAngle;
glm::vec3 target;
glm::vec3 forward;
glm::vec3 up;
glm::vec3 left;
};
// forward declarations
void initMatrices(projection_type p);
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam);
bool initHexGridBuffers(std::vector<hex_info>* hexes);
void fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex);
void fillColorBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes);
void fillHexLineBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes);
// globals
static gl_matrix_info g_scene_matrices;
static render_group* g_filled_hex_render_group;
static render_group* g_hex_line_render_group;
static render_group* g_debug_render_group;
static rg_shader_program g_default_shader;
static rg_shader_program g_line_shader;
// NOTE: entity render_group pointers are kept in the entity struct
static camera g_camera;
// TODO: testing lighting
static renPointLight g_test_light;
// interface
bool
initRenderer(SDL_Handles &handles, v2i vpDims)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GetCurrentDisplayMode(0, &handles.currentDisplayMode);
handles.window =
SDL_CreateWindow("hexgame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
vpDims.x, vpDims.y, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if (!handles.window) {
LOG(ERROR) << "Error creating window: " << SDL_GetError() << "\n";
return false;
}
handles.glContext = SDL_GL_CreateContext(handles.window);
if (!handles.glContext) {
LOG(ERROR) << "Error creating glContext: " << SDL_GetError() << "\n";
return false;
}
if (SDL_GL_SetSwapInterval(1) != 0) { // vsync
LOG(ERROR) << "SDL Errors: " << SDL_GetError() << "\n";
return false;
}
if (gl3wInit()) { // TODO: decide on extension library
LOG(ERROR) << "failed to initialize OpenGL\n";
return false;
}
LOG(INFO) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n";
LOG(INFO)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n";
LOG(INFO) << "opengl version: " << glGetString(GL_VERSION) << "\n";
glEnable(GL_DEPTH_TEST);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// TODO: glDebugMessageCallback is only availabe from >v4.3
// check and warn if context doesn't support this function here
glEnable (GL_DEBUG_OUTPUT);
glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0);
// hide VRAM debug messages
glDebugMessageControl(GL_DONT_CARE, 33361, GL_DONT_CARE, 0, 0, GL_FALSE);
const char* vs_code = utilDumpTextFile(DEFAULT_VERTEX_SHADER_FILE);
const char* fs_code = utilDumpTextFile(DEFAULT_FRAGMENT_SHADER_FILE);
bool shader_error = !rgInitShaderProgram(g_default_shader, vs_code, fs_code);
utilSafeFree(vs_code);
utilSafeFree(fs_code);
if (shader_error) {
LOG(ERROR) << "Error initializing shader program\n";
return false;
}
return true;
}
bool
addTexture(SDL_Handles &handles, const char * path)
{
// testing
LOG(INFO) << "Loading image: " << path << "\n";
SDL_Surface* image = IMG_Load(path);
if (!image)
{
LOG(ERROR) << "IMG_Load: " << IMG_GetError() << "\n";
return false;
}
GLuint tex_id;
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// store opengl id in SDL_Surface.userdat
image->userdata = (void*)(intptr_t) tex_id;
handles.texSurfaces.push_back(image);
return true;
}
v2f
getUnprojectedCoords(int32 x, int32 y, int32 vp_width, int32 vp_height)
{
// NOTE: using depth buffer may not be as accurate as doing ray-cast
GLfloat depth;
glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
glm::vec4 viewport = glm::vec4(0, 0, vp_width, vp_height);
glm::vec3 wincoord = glm::vec3(x, y, depth);
glm::vec3 vU = glm::unProject(wincoord, g_scene_matrices.view, g_scene_matrices.projection, viewport);
v2f v(vU.x, vU.y);
return v;
}
v3f
getCameraPosition()
{
return v3f(g_camera.position.x, g_camera.position.y, g_camera.position.z);
}
bool
createScene(std::vector<hex_info>* hexes, Entity* entities, uint32 entity_count)
{
// entities
for (uint i = 0; i < entity_count; i++) {
rgInitEntity(&entities[i]);
entities[i].ren_group->shader = g_default_shader;
}
initMatrices(PROJ_TYPE);
if (!initHexGridBuffers(hexes))
return false;
// debug draw vertices
uint debug_buf_len = 12; // 4 vertices, 3 floats per vertex
g_debug_render_group = rgInitSingle(g_default_shader, debug_buf_len, true, 0, GL_LINE_LOOP);
if (g_debug_render_group == nullptr)
return false;
gl_buffer& debug_vertex_buf = g_debug_render_group->render_objects[0]->vertex_buffer;
gl_buffer& debug_color_buf = g_debug_render_group->render_objects[0]->color_buffer;
gl_buffer& debug_normal_buf = g_debug_render_group->render_objects[0]->normal_buffer;
for (uint i = 0; i < debug_buf_len; i += 3) {
debug_color_buf.buffer[i] = 1.f;
debug_color_buf.buffer[i + 1] = 0.f;
debug_color_buf.buffer[i + 2] = 0.f;
debug_normal_buf.buffer[i] = 0.f;
debug_normal_buf.buffer[i + 1] = 0.f;
debug_normal_buf.buffer[i + 2] = 1.f;
}
rgBufferData(&debug_vertex_buf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER);
rgBufferData(&debug_color_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
rgBufferData(&debug_normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
// lights
// TODO: load light properties from scene/level files
//g_test_light.light_ID = glGetUniformLocation(g_entity_render_group.program_id, "light_position");
if (entities[0].ren_group != nullptr) {
g_test_light.light_ID = glGetUniformLocation(entities[0].ren_group->shader.program_id, "light_position");
g_test_light.position = glm::vec3(800, 300, 400); // above center of hexgrid
g_test_light.direction = glm::vec3(0, 0, 0) - g_test_light.position; // back towards test entity
g_test_light.color = glm::vec3(1.f, 1.f, 1.f);
g_test_light.intensity = 1.f;
}
return true;
}
void
moveCamera(bool up, bool left, bool down, bool right, bool forward, bool backward)
{
if (!up && !left && !down && !right && !forward && !backward)
return;
glm::vec3 f = g_camera.forward;
glm::vec3 u = g_camera.up;
glm::vec3 old = g_camera.position;
glm::vec3 &p = g_camera.position;
glm::vec3 v(0.f); // normalized direction
// TODO: still seems like we're adding magnitude when moving in 2 directions
#if 0
if (forward) v = glm::normalize(v + f);
if (backward) v = glm::normalize(v - f);
if (up) v = glm::normalize(v + u);
if (down) v = glm::normalize(v - u);
if (left) v -= glm::normalize(glm::cross(f, u));
if (right) v -= glm::normalize(glm::cross(u, f));
#else
if (forward) v += f;
if (backward) v -= f;
if (up) v += u;
if (down) v -= u;
if (left) v -= glm::cross(f, u);
if (right) v -= glm::cross(u, f);
#endif
p += (v * MOVE_SPEED);
glm::vec3 diff = old - p;
g_scene_matrices.view = glm::translate(g_scene_matrices.view, diff);
g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model;
}
void
rotateCamera(int32 xrel, int32 yrel)
{
camera &c = g_camera;
float &h = c.hAngle;
float &v = c.vAngle;
h += ROTATE_SPEED * xrel;
v -= ROTATE_SPEED * yrel;
// clamp vAngle to prevent gimbal lock
float a = glm::radians(CAMERA_Z_CLAMP_ANGLE);
if (v < (-1 * a)) v = (-1 * a);
if (v > a) v = a;
c.forward = glm::vec3(
glm::cos(v) * glm::sin(h),
glm::cos(v) * glm::cos(h),
glm::sin(v)
);
glm::normalize(c.forward);
c.up = glm::vec3(0,0,1);
c.left = glm::normalize(glm::cross(c.forward, c.up));
c.up = glm::normalize(glm::cross(c.left, c.forward));
g_scene_matrices.view = glm::lookAt(c.position, c.position + c.forward, c.up);
g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model;
}
// NOTE: don't need this yet
void
rollCamera(bool CW, bool CCW)
{
#if 0
if ((!CW && !CCW) || (CW && CCW))
return;
float a = 0.005f;
if (CW) a *= 1;
if (CCW) a *= -1;
camera &c = g_camera;
glm::mat4 m = glm::rotate(glm::mat4(1.f), a, c.forward);
glm::vec4 v(c.up.x, c.up.y, c.up.z, 0);
v = v * m;
g_camera.up = glm::vec3(v.x, v.y, v.z);
g_scene_matrices.view *= m;
g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model;
#endif
}
void
renderFrame(std::vector<hex_info> *hexes, Entity* entities, uint32 entity_count)
{
glClearColor(g_clear_col.R, g_clear_col.G, g_clear_col.B, g_clear_col.A);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glm::mat4 m_model = g_scene_matrices.model;
glm::mat4 m_view = g_scene_matrices.view;
glm::mat4 m_projection = g_scene_matrices.projection;
// filled hexes
// get new colors every frame
render_group* rg = g_filled_hex_render_group;
gl_buffer& color_buf = rg->render_objects[0]->color_buffer;
fillColorBuffer(color_buf.buffer, color_buf.buffer_len, hexes);
rgDraw(rg, m_model, m_view, m_projection,
g_test_light.position, g_test_light.light_ID);
// hex lines
rgDraw(g_hex_line_render_group, m_model, m_view, m_projection,
g_test_light.position, g_test_light.light_ID);
// entities
for (uint i = 0; i < entity_count; i++) {
rgDraw(entities[i].ren_group, entities[i].world_transform , m_view, m_projection,
g_test_light.position, g_test_light.light_ID);
}
}
void
renderDebug(std::vector<Point> &vertices)
{
GLfloat* buf = g_debug_render_group->render_objects[0]->vertex_buffer.buffer;
buf[0] = vertices[0].x; buf[1] = vertices[0].y; buf[2] = 0;
buf[3] = vertices[1].x; buf[4] = vertices[1].y; buf[2] = 0;
buf[6] = vertices[2].x; buf[7] = vertices[2].y; buf[8] = 0;
buf[9] = vertices[3].x; buf[10] = vertices[3].y; buf[11] = 0;
rgDraw(g_debug_render_group, g_scene_matrices.model, g_scene_matrices.view,
g_scene_matrices.projection, g_test_light.position, g_test_light.light_ID, true);
}
void
freeBuffers()
{
std::vector<render_group*> groups = {
g_filled_hex_render_group,
g_hex_line_render_group,
g_debug_render_group,
};
for (render_group* group : groups)
rgFree(group);
}
// internal
void
initMatrices(projection_type p)
{
// TODO: many constants used here should be passed as args
if (p == PERSPECTIVE)
{
g_scene_matrices.projection = glm::infinitePerspective(
glm::radians(60.f), // FoV
16.f / 9.f, // ascpect ratio
0.1f // near clip plane
);
g_camera.position = glm::vec3(640,0,100);
g_camera.target = glm::vec3(640,500,0);
//g_camera.target = glm::vec3(0,0,0);
// inital rotation should match target direction
glm::vec3 &p = g_camera.position;
glm::vec3 &t = g_camera.target;
g_camera.hAngle = 0;
g_camera.vAngle = glm::atan((t.z - p.z) / (t.y - p.y));
//////
// TODO: add call to rotate camera here to remove duplicate code
camera &c = g_camera;
float &h = c.hAngle;
float &v = c.vAngle;
c.forward = glm::vec3(
glm::cos(v) * glm::sin(h),
glm::cos(v) * glm::cos(h),
glm::sin(v)
);
glm::normalize(c.forward);
//////
g_camera.up = glm::vec3(0,1,0);
g_camera.left = glm::normalize(glm::cross(g_camera.up, g_camera.forward));
g_camera.up = glm::normalize(glm::cross(g_camera.forward, g_camera.left));
g_scene_matrices.view = glm::lookAt(
g_camera.position, // camera position
g_camera.position + g_camera.forward,
g_camera.up // "up" vector
);
}
else // ORTHO
{
// left, right, bottom, top, zNear, zFar
g_scene_matrices.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f);
g_scene_matrices.view = glm::lookAt(
glm::vec3(0.0f, 0.0f, 1.0f), // camera position
glm::vec3(0.0f, 0.0f, 0.0f), // look at position
glm::vec3(0,1,0) // "up" vector
);
}
g_scene_matrices.model = glm::mat4(1.0f);
g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model;
}
void
openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam)
{
LOG((type == GL_DEBUG_TYPE_ERROR) ? ERROR : DEBUG)
<< (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "")
<< ", type: " << type
<< ", severity: " << severity
<< ", message: " << message << "\n";
}
bool
initHexGridBuffers(std::vector<hex_info>* hexes)
{
// TODO: index duplicate vertices
// 6 triangles * 3 vertices per triangle * 3 floats per vertex = 54
uint line_vertices_per_hex = 6 * 2; // 12 vertices since we're using line segments atm
uint line_buf_len = hexes->size() * line_vertices_per_hex * 3; // 3 floats per vertex
uint vbuf_len = hexes->size() * 6 * 3 * 3;
g_filled_hex_render_group = rgInitSingle(g_default_shader, vbuf_len, true);
if (g_filled_hex_render_group == nullptr)
return false;
gl_buffer& vbuf = g_filled_hex_render_group->render_objects[0]->vertex_buffer;
gl_buffer& cbuf = g_filled_hex_render_group->render_objects[0]->color_buffer;
gl_buffer& normal_buf = g_filled_hex_render_group->render_objects[0]->normal_buffer;
for (uint i = 0; i < hexes->size(); i++)
fillTriangleBufferFromHex(vbuf.buffer, i * 54, (*hexes)[i]);
rgBufferData(&vbuf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER);
fillColorBuffer(cbuf.buffer, vbuf_len, hexes);
rgBufferData(&cbuf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER);
// cheat at vertex normals since all hexes lay flat on z-axis
for (uint i = 0; i < vbuf_len; i += 3) {
normal_buf.buffer[i] = 0.f;
normal_buf.buffer[i + 1] = 0.f;
normal_buf.buffer[i + 2] = 1.f;
}
rgBufferData(&normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
// hex lines
g_hex_line_render_group = rgInitSingle(g_default_shader, line_buf_len, true, 0, GL_LINES);
if (g_hex_line_render_group == nullptr)
return false;
gl_buffer& line_buf = g_hex_line_render_group->render_objects[0]->vertex_buffer;
gl_buffer& line_color_buf = g_hex_line_render_group->render_objects[0]->color_buffer;
gl_buffer& line_normal_buf = g_hex_line_render_group->render_objects[0]->normal_buffer;
fillHexLineBuffer(line_buf.buffer, line_buf_len, hexes);
rgBufferData(&line_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
for (uint i = 0; i < line_buf_len; i++)
line_color_buf.buffer[i] = 0.f;
rgBufferData(&line_color_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
for (uint i = 0; i < line_buf_len; i += 3) {
line_normal_buf.buffer[i] = 0.f;
line_normal_buf.buffer[i + 1] = 0.f;
line_normal_buf.buffer[i + 2] = 1.f;
}
rgBufferData(&line_normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
return true;
}
void
fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex)
{
// triangles
for (int i = 0; i < 6; i++)
{
// vertex 0
buf[idx + 0] = (GLfloat) hex.XPos;
buf[idx + 1] = (GLfloat) hex.YPos;
buf[idx + 2] = (GLfloat) 0.f;
// vertex 1
buf[idx + 3] = (GLfloat) hex.vertices[i].x;
buf[idx + 4] = (GLfloat) hex.vertices[i].y;
buf[idx + 5] = (GLfloat) 0.f;
if (i == 5) // re-use the first point for the last triangle
{
// vertex 2
buf[idx + 6] = (GLfloat) hex.vertices[0].x;
buf[idx + 7] = (GLfloat) hex.vertices[0].y;
buf[idx + 8] = (GLfloat) 0.f;
}
else
{
// vertex 2
buf[idx + 6] = (GLfloat) hex.vertices[i + 1].x;
buf[idx + 7] = (GLfloat) hex.vertices[i + 1].y;
buf[idx + 8] = (GLfloat) 0.f;
}
// we've added 9 GLfloats per loop
idx += 9;
}
}
void
fillColorBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes)
{
int buf_idx;
int buf_len_per_hex = 54; // NOTE: 3 * 3 * 6
GLfloat color_buf[3];
for (int i = 0; i < (int) hexes->size(); i++)
{
buf_idx = i * buf_len_per_hex;
hex_info hxi = (*hexes)[i];
// TODO: check performance of this since we call multiple times per frame
// maybe can cache glfloat triplets somewhere
utilConvertColor(color_buf, hxi.current_color);
for (int j = 0; j < buf_len_per_hex; j+=3)
{
buf[buf_idx + j] = color_buf[0];
buf[buf_idx + j + 1] = color_buf[1];
buf[buf_idx + j + 2] = color_buf[2];
}
}
}
void
fillHexLineBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes)
{
Point p1, p2;
int idx = 0;
for (int i = 0; i < (int) hexes->size(); i++)
{
hex_info hxi = (*hexes)[i];
for (int j = 0; j < 6; j ++)
{
if (j == 5) // wrap
{
p1 = hxi.vertices[j];
p2 = hxi.vertices[0];
}
else
{
p1 = hxi.vertices[j];
p2 = hxi.vertices[j + 1];
}
buf[idx + 0] = p1.x;
buf[idx + 1] = p1.y;
buf[idx + 2] = 0.f;
buf[idx + 3] = p2.x;
buf[idx + 4] = p2.y;
buf[idx + 5] = 0.f;
idx += 6;
}
}
}