hs

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | LICENSE

commit d34db889e0e31d8864aa8ceb9eee59aedafb213e
parent 5be99b82356fc73e2c084e08b7a08418999887c3
Author: Samdal <samdal@protonmail.com>
Date:   Mon, 16 Aug 2021 21:19:46 +0200

BSP, and a bunch of shit for anders tale

Diffstat:
Mhs_data.h | 3++-
Mhs_graphics.h | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mhs_math.h | 21+++++++++++++++------
Atodo.org | 17+++++++++++++++++
4 files changed, 279 insertions(+), 57 deletions(-)

diff --git a/hs_data.h b/hs_data.h @@ -101,9 +101,10 @@ static const char* texture_transform_vert = "layout (location = 1) in vec2 aTexCoord;\n" "out vec2 TexCoord;\n" "uniform mat4 u_transform;\n" + "uniform mat4 u_perspective;\n" "void main()\n" "{\n" - "gl_Position = u_transform * vec4(aPos, 0.0f, 1.0);\n" + "gl_Position = u_transform * u_perspective * vec4(aPos, 0.0f, 1.0);\n" "TexCoord = vec2(aTexCoord.x, aTexCoord.y);\n" "}"; diff --git a/hs_graphics.h b/hs_graphics.h @@ -7,18 +7,21 @@ #include <assert.h> #include <stdio.h> #include <stdbool.h> + #ifndef NO_STBI #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #endif -#include "hs_math.h" -#include "hs_data.h" #ifdef WIN32 #define OEMRESOURCE #include <windows.h> #endif +#include "hs_math.h" +#include "hs_data.h" + + #define hs_loop(game_data, update_func) while(hs_window_up(game_data)) {update_func; hs_end_frame(game_data);} typedef struct { @@ -50,14 +53,18 @@ typedef struct { } hs_camera; typedef struct { + vec2 curr, goal; +} hs_camera2_smooth; + +typedef struct { float pos[2], tex[2]; } hs_tex_corner; typedef hs_tex_corner hs_tex_square[6]; typedef struct { - const uint32_t width, height, sub_tex_width_count; - const float half_tile_width, sub_tex_width; + uint32_t width, height, sub_tex_width, sub_tex_height; + const float half_tile_width, half_tile_height; hs_shader_program_tex sp; hs_tex_square* vertices; } hs_tilemap; @@ -67,12 +74,44 @@ typedef struct { GLFWwindow* window; } hs_game_data; +typedef struct { + vec2 tr, bl; +} aabb2; + +typedef struct { + vec2 pos, half_size, frame_velocity; + uint32_t flags; +} entity2_hot; + +typedef struct { + hs_shader_program_tex* sp; + vec2 external_velocity; + float base_mov_speed, mov_speed_mul, fire_rate_mul, invisframe_mul; + uint16_t max_hp, hp, armour; + void* current_room; //TODO: create room struct and stuff +} entity2_cold; + +typedef struct { + entity2_hot* hot; + entity2_cold cold; +} entity2; + +enum entity2_flags { + AABB_STATIC = 1 << 0, + AABB_RIGID = 1 << 1, + AABB_CHARACTHER = 1 << 2, + player = 1 << 3, + preblink = 1 << 4, + invisiframe = 1 << 5, + confusion = 1 << 6, + stunned = 1 << 7, +}; extern void hs_close(const hs_game_data gd); extern int32_t hs_window_up(const hs_game_data gd); extern int32_t hs_get_key(const hs_game_data gd, const int key); extern void hs_clear(const float r, const float g, const float b, const float a, const GLbitfield mask); -extern void hs_enable_vattrib(const uint32_t index, const uint32_t size, +extern void hs_vattrib_enable(const uint32_t index, const uint32_t size, const GLenum type, const uint32_t stride, const size_t pointer); extern void hs_vattrib_enable_float(const uint32_t index, const uint32_t size, const uint32_t stride, const size_t pointer); @@ -110,6 +149,21 @@ extern uint32_t hs_tex2d_create_size_info(const char *filename, const GLenum for int* width, int* height); #endif +/* aabb2 */ +extern vec2 hs_aabb2_center(const aabb2 rect); +extern vec2 hs_aabb2_size(const aabb2 rect); +extern vec2 hs_aabb2_half_size(const aabb2 rect); + +/* Anders Tale Dungeon generation v3 (BSP) */ +// new_rect_index is the index of the last element that has been filled in +// the array is expected to have one more space available +extern void hs_bsp_rect_split_in_place_append(aabb2* rects, const uint32_t new_rect_index, const vec2 min_rect_size); + +/* Physics */ +extern uint_fast16_t hs_aabb_check_collide(const aabb2 r1, const aabb2 r2); +extern void hs_aabb_check_and_do_collide(aabb2* r1, aabb2* r2); +extern void hs_aabb_check_and_do_collide_static(aabb2* r1, aabb2* r2); + /* Camera stuff */ extern hs_camera hs_init_fps_camera(); extern void hs_camera_move_front(hs_camera* camera, const float scale); @@ -133,9 +187,11 @@ extern void hs_tilemap_update_vbo(const hs_tilemap tilemap); extern void hs_tilemap_draw(const hs_tilemap tilemap); extern void hs_tilemap_free(hs_tilemap* tilemap); extern void hs_tilemap_transform(const hs_tilemap tilemap, const mat4 trans); +extern void hs_tilemap_perspective(const hs_tilemap tilemap, const mat4 perspective); // vobj may be NULL extern void hs_sprite_transform(const hs_shader_program_tex sprite, const mat4 trans); +extern void hs_sprite_perspective(const hs_shader_program_tex sprite, const mat4 perspective); extern hs_shader_program_tex hs_sprite_create(const char* texture, const GLenum colour_channel, hs_vobj* vobj, const hs_game_data gd); extern void hs_sprite_draw(const hs_shader_program_tex sp); @@ -170,9 +226,11 @@ hs_uniform_create(const uint32_t program, const char *name) return glGetUniformLocation(program, name); } + +// create all the standard 3d transformation matrices in one function for convinence inline hs_coord hs_uniform_coord_create(const uint32_t program, - const char* model, const char* view, const char* proj) + const char* model, const char* view, const char* proj) { return (hs_coord) { .model = glGetUniformLocation(program, model), @@ -188,7 +246,10 @@ hs_get_key(const hs_game_data gd, const int key) } inline int -hs_window_up(const hs_game_data gd) {return !glfwWindowShouldClose(gd.window);} +hs_window_up(const hs_game_data gd) +{ + return !glfwWindowShouldClose(gd.window); +} inline void hs_close(const hs_game_data gd) @@ -197,7 +258,7 @@ hs_close(const hs_game_data gd) } inline void -hs_enable_vattrib(const uint32_t index, const uint32_t size, +hs_vattrib_enable(const uint32_t index, const uint32_t size, const GLenum type, const uint32_t stride, const size_t pointer) { glVertexAttribPointer(index, size, type, GL_FALSE, stride, (void*)pointer); @@ -208,7 +269,7 @@ inline void hs_vattrib_enable_float(const uint32_t index, const uint32_t size, const uint32_t stride, const size_t pointer) { - hs_enable_vattrib(index, size, GL_FLOAT, stride * sizeof(float), pointer * sizeof(float)); + hs_vattrib_enable(index, size, GL_FLOAT, stride * sizeof(float), pointer * sizeof(float)); } inline float @@ -233,7 +294,10 @@ char* hs_file_read(const char *file_path) { FILE *fp = fopen(file_path, "r"); - assert(fp); + if (!fp) { + fprintf(stderr, "---error reading file \"%s\"--\n", file_path); + assert(fp); + } fseek(fp, 0L, SEEK_END); uint32_t readsize = ftell(fp); @@ -339,6 +403,7 @@ hs_fbo_resize_color_create(const uint32_t width, const uint32_t height, uint32_t glGenTextures(1, tex); glBindTexture(GL_TEXTURE_2D, *tex); + // create attached texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -464,7 +529,10 @@ hs_tex2d_create(const char *filename, const GLenum format, int width, height, nr_channels; unsigned char* texture_data = stbi_load(filename, &width, &height, &nr_channels, 0); - assert(texture_data); + if (!texture_data) { + fprintf(stderr, "---error loading texture \"%s\"--\n", filename); + assert(texture_data); + } glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, texture_data); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(texture_data); @@ -487,7 +555,10 @@ hs_create_tex2d_size_info(const char *filename, const GLenum format, int nr_channels; unsigned char* texture_data = stbi_load(filename, width, height, &nr_channels, 0); - assert(texture_data); + if (!texture_data) { + fprintf(stderr, "---error loading texture \"%s\"--\n", filename); + assert(texture_data); + } glTexImage2D(GL_TEXTURE_2D, 0, format, *width, *height, 0, format, GL_UNSIGNED_BYTE, texture_data); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(texture_data); @@ -506,6 +577,89 @@ hs_init_fps_camera() }; } +inline vec2 +hs_aabb2_center(const aabb2 rect) +{ + return vec2_add(rect.bl, hs_aabb2_half_size(rect)); +} + +inline vec2 +hs_aabb2_size(const aabb2 rect) +{ + return vec2_sub(rect.tr, rect.bl); +} + +inline vec2 +hs_aabb2_half_size(const aabb2 rect) +{ + return vec2_scale(vec2_sub(rect.tr, rect.bl), 0.5f); +} + +inline void +hs_bsp_rect_split_in_place_append(aabb2* rects, const uint32_t new_rect_index, const vec2 min_rect_size) +{ + // this way of checking if the room is too small does not respect very non-square rectangles + // that well, but it is fast and Anders Tale does not really use small non-square rooms + + uint32_t rect_index; + vec2 half_rect_size; + for (uint32_t tries = 0; tries < 7; tries++) { + rect_index = rand() % new_rect_index; + + // half rect size instead of size since we are trying to split it + half_rect_size = hs_aabb2_half_size(rects[rect_index]); + if (min_rect_size.x < half_rect_size.x || min_rect_size.y < half_rect_size.y) + goto random_rect_found; + } + + // randomly trying rooms failed, + // looping through all rooms to try and find a suitable room + for (rect_index = 0; rect_index < new_rect_index; rect_index++) { + + half_rect_size = hs_aabb2_half_size(rects[rect_index]); + if (min_rect_size.x < half_rect_size.x || min_rect_size.y < half_rect_size.y) + goto random_rect_found; + } + + // uhhhhh so this will be our error message + rects[new_rect_index].tr.x = NAN; + return; + + random_rect_found:; + + const uint32_t axis = rand() % 2; + const float max_split = half_rect_size.xy[axis] - min_rect_size.xy[axis]; + + float split = random_float_negative() * max_split; + rects[new_rect_index] = rects[rect_index]; + + const float rect_center = hs_aabb2_center(rects[axis]).x; + + rects[rect_index].bl.xy[axis] = rect_center - split; + rects[new_rect_index].tr.xy[axis] = rect_center + split; +} + +inline uint_fast16_t +hs_aabb_check_collide(const aabb2 r1, const aabb2 r2) +{ + if (r1.tr.y <= r2.bl.y || r2.tr.y <= r1.bl.y || + r1.tr.x <= r2.bl.x || r2.tr.x <= r1.bl.x) + return false; + return true; +} + +inline void +hs_aabb_check_and_do_collide(aabb2* r1, aabb2* r2) +{ + const aabb2 rect1 = *r1; + const aabb2 rect2 = *r2; + if (!hs_aabb_check_collide(rect1, rect1)) return; + + const vec2 r1_center = vec2_scale(vec2_sub(rect1.tr, rect1.bl), 2.0f); + const vec2 r2_center = vec2_scale(vec2_sub(rect2.tr, rect2.bl), 2.0f); + const vec2 diff = {fabs(r1_center.x - r2_center.x), fabs(r1_center.y - r2_center.y)}; +} + inline void hs_camera_move_front(hs_camera* camera, const float scale) { @@ -589,17 +743,18 @@ hs_tilemap_set(hs_tilemap* tilemap, const uint32_t vertex, uint32_t tile) // make tiles start at 0 instead of 1 tile++; - const float sub_tex_width = tilemap->sub_tex_width; - const float xpos = sub_tex_width * (tile % tilemap->sub_tex_width_count); - const float ypos = sub_tex_width * ceilf((float)tile / (float)3); + const float width = 1.0f/tilemap->sub_tex_width; + const float height = 1.0f/tilemap->sub_tex_height; + const float xpos = width * (tile % tilemap->sub_tex_width); + const float ypos = height * ceilf((float)tile / (float)tilemap->sub_tex_width); // bottom left - tilemap->vertices[vertex][0].tex[0] = xpos - sub_tex_width; - tilemap->vertices[vertex][0].tex[1] = ypos - sub_tex_width; + tilemap->vertices[vertex][0].tex[0] = xpos - width; + tilemap->vertices[vertex][0].tex[1] = ypos - height; // bottom right tilemap->vertices[vertex][1].tex[0] = xpos; - tilemap->vertices[vertex][1].tex[1] = ypos - sub_tex_width; + tilemap->vertices[vertex][1].tex[1] = ypos - height; // top right tilemap->vertices[vertex][2].tex[0] = xpos; @@ -610,12 +765,12 @@ hs_tilemap_set(hs_tilemap* tilemap, const uint32_t vertex, uint32_t tile) tilemap->vertices[vertex][3].tex[1] = ypos; // top left - tilemap->vertices[vertex][4].tex[0] = xpos - sub_tex_width; + tilemap->vertices[vertex][4].tex[0] = xpos - width; tilemap->vertices[vertex][4].tex[1] = ypos; // bottom left - tilemap->vertices[vertex][5].tex[0] = xpos - sub_tex_width; - tilemap->vertices[vertex][5].tex[1] = ypos - sub_tex_width; + tilemap->vertices[vertex][5].tex[0] = xpos - width; + tilemap->vertices[vertex][5].tex[1] = ypos - height; } inline void @@ -630,52 +785,55 @@ hs_tilemap_sizeof(const hs_tilemap tilemap) return sizeof(hs_tex_square) * tilemap.width * tilemap.height; } +// expects width, height, sub_tex and half_tile to be filled out void hs_tilemap_init(hs_tilemap* tilemap, const char* texture, const GLenum colour_channel, const uint32_t default_tex, hs_vobj* vobj) { - assert(tilemap->width); - assert(tilemap->height); - tilemap->vertices = malloc(hs_tilemap_sizeof(*tilemap)); assert(tilemap->vertices); - vec2 offset = {-(float)tilemap->width * tilemap->half_tile_width + tilemap->half_tile_width, - (float)tilemap->height * tilemap->half_tile_width - tilemap->half_tile_width}; + const float offset_x_default = -(float)tilemap->width * tilemap->half_tile_width + tilemap->half_tile_width; + vec2 offset = {offset_x_default, (float)tilemap->height * tilemap->half_tile_height - tilemap->half_tile_height}; uint32_t vertex = 0; for (uint32_t y = 0; y < tilemap->height; y++) { - const float down = offset.y + tilemap->half_tile_width; - const float up = offset.y - tilemap->half_tile_width ; + const float bottom = offset.y + tilemap->half_tile_height; + const float top = offset.y - tilemap->half_tile_height; for (uint32_t x = 0; x < tilemap->width; x++) { const float right = offset.x + tilemap->half_tile_width; const float left = offset.x - tilemap->half_tile_width; + // triangle one tilemap->vertices[vertex][0].pos[0] = left; - tilemap->vertices[vertex][0].pos[1] = down; + tilemap->vertices[vertex][0].pos[1] = bottom; tilemap->vertices[vertex][1].pos[0] = right; - tilemap->vertices[vertex][1].pos[1] = down; + tilemap->vertices[vertex][1].pos[1] = bottom; tilemap->vertices[vertex][2].pos[0] = right; - tilemap->vertices[vertex][2].pos[1] = up; + tilemap->vertices[vertex][2].pos[1] = top; + // triangle two tilemap->vertices[vertex][3].pos[0] = right; - tilemap->vertices[vertex][3].pos[1] = up; + tilemap->vertices[vertex][3].pos[1] = top; tilemap->vertices[vertex][4].pos[0] = left; - tilemap->vertices[vertex][4].pos[1] = up; + tilemap->vertices[vertex][4].pos[1] = top; tilemap->vertices[vertex][5].pos[0] = left; - tilemap->vertices[vertex][5].pos[1] = down; + tilemap->vertices[vertex][5].pos[1] = bottom; + // set texture data hs_tilemap_set(tilemap, vertex, default_tex); - offset.x += tilemap->half_tile_width * 2; + + offset.x += tilemap->half_tile_width * 2.0f; vertex++; } - offset.x = -(float)tilemap->width * tilemap->half_tile_width + tilemap->half_tile_width; - offset.y -= tilemap->half_tile_width * 2; + + offset.x = offset_x_default; + offset.y -= tilemap->half_tile_height * 2.0f; } if (!vobj) vobj = hs_vobj_create(castf(tilemap->vertices), hs_tilemap_sizeof(*tilemap), 0, 0, GL_DYNAMIC_DRAW, 1); @@ -683,8 +841,8 @@ hs_tilemap_init(hs_tilemap* tilemap, const char* texture, const GLenum colour_ch hs_shader_program_create(hs_sp_texture_transform_create(), vobj), hs_tex2d_create(texture, colour_channel, GL_REPEAT, GL_NEAREST, 1), "u_tex"); - // anders tale uses 16/9 ratio, change this or remove it for other games - hs_tilemap_transform(*tilemap, (mat4)MAT4_169); + hs_tilemap_transform(*tilemap, (mat4)MAT4_IDENTITY); + hs_tilemap_perspective(*tilemap, (mat4)MAT4_IDENTITY); hs_vattrib_enable_float(0, 2, 4, 0); hs_vattrib_enable_float(1, 2, 4, 2); @@ -719,13 +877,21 @@ hs_tilemap_transform(const hs_tilemap tilemap, const mat4 trans) glUniformMatrix4fv(0, 1, GL_FALSE, castf(trans)); } +inline void +hs_tilemap_perspective(const hs_tilemap tilemap, const mat4 perspective) +{ + glUseProgram(tilemap.sp.p); + glUniformMatrix4fv(1, 1, GL_FALSE, castf(perspective)); +} + inline hs_shader_program_tex hs_sprite_create(const char* texture, const GLenum colour_channel, hs_vobj* vobj, const hs_game_data gd) { int width, height; uint32_t tex = hs_create_tex2d_size_info(texture, colour_channel, GL_REPEAT, GL_NEAREST, &width, &height); - const float vertices[] = HS_DEFAULT_SQUARE_SCALED_TEX_VERT_ONLY((float)width/(float)gd.width, (float)height/(float)gd.height); + const float vertices[] = HS_DEFAULT_SQUARE_SCALED_TEX_VERT_ONLY( + (float)width/(float)gd.width, (float)height/(float)gd.height); if (!vobj) vobj = hs_vobj_create(vertices, sizeof(vertices), 0, 0, GL_STATIC_DRAW, 1); const hs_shader_program_tex sp = hs_shader_program_tex_create( @@ -735,6 +901,7 @@ hs_sprite_create(const char* texture, const GLenum colour_channel, hs_vobj* vobj hs_vattrib_enable_float(1, 2, 4, 2); hs_sprite_transform(sp, (mat4)MAT4_IDENTITY); + hs_sprite_perspective(sp, (mat4)MAT4_IDENTITY); return sp; } @@ -747,6 +914,13 @@ hs_sprite_transform(const hs_shader_program_tex sprite, const mat4 trans) } inline void +hs_sprite_perspective(const hs_shader_program_tex sprite, const mat4 perspective) +{ + glUseProgram(sprite.p); + glUniformMatrix4fv(1, 1, GL_FALSE, castf(perspective)); +} + +inline void hs_sprite_draw(const hs_shader_program_tex sp) { glUseProgram(sp.p); @@ -776,8 +950,7 @@ hs_vbo_create(const float *vbuff, const uint32_t buffsize, const GLenum usage, c inline uint32_t hs_ebo_create(const uint32_t *ibuff, const uint32_t buffsize, const GLenum usage, const uint32_t count) { - if(!buffsize) - return 0; + if(!buffsize) return 0; uint32_t ebo; glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); @@ -820,27 +993,49 @@ hs_disable_vsync() glfwSwapInterval(0); } +enum hs_init_flags { + HS_NO_VSYNC = 1 << 0, + HS_WIREFRAME_MODE = 1 << 1, + HS_BLEND_MODE = 1 << 2, + HS_DEPTH_TESTING = 1 << 3, +}; + inline static hs_game_data -hs_init(const uint32_t width, const uint32_t height, const char *name) +hs_init(hs_game_data* gd, const char *name, void(*framebuffer_size_callback)(GLFWwindow*, int, int), const uint32_t flags) { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - GLFWwindow* window= glfwCreateWindow(width, height, name, NULL, NULL); + if (!gd->width) gd->width = 800; + if (!gd->height) gd->height = 600; + + GLFWwindow* window = glfwCreateWindow(gd->width, gd->height, name, NULL, NULL); assert(window); glfwMakeContextCurrent(window); assert(gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)); + glViewport(0, 0, gd->width, gd->height); - glViewport(0, 0, width, height); + if (flags & HS_BLEND_MODE) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + if (flags & HS_WIREFRAME_MODE) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + if (flags & HS_NO_VSYNC) { + hs_disable_vsync(); + } + if (flags & HS_DEPTH_TESTING) { + glEnable(GL_DEPTH_TEST); + } + if (framebuffer_size_callback) { + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + } - return (hs_game_data) { - .width = width, - .height = height, - .window = window, - }; + gd->window = window; } inline static void diff --git a/hs_math.h b/hs_math.h @@ -3,6 +3,7 @@ #include <math.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> #ifndef NO_STDIO @@ -27,12 +28,6 @@ #define MAT4_IDENTITY MAT4_DIAG(1.0f) #define VEC3_SAME(__f) (vec3){__f, __f, __f} -#define MAT4_169 \ - {{9.0f/16.0f, 0.f, 0.f, 0.f}, \ - {0.f, 1.0f, 0.f, 0.f}, \ - {0.f, 0.f, 1.0f, 0.f}, \ - {0.f, 0.f, 0.f, 1.0f}} - #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) @@ -77,6 +72,20 @@ typedef float mat3[3][3]; typedef float mat4[4][4]; inline static float +random_float() +{ + // double for better rounding + return (float)((double)rand()/RAND_MAX); +} + +inline static float +random_float_negative() +{ + // double for better rounding + return (float)((double)rand()/(RAND_MAX) * 2.0 -1.0); +} + +inline static float vec2_len(vec2 vector) { return sqrtf(sq(vector.x) + sq(vector.y)); diff --git a/todo.org b/todo.org @@ -0,0 +1,17 @@ +#+AUTHOR: Halvard Samdal + +* Collision system for aabb +- items should be pushed by entities +- I was going to say entities should stop each other, but nah entities just can't collide (problem fixed) +- Pillars and objects in the world should stop entities from exiting them +- I also want to add a "reverse" collision so that instead of making 4 walls around a room, + you just say that stuff should be inside the boundry of all the rooms and corridors +* Dungeongeneration v3 +Plan is to use BSP (Binary Space Partitioning), +should way simpler and faster, but we will have to see on game feel. +* Smooth camera +simply add a "current" pos and a "goal" pos and then each frame move towards goal pos +for a non-linear experience make the speed of the camera scale with something +change shaders to have perspective translation as well +* Upscale with combo shader instead of just nearest +https://colececil.io/blog/2017/scaling-pixel-art-without-destroying-it/