From 040097303b9efa849c98e1053667bb135cf466e1 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Wed, 18 Jul 2018 17:29:07 -0400 Subject: [PATCH] splitting up compilation units --- Makefile | 32 +- src/gooey.cpp | 100 ++++++ src/gooey.h | 98 +----- src/hexgame.cpp | 12 +- src/hexgame.h | 7 +- src/hexlib.cpp | 243 ++++++++++++++ src/hexlib.h | 316 ++---------------- src/mesh.cpp | 73 +++++ src/mesh.h | 69 +--- src/renderer.cpp | 823 ++++++++++++++++++++++++++++++++++++++++++++++ src/renderer.h | 836 +---------------------------------------------- 11 files changed, 1313 insertions(+), 1296 deletions(-) create mode 100644 src/gooey.cpp create mode 100644 src/hexlib.cpp create mode 100644 src/mesh.cpp create mode 100644 src/renderer.cpp diff --git a/Makefile b/Makefile index 3c97d28..d7a2566 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ 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 OBJ_DIR = build @@ -22,36 +22,44 @@ LDFLAGS += $(shell pkg-config --libs SDL2_image) # IMGUI IMGUI_DIR = ext/imgui -CXXFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/examples/sdl_opengl3_example -I$(IMGUI_DIR)/examples/libs/gl3w - -# assimp -LDFLAGS += -lassimp - -SOURCES = $(IMGUI_DIR)/imgui.cpp \ +CXXFLAGS += -I$(IMGUI_DIR)\ + -I$(IMGUI_DIR)/examples/sdl_opengl3_example\ + -I$(IMGUI_DIR)/examples/libs/gl3w +IMGUI_SOURCES = $(IMGUI_DIR)/imgui.cpp \ $(IMGUI_DIR)/imgui_demo.cpp \ $(IMGUI_DIR)/imgui_draw.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 - $(CXX) $(CXXFLAGS) -I/usr/include/SDL2 -Iext/aixlog/include -o $(OBJ_DIR)/hexgame $(SRC_DIR)/hexgame.cpp $(LDFLAGS) $(GL3W_LDFLAGS) $(OBJ_DIR)/* +all: $(IMGUI_OBJECTS) $(OBJ_DIR)/gl3w.o $(OBJECTS) + $(CXX) -o $(OBJ_DIR)/hexgame $(LDFLAGS) $(GL3W_LDFLAGS) $(OBJ_DIR)/* mkdir -p bin mv $(OBJ_DIR)/hexgame bin -info: +$(OBJECTS): $(OBJ_DIR)/%.o : $(SRC_DIR)/%.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +info: @echo "SOURCES:" @echo $(SOURCES) @echo "OBJECTS:" @echo $(OBJECTS) +# NOTE: couldn't find a nicer way to put these object files into build directory :( $(OBJ_DIR)/imgui.o: $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui.cpp -o $@ $(OBJ_DIR)/imgui_demo.o: $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_demo.cpp -o $@ - + $(OBJ_DIR)/imgui_draw.o: $(CXX) -c $(CXXFLAGS) $(IMGUI_DIR)/imgui_draw.cpp -o $@ diff --git a/src/gooey.cpp b/src/gooey.cpp new file mode 100644 index 0000000..7180bc9 --- /dev/null +++ b/src/gooey.cpp @@ -0,0 +1,100 @@ + +#if defined (_WIN32) + #include +#else + #include +#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()); +} + + diff --git a/src/gooey.h b/src/gooey.h index 65ed221..4f02c7f 100644 --- a/src/gooey.h +++ b/src/gooey.h @@ -1,107 +1,11 @@ -#ifndef GOOEY_H -#define GOOEY_H - - -#include -#include "imgui.h" -#include "examples/sdl_opengl3_example/imgui_impl_sdl_gl3.h" +#pragma once #include "hexgame.h" - bool initGooey(SDL_Handles &handles, v2i vp_dims); void shutdownGooey(); bool gooeyProcessEvent(SDL_Event &event); void renderGooey(SDL_Handles &handles, HexDrawMode &mode, bool &is_debug, 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 - diff --git a/src/hexgame.cpp b/src/hexgame.cpp index 5d7c5e5..04d3915 100644 --- a/src/hexgame.cpp +++ b/src/hexgame.cpp @@ -1,10 +1,10 @@ /* * TODO: - * - maybe start thinking about separating headers into different compilation units * - add some sweet unit models * - need to add indexed drawing for assimp models * - map generation * - pathfinding + * - update imgui to v1.62 -- requires changes to example api */ // Some defaults for the game layout @@ -22,13 +22,17 @@ #include #if defined(_WIN32) -#include -#include + #include + #include + #include +#else + #include #endif -#include #include +#include "aixlog.hpp" + #include "hexgame.h" #include "hexlib.h" #include "renderer.h" diff --git a/src/hexgame.h b/src/hexgame.h index e6533cc..f4a3fec 100644 --- a/src/hexgame.h +++ b/src/hexgame.h @@ -1,6 +1,5 @@ -#ifndef HEXGAME_H -#define HEXGAME_H +#pragma once #include #include @@ -10,7 +9,7 @@ #else #include #endif -#include "aixlog.hpp" + #include // vec3 #include "hexlib.h" @@ -134,5 +133,3 @@ SafeTruncateToInt32(int64 val) return (int32) val; } -#endif // HEXGAME_H - diff --git a/src/hexlib.cpp b/src/hexlib.cpp new file mode 100644 index 0000000..66ba955 --- /dev/null +++ b/src/hexlib.cpp @@ -0,0 +1,243 @@ + +#include "hexlib.h" + +// Generated code -- http://www.redblobgames.com/grids/hexagons/ + +#include +#include +#include +#include +#include +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_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_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_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 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 polygon_corners(Layout layout, Hex h) +{ + vector 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_conefill(Hex a, Hex b) +{ + vector 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 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); +} + diff --git a/src/hexlib.h b/src/hexlib.h index 21d2b7a..6235402 100644 --- a/src/hexlib.h +++ b/src/hexlib.h @@ -1,19 +1,21 @@ -#ifndef HEXLIB_H -#define HEXLIB_H +#pragma once // Generated code -- http://www.redblobgames.com/grids/hexagons/ #include -#include #include -#include -#include -using std::abs; -using std::max; 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 { double x; @@ -22,7 +24,6 @@ struct Point Point(): x(0), y(0) {} }; - struct Hex { int q; @@ -32,296 +33,51 @@ struct Hex 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 { - const double f0; - const double f1; - const double f2; - const double f3; - const double b0; - const double b1; - const double b2; - const double b3; - const 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_) {} + double f0; + double f1; + double f2; + double f3; + double b0; + double b1; + double b2; + double b3; + 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_) {} }; +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 { const Orientation orientation; const Point size; 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_linedraw(Hex a, Hex b); -// Forward declarations - - -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_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_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_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 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 polygon_corners(Layout layout, Hex h) -{ - vector 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; -} - +Point hex_to_pixel(Layout layout, Hex h); +FractionalHex pixel_to_hex(Layout layout, Point p); +vector polygon_corners(Layout layout, Hex h); // 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_conefill(Hex a, Hex b) -{ - vector results = {}; - - return results; -} +bool hex_equal(Hex a, Hex b); +vector hex_conefill(Hex a, Hex b); // 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 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); -} +bool crossingTest(vector vertices, Point p); -#endif // HEXLIB_H diff --git a/src/mesh.cpp b/src/mesh.cpp new file mode 100644 index 0000000..f02db93 --- /dev/null +++ b/src/mesh.cpp @@ -0,0 +1,73 @@ +#include + +#include // 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(); +} + diff --git a/src/mesh.h b/src/mesh.h index 1fbe9f0..893e9ef 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -5,80 +5,19 @@ #pragma once -#include - -#include // vec3 - #include #include #include -#include "aixlog.hpp" - #include "hexgame.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); +bool initAssimp(); - 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 +uint getVertexCount(); // 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; -} +Entity* convertMesh(Entity* 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(); -} +void shutdownAssimp(); diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..81f3ca7 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,823 @@ + +#include +#include // trig functions +#include // calloc + +// TODO: decide on extension library +//#include +#include + +#if defined (_WIN32) + #include +#else + #include +#endif +#include +#include +#include +#include + +#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* 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* 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* 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 *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 &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 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; + } + } + +} + diff --git a/src/renderer.h b/src/renderer.h index 928b37e..3290005 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -1,848 +1,18 @@ -#ifndef RENDERER_H -#define RENDERER_H +#pragma once #include -#include // trig functions -#include // calloc -// TODO: decide on extension library -//#include -#include - -#include -#include -#include -#include - -#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; - -// Interface - bool initRenderer(SDL_Handles &handles, v2i vpDims); bool addTexture(SDL_Handles &handles, const char * path); +v2f getUnprojectedCoords(int32 x, int32 y, int32 vp_width, int32 vp_height); bool createScene(std::vector* hexes, Entity* entities, uint32 entity_count); void moveCamera(bool up, bool left, bool down, bool right, bool forward, bool backward); void rollCamera(bool CW, bool CCW); void rotateCamera(int32 xrel, int32 yrel); -void renderFrame(const std::vector *hexes); +void renderFrame(std::vector *hexes); void renderDebug(std::vector &vertices); 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 *hexes); -void convertColor(GLfloat buf[], uint32 color); -void fillHexLineBuffer(GLfloat buf[], int len, std::vector* 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* 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* 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* 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 *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 &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 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 -