commit 933819a219cfdf77ec8d1df709646b5e75080b54 Author: Lephenixnoir Date: Sat Apr 22 22:11:16 2023 +0200 a bit slow, but that's a start diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ba078b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build-*/ +*.g3a +*.mp4 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..14d8805 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.15) +project(ChaosDrop VERSION 1.0 LANGUAGES CXX C ASM) + +list(APPEND CMAKE_MODULE_PATH "$ENV{AZUR_PATH_${AZUR_PLATFORM}}/lib/cmake") +find_package(Azur 0.1 REQUIRED) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + +set(SOURCES + src/main.cc + src/raytracing.cc) +set(ASSETS) + +if(AZUR_PLATFORM STREQUAL gint) + list(APPEND SOURCES + src/backend/gint/shader.cc) +# list(APPEND ASSETS +# ...) +endif() + +if(AZUR_PLATFORM STREQUAL linux) + list(APPEND SOURCES + src/backend/linux/render.cc + src/backend/linux/programs.cc) +endif() + +# No emscripten backend for now +if(AZUR_PLATFORM STREQUAL emscripten) +# configure_file(backend/emscripten/index.html index.html) +# list(APPEND SOURCES +# src/backend/emscripten/render.cc) + message(FATAL_ERROR "No emscripten build yet") +endif() + +add_executable(chaos-drop ${SOURCES} ${ASSETS}) +target_compile_options(chaos-drop PRIVATE -Wall -Wextra -O2) + +if(AZUR_PLATFORM STREQUAL gint) + find_package(Gint 2.10 REQUIRED) + find_package(LibProf 2.4 REQUIRED) + include(Fxconv) + fxconv_declare_assets(${ASSETS} WITH_METADATA) + + target_link_libraries(chaos-drop Azur::Azur LibProf::LibProf Gint::Gint -lm) + target_link_options(chaos-drop PRIVATE + -Wl,-Map=map -Wl,--print-memory-usage) + + include(GenerateG3A) + generate_g3a(TARGET chaos-drop + NAME "Chaos Drop" + OUTPUT "ChaosDrop.g3a" + ICONS assets/icon-uns.png assets/icon-sel.png) +endif() + +if(AZUR_PLATFORM STREQUAL linux) + target_link_libraries(chaos-drop Azur::Azur Azur::ImGui) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..2603dbc --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A reckless attempt at making a game with real-time raytracing, because my brain is filled with ludicrous ideas that keep turning out to be barely possible. diff --git a/assets/icon-sel.png b/assets/icon-sel.png new file mode 100644 index 0000000..0ce3029 Binary files /dev/null and b/assets/icon-sel.png differ diff --git a/assets/icon-uns.png b/assets/icon-uns.png new file mode 100644 index 0000000..039c096 Binary files /dev/null and b/assets/icon-uns.png differ diff --git a/assets/icon.xcf b/assets/icon.xcf new file mode 100644 index 0000000..5b854f0 Binary files /dev/null and b/assets/icon.xcf differ diff --git a/src/backend/gint/shader.cc b/src/backend/gint/shader.cc new file mode 100644 index 0000000..a362b58 --- /dev/null +++ b/src/backend/gint/shader.cc @@ -0,0 +1,56 @@ +#include "../../chaos-drop.h" +#include + +uint8_t CD_RAYTRACE_SHADER_ID = -1; + +struct cd_raytrace_cmd +{ + /* Shader ID for Azur */ + uint8_t shader_id; + uint8_t _[3]; + + /* Camera used for rendering */ + struct camera const *camera; + /* Current y value */ + int y; +}; + +#include +/* TODO: Write raytrace shader in assembler (MUCH NEEDED) */ +void cd_raytrace_shader(void *uniforms0, void *cmd0, void *fragment) +{ + memset(fragment, 0x55, azrp_width * azrp_frag_height * 2); + + uint32_t uniforms = (uint32_t)uniforms0; + struct cd_raytrace_cmd *cmd = (struct cd_raytrace_cmd *)cmd0; + + int frag_height = uniforms; + int h = (frag_height > 112 - cmd->y) ? 112 - cmd->y : frag_height; + render_fragment(cmd->camera, (uint16_t *)fragment, cmd->y, h); + cmd->y += h; +} + +GCONSTRUCTOR +static void register_shader(void) +{ + CD_RAYTRACE_SHADER_ID = azrp_register_shader(cd_raytrace_shader); +} + +void cd_raytrace(struct camera const *camera) +{ + prof_enter(azrp_perf_cmdgen); + + struct cd_raytrace_cmd cmd; + cmd.shader_id = CD_RAYTRACE_SHADER_ID; + cmd.camera = camera; + cmd.y = 0; + + azrp_queue_command(&cmd, sizeof cmd, 0, azrp_frag_count); + prof_leave(azrp_perf_cmdgen); +} + +void cd_raytrace_configure(void) +{ + uint32_t value = azrp_frag_height; + azrp_set_uniforms(CD_RAYTRACE_SHADER_ID, (void *)value); +} diff --git a/src/backend/linux/programs.cc b/src/backend/linux/programs.cc new file mode 100644 index 0000000..c9adcd2 --- /dev/null +++ b/src/backend/linux/programs.cc @@ -0,0 +1,53 @@ +#include "programs.h" +#include + +//--- +// 2D Texture shader +//--- + +ProgramTexture::ProgramTexture(): Program() +{ + glBindVertexArray(this->vao); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + // TODO: Better way to access Azur's internal shaders + extern char const *azur_glsl__vs_tex2d; + extern char const *azur_glsl__fs_tex2d; + + this->prog = azur::gl::loadProgramSources( + GL_VERTEX_SHADER, azur_glsl__vs_tex2d, + GL_FRAGMENT_SHADER, azur_glsl__fs_tex2d, + 0); +} + +void ProgramTexture::set_vertex_attributes() const +{ + glVertexAttribPointer(glGetAttribLocation(this->prog, "a_vertex"), + 2, GL_FLOAT, GL_FALSE, + sizeof(ProgramTexture_Attributes), + (void *)offsetof(ProgramTexture_Attributes, vertex) + ); + glVertexAttribPointer(glGetAttribLocation(this->prog, "a_texture_pos"), + 2, GL_FLOAT, GL_FALSE, + sizeof(ProgramTexture_Attributes), + (void *)offsetof(ProgramTexture_Attributes, uv) + ); +} + +void ProgramTexture::add_texture(int x, int y, int width, int height) +{ + ProgramTexture_Attributes attr[4] = { + { glm::vec2(x, y), glm::vec2(0.0, 0.0) }, + { glm::vec2(x+width, y), glm::vec2(1.0, 0.0) }, + { glm::vec2(x, y+height), glm::vec2(0.0, 1.0) }, + { glm::vec2(x+width, y+height), glm::vec2(1.0, 1.0) }, + }; + + this->vertices.push_back(attr[0]); + this->vertices.push_back(attr[1]); + this->vertices.push_back(attr[2]); + this->vertices.push_back(attr[1]); + this->vertices.push_back(attr[2]); + this->vertices.push_back(attr[3]); +} diff --git a/src/backend/linux/programs.h b/src/backend/linux/programs.h new file mode 100644 index 0000000..241e80c --- /dev/null +++ b/src/backend/linux/programs.h @@ -0,0 +1,188 @@ +//-Program: wrapper for shader + VAO + VBO (from Magic Lab) + +#pragma once + +#include +#include +#include + +template +struct Program +{ + Program(); + virtual ~Program(); + + /* Since this holds non-trivial OpenGL objects, disable copy/move */ + Program(Program const &other) = delete; + Program(Program &&other) = delete; + + /* Adds a vertex to the vertex data buffer */ + void add_vertex(VertexAttributes const &attributes); + + /* Set vertex attributes when the buffer is loaded */ + virtual void set_vertex_attributes() const = 0; + + /* Update the VBO and draw */ + void prepare_draw(); + void draw(); + + /* Utilities to set uniforms */ + void set_uniform(char const *name, float f); + void set_uniform(char const *name, float f1, float f2); + void set_uniform(char const *name, float f1, float f2, float f3); + void set_uniform(char const *name, glm::vec2 const &v2); + void set_uniform(char const *name, glm::vec3 const &v3); + void set_uniform(char const *name, glm::vec4 const &v4); + void set_uniform(char const *name, glm::mat2 const &m2); + void set_uniform(char const *name, glm::mat3 const &m3); + void set_uniform(char const *name, glm::mat4 const &m4); + + /* Program ID */ + GLuint prog; + /* Vertex Array Object and Vertex Buffer Object with parameters */ + GLuint vao, vbo; + + /* Vertex attributes to be loaded in the VBO */ + std::vector vertices; + /* Size of the VBO, in number of vertices */ + /* Number of spots currently used in [vertex_data] */ + size_t vbo_size; +}; + +template +Program::Program(): vertices {} +{ + prog = 0; + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + + vbo_size = 0; +} + +template +void Program::add_vertex(T const &attributes) +{ + vertices.push_back(attributes); +} + +template +void Program::prepare_draw() +{ + if(!vertices.size()) return; + + glBindVertexArray(vao); + glUseProgram(prog); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + /* If the size of the VBO is too small or much larger than needed, resize + it; otherwise, simply swap the data without reallocating */ + if(vbo_size < vertices.size() || vbo_size > vertices.size() * 4) { + glBufferData(GL_ARRAY_BUFFER, sizeof(T) * vertices.size(), + vertices.data(), GL_DYNAMIC_DRAW); + } + else { + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(T) * vertices.size(), + vertices.data()); + } + + this->set_vertex_attributes(); +} + +template +void Program::draw() +{ + prepare_draw(); + + // TODO: Do not hardcode the specific operation GL_TRIANGLES + glDrawArrays(GL_TRIANGLES, 0, vertices.size()); +} + +template +void Program::set_uniform(char const *name, float f) +{ + glUniform1f(glGetUniformLocation(this->prog, name), f); +} + +template +void Program::set_uniform(char const *name, float f1, float f2) +{ + glUniform2f(glGetUniformLocation(this->prog, name), f1, f2); +} + +template +void Program::set_uniform(char const *name, float f1, float f2, float f3) +{ + glUniform3f(glGetUniformLocation(this->prog, name), f1, f2, f3); +} + +template +void Program::set_uniform(char const *name, glm::vec2 const &v2) +{ + glUniform2fv(glGetUniformLocation(this->prog, name), 1, &v2[0]); +} + +template +void Program::set_uniform(char const *name, glm::vec3 const &v3) +{ + glUniform3fv(glGetUniformLocation(this->prog, name), 1, &v3[0]); +} + +template +void Program::set_uniform(char const *name, glm::vec4 const &v4) +{ + glUniform4fv(glGetUniformLocation(this->prog, name), 1, &v4[0]); +} + +template +void Program::set_uniform(char const *name, glm::mat2 const &m2) +{ + glUniformMatrix2fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE, + &m2[0][0]); +} + +template +void Program::set_uniform(char const *name, glm::mat3 const &m3) +{ + glUniformMatrix3fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE, + &m3[0][0]); +} + +template +void Program::set_uniform(char const *name, glm::mat4 const &m4) +{ + glUniformMatrix4fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE, + &m4[0][0]); +} + +template +Program::~Program() +{ + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(prog); +} + +//--- +// 2D texture shader +//--- + +/* 6 vertices (a quad) for each texture */ +struct ProgramTexture_Attributes +{ + /* Location of the vertex */ + glm::vec2 vertex; + /* Location within the texture */ + glm::vec2 uv; +}; + +struct ProgramTexture: public Program +{ + ProgramTexture(); + void set_vertex_attributes() const override; + + /* Add a full texture of the specified size + TODO: This always uses texture #0 + TODO: Specify sub-regions */ + void add_texture(int x, int y, int width, int height); +}; diff --git a/src/backend/linux/render.cc b/src/backend/linux/render.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/chaos-drop.h b/src/chaos-drop.h new file mode 100644 index 0000000..5b98e49 --- /dev/null +++ b/src/chaos-drop.h @@ -0,0 +1,85 @@ +/* The world is a drop chute, arranged horizontally. The coordinate system is + right-handed, with the following orientation from the top view of the chute: + + z + ^ + | + | + y (x)-----> x + + The player primarily moves in the +y direction (... although in practice + objects move towards the player to better use the limited range of fixed- + point values). + + We compute directions of rays based on points on the virtual screen placed + in the world, in front of the camera. We make rays start at the camera + position so the distance between the camera and screen does not matter; we + arbitrarily place the screen at a distance which makes the screen height + correspond to 2*HALF_SCREEN_HEIGHT world units. This is to help avoid + precision loss with fixed-point numbers. */ + +#include +#include +using namespace libnum; +struct mat3; + +/* Number of world units that we fix the screen's half height to. The screen is + placed at the correct distance to make that happen. */ +#define HALF_SCREEN_HEIGHT 2 +/* World boundaries */ +#define WORLD_SIZE 8 /* (± 4) */ +/* Viewport size, in pixels */ +#define VWIDTH 198 +#define VHEIGHT 112 + +struct camera { + num fov; + num screen_distance; + + /* Camera position (usually stays at z=0, objects move towards it) */ + vec3 pos; + /* Angles of rotation */ + float yaw, pitch, roll; + num cos_r, sin_r; + num cos_p, sin_p; + num cos_y, sin_y; + /* Corresponding directions, in world coordinates */ + vec3 forward, right, up; + + /* Game stuff... */ + + num neon_position; +}; + +void camera_set_fov(struct camera *camera, float fov_degrees); +mat3 matrix_camera2world(struct camera const *camera); +void camera_update_angles(struct camera *camera); +void render_fragment(struct camera const *camera, uint16_t *fragment, + int y_start, int y_height); + +//=== Azur shader wrapping the raytracing ===// + +void cd_raytrace(struct camera const *camera); +void cd_raytrace_configure(void); + +//=== Input management ===// + +struct input { + bool left; + bool right; + bool up; + bool down; + bool OPTN; +}; + +//=== Additions to libnum ===// + +struct mat3 +{ + num x11, x12, x13; + num x21, x22, x23; + num x31, x32, x33; +}; + +mat3 operator *(mat3 const &A, mat3 const &B); +vec3 operator *(mat3 const &M, vec3 const &u); diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..3e1b0ad --- /dev/null +++ b/src/main.cc @@ -0,0 +1,269 @@ +#define __BSD_VISIBLE 1 +#include +#include +#include +#include "chaos-drop.h" + +#if defined(AZUR_TOOLKIT_SDL) +#include +#include +#include +#include "backend/linux/programs.h" + +static uint16_t vram[VWIDTH * VHEIGHT]; +std::unique_ptr shader_texture = nullptr; +static GLuint tex_vram; + +static void view_update(void) +{ + SDL_Window *window = azur_sdl_window(); + int width, height; + SDL_GetWindowSize(window, &width, &height); + + /* Transform from pixel coordinates within the winfow to GL coordinates */ + glm::mat3 tr_pixel2gl( + 2.0f / width, 0.0f, 0.0f, + 0.0f, -2.0f / height, 0.0f, + -1.0f, 1.0f, 1.0f); + + glUseProgram(shader_texture->prog); + shader_texture->set_uniform("u_transform", tr_pixel2gl); +} + +static void init(void) +{ + shader_texture = std::make_unique(); + view_update(); + memset(vram, 0x55, sizeof vram); + + glGenTextures(1, &tex_vram); + glBindTexture(GL_TEXTURE_2D, tex_vram); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VWIDTH, VHEIGHT, 0, GL_RGB, + GL_UNSIGNED_SHORT_5_6_5, vram); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +} + +static void quit(void) +{ +} + +static int platform_update(struct input *input) +{ + SDL_Event e; + *input = (struct input){}; + + while(SDL_PollEvent(&e)) { +// ImGui_ImplSDL2_ProcessEvent(&e); +// render_needed = std::max(render_needed, 1); + + if(e.type == SDL_QUIT) + return 1; + if(e.type == SDL_WINDOWEVENT && + e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + view_update(); + } + } + + Uint8 const *state = SDL_GetKeyboardState(NULL); + input->up = state[SDL_SCANCODE_UP]; + input->down = state[SDL_SCANCODE_DOWN]; + input->left = state[SDL_SCANCODE_LEFT]; + input->right = state[SDL_SCANCODE_RIGHT]; + return 0; +} + +static void platform_render(void) +{ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VWIDTH, VHEIGHT, 0, GL_RGB, + GL_UNSIGNED_SHORT_5_6_5, vram); + + SDL_Window *window = azur_sdl_window(); + + shader_texture->vertices.clear(); + shader_texture->add_texture(0, 0, 3*VWIDTH, 3*VHEIGHT); + shader_texture->draw(); + + SDL_GL_SwapWindow(window); +} + +#elif defined(AZUR_TOOLKIT_GINT) + +#include +#include +#include +#include +#include +#include +#define vram gint_vram + +static int platform_update(struct input *input) +{ + key_event_t e; + + while((e = pollevent()).type != KEYEV_NONE) { + if(e.type == KEYEV_UP) + continue; + + if(e.key == KEY_EXIT) + return 1; + if(e.key == KEY_MENU) + gint_osmenu(); + if(e.key == KEY_OPTN) + input->OPTN = true; + } + + input->left = keydown(KEY_LEFT); + input->right = keydown(KEY_RIGHT); + input->up = keydown(KEY_UP); + input->down = keydown(KEY_DOWN); + return 0; +} + +static void platform_render(void) +{ + azrp_update(); +} + +static void init(void) +{ + prof_init(); + azrp_config_scale(2); + cd_raytrace_configure(); +} + +static void quit(void) +{ + prof_quit(); +} + +#endif + +mat3 operator *(mat3 const &A, mat3 const &B) +{ + mat3 C; + + C.x11 = A.x11 * B.x11 + A.x12 * B.x21 + A.x13 * B.x31; + C.x12 = A.x11 * B.x12 + A.x12 * B.x22 + A.x13 * B.x32; + C.x13 = A.x11 * B.x13 + A.x12 * B.x23 + A.x13 * B.x33; + + C.x21 = A.x21 * B.x11 + A.x22 * B.x21 + A.x23 * B.x31; + C.x22 = A.x21 * B.x12 + A.x22 * B.x22 + A.x23 * B.x32; + C.x23 = A.x21 * B.x13 + A.x22 * B.x23 + A.x23 * B.x33; + + C.x31 = A.x31 * B.x11 + A.x32 * B.x21 + A.x33 * B.x31; + C.x32 = A.x31 * B.x12 + A.x32 * B.x22 + A.x33 * B.x32; + C.x33 = A.x31 * B.x13 + A.x32 * B.x23 + A.x33 * B.x33; + + return C; +} + +vec3 operator * (mat3 const &M, vec3 const &u) +{ + vec3 v; + + v.x = M.x11 * u.x + M.x12 * u.y + M.x13 * u.z; + v.y = M.x21 * u.x + M.x22 * u.y + M.x23 * u.z; + v.z = M.x31 * u.x + M.x32 * u.y + M.x33 * u.z; + + return v; +} + +static struct camera *camera = NULL; +static bool debug = false; + +void render(void) +{ +#ifdef AZUR_TOOLKIT_GINT + azrp_perf_clear(); + cd_raytrace(camera); + platform_render(); + + if(debug) { + drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE); + dprint(4, 209, C_BLACK, "render:%4d+%4dus", + prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524), + prof_time(azrp_perf_r61524)); + r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT); + } +#else + render_fragment(camera, vram, 0, VHEIGHT); + platform_render(); +#endif +} + +int update(void) +{ + struct input input = {}; + if(platform_update(&input)) + return 1; + + /* Yes I'm aware I'm always changing the angles */ + bool changed = false; + + static num const mv_speed = 0.35; + static num const wall = WORLD_SIZE * 0.45; /* margin */ + static float const rot_speed = 0.01; + static float const rot_snap = 0.6; + static float const rot_max = 0.2; + static num const neon_speed = 2; + + if(input.left) { + camera->pos.x = max(camera->pos.x - mv_speed, -wall); + camera->yaw = fmaxf(camera->yaw - rot_speed, -rot_max); + changed = true; + } + else if(input.right) { + camera->pos.x = min(camera->pos.x + mv_speed, wall); + camera->yaw = fminf(camera->yaw + rot_speed, rot_max); + changed = true; + } + else { + camera->yaw *= rot_snap; + changed = true; + } + + if(input.up) { + camera->pos.z = min(camera->pos.z + mv_speed, wall); + camera->pitch = fmaxf(camera->pitch - rot_speed, -rot_max); + changed = true; + } + else if(input.down) { + camera->pos.z = max(camera->pos.z - mv_speed, -wall); + camera->pitch = fminf(camera->pitch + rot_speed, rot_max); + changed = true; + } + else { + camera->pitch *= rot_snap; + changed = true; + } + + if(changed) + camera_update_angles(camera); + + camera->neon_position = + (camera->neon_position + num(32) - neon_speed) % num(32); + + if(input.OPTN) + debug = !debug; + return 0; +} + +int main(void) +{ + if(azur_init("Chaos Drop!", 3*VWIDTH, 3*VHEIGHT)) + return 1; + + struct camera c = {}; + camera = &c; + /* TODO: Why do I need such a low FOV?! */ + camera_set_fov(camera, 80.0); + camera_update_angles(camera); + + init(); + int rc = azur_main_loop(render, 30, update, -1, AZUR_MAIN_LOOP_TIED); + quit(); + return rc; +} diff --git a/src/raytracing.cc b/src/raytracing.cc new file mode 100644 index 0000000..754ea51 --- /dev/null +++ b/src/raytracing.cc @@ -0,0 +1,243 @@ +#define __BSD_VISIBLE 1 +#include "chaos-drop.h" +#include +#include + +void camera_set_fov(struct camera *camera, float fov_degrees) +{ + camera->fov = num(fov_degrees); + float fov_radians = fov_degrees * 3.14159 / 180; + /* Use FOV as the horizontal viewing angle */ + float sd = (VWIDTH * HALF_SCREEN_HEIGHT / VHEIGHT) / tanf(fov_radians / 2); + /* The screen is at such a distance that 16 units is half the height. We + don't care where it is placed as we'll always send rays from the camera + itself. This is to ensure good ranges for fixed point values */ + camera->screen_distance = num(sd); +} + +mat3 matrix_camera2world(struct camera const *camera) +{ + num cos_r = camera->cos_r; + num sin_r = camera->sin_r; + num cos_p = camera->cos_p; + num sin_p = camera->sin_p; + num cos_y = camera->cos_y; + num sin_y = camera->sin_y; + +#if 0 + mat3 m_roll = { + cos_r, 0, sin_r, + 0, 1, 0, + -sin_r, 0, cos_r, + }; + mat3 m_pitch = { + 1, 0, 0, + 0, cos_p, +sin_p, + 0, -sin_p, cos_p, + }; + mat3 m_yaw = { + cos_y, +sin_y, 0, + -sin_y, cos_y, 0, + 0, 0, 1, + }; + mat3 M = m_roll * (m_pitch * m_yaw); +#endif + + mat3 m_anti_roll = { + cos_r, 0, -sin_r, + 0, 1, 0, + +sin_r, 0, cos_r, + }; + mat3 m_anti_pitch = { + 1, 0, 0, + 0, cos_p, -sin_p, + 0, +sin_p, cos_p, + }; + mat3 m_anti_yaw = { + cos_y, -sin_y, 0, + +sin_y, cos_y, 0, + 0, 0, 1, + }; + return m_anti_yaw * (m_anti_pitch * m_anti_roll); +} + +void camera_update_angles(struct camera *camera) +{ + float c, s; + + sincosf(camera->roll, &s, &c); + camera->sin_r = s; + camera->cos_r = c; + + sincosf(camera->pitch, &s, &c); + camera->sin_p = s; + camera->cos_p = c; + + sincosf(camera->yaw, &s, &c); + camera->sin_y = s; + camera->cos_y = c; + + mat3 c2w = matrix_camera2world(camera); + camera->forward = c2w * vec3(0, 1, 0); + camera->right = c2w * vec3(1, 0, 0); + camera->up = c2w * vec3(0, 0, 1); +} + +using snum = num16; +using svec3 = vec; +svec3 svec3_of_vec3(vec3 v) +{ + return svec3(snum(v.x), snum(v.y), snum(v.z)); +} + +enum object { + /* Wall sides */ + LEFT = 1, TOP, RIGHT, BOTTOM, +}; + +static int cast_ray(svec3 const &origin, svec3 const &rayDir, snum *t) +{ + snum tx_rx_rz, tz_rx_rz; + int hitx = 0, hitz = 0; + + if(rayDir.x > 0) { + tx_rx_rz = (snum(WORLD_SIZE / 2) - origin.x) * rayDir.z; + hitx = RIGHT; + } + else if(rayDir.x < 0) { + tx_rx_rz = (snum(-WORLD_SIZE / 2) - origin.x) * rayDir.z; + hitx = LEFT; + } + + if(rayDir.z > 0) { + tz_rx_rz = (snum(WORLD_SIZE / 2) - origin.z) * rayDir.x; + hitz = TOP; + } + else if(rayDir.z < 0) { + tz_rx_rz = (snum(-WORLD_SIZE / 2) - origin.z) * rayDir.x; + hitz = BOTTOM; + } + + // static int done = 0; + // if(++done <= 4) + // printf("tx=%f tz=%f rx=%f rz=%f tx_rx_rz=%f tz_rx_rz=%f\n", + // (float)tx, (float)tz, (float)rayDir.x, (float)rayDir.z, + // (float)tx_rx_rz, (float)tz_rx_rz); + + int rx_rz_sign = (rayDir.x < 0) ^ (rayDir.z < 0); + + if(hitz && (!hitx || (tz_rx_rz < tx_rx_rz) ^ rx_rz_sign)) { + if(t) { + if(hitz == TOP) + *t = num16::div_positive(snum(WORLD_SIZE / 2) - origin.z, + rayDir.z); + else + *t = num16::div_positive(origin.z + snum(WORLD_SIZE / 2), + -rayDir.z); + } + return hitz; + } + else if(hitx) { + if(t) { + if(hitx == RIGHT) + *t = num16::div_positive(snum(WORLD_SIZE / 2) - origin.x, + rayDir.x); + else + *t = num16::div_positive(origin.x + snum(WORLD_SIZE / 2), + -rayDir.x); + } + return hitx; + } + + return 0; +} + +void render_fragment(struct camera const *camera, uint16_t *fragment, + int y_start, int y_height) +{ + svec3 origin = svec3_of_vec3(camera->pos); + svec3 forward = svec3_of_vec3(camera->forward); + svec3 right = svec3_of_vec3(camera->right); + svec3 up = svec3_of_vec3(camera->up); + + /* Screen center in front of the camera */ + svec3 screen_center = origin + snum(camera->screen_distance) * forward; + /* Unitary movements, in world coordinates, on screen placed in world, + corresponding to individual pixel sizes */ + svec3 pixel_dy = snum(HALF_SCREEN_HEIGHT) * up / snum(VHEIGHT / 2); + svec3 pixel_dx = snum(HALF_SCREEN_HEIGHT) * right / snum(VHEIGHT / 2); + + svec3 rayDir_row = screen_center - origin + + snum(VHEIGHT/2 - y_start) * pixel_dy + + snum(0 - VWIDTH/2) * pixel_dx; +/* printf("%f %f %f\n", + (float)rayDir_row.x, + (float)rayDir_row.y, + (float)rayDir_row.z); */ + + for(int y = y_start; y < y_start + y_height; y++) { + svec3 rayDir = rayDir_row; + + for(int x = 0; x < VWIDTH; x++) { + snum t; + int obj = cast_ray(origin, rayDir, &t); + int recolor = 0; + snum collision_y; + + if(obj == LEFT || obj == RIGHT) { + svec3 collision; + collision.z = origin.z + t * rayDir.z; + + if(collision.z >= snum(-WORLD_SIZE / 4) + && collision.z < snum(WORLD_SIZE / 4)) { + collision.x = origin.x + t * rayDir.x; + collision.y = origin.y + t * rayDir.y; + rayDir.x = -rayDir.x; + obj = cast_ray(collision, rayDir, &t); + recolor = 1 + (obj == RIGHT); + collision_y = collision.y + t * rayDir.y; + rayDir.x = -rayDir.x; + } + else { + collision_y = origin.y + t * rayDir.y; + } + } + else { + collision_y = origin.y + t * rayDir.y; + } + + static uint16_t const colors[5] = { + 0x0000, 0xf800, 0x07e0, 0x001f, 0xffe0, + }; + uint16_t color = colors[obj]; + + /* Don't show neons that are too far to avoid flickering */ + if(collision_y < 64) { + snum neon_pos = snum(camera->neon_position); + if(obj == TOP || obj == BOTTOM) + neon_pos += snum(16); + snum neon = collision_y - neon_pos; + neon.v = neon.v & (snum(32).v - 1); + /* Also make neons larger when they're far to further avoid + flickering */ + snum neon_size = 1; + if(collision_y > 20) + neon_size += ((collision_y-snum(20)) * snum(1.0/4)); + if(neon <= neon_size) + color = 0xffff; + } + + if(recolor == 1) + color = ((color & 0xf7de) >> 1) + (0xf000 >> 1); + if(recolor == 2) + color = ((color & 0xf7de) >> 1) + (0x001f >> 1); + + *fragment++ = color; + rayDir += pixel_dx; + } + + rayDir_row -= pixel_dy; + } + +// printf("%f %f %f\n", (float)rayDir.x, (float)rayDir.y, (float)rayDir.z); +}