Browse Source

initial commit

new file:   .gitignore
	new file:   .gitmodules
	new file:   Makefile
	new file:   README.txt
	new file:   examples/Makefile
	new file:   examples/main.cpp
	new file:   ext/stb_libs
	new file:   include/camera.h
	new file:   include/dumbLog.h
	new file:   include/entity.h
	new file:   include/mesh.h
	new file:   include/platform_wait_for_vblank.h
	new file:   include/render_group.h
	new file:   include/renderer.h
	new file:   include/util.h
	new file:   src/camera.cpp
	new file:   src/dumbLog.cpp
	new file:   src/entity.cpp
	new file:   src/mesh.cpp
	new file:   src/render_group.cpp
	new file:   src/renderer.cpp
	new file:   src/util.cpp
testing
cinnaboot 6 years ago
commit
44c07b7ab8
  1. 7
      .gitignore
  2. 3
      .gitmodules
  3. 28
      Makefile
  4. 11
      README.txt
  5. 12
      examples/Makefile
  6. 9
      examples/main.cpp
  7. 1
      ext/stb_libs
  8. 45
      include/camera.h
  9. 28
      include/dumbLog.h
  10. 39
      include/entity.h
  11. 43
      include/mesh.h
  12. 102
      include/platform_wait_for_vblank.h
  13. 96
      include/render_group.h
  14. 58
      include/renderer.h
  15. 142
      include/util.h
  16. 193
      src/camera.cpp
  17. 38
      src/dumbLog.cpp
  18. 164
      src/entity.cpp
  19. 175
      src/mesh.cpp
  20. 277
      src/render_group.cpp
  21. 232
      src/renderer.cpp
  22. 234
      src/util.cpp

7
.gitignore vendored

@ -0,0 +1,7 @@
bin/
build/
doc/
msvc/.vs/
msvc/x64/
tags
*.swp

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "ext/stb_libs"]
path = ext/stb_libs
url = https://github.com/nothings/stb.git

28
Makefile

