Browse Source

splitting up compilation units

master
cinnaboot 8 years ago
parent
commit
040097303b
  1. 32
      Makefile
  2. 100
      src/gooey.cpp
  3. 98
      src/gooey.h
  4. 12
      src/hexgame.cpp
  5. 7
      src/hexgame.h
  6. 243
      src/hexlib.cpp
  7. 316
      src/hexlib.h
  8. 73
      src/mesh.cpp
  9. 69
      src/mesh.h
  10. 823
      src/renderer.cpp
  11. 836
      src/renderer.h

32
Makefile

@ -1,7 +1,7 @@
CXX = g++ CXX = g++
CXXFLAGS = -std=c++11 -g -ggdb -Wall CXXFLAGS = -std=c++11 -g -ggdb -Wall -I/usr/include/SDL2 -Iext/aixlog/include
SRC_DIR = src SRC_DIR = src
OBJ_DIR = build OBJ_DIR = build
@ -22,36 +22,44 @@ LDFLAGS += $(shell pkg-config --libs SDL2_image)
# IMGUI # IMGUI
IMGUI_DIR = ext/imgui IMGUI_DIR = ext/imgui
CXXFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/examples/sdl_opengl3_example -I$(IMGUI_DIR)/examples/libs/gl3w CXXFLAGS += -I$(IMGUI_DIR)\
-I$(IMGUI_DIR)/examples/sdl_opengl3_example\
# assimp -I$(IMGUI_DIR)/examples/libs/gl3w
LDFLAGS += -lassimp IMGUI_SOURCES = $(IMGUI_DIR)/imgui.cpp \
SOURCES = $(IMGUI_DIR)/imgui.cpp \
$(IMGUI_DIR)/imgui_demo.cpp \ $(IMGUI_DIR)/imgui_demo.cpp \
$(IMGUI_DIR)/imgui_draw.cpp \ $(IMGUI_DIR)/imgui_draw.cpp \
$(IMGUI_DIR)/examples/sdl_opengl3_example/imgui_impl_sdl_gl3.cpp \ $(IMGUI_DIR)/examples/sdl_opengl3_example/imgui_impl_sdl_gl3.cpp \
OBJECTS := $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(notdir $(SOURCES))) IMGUI_OBJECTS := $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(notdir $(IMGUI_SOURCES)))
# assimp
LDFLAGS += -lassimp
SOURCES = $(wildcard $(SRC_DIR)/*.cpp)
OBJECTS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SOURCES))
all: $(OBJECTS) $(OBJ_DIR)/gl3w.o all: $(IMGUI_OBJECTS) $(OBJ_DIR)/gl3w.o $(OBJECTS)
$(CXX) $(CXXFLAGS) -I/usr/include/SDL2 -Iext/aixlog/include -o $(OBJ_DIR)/hexgame $(SRC_DIR)/hexgame.cpp $(LDFLAGS) $(GL3W_LDFLAGS) $(OBJ_DIR)/* $(CXX) -o $(OBJ_DIR)/hexgame $(LDFLAGS) $(GL3W_LDFLAGS) $(OBJ_DIR)/*
mkdir -p bin mkdir -p bin
mv $(OBJ_DIR)/hexgame bin mv $(OBJ_DIR)/hexgame bin
info: $(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
info:
@echo "SOURCES:" @echo "SOURCES:"
@echo $(SOURCES) @echo $(SOURCES)
@echo "OBJECTS:" @echo "OBJECTS:"
@echo $(OBJECTS) @echo $(OBJECTS)
# NOTE: couldn't find a nicer way to put these object files into build directory :(
$(OBJ_DIR)/imgui.o: $(OBJ_DIR)/imgui.o:
$(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui.cpp -o $@ $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui.cpp -o $@
$(OBJ_DIR)/imgui_demo.o: $(OBJ_DIR)/imgui_demo.o:
$(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_demo.cpp -o $@ $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_demo.cpp -o $@
$(OBJ_DIR)/imgui_draw.o: $(OBJ_DIR)/imgui_draw.o:
$(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_draw.cpp -o $@ $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_draw.cpp -o $@

100
src/gooey.cpp

@ -0,0 +1,100 @@
#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include "imgui.h"
#include "examples/sdl_opengl3_example/imgui_impl_sdl_gl3.h"
#include "gooey.h"
bool
initGooey(SDL_Handles &handles, v2i vp_dims /*TODO: pass in game state*/)
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = NULL; // don't save window state to imgui.ini
ImGui_ImplSdlGL3_Init(handles.window);
io.DisplaySize.x = vp_dims.x;
io.DisplaySize.y = vp_dims.y;
ImGui::StyleColorsDark();
return true;
}
void
shutdownGooey()
{
ImGui_ImplSdlGL3_Shutdown();
ImGui::DestroyContext();
}
bool
gooeyProcessEvent(SDL_Event &event)
{
ImGui_ImplSdlGL3_ProcessEvent(&event);
return ImGui::GetIO().WantCaptureMouse;
}
void
renderGooey(SDL_Handles &handles, HexDrawMode &mode, bool &is_debug,
hex_info* start_hex, hex_info* current_hex, bool &is_selecting)
{
ImGui_ImplSdlGL3_NewFrame(handles.window);
ImGuiWindowFlags window_flags = 0;
window_flags |= ImGuiWindowFlags_NoTitleBar;
window_flags |= ImGuiWindowFlags_NoScrollbar;
window_flags |= ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoResize;
window_flags |= ImGuiWindowFlags_NoCollapse;
ImGui::SetNextWindowPos(ImVec2(0,0));
ImGui::SetNextWindowSize(ImVec2(300, 720));
ImGui::SetNextWindowBgAlpha(0.3f);
bool show_window;
ImGui::Begin("", &show_window, window_flags);
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("%.3f ms/frame, (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::Text("Draw Mode:");
if (ImGui::RadioButton("None", (mode == NONE)))
mode = NONE;
if (ImGui::RadioButton("Fill", (mode == FILL)))
mode = FILL;
if (ImGui::RadioButton("Line", (mode == LINE)))
mode = LINE;
if (ImGui::RadioButton("Cone Fill", (mode == CONE_FILL)))
mode = CONE_FILL;
ImGui::Checkbox("Debug Render", &is_debug);
ImGui::SameLine(); ImGui::TextUnformatted(is_debug ? "true" : "false");
ImGui::Text("is_selecting");
ImGui::SameLine(); ImGui::TextUnformatted(is_selecting ? "true" : "false");
// testing SDL_image
SDL_Surface* image = handles.texSurfaces[0];
ImGui::Image((void*)(intptr_t) image->userdata, ImVec2(image->w, image->h));
//ImGui::ShowMetricsWindow();
if (current_hex)
{
Hex ch = current_hex->hex;
ImGui::Text("current_hex: %i, %i, %i", ch.q, ch.r, ch.s);
}
if (start_hex)
{
Hex sh = start_hex->hex;
ImGui::Text("start_hex: %i, %i, %i", sh.q, sh.r, sh.s);
}
ImGui::End();
ImGui::Render();
ImGui_ImplSdlGL3_RenderDrawData(ImGui::GetDrawData());
}

98
src/gooey.h

@ -1,107 +1,11 @@
#ifndef GOOEY_H #pragma once
#define GOOEY_H
#include <SDL.h>
#include "imgui.h"
#include "examples/sdl_opengl3_example/imgui_impl_sdl_gl3.h"
#include "hexgame.h" #include "hexgame.h"
bool initGooey(SDL_Handles &handles, v2i vp_dims); bool initGooey(SDL_Handles &handles, v2i vp_dims);
void shutdownGooey(); void shutdownGooey();
bool gooeyProcessEvent(SDL_Event &event); bool gooeyProcessEvent(SDL_Event &event);
void renderGooey(SDL_Handles &handles, HexDrawMode &mode, bool &is_debug, void renderGooey(SDL_Handles &handles, HexDrawMode &mode, bool &is_debug,
hex_info* start_hex, hex_info* current_hex, bool &is_selecting); hex_info* start_hex, hex_info* current_hex, bool &is_selecting);
bool
initGooey(SDL_Handles &handles, v2i vp_dims /*TODO: pass in game state*/)
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = NULL; // don't save window state to imgui.ini
ImGui_ImplSdlGL3_Init(handles.window);
io.DisplaySize.x = vp_dims.x;
io.DisplaySize.y = vp_dims.y;
ImGui::StyleColorsDark();
return true;
}
void
shutdownGooey()
{
ImGui_ImplSdlGL3_Shutdown();
ImGui::DestroyContext();
}
bool
gooeyProcessEvent(SDL_Event &event)
{
ImGui_ImplSdlGL3_ProcessEvent(&event);
return ImGui::GetIO().WantCaptureMouse;
}
void
renderGooey(SDL_Handles &handles, HexDrawMode &mode, bool &is_debug,
hex_info* start_hex, hex_info* current_hex, bool &is_selecting)
{
ImGui_ImplSdlGL3_NewFrame(handles.window);
ImGuiWindowFlags window_flags = 0;
window_flags |= ImGuiWindowFlags_NoTitleBar;
window_flags |= ImGuiWindowFlags_NoScrollbar;
window_flags |= ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoResize;
window_flags |= ImGuiWindowFlags_NoCollapse;
ImGui::SetNextWindowPos(ImVec2(0,0));
ImGui::SetNextWindowSize(ImVec2(300, 720));
ImGui::SetNextWindowBgAlpha(0.3f);
bool show_window;
ImGui::Begin("", &show_window, window_flags);
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("%.3f ms/frame, (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::Text("Draw Mode:");
if (ImGui::RadioButton("None", (mode == NONE)))
mode = NONE;
if (ImGui::RadioButton("Fill", (mode == FILL)))
mode = FILL;
if (ImGui::RadioButton("Line", (mode == LINE)))
mode = LINE;
if (ImGui::RadioButton("Cone Fill", (mode == CONE_FILL)))
mode = CONE_FILL;
ImGui::Checkbox("Debug Render", &is_debug);
ImGui::SameLine(); ImGui::TextUnformatted(is_debug ? "true" : "false");
ImGui::Text("is_selecting");
ImGui::SameLine(); ImGui::TextUnformatted(is_selecting ? "true" : "false");
// testing SDL_image
SDL_Surface* image = handles.texSurfaces[0];
ImGui::Image((void*)(intptr_t) image->userdata, ImVec2(image->w, image->h));
//ImGui::ShowMetricsWindow();
if (current_hex)
{
Hex ch = current_hex->hex;
ImGui::Text("current_hex: %i, %i, %i", ch.q, ch.r, ch.s);
}
if (start_hex)
{
Hex sh = start_hex->hex;
ImGui::Text("start_hex: %i, %i, %i", sh.q, sh.r, sh.s);
}
ImGui::End();
ImGui::Render();
ImGui_ImplSdlGL3_RenderDrawData(ImGui::GetDrawData());
}
#endif // GOOEY_H

12
src/hexgame.cpp

@ -1,10 +1,10 @@
/* /*
* TODO: * TODO:
* - maybe start thinking about separating headers into different compilation units
* - add some sweet unit models * - add some sweet unit models
* - need to add indexed drawing for assimp models * - need to add indexed drawing for assimp models
* - map generation * - map generation
* - pathfinding * - pathfinding
* - update imgui to v1.62 -- requires changes to example api
*/ */
// Some defaults for the game layout // Some defaults for the game layout
@ -22,13 +22,17 @@
#include <vector> #include <vector>
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <windows.h>
#include <Winuser.h> #include <Winuser.h>
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif #endif
#include <SDL.h>
#include <SDL_image.h> #include <SDL_image.h>
#include "aixlog.hpp"
#include "hexgame.h" #include "hexgame.h"
#include "hexlib.h" #include "hexlib.h"
#include "renderer.h" #include "renderer.h"

7
src/hexgame.h

@ -1,6 +1,5 @@
#ifndef HEXGAME_H #pragma once
#define HEXGAME_H
#include <vector> #include <vector>
#include <cassert> #include <cassert>
@ -10,7 +9,7 @@
#else #else
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#endif #endif
#include "aixlog.hpp"
#include <glm/glm.hpp> // vec3 #include <glm/glm.hpp> // vec3
#include "hexlib.h" #include "hexlib.h"
@ -134,5 +133,3 @@ SafeTruncateToInt32(int64 val)
return (int32) val; return (int32) val;
} }
#endif // HEXGAME_H

