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