a bit slow, but that's a start

This commit is contained in:
Lephenixnoir 2023-04-22 22:11:16 +02:00
commit 933819a219
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
13 changed files with 956 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/build-*/
*.g3a
*.mp4

58
CMakeLists.txt Normal file
View File

@ -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()

1
README.md Normal file
View File

@ -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.

BIN
assets/icon-sel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/icon-uns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/icon.xcf Normal file

Binary file not shown.

View File

@ -0,0 +1,56 @@
#include "../../chaos-drop.h"
#include <azur/gint/render.h>
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 <string.h>
/* 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);
}

View File

@ -0,0 +1,53 @@
#include "programs.h"
#include <glm/glm.hpp>
//---
// 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]);
}

View File

@ -0,0 +1,188 @@
//-Program: wrapper for shader + VAO + VBO (from Magic Lab)
#pragma once
#include <azur/gl/gl.h>
#include <glm/glm.hpp>
#include <vector>
template<typename VertexAttributes>
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<VertexAttributes> vertices;
/* Size of the VBO, in number of vertices */
/* Number of spots currently used in [vertex_data] */
size_t vbo_size;
};
template<typename T>
Program<T>::Program(): vertices {}
{
prog = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
vbo_size = 0;
}
template<typename T>
void Program<T>::add_vertex(T const &attributes)
{
vertices.push_back(attributes);
}
template<typename T>
void Program<T>::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<typename T>
void Program<T>::draw()
{
prepare_draw();
// TODO: Do not hardcode the specific operation GL_TRIANGLES
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
}
template<typename T>
void Program<T>::set_uniform(char const *name, float f)
{
glUniform1f(glGetUniformLocation(this->prog, name), f);
}
template<typename T>
void Program<T>::set_uniform(char const *name, float f1, float f2)
{
glUniform2f(glGetUniformLocation(this->prog, name), f1, f2);
}
template<typename T>
void Program<T>::set_uniform(char const *name, float f1, float f2, float f3)
{
glUniform3f(glGetUniformLocation(this->prog, name), f1, f2, f3);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::vec2 const &v2)
{
glUniform2fv(glGetUniformLocation(this->prog, name), 1, &v2[0]);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::vec3 const &v3)
{
glUniform3fv(glGetUniformLocation(this->prog, name), 1, &v3[0]);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::vec4 const &v4)
{
glUniform4fv(glGetUniformLocation(this->prog, name), 1, &v4[0]);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::mat2 const &m2)
{
glUniformMatrix2fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE,
&m2[0][0]);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::mat3 const &m3)
{
glUniformMatrix3fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE,
&m3[0][0]);
}
template<typename T>
void Program<T>::set_uniform(char const *name, glm::mat4 const &m4)
{
glUniformMatrix4fv(glGetUniformLocation(this->prog, name), 1, GL_FALSE,
&m4[0][0]);
}
template<typename T>
Program<T>::~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_Attributes>
{
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);
};

View File

85
src/chaos-drop.h Normal file
View File

@ -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 <num/num.h>
#include <num/vec.h>
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);

269
src/main.cc Normal file
View File

@ -0,0 +1,269 @@
#define __BSD_VISIBLE 1
#include <azur/azur.h>
#include <string.h>
#include <math.h>
#include "chaos-drop.h"
#if defined(AZUR_TOOLKIT_SDL)
#include <SDL2/SDL.h>
#include <azur/gl/gl.h>
#include <memory>
#include "backend/linux/programs.h"
static uint16_t vram[VWIDTH * VHEIGHT];
std::unique_ptr<ProgramTexture> 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<ProgramTexture>();
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 <azur/gint/render.h>
#include <gint/gint.h>
#include <gint/keyboard.h>
#include <gint/display.h>
#include <gint/drivers/r61524.h>
#include <libprof.h>
#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;
}

243
src/raytracing.cc Normal file
View File

@ -0,0 +1,243 @@
#define __BSD_VISIBLE 1
#include "chaos-drop.h"
#include <stdio.h>
#include <math.h>
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<snum,3>;
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);
}