243
src/hexlib.cpp

@ -0,0 +1,243 @@
#include "hexlib.h"
// Generated code -- http://www.redblobgames.com/grids/hexagons/
#include <cmath>
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <iterator>
using std::abs;
using std::max;
using std::vector;
struct OffsetCoord
{
const int col;
const int row;
OffsetCoord(int col_, int row_): col(col_), row(row_) {}
};
Hex hex_add(Hex a, Hex b)
{
return Hex(a.q + b.q, a.r + b.r, a.s + b.s);
}
Hex hex_subtract(Hex a, Hex b)
{
return Hex(a.q - b.q, a.r - b.r, a.s - b.s);
}
Hex hex_scale(Hex a, int k)
{
return Hex(a.q * k, a.r * k, a.s * k);
}
const vector<Hex> hex_directions = {Hex(1, 0, -1), Hex(1, -1, 0), Hex(0, -1, 1), Hex(-1, 0, 1), Hex(-1, 1, 0), Hex(0, 1, -1)};
Hex hex_direction(int direction)
{
return hex_directions[direction];
}
Hex hex_neighbor(Hex hex, int direction)
{
return hex_add(hex, hex_direction(direction));
}
const vector<Hex> hex_diagonals = {Hex(2, -1, -1), Hex(1, -2, 1), Hex(-1, -1, 2), Hex(-2, 1, 1), Hex(-1, 2, -1), Hex(1, 1, -2)};
Hex hex_diagonal_neighbor(Hex hex, int direction)
{
return hex_add(hex, hex_diagonals[direction]);
}
int hex_length(Hex hex)
{
return int((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2);
}
int hex_distance(Hex a, Hex b)
{
return hex_length(hex_subtract(a, b));
}
Hex hex_round(FractionalHex h)
{
int q = int(round(h.q));
int r = int(round(h.r));
int s = int(round(h.s));
double q_diff = abs(q - h.q);
double r_diff = abs(r - h.r);
double s_diff = abs(s - h.s);
if (q_diff > r_diff && q_diff > s_diff)
{
q = -r - s;
}
else
if (r_diff > s_diff)
{
r = -q - s;
}
else
{
s = -q - r;
}
return Hex(q, r, s);
}
FractionalHex hex_lerp(FractionalHex a, FractionalHex b, double t)
{
return FractionalHex(a.q * (1 - t) + b.q * t, a.r * (1 - t) + b.r * t, a.s * (1 - t) + b.s * t);
}
vector<Hex> hex_linedraw(Hex a, Hex b)
{
int N = hex_distance(a, b);
FractionalHex a_nudge = FractionalHex(a.q + 0.000001, a.r + 0.000001, a.s - 0.000002);
FractionalHex b_nudge = FractionalHex(b.q + 0.000001, b.r + 0.000001, b.s - 0.000002);
vector<Hex> results = {};
double step = 1.0 / max(N, 1);
for (int i = 0; i <= N; i++)
{
results.push_back(hex_round(hex_lerp(a_nudge, b_nudge, step * i)));
}
return results;
}
const int EVEN = 1;
const int ODD = -1;
OffsetCoord qoffset_from_cube(int offset, Hex h)
{
int col = h.q;
int row = h.r + int((h.q + offset * (h.q & 1)) / 2);
return OffsetCoord(col, row);
}
Hex qoffset_to_cube(int offset, OffsetCoord h)
{
int q = h.col;
int r = h.row - int((h.col + offset * (h.col & 1)) / 2);
int s = -q - r;
return Hex(q, r, s);
}
OffsetCoord roffset_from_cube(int offset, Hex h)
{
int col = h.q + int((h.r + offset * (h.r & 1)) / 2);
int row = h.r;
return OffsetCoord(col, row);
}
Hex roffset_to_cube(int offset, OffsetCoord h)
{
int q = h.col - int((h.row + offset * (h.row & 1)) / 2);
int r = h.row;
int s = -q - r;
return Hex(q, r, s);
}
Point hex_to_pixel(Layout layout, Hex h)
{
Orientation M = layout.orientation;
Point size = layout.size;
Point origin = layout.origin;
double x = (M.f0 * h.q + M.f1 * h.r) * size.x;
double y = (M.f2 * h.q + M.f3 * h.r) * size.y;
return Point(x + origin.x, y + origin.y);
}
FractionalHex pixel_to_hex(Layout layout, Point p)
{
Orientation M = layout.orientation;
Point size = layout.size;
Point origin = layout.origin;
Point pt = Point((p.x - origin.x) / size.x, (p.y - origin.y) / size.y);
double q = M.b0 * pt.x + M.b1 * pt.y;
double r = M.b2 * pt.x + M.b3 * pt.y;
return FractionalHex(q, r, -q - r);
}
Point hex_corner_offset(Layout layout, int corner)
{
Orientation M = layout.orientation;
Point size = layout.size;
double angle = 2.0 * M_PI * (M.start_angle - corner) / 6;
return Point(size.x * cos(angle), size.y * sin(angle));
}
vector<Point> polygon_corners(Layout layout, Hex h)
{
vector<Point> corners = {};
Point center = hex_to_pixel(layout, h);
for (int i = 0; i < 6; i++)
{
Point offset = hex_corner_offset(layout, i);
corners.push_back(Point(center.x + offset.x, center.y + offset.y));
}
return corners;
}
// custom hex functions
bool
hex_equal(Hex a, Hex b)
{
return (a.q == b.q && a.r == b.r && a.s == b.s);
}
vector<Hex>
hex_conefill(Hex a, Hex b)
{
vector<Hex> results = {};
return results;
}
// NOTE: implementation of this line crossing test
// https://graphics.stanford.edu/pub/Graphics/RTNews/html/rtnv5n3.html#art3
// NOTE: assumes use of convex polygons with vertices in CCW layout
bool
crossingTest(vector<Point> vertices, Point p)
{
int numVertices = vertices.size(), intersect_count = 0;
Point vert0, vert1;
double m, x;
if (numVertices < 3)
return false;
vert0 = vertices[0];
for (int i = 1; i < numVertices + 1; i++)
{
// use first vertex for the last edge
if (i == numVertices)
vert1 = vertices[0];
else
vert1 = vertices[i];
// check if edge can intersect the +X ray
// NOTE: upward/downward crossing excludes one vertex in the case that
// the ray intersects a vertex
if ((vert0.y > p.y && vert1.y <= p.y) || // downward crossing
(vert0.y <= p.y && vert1.y > p.y)) // upward crossing
{
m = (vert1.y - vert0.y) / (vert1.x - vert0.x);
x = (p.y - vert0.y) / m + vert0.x;
// if x is right of p.x it must intersect
if (x >= p.x)
intersect_count++;
// 2 intersections of convex polygon means we started outside
if (intersect_count > 1)
return false;
}
// start with previous vertex on next loop
vert0 = vert1;
}
return (intersect_count == 1);
}

316
src/hexlib.h

@ -1,19 +1,21 @@
#ifndef HEXLIB_H #pragma once
#define HEXLIB_H
// Generated code -- http://www.redblobgames.com/grids/hexagons/ // Generated code -- http://www.redblobgames.com/grids/hexagons/
#include <cmath> #include <cmath>
#include <cstdlib>
#include <vector> #include <vector>
#include <algorithm>
#include <iterator>
using std::abs;
using std::max;
using std::vector; using std::vector;
struct FractionalHex
{
const double q;
const double r;
const double s;
FractionalHex(double q_, double r_, double s_): q(q_), r(r_), s(s_) {}
};
struct Point struct Point
{ {
double x; double x;
@ -22,7 +24,6 @@ struct Point
Point(): x(0), y(0) {} Point(): x(0), y(0) {}
}; };
struct Hex struct Hex
{ {
int q; int q;
@ -32,296 +33,51 @@ struct Hex
Hex(): q(0), r(0), s(0) {} Hex(): q(0), r(0), s(0) {}
}; };
struct FractionalHex
{
const double q;
const double r;
const double s;
FractionalHex(double q_, double r_, double s_): q(q_), r(r_), s(s_) {}
};
struct OffsetCoord
{
const int col;
const int row;
OffsetCoord(int col_, int row_): col(col_), row(row_) {}
};
struct Orientation struct Orientation
{ {
const double f0; double f0;
const double f1; double f1;
const double f2; double f2;
const double f3; double f3;
const double b0; double b0;
const double b1; double b1;
const double b2; double b2;
const double b3; double b3;
const double start_angle; double start_angle;
Orientation(double f0_, double f1_, double f2_, double f3_, double b0_, double b1_, double b2_, double b3_, double start_angle_): f0(f0_), f1(f1_), f2(f2_), f3(f3_), b0(b0_), b1(b1_), b2(b2_), b3(b3_), start_angle(start_angle_) {} Orientation(double f0_, double f1_, double f2_, double f3_, double b0_,
double b1_, double b2_, double b3_, double start_angle_):
f0(f0_), f1(f1_), f2(f2_), f3(f3_), b0(b0_), b1(b1_), b2(b2_),
b3(b3_), start_angle(start_angle_) {}
}; };
const Orientation layout_pointy = Orientation(sqrt(3.0), sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0, sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5);
const Orientation layout_flat = Orientation(3.0 / 2.0, 0.0, sqrt(3.0) / 2.0, sqrt(3.0), 2.0 / 3.0, 0.0, -1.0 / 3.0, sqrt(3.0) / 3.0, 0.0);
struct Layout struct Layout
{ {
const Orientation orientation; const Orientation orientation;
const Point size; const Point size;
const Point origin; const Point origin;
Layout(Orientation orientation_, Point size_, Point origin_): orientation(orientation_), size(size_), origin(origin_) {} Layout(Orientation orientation_, Point size_, Point origin_):
orientation(orientation_), size(size_), origin(origin_) {}
}; };
int hex_length(Hex hex);
int hex_distance(Hex a, Hex b);
Hex hex_round(FractionalHex h);
vector<Hex> hex_linedraw(Hex a, Hex b);
// Forward declarations Point hex_to_pixel(Layout layout, Hex h);
FractionalHex pixel_to_hex(Layout layout, Point p);
vector<Point> polygon_corners(Layout layout, Hex h);
Hex hex_add(Hex a, Hex b)
{
return Hex(a.q + b.q, a.r + b.r, a.s + b.s);
}
Hex hex_subtract(Hex a, Hex b)
{
return Hex(a.q - b.q, a.r - b.r, a.s - b.s);
}
Hex hex_scale(Hex a, int k)
{
return Hex(a.q * k, a.r * k, a.s * k);
}
const vector<Hex> hex_directions = {Hex(1, 0, -1), Hex(1, -1, 0), Hex(0, -1, 1), Hex(-1, 0, 1), Hex(-1, 1, 0), Hex(0, 1, -1)};
Hex hex_direction(int direction)
{
return hex_directions[direction];
}
Hex hex_neighbor(Hex hex, int direction)
{
return hex_add(hex, hex_direction(direction));
}
const vector<Hex> hex_diagonals = {Hex(2, -1, -1), Hex(1, -2, 1), Hex(-1, -1, 2), Hex(-2, 1, 1), Hex(-1, 2, -1), Hex(1, 1, -2)};
Hex hex_diagonal_neighbor(Hex hex, int direction)
{
return hex_add(hex, hex_diagonals[direction]);
}
int hex_length(Hex hex)
{
return int((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2);
}
int hex_distance(Hex a, Hex b)
{
return hex_length(hex_subtract(a, b));
}
Hex hex_round(FractionalHex h)
{
int q = int(round(h.q));
int r = int(round(h.r));
int s = int(round(h.s));
double q_diff = abs(q - h.q);
double r_diff = abs(r - h.r);
double s_diff = abs(s - h.s);
if (q_diff > r_diff && q_diff > s_diff)
{
q = -r - s;
}
else
if (r_diff > s_diff)
{
r = -q - s;
}
else
{
s = -q - r;
}
return Hex(q, r, s);
}
FractionalHex hex_lerp(FractionalHex a, FractionalHex b, double t)
{
return FractionalHex(a.q * (1 - t) + b.q * t, a.r * (1 - t) + b.r * t, a.s * (1 - t) + b.s * t);
}
vector<Hex> hex_linedraw(Hex a, Hex b)
{
int N = hex_distance(a, b);
FractionalHex a_nudge = FractionalHex(a.q + 0.000001, a.r + 0.000001, a.s - 0.000002);
FractionalHex b_nudge = FractionalHex(b.q + 0.000001, b.r + 0.000001, b.s - 0.000002);
vector<Hex> results = {};
double step = 1.0 / max(N, 1);
for (int i = 0; i <= N; i++)
{
results.push_back(hex_round(hex_lerp(a_nudge, b_nudge, step * i)));
}
return results;
}
const int EVEN = 1;
const int ODD = -1;
OffsetCoord qoffset_from_cube(int offset, Hex h)
{
int col = h.q;
int row = h.r + int((h.q + offset * (h.q & 1)) / 2);
return OffsetCoord(col, row);
}
Hex qoffset_to_cube(int offset, OffsetCoord h)
{
int q = h.col;
int r = h.row - int((h.col + offset * (h.col & 1)) / 2);
int s = -q - r;
return Hex(q, r, s);
}
OffsetCoord roffset_from_cube(int offset, Hex h)
{
int col = h.q + int((h.r + offset * (h.r & 1)) / 2);
int row = h.r;
return OffsetCoord(col, row);
}
Hex roffset_to_cube(int offset, OffsetCoord h)
{
int q = h.col - int((h.row + offset * (h.row & 1)) / 2);
int r = h.row;
int s = -q - r;
return Hex(q, r, s);
}
const Orientation layout_pointy = Orientation(sqrt(3.0), sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0, sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5);
const Orientation layout_flat = Orientation(3.0 / 2.0, 0.0, sqrt(3.0) / 2.0, sqrt(3.0), 2.0 / 3.0, 0.0, -1.0 / 3.0, sqrt(3.0) / 3.0, 0.0);
Point hex_to_pixel(Layout layout, Hex h)
{
Orientation M = layout.orientation;
Point size = layout.size;
Point origin = layout.origin;
double x = (M.f0 * h.q + M.f1 * h.r) * size.x;
double y = (M.f2 * h.q + M.f3 * h.r) * size.y;
return Point(x + origin.x, y + origin.y);
}
FractionalHex pixel_to_hex(Layout layout, Point p)
{
Orientation M = layout.orientation;
Point size = layout.size;
Point origin = layout.origin;
Point pt = Point((p.x - origin.x) / size.x, (p.y - origin.y) / size.y);
double q = M.b0 * pt.x + M.b1 * pt.y;
double r = M.b2 * pt.x + M.b3 * pt.y;
return FractionalHex(q, r, -q - r);
}
Point hex_corner_offset(Layout layout, int corner)
{
Orientation M = layout.orientation;
Point size = layout.size;
double angle = 2.0 * M_PI * (M.start_angle - corner) / 6;
return Point(size.x * cos(angle), size.y * sin(angle));
}
vector<Point> polygon_corners(Layout layout, Hex h)
{
vector<Point> corners = {};
Point center = hex_to_pixel(layout, h);
for (int i = 0; i < 6; i++)
{
Point offset = hex_corner_offset(layout, i);
corners.push_back(Point(center.x + offset.x, center.y + offset.y));
}
return corners;
}
// custom hex functions // custom hex functions
bool hex_equal(Hex a, Hex b);
bool vector<Hex> hex_conefill(Hex a, Hex b);
hex_equal(Hex a, Hex b)
{
return (a.q == b.q && a.r == b.r && a.s == b.s);
}
vector<Hex>
hex_conefill(Hex a, Hex b)
{
vector<Hex> results = {};
return results;
}
// NOTE: implementation of this line crossing test // NOTE: implementation of this line crossing test
// https://graphics.stanford.edu/pub/Graphics/RTNews/html/rtnv5n3.html#art3 // https://graphics.stanford.edu/pub/Graphics/RTNews/html/rtnv5n3.html#art3
// NOTE: assumes use of convex polygons with vertices in CCW layout // NOTE: assumes use of convex polygons with vertices in CCW layout
bool bool crossingTest(vector<Point> vertices, Point p);
crossingTest(vector<Point> vertices, Point p)
{
int numVertices = vertices.size(), intersect_count = 0;
Point vert0, vert1;
double m, x;
if (numVertices < 3)
return false;
vert0 = vertices[0];
for (int i = 1; i < numVertices + 1; i++)
{
// use first vertex for the last edge
if (i == numVertices)
vert1 = vertices[0];
else
vert1 = vertices[i];
// check if edge can intersect the +X ray
// NOTE: upward/downward crossing excludes one vertex in the case that
// the ray intersects a vertex
if ((vert0.y > p.y && vert1.y <= p.y) || // downward crossing
(vert0.y <= p.y && vert1.y > p.y)) // upward crossing
{
m = (vert1.y - vert0.y) / (vert1.x - vert0.x);
x = (p.y - vert0.y) / m + vert0.x;
// if x is right of p.x it must intersect
if (x >= p.x)
intersect_count++;
// 2 intersections of convex polygon means we started outside
if (intersect_count > 1)
return false;
}
// start with previous vertex on next loop
vert0 = vert1;
}
return (intersect_count == 1);
}
#endif // HEXLIB_H

73
src/mesh.cpp

@ -0,0 +1,73 @@
#include <cassert>
#include <glm/glm.hpp> // vec3
#include "aixlog.hpp"
#include "mesh.h"
/* the global Assimp scene object */
const aiScene* g_scene = NULL;
bool
initAssimp()
{
/* get a handle to the predefined STDOUT log stream and attach
* it to the logging system. It remains active for all further
* calls to aiImportFile(Ex) and aiApplyPostProcessing. */
aiLogStream stream = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT,NULL);
aiAttachLogStream(&stream);
g_scene = aiImportFile("../data/animated.block.dae", aiProcessPreset_TargetRealtime_MaxQuality);
if (g_scene->mNumMeshes != 1) {
LOG(ERROR) << "We Can only handle 1 mesh per entity atm\n";
return false;
}
return true;
}
uint
getVertexCount()
{
assert(g_scene);
return g_scene->mMeshes[0]->mNumVertices;
}
// TODO: probably don't really need to copy this here
// let assimp manage the storage, and just reference indexes after passing
// to opengl
// copy data from assimp for use in our renderer
Entity*
convertMesh(Entity* e)
{
assert(g_scene);
uint numVertices = getVertexCount();
assert(e && e->num_vertices == numVertices);
// copy vertices
for (uint i = 0; i < numVertices; i++) {
aiVector3D v_in = g_scene->mMeshes[0]->mVertices[i];
glm::vec3 &v_out = e->vertices[i];
v_out.x = v_in.x;
v_out.y = v_in.y;
v_out.z = v_in.z;
}
return e;
}
void
shutdownAssimp()
{
/* cleanup - calling 'aiReleaseImport' is important, as the library
keeps internal resources until the scene is freed again. Not
doing so can cause severe resource leaking. */
aiReleaseImport(g_scene);
aiDetachAllLogStreams();
}

69
src/mesh.h

@ -5,80 +5,19 @@
#pragma once #pragma once
#include <cassert>
#include <glm/glm.hpp> // vec3
#include <assimp/cimport.h> #include <assimp/cimport.h>
#include <assimp/scene.h> #include <assimp/scene.h>
#include <assimp/postprocess.h> #include <assimp/postprocess.h>
#include "aixlog.hpp"
#include "hexgame.h" #include "hexgame.h"
/* the global Assimp scene object */ bool initAssimp();
const aiScene* g_scene = NULL;
bool
initAssimp()
{
/* get a handle to the predefined STDOUT log stream and attach
* it to the logging system. It remains active for all further
* calls to aiImportFile(Ex) and aiApplyPostProcessing. */
aiLogStream stream = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT,NULL);
aiAttachLogStream(&stream);
g_scene = aiImportFile("../data/animated.block.dae", aiProcessPreset_TargetRealtime_MaxQuality);
if (g_scene->mNumMeshes != 1) { uint getVertexCount();
LOG(ERROR) << "We Can only handle 1 mesh per entity atm\n";
return false;
}
return true;
}
uint
getVertexCount()
{
assert(g_scene);
return g_scene->mMeshes[0]->mNumVertices;
}
// TODO: probably don't really need to copy this here
// let assimp manage the storage, and just reference indexes after passing
// to opengl
// copy data from assimp for use in our renderer // copy data from assimp for use in our renderer
Entity* Entity* convertMesh(Entity* e);
convertMesh(Entity* e)
{
assert(g_scene);
uint numVertices = getVertexCount();
assert(e && e->num_vertices == numVertices);
// copy vertices
for (uint i = 0; i < numVertices; i++) {
aiVector3D v_in = g_scene->mMeshes[0]->mVertices[i];
glm::vec3 &v_out = e->vertices[i];
v_out.x = v_in.x;
v_out.y = v_in.y;
v_out.z = v_in.z;
}
return e;
}
void void shutdownAssimp();
shutdownAssimp()
{
/* cleanup - calling 'aiReleaseImport' is important, as the library
keeps internal resources until the scene is freed again. Not
doing so can cause severe resource leaking. */
aiReleaseImport(g_scene);
aiDetachAllLogStreams();
}