@ -0,0 +1,28 @@
CXX = g++
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -Iinclude -I/usr/include/SDL2 -Iext/stb_libs
EXAMPLEDIR = examples
OBJDIR = build
SRCDIR = src
LIBNAME = libTangerine.a
RENDER_SOURCES = $(wildcard $(SRCDIR)/*.cpp)
RENDER_OBJECTS = $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(RENDER_SOURCES))
all: $(RENDER_OBJECTS)
ar -crs $(OBJDIR)/$(LIBNAME) $(RENDER_OBJECTS)
.PHONY: all
-include $(OBJDIR)/*.d
$(RENDER_OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
$(CXX) $(CXXFLAGS) -c -MMD $< -o $@
examples:
$(MAKE) -C $(EXAMPLEDIR)
.PHONY: examples
clean:
rm $(OBJDIR)/*
.PHONY: clean

11
README.txt

@ -0,0 +1,11 @@
Tangerine
A small OpenGL 3+ renderer and game engine
NOTE: This is still a work in progress
Dependencies:
glew
SDL2
assimp
stb_image

12
examples/Makefile

@ -0,0 +1,12 @@
CXX = g++
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -I../include -I/usr/include/SDL2 -I../ext/stb_libs
LDFLAGS = -lSDL2 -lGLEW -lGL -lassimp
OBJDIR = ../build
BINARY = example
BINDIR = bin
all:
$(CXX) $(CXXFLAGS) -c -MMD main.cpp -o $(OBJDIR)/$(BINARY).o
mkdir -p $(BINDIR)
$(CXX) -o $(BINDIR)/$(BINARY) $(LDFLAGS) $(OBJDIR)/$(BINARY).o $(OBJDIR)/libTangerine.a

9
examples/main.cpp

@ -0,0 +1,9 @@
#include "renderer.h"
int
main()
{
return 0;
}

1
ext/stb_libs

@ -0,0 +1 @@
Subproject commit b42009b3b9d4ca35bc703f5310eedc74f584be58

45
include/camera.h

@ -0,0 +1,45 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "util.h"
struct camera
{
float hAngle;
float vAngle;
glm::vec3 position;
glm::vec3 forward;
glm::vec3 up;
glm::vec3 left;
glm::vec3 target;
glm::vec3 world_up;
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
glm::mat4 MVP;
};
enum projection_type
{
PERSPECTIVE,
ORTHOGRAPHIC,
};
v2f cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height);
v3f cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims);
bool cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection);
void cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up);
void cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward);
void cameraRotate(camera& cam, int32 xrel, int32 yrel);
void cameraRoll(camera& cam, bool CW, bool CCW);

28
include/dumbLog.h

@ -0,0 +1,28 @@
#include <iomanip>
#include <iostream>
enum log_level {
Error,
Warning,
Info,
Debug
};
struct dumbLog
{
std::ostream* OUT = &std::cout;
void setOutputStream(std::ostream* out);
const char* logLevelToString(log_level level);
std::tm* getCurrentTime();
int getCurrentMS();
};
static dumbLog logger;
#define LOG(level) *logger.OUT \
<< std::put_time(logger.getCurrentTime(), "%F %T.") << logger.getCurrentMS() << " " \
<< "[" << logger.logLevelToString(level) << "] " \
<< "(" << __FUNCTION__ << ") "

39
include/entity.h

@ -0,0 +1,39 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "mesh.h"
#include "render_group.h"
struct Entity
{
glm::mat4 world_transform;
glm::vec3 scale;
glm::vec3 translation;
glm::vec4 rotation;
meMeshGroup mesh_group;
render_group* ren_group;
char model_filename[256];
};
inline void
entTranslate(Entity& e, glm::vec3 v)
{
e.world_transform = glm::translate(e.world_transform, v);
}
inline void
entScale(Entity& e, glm::vec3 v)
{
e.world_transform = glm::scale(e.world_transform, v);
}
bool entInit(Entity& e, const char* data_dir, rg_shader_program& shader);
void entFree(Entity& e);
void entSetWorldPosition(Entity& e, float x, float y, float z);

43
include/mesh.h

@ -0,0 +1,43 @@
/*
* mesh.h
* - wrapper for assimp http://www.assimp.org
*/
#pragma once
#include <glm/glm.hpp>
#include "util.h"
struct meMeshInfo
{
glm::mat4 model_transform;
uint num_vertices;
glm::vec3* vertices;
glm::vec3* normals;
glm::vec3* texture_coords; // NOTE: using vec3 to stay aligned with other props
uint num_indices;
uint* indices;
glm::vec3 diffuse_color;
bool use_texture;
util_image diffuse_texture;
};
struct meMeshGroup
{
bool use_normals;
uint num_meshes;
meMeshInfo** meshes;
// animation/bonemapping info here...
};
bool meInitAssimp();
bool meLoadFromFile(meMeshGroup& mesh_group, const char* data_dir, const char* filename);
void meFreeMeshGroup(meMeshGroup& mesh_group);
void meShutdownAssimp();

102
include/platform_wait_for_vblank.h

@ -0,0 +1,102 @@
// attempt to fix vsync with opengl on windows
// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmthk/nf-d3dkmthk-d3dkmtwaitforverticalblankevent
// NOTE: requires installing windows driver kit addon for msvc
// TODO: not working with hdc provided by SDL, try enumerating display devices as described here:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/d3dkmthk/nf-d3dkmthk-d3dkmtopenadapterfromhdc
// NOTE: the above seems to make some difference in lowering cpu usage, but cpu stays at max frequency even when relatively idle.
// TODO: if d3dkmt... doensn't work, try DWMFlush() https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmflush
// glfw uses this https://github.com/glfw/glfw/blob/master/src/wgl_context.c
//
// if that doesn't work... https://docs.microsoft.com/en-us/windows/desktop/api/timeapi/nf-timeapi-timebeginperiod
// to increase schedular granularity. can then use sleep(1) for 1ms resolution
#pragma once
#include <cstdint>
#include "SDL_syswm.h"
#if defined(_WIN32)
#include "windows.h"
#include "d3dkmthk.h"
typedef uint32_t D3DKMT_HANDLE;
typedef struct {
D3DKMT_HANDLE hAdapter = NULL;
UINT vidID = 0;
} platform_win_device_handles;
platform_win_device_handles g_platform_win_device_handles;
#endif // _WIN32
inline bool
platform_init(SDL_Window* window)
{
#if defined(_WIN32)
D3DKMT_HANDLE hAdapter = NULL;
UINT vidID = 0;
D3DKMT_OPENADAPTERFROMHDC OpenAdapterData;
DISPLAY_DEVICE dd;
HDC hdc;
memset(&dd, 0, sizeof(dd));
dd.cb = sizeof dd;
for (int i = 0; EnumDisplayDevicesA(NULL, i, &dd, 0); ++i) {
if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
break;
}
hdc = CreateDC(NULL, dd.DeviceName, NULL, NULL);
if (hdc == NULL)
return false;
OpenAdapterData.hDc = hdc;
if ((D3DKMTOpenAdapterFromHdc(&OpenAdapterData)) >= 0) {
DeleteDC(hdc);
g_platform_win_device_handles.hAdapter = OpenAdapterData.hAdapter;
g_platform_win_device_handles.vidID = OpenAdapterData.VidPnSourceId;
return true;
}
DeleteDC(hdc);
return false;
#endif // _WIN32
return true;
}
inline void
platform_wait_for_vblank(bool wait=true)
{
if (!wait) return;
#if defined(_WIN32)
_D3DKMT_WAITFORVERTICALBLANKEVENT waitForVBlankData;
memset(&waitForVBlankData, 0, sizeof(waitForVBlankData));
waitForVBlankData.hAdapter = g_platform_win_device_handles.hAdapter;
waitForVBlankData.VidPnSourceId = g_platform_win_device_handles.vidID;
NTSTATUS ret = D3DKMTWaitForVerticalBlankEvent(&waitForVBlankData);
switch (ret) {
case 0x00000000: // STATUS_SUCCESS
//OutputDebugString("Success\r\n");
break;
//case STATUS_DEVICE_REMOVED:
// OutputDebugString("STATUS_DEVICE_REMOVED\r\n");
// break;
case STATUS_INVALID_PARAMETER:
OutputDebugString("STATUS_INVALID_PARAMETER\r\n");
break;
default:
OutputDebugString("????");
}
#endif // _WIN32
// TODO: implement/test for linux
}

96
include/render_group.h

@ -0,0 +1,96 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "util.h"
// TODO: can these structs be used as opaque pointers?
struct gl_buffer
{
GLuint buffer_id;
uint count; // NOTE: number of elements in buffer
uint max_count; // NOTE: maxium number of elements buffer can fit
GLfloat* buffer;
};
struct gl_index_buffer
{
GLuint buffer_id;
uint count; // NOTE: number of elements in buffer
uint max_count; // NOTE: maxium number of elements buffer can fit
uint* buffer;
};
struct render_object
{
// TODO: can remove these once we're using the global color palatte for all textures
bool use_texture;
GLuint tex_id;
gl_buffer vertex_buffer;
gl_buffer normal_buffer;
gl_buffer uv_buffer;
gl_index_buffer index_buffer;
};
struct rg_shader_program
{
GLuint program_id;
GLuint model_matrix_id;
GLuint view_matrix_id;
GLuint projection_matrix_id;
GLuint normal_matrix_id;
GLuint vertex_array_id;
GLuint sampler_id;
GLuint num_lights_id;
};
struct render_group
{
uint num_objects;
render_object** render_objects;
rg_shader_program shader;
bool use_normals;
bool draw_indexed;
GLenum draw_mode = GL_TRIANGLES;
char name[64];
};
// TODO: maybe pull out to seperate header
struct rg_point_light
{
uint light_ID;
glm::vec3 position;
glm::vec3 color;
float intensity;
};
render_object * rgAllocateRenderObject(uint buffer_len, uint index_len = 0);
// NOTE: initializes a render_group with 1 render_object allocated
render_group* rgInitSingle(rg_shader_program& sp, uint vertex_buffer_len,
bool use_normals = false, uint index_buffer_len = 0,
GLenum draw_mode = GL_TRIANGLES);
bool rgInitShaderProgram(rg_shader_program& sp, const char * vertex_code, const char * frag_code);
bool rgInitGLTexture(GLuint& tex_id, util_image image);
void rgInitGLFloatBuffer(gl_buffer* buffer, GLenum usage, GLenum target);
void rgInitGLIndexBuffer(gl_index_buffer* index_buffer, GLenum usage, GLenum target);
void rgUpdateGLBuffer(gl_buffer& buffer);
void rgDraw(render_group* rg, glm::mat4 model_matrix,
glm::mat4 view_matrix, glm::mat4 projection_matrix,
rg_point_light* lights, uint num_lights,
bool update_vertex_data = false);
void rgFree(render_group* rg);

58
include/renderer.h

@ -0,0 +1,58 @@
#pragma once
#include <vector>
#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "camera.h"
#include "entity.h"
#include "render_group.h"
#include "util.h"
struct SDL_Handles
{
SDL_Window *window;
SDL_GLContext glContext;
SDL_DisplayMode currentDisplayMode;
std::vector<SDL_Surface*> texSurfaces;
};
struct rg_point_light;
struct render_state
{
v2i viewport_dims;
bool is_debug_draw;
SDL_Handles handles;
camera cam;
util_image palette_image;
// TODO: this needs to be initialized outside struct because calloc
GLuint palette_id = UINT_MAX;
render_group* filled_hex_render_group;
render_group* hex_line_render_group;
render_group* debug_render_group;
// NOTE: entity render groups are stored on the entity object
rg_shader_program default_shader;
rg_point_light* lights;
uint num_lights;
uint max_lights;
};
bool renInit(render_state* rs);
void renFreeBuffers(render_state* rs);
void renRenderFrame(render_state* rs, Entity* entities, uint32 entity_count);
//void renRenderDebug(render_state* rs, std::vector<Point> &vertices);

142
include/util.h

@ -0,0 +1,142 @@
#pragma once
#include <cstdint>
#include <cassert>
typedef float real32;
typedef float GLfloat;
typedef double real64;
typedef int32_t bool32;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint32_t uint32;
typedef uint32_t uint;
struct v2f
{
v2f(): x(0), y(0) {}
v2f(real64 a, real64 b): x(a), y(b) {}
real64 x;
real64 y;
};
struct v2i
{
v2i(int a, int b): x(a), y(b) {}
v2i() : x(0), y(0) {}
int32 x;
int32 y;
};
struct v3f
{
v3f(): x(0), y(0), z(0) {}
v3f(real64 a, real64 b, real64 c): x(a), y(b), z(c) {}
real64 x;
real64 y;
real64 z;
};
struct v3i
{
int32 x;
int32 y;
int32 z;
};
struct v4i
{
int32 x0;
int32 y0;
int32 x1;
int32 y1;
};
struct util_RGBA
{
real32 R;
real32 G;
real32 B;
real32 A;
};
struct util_image
{
int32 w;
int32 h;
int32 bits_per_channel;
int32 num_channels;
uint data_len;
uint8* pixels;
char file_path[256];
};
inline real32
SafeRatio(real32 dividend, real32 divisor)
{
if (divisor == 0)
// TODO: log warning (don't require aixlog)
return 0;
else
return dividend / divisor;
}
inline int32
SafeTruncateToInt32(int64 val)
{
assert(val <= INT32_MAX && val >= INT32_MIN);
return (int32) val;
}
inline void
utilConvertColor(GLfloat buf[3], uint32 color)
{
// NOTE: not using the alpha values
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255;
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255;
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255;
}
//-----------------
// C Strings
// NOTE: max_len should be the allocated size of dest
bool utilCopyCStr(char* dest, const char* src, uint max_len);
// NOTE: returns new string with '/' between
// NOTE: max_len should be the size of return buffer.
bool utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len);
const char* utilBaseName(const char* path_str);
// NOTE: returns true if the first 'sz' characters from each string match
bool utilMatchPrefix(const char* lhs, const char* rhs, int sz);
//-----------------
// Memory allocation
// NOTE: Wrapper for calloc that will send error message on out of memory
#define UTIL_ALLOC(len, type) (type *) utilLogAlloc((len), sizeof(type), __FILE__, __LINE__)
void* utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line);
void utilSafeFree(const void* mem);
//-----------------
// File I/O
char* utilDumpTextFile(const char* filename);
bool utilWriteTextFile(const char* filename, const char* text);
//-----------------
// utilImage
util_image utilLoadImage(const char* base_dir, const char* filename);
void utilFreeImage(util_image image);
v2f utilGetPaletteCoords(util_image& palette_image, uint color_index);

