Browse Source
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.cpptesting
commit
44c07b7ab8
22 changed files with 1937 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||||||
|
bin/ |
||||||
|
build/ |
||||||
|
doc/ |
||||||
|
msvc/.vs/ |
||||||
|
msvc/x64/ |
||||||
|
tags |
||||||
|
*.swp |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
[submodule "ext/stb_libs"] |
||||||
|
path = ext/stb_libs |
||||||
|
url = https://github.com/nothings/stb.git |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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
|
||||||
@ -0,0 +1,9 @@ |
|||||||
|
|
||||||
|
#include "renderer.h" |
||||||
|
|
||||||
|
|
||||||
|
int |
||||||
|
main() |
||||||
|
{ |
||||||
|
return 0; |
||||||
|
} |
||||||
@ -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); |
||||||
@ -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__ << ") " |
||||||
|
|
||||||
@ -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); |
||||||
@ -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(); |
||||||
|
|
||||||
@ -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
|
||||||
|
} |
||||||
|
|
||||||
@ -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); |
||||||
|
|
||||||
@ -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);
|
||||||
|
|
||||||
@ -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); |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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…
Reference in new issue