823
src/renderer.cpp

@ -0,0 +1,823 @@
#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"
#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
const char * VERTEX_SHADER_CODE =
"#version 330 core\n"
"in vec3 vertexPosition_modelspace;\n"
"in vec3 vertexColor;\n"
"out vec3 fragmentColor;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
" gl_Position = projection * view * model * vec4(vertexPosition_modelspace, 1);\n"
" fragmentColor = vertexColor;\n"
"}";
const char * FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"in vec3 fragmentColor;\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = fragmentColor;\n"
"}";
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;
};
typedef struct gl_buffer
{
GLuint buffer_id = 0;
size_t buffer_len = 0; // NOTE: number of elements in buffer
GLfloat* buffer = nullptr;
} gl_buffer;
typedef struct gl_render_group
{
// NOTE: For now the renderFrame function will assume these are the same size
gl_buffer vertex_buffer;
gl_buffer color_buffer;
GLuint program_id = 0;
GLuint model_matrix_id = 0;
GLuint view_matrix_id = 0;
GLuint projection_matrix_id = 0;
GLuint vertex_array_id = 0;
Entity* entity = nullptr;
} gl_render_group;
gl_matrix_info g_scene_matrices;
gl_render_group g_filled_hex_render_group;
gl_render_group g_hex_line_render_group;
gl_render_group g_debug_render_group;
gl_render_group g_entity_render_group;
camera g_camera;
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
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 1;
}
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;
}
// NOTE: model_name, view_name, and projection_name should match the matrix variable
// names in the shader source
bool
initShaderProgram(gl_render_group &rg, const char * vertex_code, const char * frag_code,
const char* model_name, const char* view_name, const char* projection_name)
{
glGenVertexArrays(1, &rg.vertex_array_id);
glBindVertexArray(rg.vertex_array_id);
GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertex_shader_id, 1, &vertex_code, NULL);
glShaderSource(fragment_shader_id, 1, &frag_code, NULL);
glCompileShader(vertex_shader_id);
glCompileShader(fragment_shader_id);
rg.program_id = glCreateProgram();
glAttachShader(rg.program_id, vertex_shader_id);
glAttachShader(rg.program_id, fragment_shader_id);
glLinkProgram(rg.program_id);
rg.model_matrix_id = glGetUniformLocation(rg.program_id, model_name);
rg.view_matrix_id = glGetUniformLocation(rg.program_id, view_name);
rg.projection_matrix_id = glGetUniformLocation(rg.program_id, projection_name);
glDetachShader(rg.program_id, vertex_shader_id);
glDetachShader(rg.program_id, fragment_shader_id);
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
// check for errors
GLint isLinked = 0;
glGetProgramiv(rg.program_id, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE) {
GLint maxLength = 0;
glGetProgramiv(rg.program_id, GL_INFO_LOG_LENGTH, &maxLength);
// The maxLength includes the NULL character
GLchar infoLog[maxLength];
glGetProgramInfoLog(rg.program_id, maxLength, &maxLength, &infoLog[0]);
LOG(ERROR) << infoLog << "\n";
// The program is useless now. So delete it.
glDeleteProgram(rg.program_id);
return false;
}
return true;
}
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);
// 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)
);
//////
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::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;
}
// NOTE: this will only work after a call to glBindBuffer
bool
checkGLBufferSize(GLenum buf_type, int expected_size, int line_num)
{
GLint gl_size;
glGetBufferParameteriv(buf_type, GL_BUFFER_SIZE ,&gl_size);
if (expected_size != gl_size)
{
LOG(ERROR) << "line: " << line_num << "gl buffer size mismatch\n";
return false;
}
return true;
}
bool
initGLBufferObject(gl_buffer* buf_obj, int len, GLenum usage, GLfloat data[])
{
buf_obj->buffer_len = (size_t) len;
buf_obj->buffer = (GLfloat*) std::calloc(len, sizeof(GLfloat));
if (data)
{
for (int i = 0; i < len; i++)
buf_obj->buffer[i] = data[i];
}
glGenBuffers(1, &buf_obj->buffer_id);
glBindBuffer(GL_ARRAY_BUFFER, buf_obj->buffer_id);
glBufferData(GL_ARRAY_BUFFER, len * sizeof(GLfloat), buf_obj->buffer, usage);
if (!checkGLBufferSize(GL_ARRAY_BUFFER, len * sizeof(GLfloat), __LINE__))
return false;
return true;
}
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);
// TODO: Line drawing quality seems to depend on graphics driver whims
// maybe try using textured billboards instead?
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
SDL_GL_SetSwapInterval(1); // vsync
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 == NULL)
{
// TODO: log error SDL_GetError()
return false;
}
handles.glContext = SDL_GL_CreateContext(handles.window);
// TODO: decide on extension library
gl3wInit();
#if 0
// Initialize GLEW
// glewExperimental is only needed in GLEW <= 1.13.0
// we can require version 2.0.0+
//glewExperimental = true; // Needed for core profile
GLenum err = glewInit();
if (err != GLEW_OK) {
LOG(ERROR) << "Failed to initilize GLEW" << glewGetErrorString(err) << "\n";
return false;
}
#endif
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);
// TODO: blending, these options break the http://www.opengl-tutorial.org tutorials atm
// Setup render state: alpha-blending enabled, polygon fill
#if 1
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// 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);
if (!initShaderProgram(
g_filled_hex_render_group, VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_hex_line_render_group, VERTEX_SHADER_CODE, LINE_FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_debug_render_group, VERTEX_SHADER_CODE, DEBUG_FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_entity_render_group, VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE,
"model", "view", "projection"))
{
return false;
}
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;
}
}
// NOTE: helper for fillColorBuffer() to convert uint32 color to GLfloat triplet
void
convertColor(GLfloat buf[3], uint32 color)
{
// NOTE: not using the alpha values for now
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255;
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255;
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255;
}
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];
convertColor(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;
}
}
}
bool
createScene(std::vector<hex_info>* hexes, Entity* entities, uint32 entity_count)
{
initMatrices(PROJ_TYPE);
// Vertex Data
// TODO: index duplicate vertices
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-9-vbo-indexing/
int hex_count = (int) hexes->size();
// 6 triangles * 3 vertices per triangle * 3 floats per vertex = 54
int vbuf_len = hex_count * 6 * 3 * 3;
// TODO: surely there's a way to use indexed drawing for line vertices eg) line_loop
// and still use one buffer for multiple shapes/paths gldrawarraysintanced?, gldrawelements?
// https://gamedev.stackexchange.com/questions/104310/opengl-4-5-primitive-restart-vs-base-index
int line_vertices_per_hex = 6 * 2; // 12 vertices since we're using line segments atm
//gl_buffer *line_vertex_buffer = &g_hex_line_render_group.vertex_buffer;
int line_buf_len = hexes->size() * line_vertices_per_hex * 3; // 3 floats per vertex
// temporary buffers
GLfloat* vbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* cbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* line_buf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
for (int i = 0; i < hex_count; i++)
fillTriangleBufferFromHex(vbuf, 54 * i, (*hexes)[i]);
if (!initGLBufferObject(&g_filled_hex_render_group.vertex_buffer, vbuf_len, GL_DYNAMIC_DRAW, vbuf))
return false;
// color data for hex vertices
fillColorBuffer(cbuf, vbuf_len, hexes);
if (!initGLBufferObject(&g_filled_hex_render_group.color_buffer, vbuf_len, GL_DYNAMIC_DRAW, cbuf))
return false;
// hex line vertex data
fillHexLineBuffer(line_buf, line_buf_len, hexes);
if (!initGLBufferObject(&g_hex_line_render_group.vertex_buffer, line_buf_len, GL_STATIC_DRAW, line_buf))
return false;
// free temporary buffers
// TODO: temp buffers won't be freed if exit early is hit
std::free(vbuf);
std::free(cbuf);
std::free(line_buf);
vbuf = cbuf = line_buf = nullptr;
// debug draw vertexes
// 4 vertices, 3 floats per vertex
int len = 4 * 3;
if (!initGLBufferObject(&g_debug_render_group.vertex_buffer, len, GL_DYNAMIC_DRAW, 0))
return false;
///////////////
// TODO: Testing Entities/assimp model loading
g_entity_render_group.entity = &entities[0];
entities[0].model_transform = glm::scale(glm::mat4(1), glm::vec3(100, 100, 100));
uint entity_buf_len = entities[0].num_vertices * 3;
GLfloat* entity_buf = (GLfloat*) std::calloc(entity_buf_len, sizeof(GLfloat));
GLfloat* entity_color_buf = (GLfloat*) std::calloc(entity_buf_len, sizeof(GLfloat));
// dump vertices into temporary buffer
uint vertex_index = 0;
uint vertex_prop_index = 0;
GLfloat entity_test_color[3];
convertColor(entity_test_color, 0xF46000FF);
for (uint j = 0; j < entity_buf_len; j++) {
const glm::vec3& vertex = entities[0].vertices[vertex_index];
switch (vertex_prop_index) {
case 0: entity_buf[j] = vertex.x; break;
case 1: entity_buf[j] = vertex.y; break;
case 2: entity_buf[j] = vertex.z; break;
}
entity_color_buf[j] = entity_test_color[vertex_prop_index];
vertex_prop_index++;
if (vertex_prop_index == 3) {
vertex_prop_index = 0;
vertex_index++;
}
}
if (!initGLBufferObject(&g_entity_render_group.vertex_buffer, entity_buf_len,
GL_DYNAMIC_DRAW, entity_buf))
return false;
if (!initGLBufferObject(&g_entity_render_group.color_buffer, entity_buf_len,
GL_DYNAMIC_DRAW, entity_color_buf))
return false;
std::free(entity_buf);
std::free(entity_color_buf);
entity_buf = entity_color_buf = nullptr;
/////////
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
if (forward) v += f;
if (backward) v -= f;
if (up) v += u;
if (down) v -= u;
if (left)
{
glm::vec3 l = glm::cross(f, u);
v -= l;
}
if (right)
{
glm::vec3 r = glm::cross(u, f);
v -= r;
}
// TODO: this still doesn't fix side to side movement magnitude when vAngle is
// close to +- 90 degrees
glm::normalize(v);
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)
);
c.up = glm::vec3(0,0,1);
c.left = glm::cross(c.forward, c.up);
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
drawRenderGroup(gl_render_group* rg, GLenum draw_mode, bool update_vertex_data = false)
{
glUseProgram(rg->program_id);
// Send our transformation to the currently bound shader
glm::mat4 model_matrix;
if (rg->entity)
model_matrix = rg->entity->model_transform;
else
model_matrix = g_scene_matrices.model;
glUniformMatrix4fv(rg->model_matrix_id, 1, GL_FALSE, &model_matrix[0][0]);
glUniformMatrix4fv(rg->view_matrix_id, 1, GL_FALSE, &g_scene_matrices.view[0][0]);
glUniformMatrix4fv(rg->projection_matrix_id, 1, GL_FALSE, &g_scene_matrices.projection[0][0]);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, rg->vertex_buffer.buffer_id);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
if (update_vertex_data)
{
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->vertex_buffer.buffer_len * sizeof(GLfloat),
rg->vertex_buffer.buffer);
}
// 2nd attribute buffer : colors
if (rg->color_buffer.buffer)
{
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, rg->color_buffer.buffer_id);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
// TODO: maybe add an option to not send color data every frame?
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->color_buffer.buffer_len * sizeof(GLfloat),
rg->color_buffer.buffer);
}
// draw
glDrawArrays(draw_mode, 0, rg->vertex_buffer.buffer_len / 3);
// cleanup
glDisableVertexAttribArray(0);
if (rg->color_buffer.buffer)
glDisableVertexAttribArray(1);
}
void
renderFrame(std::vector<hex_info> *hexes)
{
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);
// filled hexes
// get new colors every frame
gl_render_group* rg = &g_filled_hex_render_group;
fillColorBuffer(rg->color_buffer.buffer, rg->color_buffer.buffer_len, hexes);
drawRenderGroup(rg, GL_TRIANGLES);
// hex lines
drawRenderGroup(&g_hex_line_render_group, GL_LINES);
// TODO: testing entity rendering
drawRenderGroup(&g_entity_render_group, GL_TRIANGLES);
}
void
renderDebug(std::vector<Point> &vertices)
{
// TODO: indexed line drawing
real64 buf[4 * 3] = {
vertices[0].x, vertices[0].y, 0,
vertices[1].x, vertices[1].y, 0,
vertices[2].x, vertices[2].y, 0,
vertices[3].x, vertices[3].y, 0,
};
// copy vertexes to render group
gl_render_group* rg = &g_debug_render_group;
for (int i = 0; i < 12; i++)
rg->vertex_buffer.buffer[i] = buf[i];
drawRenderGroup(rg, GL_LINE_LOOP, true);
}
void
freeBuffers()
{
std::vector<gl_render_group> groups = {
g_filled_hex_render_group,
g_hex_line_render_group,
g_debug_render_group,
//g_entity_render_group TODO: handle enitiy memory separately
};
for (gl_render_group group : groups)
{
if (group.vertex_buffer.buffer)
{
std::free(group.vertex_buffer.buffer);
group.vertex_buffer.buffer = 0;
}
if (group.color_buffer.buffer)
{
std::free(group.color_buffer.buffer);
group.color_buffer.buffer = 0;
}
}
}