193
src/camera.cpp

@ -0,0 +1,193 @@
#include <GL/glew.h>
#include "camera.h"
// TODO: add these props to scene json
#define MOVE_SPEED 5.f
#define ROTATE_SPEED 0.005f
#define CAMERA_Z_CLAMP_ANGLE 85.f
#define FOV 60.f
#define ASPECT_RATIO 16.f/9.f
#define NEAR_CLIP_PLANE 20.f
// forward declarations
inline glm::vec3 convertv3f(v3f v);
// interface
void
cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up)
{
cam.position = position;
cam.target = target;
cam.world_up = world_up;
cam.projection = glm::infinitePerspective(glm::radians(FOV), ASPECT_RATIO, NEAR_CLIP_PLANE);
cam.forward = glm::normalize(target - position);
cam.left = glm::normalize(glm::cross(cam.world_up, cam.forward));
cam.up = glm::normalize(glm::cross(cam.forward, cam.left));
cam.hAngle = glm::atan(cam.forward.x, cam.forward.y);
// NOTE: using pythagoras' to get absolute value of relative axis for vAngle component
real32 len = glm::sqrt(glm::pow(cam.forward.y, 2) + glm::pow(cam.forward.x, 2));
cam.vAngle = glm::atan(cam.forward.z, len);
cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up);
cam.model = glm::mat4(1.0f);
cam.MVP = cam.projection * cam.view * cam.model;
}
void
cameraInitOrthographic(/*camera& cam, */)
{
#if 0
// left, right, bottom, top, zNear, zFar
cam.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f);
cam.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
);
cam.model = glm::mat4(1.0f);
cam.MVP = cam.projection * cam.view * cam.model;
#endif
}
v2f
cameraUnproject(camera& cam, int x, int y, int vp_width, int 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, cam.view, cam.projection, viewport);
v2f v(vU.x, vU.y);
return v;
}
v3f
cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims)
{
// NOTE: http://antongerdelan.net/opengl/raycasting.html
float x = 2.f * vp_coords.x / vp_dims.x - 1.f;
float y = 2.f * vp_coords.y / vp_dims.y - 1.f;
glm::vec4 ray_clip = glm::vec4(x, y, -1.f, 1.f);
glm::vec4 ray_eye = glm::inverse(cam.projection) * ray_clip;
ray_eye = glm::vec4(ray_eye.x, ray_eye.y, -1.f, 0); // NOTE: reset as ray
glm::vec4 ray_world = glm::normalize(glm::inverse(cam.view) * ray_eye);
return v3f(ray_world.x, ray_world.y, ray_world.z);
}
bool
cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection)
{
// NOTE: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection
glm::vec3 c_o = cam.position;
glm::vec3 r = convertv3f(ray);
glm::vec3 p_o = convertv3f(plane_origin);
glm::vec3 p_n = convertv3f(plane_normal);
float divisor = glm::dot(r, p_n);
if (divisor <= 0.000001f && divisor >= -0.000001f) // NOTE: ray and plane are co-planar
return false;
float distance = glm::dot((p_o - c_o), p_n) / divisor;
glm::vec3 xsect = c_o + (r * distance);
intersection = v3f(xsect.x, xsect.y, xsect.z);
return true;
}
void
cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward)
{
if (!up && !left && !down && !right && !forward && !backward)
return;
glm::vec3 f = cam.forward;
glm::vec3 u = cam.up;
glm::vec3 old = cam.position;
glm::vec3 &p = cam.position;
glm::vec3 v(0.f); // normalized direction
// TODO: still seems like we're adding magnitude when moving in 2 directions
#if 0
if (forward) v = glm::normalize(v + f);
if (backward) v = glm::normalize(v - f);
if (up) v = glm::normalize(v + u);
if (down) v = glm::normalize(v - u);
if (left) v -= glm::normalize(glm::cross(f, u));
if (right) v -= glm::normalize(glm::cross(u, f));
#else
if (forward) v += f;
if (backward) v -= f;
if (up) v += u;
if (down) v -= u;
if (left) v -= glm::cross(f, u);
if (right) v -= glm::cross(u, f);
#endif
p += (v * MOVE_SPEED);
glm::vec3 diff = old - p;
cam.view = glm::translate(cam.view, diff);
cam.MVP = cam.projection * cam.view * cam.model;
}
void
cameraRotate(camera& cam, int32 xrel, int32 yrel)
{
float &h = cam.hAngle;
float &v = cam.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;
cam.forward = glm::vec3(
glm::cos(v) * glm::sin(h),
glm::cos(v) * glm::cos(h),
glm::sin(v)
);
glm::normalize(cam.forward);
cam.left = glm::normalize(glm::cross(cam.forward, cam.world_up));
cam.up = glm::normalize(glm::cross(cam.left, cam.forward));
cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up);
cam.MVP = cam.projection * cam.view * cam.model;
}
void
cameraRoll(camera& cam, bool CW, bool CCW)
{
if ((!CW && !CCW) || (CW && CCW))
return;
float a = 0.005f;
if (CW) a *= 1;
if (CCW) a *= -1;
glm::mat4 m = glm::rotate(glm::mat4(1.f), a, cam.forward);
glm::vec4 v(cam.up.x, cam.up.y, cam.up.z, 0);
v = v * m;
cam.up = glm::vec3(v.x, v.y, v.z);
cam.view *= m;
cam.MVP = cam.projection * cam.view * cam.model;
}
// internal
inline glm::vec3
convertv3f(v3f v)
{
return glm::vec3(v.x, v.y, v.z);
}