836
src/renderer.h

@ -1,848 +1,18 @@
#ifndef RENDERER_H #pragma once
#define RENDERER_H
#include <vector> #include <vector>
#include <cmath> // trig functions
#include <cstdlib> // calloc
// TODO: decide on extension library
//#include <GL/glew.h>
#include <GL/gl3w.h>
#include <SDL.h>
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "hexlib.h"
#include "hexgame.h" #include "hexgame.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
const char * VERTEX_SHADER_CODE =
"#version 330 core\n"
"in vec3 vertexPosition_modelspace;\n"
"in vec3 vertexColor;\n"
"out vec3 fragmentColor;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
" gl_Position = projection * view * model * vec4(vertexPosition_modelspace, 1);\n"
" fragmentColor = vertexColor;\n"
"}";
const char * FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"in vec3 fragmentColor;\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = fragmentColor;\n"
"}";
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;
};
typedef struct gl_buffer
{
GLuint buffer_id = 0;
size_t buffer_len = 0; // NOTE: number of elements in buffer
GLfloat* buffer = nullptr;
} gl_buffer;
typedef struct gl_render_group
{
// NOTE: For now the renderFrame function will assume these are the same size
gl_buffer vertex_buffer;
gl_buffer color_buffer;
GLuint program_id = 0;
GLuint model_matrix_id = 0;
GLuint view_matrix_id = 0;
GLuint projection_matrix_id = 0;
GLuint vertex_array_id = 0;
Entity* entity = nullptr;
} gl_render_group;
gl_matrix_info g_scene_matrices;
gl_render_group g_filled_hex_render_group;
gl_render_group g_hex_line_render_group;
gl_render_group g_debug_render_group;
gl_render_group g_entity_render_group;
camera g_camera;
// Interface
bool initRenderer(SDL_Handles &handles, v2i vpDims); bool initRenderer(SDL_Handles &handles, v2i vpDims);
bool addTexture(SDL_Handles &handles, const char * path); bool addTexture(SDL_Handles &handles, const char * path);
v2f getUnprojectedCoords(int32 x, int32 y, int32 vp_width, int32 vp_height);
bool createScene(std::vector<hex_info>* hexes, Entity* entities, uint32 entity_count); bool createScene(std::vector<hex_info>* hexes, Entity* entities, uint32 entity_count);
void moveCamera(bool up, bool left, bool down, bool right, bool forward, bool backward); void moveCamera(bool up, bool left, bool down, bool right, bool forward, bool backward);
void rollCamera(bool CW, bool CCW); void rollCamera(bool CW, bool CCW);
void rotateCamera(int32 xrel, int32 yrel); void rotateCamera(int32 xrel, int32 yrel);
void renderFrame(const std::vector<hex_info> *hexes); void renderFrame(std::vector<hex_info> *hexes);
void renderDebug(std::vector<Point> &vertices); void renderDebug(std::vector<Point> &vertices);
void freeBuffers(); void freeBuffers();
// forward declarations
bool initShaderProgram(gl_render_group &rg, const char * vertex_code, const char * frag_code,
const char* model_name, const char* view_name, const char* projection_name);
// enable debug output https://www.khronos.org/opengl/wiki/OpenGL_Error
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam);
void fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex);
void fillColorBuffer(GLfloat buf[], int len, std::vector<hex_info> *hexes);
void convertColor(GLfloat buf[], uint32 color);
void fillHexLineBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes);
bool checkGLBufferSize(GLenum buf_type, int expected_size, int line_num);
bool initGLBufferObject(gl_buffer* buf_obj, int len, GLenum usage, GLfloat data[]);
void drawRenderGroup(gl_render_group* rg, GLenum draw_mode, bool update_vertex_data);
void initMatrices(projection_type p);
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);
// TODO: Line drawing quality seems to depend on graphics driver whims
// maybe try using textured billboards instead?
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
SDL_GL_SetSwapInterval(1); // vsync
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 == NULL)
{
// TODO: log error SDL_GetError()
return false;
}
handles.glContext = SDL_GL_CreateContext(handles.window);
// TODO: decide on extension library
gl3wInit();
#if 0
// Initialize GLEW
// glewExperimental is only needed in GLEW <= 1.13.0
// we can require version 2.0.0+
//glewExperimental = true; // Needed for core profile
GLenum err = glewInit();
if (err != GLEW_OK) {
LOG(ERROR) << "Failed to initilize GLEW" << glewGetErrorString(err) << "\n";
return false;
}
#endif
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);
// TODO: blending, these options break the http://www.opengl-tutorial.org tutorials atm
// Setup render state: alpha-blending enabled, polygon fill
#if 1
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// 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);
if (!initShaderProgram(
g_filled_hex_render_group, VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_hex_line_render_group, VERTEX_SHADER_CODE, LINE_FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_debug_render_group, VERTEX_SHADER_CODE, DEBUG_FRAGMENT_SHADER_CODE,
"model", "view", "projection")
|| !initShaderProgram(
g_entity_render_group, VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE,
"model", "view", "projection"))
{
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 1;
}
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;
}
// NOTE: model_name, view_name, and projection_name should match the matrix variable
// names in the shader source
bool
initShaderProgram(gl_render_group &rg, const char * vertex_code, const char * frag_code,
const char* model_name, const char* view_name, const char* projection_name)
{
glGenVertexArrays(1, &rg.vertex_array_id);
glBindVertexArray(rg.vertex_array_id);
GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertex_shader_id, 1, &vertex_code, NULL);
glShaderSource(fragment_shader_id, 1, &frag_code, NULL);
glCompileShader(vertex_shader_id);
glCompileShader(fragment_shader_id);
rg.program_id = glCreateProgram();
glAttachShader(rg.program_id, vertex_shader_id);
glAttachShader(rg.program_id, fragment_shader_id);
glLinkProgram(rg.program_id);
rg.model_matrix_id = glGetUniformLocation(rg.program_id, model_name);
rg.view_matrix_id = glGetUniformLocation(rg.program_id, view_name);
rg.projection_matrix_id = glGetUniformLocation(rg.program_id, projection_name);
glDetachShader(rg.program_id, vertex_shader_id);
glDetachShader(rg.program_id, fragment_shader_id);
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
// check for errors
GLint isLinked = 0;
glGetProgramiv(rg.program_id, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE) {
GLint maxLength = 0;
glGetProgramiv(rg.program_id, GL_INFO_LOG_LENGTH, &maxLength);
// The maxLength includes the NULL character
GLchar infoLog[maxLength];
glGetProgramInfoLog(rg.program_id, maxLength, &maxLength, &infoLog[0]);
LOG(ERROR) << infoLog << "\n";
// The program is useless now. So delete it.
glDeleteProgram(rg.program_id);
return false;
}
return true;
}
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);
// 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)
);
//////
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::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;
}
bool
createScene(std::vector<hex_info>* hexes, Entity* entities, uint32 entity_count)
{
initMatrices(PROJ_TYPE);
// Vertex Data
// TODO: index duplicate vertices
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-9-vbo-indexing/
int hex_count = (int) hexes->size();
// 6 triangles * 3 vertices per triangle * 3 floats per vertex = 54
int vbuf_len = hex_count * 6 * 3 * 3;
// TODO: surely there's a way to use indexed drawing for line vertices eg) line_loop
// and still use one buffer for multiple shapes/paths gldrawarraysintanced?, gldrawelements?
// https://gamedev.stackexchange.com/questions/104310/opengl-4-5-primitive-restart-vs-base-index
int line_vertices_per_hex = 6 * 2; // 12 vertices since we're using line segments atm
//gl_buffer *line_vertex_buffer = &g_hex_line_render_group.vertex_buffer;
int line_buf_len = hexes->size() * line_vertices_per_hex * 3; // 3 floats per vertex
// temporary buffers
GLfloat* vbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* cbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* line_buf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
for (int i = 0; i < hex_count; i++)
fillTriangleBufferFromHex(vbuf, 54 * i, (*hexes)[i]);
if (!initGLBufferObject(&g_filled_hex_render_group.vertex_buffer, vbuf_len, GL_DYNAMIC_DRAW, vbuf))
return false;
// color data for hex vertices
fillColorBuffer(cbuf, vbuf_len, hexes);
if (!initGLBufferObject(&g_filled_hex_render_group.color_buffer, vbuf_len, GL_DYNAMIC_DRAW, cbuf))
return false;
// hex line vertex data
fillHexLineBuffer(line_buf, line_buf_len, hexes);
if (!initGLBufferObject(&g_hex_line_render_group.vertex_buffer, line_buf_len, GL_STATIC_DRAW, line_buf))
return false;
// free temporary buffers
// TODO: temp buffers won't be freed if exit early is hit
std::free(vbuf);
std::free(cbuf);
std::free(line_buf);
vbuf = cbuf = line_buf = nullptr;
// debug draw vertexes
// 4 vertices, 3 floats per vertex
int len = 4 * 3;
if (!initGLBufferObject(&g_debug_render_group.vertex_buffer, len, GL_DYNAMIC_DRAW, 0))
return false;
///////////////
// TODO: Testing Entities/assimp model loading
g_entity_render_group.entity = &entities[0];
entities[0].model_transform = glm::scale(glm::mat4(1), glm::vec3(100, 100, 100));
uint entity_buf_len = entities[0].num_vertices * 3;
GLfloat* entity_buf = (GLfloat*) std::calloc(entity_buf_len, sizeof(GLfloat));
GLfloat* entity_color_buf = (GLfloat*) std::calloc(entity_buf_len, sizeof(GLfloat));
// dump vertices into temporary buffer
uint vertex_index = 0;
uint vertex_prop_index = 0;
GLfloat entity_test_color[3];
convertColor(entity_test_color, 0xF46000FF);
for (uint j = 0; j < entity_buf_len; j++) {
const glm::vec3& vertex = entities[0].vertices[vertex_index];
switch (vertex_prop_index) {
case 0: entity_buf[j] = vertex.x; break;
case 1: entity_buf[j] = vertex.y; break;
case 2: entity_buf[j] = vertex.z; break;
}
entity_color_buf[j] = entity_test_color[vertex_prop_index];
vertex_prop_index++;
if (vertex_prop_index == 3) {
vertex_prop_index = 0;
vertex_index++;
}
}
if (!initGLBufferObject(&g_entity_render_group.vertex_buffer, entity_buf_len,
GL_DYNAMIC_DRAW, entity_buf))
return false;
if (!initGLBufferObject(&g_entity_render_group.color_buffer, entity_buf_len,
GL_DYNAMIC_DRAW, entity_color_buf))
return false;
std::free(entity_buf);
std::free(entity_color_buf);
entity_buf = entity_color_buf = nullptr;
/////////
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
if (forward) v += f;
if (backward) v -= f;
if (up) v += u;
if (down) v -= u;
if (left)
{
glm::vec3 l = glm::cross(f, u);
v -= l;
}
if (right)
{
glm::vec3 r = glm::cross(u, f);
v -= r;
}
// TODO: this still doesn't fix side to side movement magnitude when vAngle is
// close to +- 90 degrees
glm::normalize(v);
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)
);
c.up = glm::vec3(0,0,1);
c.left = glm::cross(c.forward, c.up);
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
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;
}
}
// NOTE: helper for fillColorBuffer() to convert uint32 color to GLfloat triplet
void
convertColor(GLfloat buf[3], uint32 color)
{
// NOTE: not using the alpha values for now
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255;
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255;
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255;
}
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];
convertColor(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;
}
}
}
// NOTE: this will only work after a call to glBindBuffer
bool
checkGLBufferSize(GLenum buf_type, int expected_size, int line_num)
{
GLint gl_size;
glGetBufferParameteriv(buf_type, GL_BUFFER_SIZE ,&gl_size);
if (expected_size != gl_size)
{
LOG(ERROR) << "line: " << line_num << "gl buffer size mismatch\n";
return false;
}
return true;
}
bool
initGLBufferObject(gl_buffer* buf_obj, int len, GLenum usage, GLfloat data[])
{
buf_obj->buffer_len = (size_t) len;
buf_obj->buffer = (GLfloat*) std::calloc(len, sizeof(GLfloat));
if (data)
{
for (int i = 0; i < len; i++)
buf_obj->buffer[i] = data[i];
}
glGenBuffers(1, &buf_obj->buffer_id);
glBindBuffer(GL_ARRAY_BUFFER, buf_obj->buffer_id);
glBufferData(GL_ARRAY_BUFFER, len * sizeof(GLfloat), buf_obj->buffer, usage);
if (!checkGLBufferSize(GL_ARRAY_BUFFER, len * sizeof(GLfloat), __LINE__))
return false;
return true;
}
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";
}
void
drawRenderGroup(gl_render_group* rg, GLenum draw_mode, bool update_vertex_data = false)
{
glUseProgram(rg->program_id);
// Send our transformation to the currently bound shader
glm::mat4 model_matrix;
if (rg->entity)
model_matrix = rg->entity->model_transform;
else
model_matrix = g_scene_matrices.model;
glUniformMatrix4fv(rg->model_matrix_id, 1, GL_FALSE, &model_matrix[0][0]);
glUniformMatrix4fv(rg->view_matrix_id, 1, GL_FALSE, &g_scene_matrices.view[0][0]);
glUniformMatrix4fv(rg->projection_matrix_id, 1, GL_FALSE, &g_scene_matrices.projection[0][0]);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, rg->vertex_buffer.buffer_id);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
if (update_vertex_data)
{
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->vertex_buffer.buffer_len * sizeof(GLfloat),
rg->vertex_buffer.buffer);
}
// 2nd attribute buffer : colors
if (rg->color_buffer.buffer)
{
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, rg->color_buffer.buffer_id);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
// TODO: maybe add an option to not send color data every frame?
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->color_buffer.buffer_len * sizeof(GLfloat),
rg->color_buffer.buffer);
}
// draw
glDrawArrays(draw_mode, 0, rg->vertex_buffer.buffer_len / 3);
// cleanup
glDisableVertexAttribArray(0);
if (rg->color_buffer.buffer)
glDisableVertexAttribArray(1);
}
void
renderFrame(std::vector<hex_info> *hexes)
{
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);
// filled hexes
// get new colors every frame
gl_render_group* rg = &g_filled_hex_render_group;
fillColorBuffer(rg->color_buffer.buffer, rg->color_buffer.buffer_len, hexes);
drawRenderGroup(rg, GL_TRIANGLES);
// hex lines
drawRenderGroup(&g_hex_line_render_group, GL_LINES);
// TODO: testing entity rendering
drawRenderGroup(&g_entity_render_group, GL_TRIANGLES);
}
void
renderDebug(std::vector<Point> &vertices)
{
// TODO: indexed line drawing
real64 buf[4 * 3] = {
vertices[0].x, vertices[0].y, 0,
vertices[1].x, vertices[1].y, 0,
vertices[2].x, vertices[2].y, 0,
vertices[3].x, vertices[3].y, 0,
};
// copy vertexes to render group
gl_render_group* rg = &g_debug_render_group;
for (int i = 0; i < 12; i++)
rg->vertex_buffer.buffer[i] = buf[i];
drawRenderGroup(rg, GL_LINE_LOOP, true);
}
void
freeBuffers()
{
std::vector<gl_render_group> groups = {
g_filled_hex_render_group,
g_hex_line_render_group,
g_debug_render_group,
//g_entity_render_group TODO: handle enitiy memory separately
};
for (gl_render_group group : groups)
{
if (group.vertex_buffer.buffer)
{
std::free(group.vertex_buffer.buffer);
group.vertex_buffer.buffer = 0;
}
if (group.color_buffer.buffer)
{
std::free(group.color_buffer.buffer);
group.color_buffer.buffer = 0;
}
}
}
#endif // RENDERER_H

Loading…
Cancel
Save