38
src/dumbLog.cpp

@ -0,0 +1,38 @@
#include <ctime>
#include <chrono>
#include "dumbLog.h"
void dumbLog::setOutputStream(std::ostream* out)
{
this->OUT = out;
}
const char*
dumbLog::logLevelToString(log_level level)
{
switch (level) {
case log_level::Error: return "Error";
case log_level::Warning: return "Warning";
case log_level::Info: return "Info";
default: return "Potato";
}
};
std::tm*
dumbLog::getCurrentTime()
{
auto now = std::chrono::system_clock::now();
auto t_c = std::chrono::system_clock::to_time_t(now);
return std::localtime(&t_c);
}
int
dumbLog::getCurrentMS()
{
auto now = std::chrono::system_clock::now();
long long total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
return int(total_ms % 1000);
}

164
src/entity.cpp

@ -0,0 +1,164 @@
#include "dumbLog.h"
#include "entity.h"
void initEntityRG(Entity& e, rg_shader_program& shader, bool use_normals);
bool initEntityROs(Entity& e);
bool convertMeshInfo(meMeshInfo* mesh, render_object* ro, bool use_normals, bool use_texture);
bool
entInit(Entity& e, const char* data_dir, rg_shader_program& shader)
{
e.world_transform = glm::mat4(1.0);
entScale(e, e.scale);
uint max_len = 256; // TODO: define max_len for property strings
char full_path[max_len];
if (utilConcatPath(full_path, data_dir, e.model_filename, max_len)) {
if (meLoadFromFile(e.mesh_group, data_dir, full_path)) {
initEntityRG(e, shader, e.mesh_group.use_normals);
entSetWorldPosition(e, e.translation.x, e.translation.y, e.translation.z);
if (!initEntityROs(e))
goto cleanup;
} else {
goto cleanup;
}
} else {
LOG(Error) << data_dir << " + " << e.model_filename << " is too long\n";
goto cleanup;
}
return true;
cleanup:
entFree(e);
return false;
}
void
entFree(Entity& e)
{
meFreeMeshGroup(e.mesh_group);
rgFree(e.ren_group);
}
void
entSetWorldPosition(Entity& e, float x, float y, float z)
{
e.world_transform[3][0] = e.translation.x = x;
e.world_transform[3][1] = e.translation.y = y;
e.world_transform[3][2] = e.translation.z = z;
}
void
initEntityRG(Entity& e, rg_shader_program& shader, bool use_normals)
{
render_group* rg = UTIL_ALLOC(1, render_group);
e.ren_group = rg;
e.ren_group->shader = shader;
rg->draw_indexed = true;
rg->draw_mode = GL_TRIANGLES;
rg->use_normals = use_normals;
uint num_meshes = rg->num_objects = e.mesh_group.num_meshes;
rg->render_objects = UTIL_ALLOC(num_meshes, render_object*);
}
bool
initEntityROs(Entity& e)
{
render_group* rg = e.ren_group;
for (uint i = 0; i < e.mesh_group.num_meshes; i++)
{
meMeshInfo* mesh = e.mesh_group.meshes[i];
uint buffer_len = mesh->num_vertices * 3;
uint index_len = mesh->num_indices;
render_object* ro = rgAllocateRenderObject(buffer_len, index_len);
rg->render_objects[i] = ro;
if (ro == nullptr)
return false;
if (!convertMeshInfo(mesh, ro, rg->use_normals, mesh->use_texture)) {
rgFree(rg);
return false;
}
if (mesh->use_texture) {
ro->use_texture = true;
if (!rgInitGLTexture(ro->tex_id, mesh->diffuse_texture)) {
LOG(Error) << "Error initializing GL texture\n";
return false;
}
}
}
return true;
}
bool
convertMeshInfo(meMeshInfo* mesh, render_object* ro, bool use_normals, bool use_texture)
{
uint vertex_buf_len = mesh->num_vertices * 3;
GLfloat* vertex_buf = ro->vertex_buffer.buffer;
GLfloat* normal_buf = ro->normal_buffer.buffer;
GLfloat* uv_buf = ro->uv_buffer.buffer;
uint* index_buf = ro->index_buffer.buffer;
if (!vertex_buf || !normal_buf || !index_buf)
return false;
// dump vertices, colors, normals, and texture coords into render_group buffers
uint vertex_index = 0;
uint vertex_prop_index = 0;
for (uint i = 0; i < vertex_buf_len; i++) {
const glm::vec3& vertex = mesh->vertices[vertex_index];
switch (vertex_prop_index) {
case 0: vertex_buf[i] = vertex.x; break;
case 1: vertex_buf[i] = vertex.y; break;
case 2: vertex_buf[i] = vertex.z; break;
}
if (use_normals) {
const glm::vec3& normal = mesh->normals[vertex_index];
switch (vertex_prop_index) {
case 0: normal_buf[i] = normal.x; break;
case 1: normal_buf[i] = normal.y; break;
case 2: normal_buf[i] = normal.z; break;
}
}
if (use_texture) {
const glm::vec3& uv = mesh->texture_coords[vertex_index];
switch (vertex_prop_index) {
case 0: uv_buf[i] = uv.x; break;
case 1: uv_buf[i] = uv.y; break;
case 2: uv_buf[i] = uv.z; break;
}
}
vertex_prop_index++;
if (vertex_prop_index == 3) {
vertex_prop_index = 0;
vertex_index++;
}
}
// dump indices
for (uint i = 0; i < mesh->num_indices; i++)
index_buf[i] = mesh->indices[i];
rgInitGLFloatBuffer(&ro->vertex_buffer, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER);
if (use_normals)
rgInitGLFloatBuffer(&ro->normal_buffer, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
if (use_texture)
rgInitGLFloatBuffer(&ro->uv_buffer, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
rgInitGLIndexBuffer(&ro->index_buffer, GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
return true;
}

175
src/mesh.cpp

@ -0,0 +1,175 @@
#include <cassert>
#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "mesh.h"
// forward declarations
void assimpLogCB(const char* message, char* user);
meMeshInfo* allocateMeshInfo(uint num_vertices, uint num_indices, bool has_normals, bool has_texture);
void freeMesh(meMeshInfo* mesh);
inline glm::vec3 copyVector(aiVector3D v_in, glm::vec3& v_out);
meMeshInfo* copyMeshInfo(const char* data_dir, const aiScene* scene, aiMesh* mesh);
// interface
bool
meInitAssimp()
{
LOG(Info) << "Initializing Assimp\n";
aiLogStream ls;
ls.callback = assimpLogCB;
aiAttachLogStream(&ls);
return true;
}
bool
meLoadFromFile(meMeshGroup& mesh_group, const char* data_dir, const char* filename)
{
LOG(Info) << "Loading file: " << filename << "\n";
const aiScene* scene = aiImportFile(filename, aiProcessPreset_TargetRealtime_MaxQuality);
if (!scene) {
LOG(Error) << "Error loading file: " << filename << "\n";
return false;
}
if (scene->mNumMeshes < 1) {
LOG(Error) << "Scene contains no meshes\n";
return false;
}
mesh_group.num_meshes = scene->mNumMeshes;
mesh_group.meshes = UTIL_ALLOC(mesh_group.num_meshes, meMeshInfo*);
mesh_group.use_normals = scene->mMeshes[0]->HasNormals();
for (uint i = 0; i < scene->mNumMeshes; i++) {
mesh_group.meshes[i] = copyMeshInfo(data_dir, scene, scene->mMeshes[i]);
}
// free memeory from assimp
aiReleaseImport(scene);
return true;
}
void
meFreeMeshGroup(meMeshGroup& mesh_group)
{
for (uint i = 0; i < mesh_group.num_meshes; i++)
freeMesh(mesh_group.meshes[i]);
utilSafeFree(mesh_group.meshes);
}
void
meShutdownAssimp()
{
aiDetachAllLogStreams();
}
// internal
void
assimpLogCB(const char* message, char* user)
{
// NOTE: filter 'info' messages from assimp
if (!utilMatchPrefix(message, "Info,", 5))
LOG(Info) << message << "\n";
}
meMeshInfo*
allocateMeshInfo(uint num_vertices, uint num_indices, bool has_normals, bool has_texture)
{
meMeshInfo* mi = UTIL_ALLOC(1, meMeshInfo);
mi->model_transform = glm::mat4(1);
// allocate buffers for vertex and index data from mesh
mi->num_vertices = num_vertices;
mi->vertices = UTIL_ALLOC(mi->num_vertices, glm::vec3);
mi->num_indices = num_indices;
mi->indices = UTIL_ALLOC(num_indices, uint);
if (has_normals) mi->normals = UTIL_ALLOC(mi->num_vertices, glm::vec3);
if (has_texture) mi->texture_coords = UTIL_ALLOC(mi->num_vertices, glm::vec3);
return mi;
}
void
freeMesh(meMeshInfo* mesh)
{
utilFreeImage(mesh->diffuse_texture);
utilSafeFree(mesh->vertices);
utilSafeFree(mesh->normals);
utilSafeFree(mesh->texture_coords);
utilSafeFree(mesh->indices);
utilSafeFree(mesh);
}
inline glm::vec3
copyVector(aiVector3D v_in, glm::vec3& v_out)
{
v_out.x = v_in.x;
v_out.y = v_in.y;
v_out.z = v_in.z;
return v_out;
}
meMeshInfo*
copyMeshInfo(const char* data_dir, const aiScene* scene, aiMesh* mesh)
{
bool has_tex = mesh->HasTextureCoords(0);
bool has_normals = mesh->HasNormals();
meMeshInfo* mi = allocateMeshInfo(mesh->mNumVertices, mesh->mNumFaces * 3, has_normals, has_tex);
// copy vertices, normals, and texture coords
for (uint i = 0; i < mi->num_vertices; i++) {
copyVector(mesh->mVertices[i], mi->vertices[i]);
if (has_normals)
copyVector(mesh->mNormals[i], mi->normals[i]);
if (has_tex) {
mi->texture_coords[i].x = mesh->mTextureCoords[0][i].x;
mi->texture_coords[i].y = mesh->mTextureCoords[0][i].y;
}
}
// copy indices
for (uint i = 0; i < mesh->mNumFaces; i++)
for (uint j = 0; j < 3; j++)
mi->indices[i * 3 + j] = mesh->mFaces[i].mIndices[j];
// material
aiMaterial* mat = scene->mMaterials[mesh->mMaterialIndex];
aiColor3D color(0.f, 0.f, 0.f);
if (AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE, color)) {
mi->diffuse_color.r = color.r;
mi->diffuse_color.g = color.g;
mi->diffuse_color.b = color.b;
}
if (has_tex && mat->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
aiString file_name;
if (AI_SUCCESS != mat->GetTexture(aiTextureType_DIFFUSE, 0, &file_name, NULL, NULL, NULL, NULL, NULL)) {
LOG(Error) << "No diffuse texture from assimp\n";
} else {
LOG(Info) << "Loading texture file: " << file_name.C_Str() << "\n";
mi->diffuse_texture = utilLoadImage(data_dir, file_name.C_Str());
mi->use_texture = true;
}
}
return mi;
}

277
src/render_group.cpp

@ -0,0 +1,277 @@
#include <sstream> // TODO: remove this and make something in util.h
#include <cassert>
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "render_group.h"
#include "util.h"
#define INFO_LOG_MAX_LENGTH 312
// forward declarations
void freeRenderObject(render_object* ro);
void rgInitGLIndexBuffer(gl_index_buffer* index_buffer, GLenum usage, GLenum target);
// interface
render_object *
rgAllocateRenderObject(uint buffer_len, uint index_len)
{
render_object* ro = UTIL_ALLOC(1, render_object);
if (ro == nullptr)
return nullptr;
ro->vertex_buffer.buffer = UTIL_ALLOC(buffer_len, GLfloat);
ro->vertex_buffer.count = ro->vertex_buffer.max_count = buffer_len;
ro->normal_buffer.buffer = UTIL_ALLOC(buffer_len, GLfloat);
ro->normal_buffer.count = ro->normal_buffer.max_count = buffer_len;
ro->uv_buffer.buffer = UTIL_ALLOC(buffer_len, GLfloat);
ro->uv_buffer.count = ro->uv_buffer.max_count = buffer_len;
if (index_len > 0) {
ro->index_buffer.buffer = UTIL_ALLOC(index_len, uint);
ro->index_buffer.count = index_len;
}
if (ro->vertex_buffer.buffer == nullptr ||
ro->normal_buffer.buffer == nullptr ||
((index_len > 0) && (ro->vertex_buffer.buffer == nullptr)))
{
freeRenderObject(ro);
return nullptr;
}
return ro;
}
render_group*
rgInitSingle(rg_shader_program& sp, uint vertex_buffer_len,
bool use_normals, uint index_buffer_len,
GLenum draw_mode)
{
render_group* rg = UTIL_ALLOC(1, render_group);
rg->shader = sp;
rg->use_normals = use_normals;
rg->draw_indexed = (index_buffer_len > 0);
rg->draw_mode = draw_mode;
rg->render_objects = UTIL_ALLOC(1, render_object*);
rg->render_objects[0] = rgAllocateRenderObject(vertex_buffer_len, index_buffer_len);
rg->num_objects = 1;
if (rg->render_objects[0] == nullptr) {
LOG(Error) << "Allocation Error\n";
rgFree(rg);
}
return rg;
}
bool
rgInitShaderProgram(rg_shader_program& sp, const char * vertex_code, const char * frag_code)
{
glGenVertexArrays(1, &sp.vertex_array_id);
glBindVertexArray(sp.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);
sp.program_id = glCreateProgram();
glAttachShader(sp.program_id, vertex_shader_id);
glAttachShader(sp.program_id, fragment_shader_id);
glLinkProgram(sp.program_id);
sp.model_matrix_id = glGetUniformLocation(sp.program_id, "model");
sp.view_matrix_id = glGetUniformLocation(sp.program_id, "view");
sp.projection_matrix_id = glGetUniformLocation(sp.program_id, "projection");
sp.normal_matrix_id = glGetUniformLocation(sp.program_id, "normal_matrix");
sp.num_lights_id = glGetUniformLocation(sp.program_id, "num_lights");
sp.sampler_id = glGetUniformLocation(sp.program_id, "sampler");
glDetachShader(sp.program_id, vertex_shader_id);
glDetachShader(sp.program_id, fragment_shader_id);
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
// TODO: quick hack to allow 'dynamic' stack allocation for msvc
// also remove INFO_LOG_MAX_LENGTH define
GLint isLinked = 0;
glGetProgramiv(sp.program_id, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE) {
GLint maxLength = INFO_LOG_MAX_LENGTH;
glGetProgramiv(sp.program_id, GL_INFO_LOG_LENGTH, &maxLength);
#ifdef _WIN32
GLchar infoLog[312] = { 0 };
#else
GLchar infoLog[maxLength] = { 0 };
#endif
glGetProgramInfoLog(sp.program_id, maxLength, &maxLength, &infoLog[0]);
LOG(Error) << infoLog << "\n";
glDeleteProgram(sp.program_id);
return false;
}
return true;
}
bool
rgInitGLTexture(GLuint& tex_id, util_image image)
{
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
GLenum pixel_format = (image.num_channels == 3) ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, pixel_format, image.w, image.h, 0,
pixel_format, 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);
return (glGetError() == GL_NO_ERROR);
}
void
rgInitGLFloatBuffer(gl_buffer* buffer, GLenum usage, GLenum target)
{
glGenBuffers(1, &buffer->buffer_id);
glBindBuffer(target, buffer->buffer_id);
glBufferData(target, buffer->max_count * sizeof(GLfloat), buffer->buffer, usage);
}
void
rgInitGLIndexBuffer(gl_index_buffer* index_buffer, GLenum usage, GLenum target)
{
glGenBuffers(1, &index_buffer->buffer_id);
glBindBuffer(target, index_buffer->buffer_id);
glBufferData(target, index_buffer->count * sizeof(uint), index_buffer->buffer, usage);
}
void
rgUpdateGLBuffer(gl_buffer& buffer)
{
glBindBuffer(GL_ARRAY_BUFFER, buffer.buffer_id);
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer.count * sizeof(GLfloat), buffer.buffer);
}
void
rgDraw(render_group* rg, glm::mat4 model_matrix,
glm::mat4 view_matrix, glm::mat4 projection_matrix,
rg_point_light* lights, uint num_lights, bool update_vertex_data)
{
for (uint i = 0; i < rg->num_objects; i++) {
render_object* ro = rg->render_objects[i];
glUseProgram(rg->shader.program_id);
glUniformMatrix4fv(rg->shader.model_matrix_id, 1, GL_FALSE, &model_matrix[0][0]);
glUniformMatrix4fv(rg->shader.view_matrix_id, 1, GL_FALSE, &view_matrix[0][0]);
glUniformMatrix4fv(rg->shader.projection_matrix_id, 1, GL_FALSE, &projection_matrix[0][0]);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, ro->vertex_buffer.buffer_id);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
if (update_vertex_data)
{
glBufferSubData(GL_ARRAY_BUFFER, 0, ro->vertex_buffer.count * sizeof(GLfloat),
ro->vertex_buffer.buffer);
}
// 2rd attribute buffer: normals
if (rg->use_normals) {
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, ro->normal_buffer.buffer_id);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glm::mat3 normal_matrix = glm::transpose(glm::inverse(glm::mat3(model_matrix)));
glUniformMatrix3fv(rg->shader.normal_matrix_id, 1, GL_FALSE, &normal_matrix[0][0]);
// TODO: use Uniform Buffer Objects to update light data
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt
// https://www.khronos.org/opengl/wiki/Uniform_Buffer_Objects
glUniform1ui(rg->shader.num_lights_id, num_lights);
for (uint i = 0; i < num_lights; i++) {
// TODO: don't do this every frame * every render_group
std::stringstream ss;
ss << "lights[" << i << "].position";
int light_pos_loc = glGetUniformLocation(rg->shader.program_id, ss.str().c_str());
glUniform3fv(light_pos_loc, 1, &lights[i].position[0]);
}
}
// 3rd attribute buffer: UV coordinates
if (ro->use_texture) {
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, ro->uv_buffer.buffer_id);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glBindTexture(GL_TEXTURE_2D, ro->tex_id);
glUniform1i(rg->shader.sampler_id, 0);
}
// draw
if (rg->draw_indexed) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ro->index_buffer.buffer_id);
glDrawElements(rg->draw_mode, ro->index_buffer.count, GL_UNSIGNED_INT, 0);
} else {
glDrawArrays(rg->draw_mode, 0, ro->vertex_buffer.count / 3);
}
// cleanup
glDisableVertexAttribArray(0);
if (rg->use_normals)
glDisableVertexAttribArray(1);
if (ro->use_texture)
glDisableVertexAttribArray(2);
glUseProgram(0);
}
}
void
rgFree(render_group* rg)
{
if (rg == nullptr) {
LOG(Error) << "tried to free nullptr\n";
return;
}
for (uint i = 0; i < rg->num_objects; i++)
freeRenderObject(rg->render_objects[i]);
utilSafeFree(rg->render_objects);
utilSafeFree(rg);
}
// internal
void
freeRenderObject(render_object* ro)
{
if (ro == nullptr) {
LOG(Error) << "Tried to free nullptr\n";
return;
}
glDeleteTextures(1, &ro->tex_id);
utilSafeFree(ro->vertex_buffer.buffer);
utilSafeFree(ro->normal_buffer.buffer);
utilSafeFree(ro->uv_buffer.buffer);
utilSafeFree(ro->index_buffer.buffer);
utilSafeFree(ro);
}

232
src/renderer.cpp

@ -0,0 +1,232 @@
#include <vector>
#include <cmath> // trig functions
#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "renderer.h"
#define DEFAULT_VERTEX_SHADER_FILE "../data/default.vs"
#define DEFAULT_FRAGMENT_SHADER_FILE "../data/default.fs"
#define MAX_LIGHTS 10 // NOTE: needs to match the fragment shader source
#define DEFAULT_PALETTE_DIR "../data"
#define DEFAULT_PALETTE_FILE "palette.png"
#define CLEAR_COL_R 55.f / 255.f
#define CLEAR_COL_G 55.f / 255.f
#define CLEAR_COL_B 55.f / 255.f
#define CLEAR_COL_A 1.f
#define USE_SECOND_MONITOR 0
// TODO: add this to json scene properties and render_state
util_RGBA g_clear_col = {1.f,1.f,1.f,1.f};
// forward declarations
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam);
bool initDebugRenderGroup(render_state* rs);
// interface
bool
renInit(render_state* rs)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GetCurrentDisplayMode(0, &rs->handles.currentDisplayMode);
uint display_id = 0;
if (USE_SECOND_MONITOR && SDL_GetNumVideoDisplays() > 1)
display_id = 1;
rs->handles.window = SDL_CreateWindow(
"hexgame",
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id),
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id),
rs->viewport_dims.x,
rs->viewport_dims.y,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE
);
if (!rs->handles.window) {
LOG(Error) << "Error creating window: " << SDL_GetError() << "\n";
return false;
}
rs->handles.glContext = SDL_GL_CreateContext(rs->handles.window);
if (!rs->handles.glContext) {
LOG(Error) << "Error creating glContext: " << SDL_GetError() << "\n";
return false;
}
if (SDL_GL_SetSwapInterval(1) != 0) { // vsync
LOG(Error) << "SDL Errors: " << SDL_GetError() << "\n";
return false;
}
if (glewInit()) {
LOG(Error) << "failed to initialize OpenGL\n";
return false;
}
LOG(Info) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n";
LOG(Info)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n";
LOG(Info) << "opengl version: " << glGetString(GL_VERSION) << "\n";
glEnable(GL_DEPTH_TEST);
glEnable(GL_LINE_SMOOTH);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#if 0
// TODO: blending messes up rendering with mesa on intel graphics 4000
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
#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);
const char* vs_code = utilDumpTextFile(DEFAULT_VERTEX_SHADER_FILE);
const char* fs_code = utilDumpTextFile(DEFAULT_FRAGMENT_SHADER_FILE);
bool shader_error = !rgInitShaderProgram(rs->default_shader, vs_code, fs_code);
utilSafeFree(vs_code);
utilSafeFree(fs_code);
if (shader_error) {
LOG(Error) << "Error initializing shader program\n";
return false;
}
rs->max_lights = MAX_LIGHTS;
rs->lights = UTIL_ALLOC(rs->max_lights, rg_point_light);
rs->palette_image = utilLoadImage(DEFAULT_PALETTE_DIR, DEFAULT_PALETTE_FILE);
if (!rgInitGLTexture(rs->palette_id, rs->palette_image)) {
LOG(Error) << "Error creating texture\n";
return false;
}
if (!initDebugRenderGroup(rs)) {
LOG(Error) << "Error initializing debug render group\n";
return false;
}
g_clear_col.R = CLEAR_COL_R;
g_clear_col.B = CLEAR_COL_B;
g_clear_col.G = CLEAR_COL_G;
g_clear_col.A = CLEAR_COL_A;
return true;
}
void
renFreeBuffers(render_state* rs)
{
utilSafeFree(rs->lights);
utilFreeImage(rs->palette_image);
glDeleteTextures(1, &rs->palette_id);
rgFree(rs->filled_hex_render_group);
rgFree(rs->hex_line_render_group);
rgFree(rs->debug_render_group);
}
void
renRenderFrame(render_state* rs, Entity* entities, uint32 entity_count)
{
glClearColor(g_clear_col.R, g_clear_col.G, g_clear_col.B, g_clear_col.A);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//glm::mat4 m_model = rs->cam.model;
glm::mat4 m_view = rs->cam.view;
glm::mat4 m_projection = rs->cam.projection;
#if 0
// filled hexes
// get new colors every frame
hgUpdateUVBuffer(hg, rs->filled_hex_render_group);
rgDraw(rs->filled_hex_render_group, m_model, m_view, m_projection,
rs->lights, rs->num_lights);
// hex lines
rgDraw(rs->hex_line_render_group, m_model, m_view, m_projection,
rs->lights, rs->num_lights);
#endif
// entities
for (uint i = 0; i < entity_count; i++) {
rgDraw(entities[i].ren_group, entities[i].world_transform , m_view, m_projection,
rs->lights, rs->num_lights);
}
}
#if 0
void
renRenderDebug(render_state* rs, std::vector<Point> &vertices)
{
GLfloat* buf = rs->debug_render_group->render_objects[0]->vertex_buffer.buffer;
buf[0] = vertices[0].x; buf[1] = vertices[0].y; buf[2] = 0;
buf[3] = vertices[1].x; buf[4] = vertices[1].y; buf[2] = 0;
buf[6] = vertices[2].x; buf[7] = vertices[2].y; buf[8] = 0;
buf[9] = vertices[3].x; buf[10] = vertices[3].y; buf[11] = 0;
rgDraw(rs->debug_render_group, rs->cam.model, rs->cam.view,
rs->cam.projection, rs->lights, rs->num_lights, true);
}
#endif
// internal
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
initDebugRenderGroup(render_state* rs)
{
uint debug_buf_len = 12; // 4 vertices, 3 floats per vertex
rs->debug_render_group = rgInitSingle(rs->default_shader, debug_buf_len, true, 0, GL_LINE_LOOP);
if (rs->debug_render_group == nullptr)
return false;
gl_buffer& debug_vertex_buf = rs->debug_render_group->render_objects[0]->vertex_buffer;
gl_buffer& debug_normal_buf = rs->debug_render_group->render_objects[0]->normal_buffer;
for (uint i = 0; i < debug_buf_len; i += 3) {
debug_normal_buf.buffer[i] = 0.f;
debug_normal_buf.buffer[i + 1] = 0.f;
debug_normal_buf.buffer[i + 2] = 1.f;
}
rgInitGLFloatBuffer(&debug_vertex_buf, GL_DYNAMIC_DRAW, GL_ARRAY_BUFFER);
rgInitGLFloatBuffer(&debug_normal_buf, GL_STATIC_DRAW, GL_ARRAY_BUFFER);
return true;
}

234
src/util.cpp

@ -0,0 +1,234 @@
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "dumbLog.h"
#include "util.h"
#define PALETTE_ROWS 16
#define PALETTE_COLUMNS 16
#define PALETTE_BORDER 1
const uint MAX_FILESIZE = 2 * 1024 * 1024; // 2MB
const uint MAX_STRING_LENGTH = 1024;
//-----------------
// C Strings
const char*
utilBaseName(const char* path_str)
{
assert(std::strlen(path_str) < MAX_STRING_LENGTH);
const char* output = std::strrchr(path_str, '/');
if (output)
return output;
else
return path_str;
}
bool
utilCopyCStr(char* dest, const char* src, uint max_len)
{
assert(std::strlen(src) < MAX_STRING_LENGTH && max_len <= MAX_STRING_LENGTH);
if (std::strlen(src) + 1 > max_len)
return false;
std::memcpy(dest, src, std::strlen(src) + 1);
return true;
}
bool
utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len)
{
size_t l1 = std::strlen(base_dir);
size_t l2 = std::strlen(file_name);
size_t padded = l1 + l2 + 2; // NOTE: null term + '/'
if (padded <= MAX_STRING_LENGTH && padded <= max_len) {
int c = std::snprintf(out, padded, "%s/%s", base_dir, file_name);
if (c > 0)
return true;
else
return false;
}
return false;
}
bool
utilMatchPrefix(const char* lhs, const char* rhs, int sz)
{
int rc = strncmp(lhs, rhs, sz);
return (rc >= 0);
}
//-----------------
// Memory allocation
void *
utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line)
{
assert(item_count > 0); // that was a fun bug
void* mem = std::calloc(item_count, type_size);
if (mem == nullptr) {
LOG(Error) << "Memory allocation failed, called from "
<< file_name << ":" << line;
}
assert(mem != nullptr); // might as well stop execution here
return mem;
}
void utilSafeFree(const void* mem)
{
if (mem != nullptr) {
std::free((void *) mem);
mem = nullptr;
}
}
//-----------------
// File I/O
// TODO: don't use ftell() to get filesize
// https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek%28%29+and+ftell%28%29+to+compute+the+size+of+a+regular+file
char *
utilDumpTextFile(const char* filename)
{
LOG(Info) << "loading filename, " << filename << "\n";
std::FILE* fp = std::fopen(filename, "rt");
assert(fp);
std::fseek(fp, 0, SEEK_END);
uint length = std::ftell(fp);
std::fseek(fp, 0, SEEK_SET);
assert(length < MAX_FILESIZE);
// TODO: check error codes for fseek and ftell
char* buf = UTIL_ALLOC(length, char);
assert(buf);
std::fread(buf, sizeof(char), length, fp);
// TODO: check fp w/ ferror() here
return buf;
}
// TODO: might want to do the base_dir concat in this function to prevent clobbering
// user files on accident
bool
utilWriteTextFile(const char* filename, const char* text)
{
size_t text_len = std::strlen(text);
if (text_len >= MAX_FILESIZE) {
LOG(Error) << "that string is too big\n";
return false;
}
std::FILE* fp = fopen(filename, "wt");
if (fp) {
size_t written = fwrite(text, sizeof(char), text_len, fp);
fclose(fp);
if (written == text_len) {
LOG(Debug) << "successfuly wrote " << written << " bytes\n";
return true;
} else {
LOG(Error) << "error writing to file: " << filename << "\n";
return false;
}
}
LOG(Debug) << text << "\n";
return false;
}
//-----------------
// utilImage
util_image
utilLoadImage(const char* base_dir, const char* filename)
{
const uint max_path = 256; // NOTE: util_image.file_path is char[256]
const char* base_name = utilBaseName(filename);
// NOTE: +1 for '/' char, +1 for null
uint l = std::strlen(base_dir) + std::strlen(base_name) + 2;
assert(l < max_path);
char full_path[l];
std::memcpy(full_path, base_dir, std::strlen(base_dir) + 1);
std::strcat(std::strcat(full_path, "/"), base_name);
LOG(Info) << "Loading Image: " << full_path << "\n";
util_image image;
std::memcpy(image.file_path, full_path, std::strlen(full_path) + 1);
stbi_set_flip_vertically_on_load(1);
image.pixels = stbi_load(full_path, &image.w, &image.h, &image.num_channels, 0);
image.bits_per_channel = 8;
image.data_len = image.w * image.h * image.num_channels; // NOTE: assumes 8 bits per channel
if (image.pixels == 0) {
LOG(Error) << stbi_failure_reason() << "\n";
utilFreeImage(image);
return image;
}
LOG(Info) << "Image properties, data_len: " << image.data_len
<< ", width: " << image.w << ", height: " << image.h << "\n";
return image;
}
void
utilFreeImage(util_image image)
{
image.w = image.h = image.data_len = 0;
image.bits_per_channel = 8;
image.num_channels = 4;
std::memset(image.file_path, 0, std::strlen(image.file_path) + 1);
utilSafeFree(image.pixels);
}
// need to init default values from scene file, or finally make default config?
v2f
utilGetPaletteCoords(util_image& palette_image, uint color_index)
{
v2i tex_coords;
v2f normalized;
if (PALETTE_ROWS * PALETTE_COLUMNS < color_index) {
LOG(Error) << "index out of range\n";
return normalized;
}
if (palette_image.pixels == nullptr) {
LOG(Error) << "palatte image not loaded\n";
return normalized;
}
// NOTE: we're aiming to sample near the middle of the palette 'square' so GL_NEAREST
// doesn't try to sample the border color
v2i palette_square;
palette_square.x = palette_image.w / PALETTE_ROWS;
palette_square.y = palette_image.h / PALETTE_COLUMNS;
uint v_row = (uint) std::floor(color_index / PALETTE_ROWS);
uint u_column = color_index % PALETTE_ROWS;
tex_coords.x = u_column * palette_square.x + palette_square.x / 2;
tex_coords.y = v_row * palette_square.y + palette_square.y / 2;
// NOTE: flip y coord
tex_coords.y = palette_image.h - tex_coords.y;
normalized.x = (float) tex_coords.x / palette_image.w;
normalized.y = (float) tex_coords.y / palette_image.h;
return normalized;
}
Loading…
Cancel
Save