diff --git a/CMakeLists.txt b/CMakeLists.txt index c52171d..4826895 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(SOURCES src/main.cc + src/game.cc src/raytracing.cc) set(ASSETS) diff --git a/src/backend/gint/shader.cc b/src/backend/gint/shader.cc index f2cc9e3..2fad0f9 100644 --- a/src/backend/gint/shader.cc +++ b/src/backend/gint/shader.cc @@ -16,12 +16,9 @@ struct cd_raytrace_cmd int y; }; -#include -/* TODO: Write raytrace shader in assembler (MUCH NEEDED) */ +/* TODO: Write (part of) the raytrace shader in assembler */ 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; diff --git a/src/chaos-drop.h b/src/chaos-drop.h index 0ca17a5..1aa7c48 100644 --- a/src/chaos-drop.h +++ b/src/chaos-drop.h @@ -1,57 +1,91 @@ -/* 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: +/* Chaos Drop: A classic falling game incorporating raytracing */ - z - ^ - | - | - y (x)-----> x +#pragma once - 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. */ +#define __BSD_VISIBLE 1 +#include #include #include -using namespace libnum; -struct mat3; +#include "util.h" -/* 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. */ +//-- +// World and camera +//--- + +/* World boundaries (on each side; the tube is thus 8x8). The size doesn't + matter for modeling purposes but this value is chosen to facilitate low- + precision computations with num16. */ +#define WORLD_SIZE 4 +/* Size, in world space, that half of the virtual screen's height should + occupy. Larger values will push the virtual screen away from the camera. + Mathematically any positive value would work. This value is again chosen to + improve the precision of num16 computations. */ #define HALF_SCREEN_HEIGHT 2 -/* World boundaries */ -#define WORLD_SIZE 8 /* (± 4) */ -/* Viewport size, in pixels */ -#define VWIDTH 198 -#define VHEIGHT 112 +/* Viewport/virtual screen size, in pixels */ +#define VWIDTH 198 +#define VHEIGHT 112 +/* Coordinate system + + The world is a drop chute, arranged horizontally to fit Azur's standard + coordinate system. Coordinates are right-handed, with the following + orientation looking forward (down the chute): + + z + ^ + | + y (x)---> x + + There is no player-centered coordinate system since we do all the raytracing + in the world space. */ struct camera { + /* Horizontal FOV in degrees */ num fov; + /* Screen distance (see raytracing.cc for details on how this is chosen) */ num screen_distance; - /* Camera position (usually stays at z=0, objects move towards it) */ + /* Camera position in world space */ 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 */ + /* Corresponding directions, in world coordinates. This forms a local + coordinate system (which is only used ever for ray generation) */ vec3 forward, right, up; + /* (Game-specific) Targeted roll position (0..3) */ int roll_quadrant; }; +/* Set the camera's horizontal FOV in degrees. */ +void camera_set_fov(struct camera *camera, float fov_degrees); +/* Compute the tranformation matrix that turns coordinates relative to the + camera into world coordinates. */ +mat3 camera_camera2world_matrix(struct camera const *camera); +/* Update camera's internal variables after modifying the angles. */ +void camera_update_angles(struct camera *camera); +/* Determines whether the camera is rolling. This function returns true only + during an initial portion of the roll animation. It returns false as soon as + the player should be allowed to roll again. */ +bool camera_rolling(struct camera const *camera); +/* Perform rolling by bringing the roll angle closer to the targeted roll + position. How much closer is controlled by 0 < snap_factor ≤ 1. With + snap_factor=1, the roll snaps instantly. Any other constant value gives an + exponential snap. */ +void camera_roll(struct camera *camera, float snap_factor); + +//--- +// Game levels +//--- + +/* Mirror along the left and right walls. */ struct element_mirror { num begin, end; }; +/* Object plane intersecting the chute. */ enum element_plane_type { ELEMENT_PLANE_DAMAGE, ELEMENT_PLANE_ARROW, @@ -61,17 +95,25 @@ struct element_plane { num y; uint32_t shape; uint16_t type; - uint16_t data; /* ARROW: direction */ + /* ARROW: arrow direction (same numbering as camera->roll_quadrant) */ + uint16_t data; }; +/* Tutorial text or sarcastic commentary. */ struct element_text { num begin, end; char const *str; }; struct level { + /* Level name (usually just two digits) */ char const *name; + /* Depth at which the level is completed */ num finish; + /* Lists of elements, all of which should be sorted by increasing order of + begin/y members (and only one element of each type should exist for each + value of y; if there are multiple, only the first will be shown) */ + int mirror_count; struct element_mirror *mirrors; @@ -82,6 +124,14 @@ struct level { struct element_text *texts; }; +/* Check whether world coordinates x/z would collide with an object plane of + the provided shape. */ +bool level_plane_collides(num16 x, num16 z, uint32_t shape); + +//--- +// Dynamic world information +//--- + struct world { num16 neon_position; num16 neon_period; @@ -96,19 +146,20 @@ struct world { int text_index; }; -void camera_set_fov(struct camera *camera, float fov_degrees); -mat3 matrix_camera2world(struct camera const *camera); -void camera_update_angles(struct camera *camera); +//--- +// Raytracing +//--- + +/* Core function. Renders an image of the provided world through the provided + camera. Renders lines [y_start ... y_start+y_height) on the specified VRAM + fragment. */ void render_fragment(struct camera const *camera, struct world const *world, uint16_t *fragment, int y_start, int y_height); -bool camera_rolling(struct camera const *camera); -void camera_roll(struct camera *camera, float snap_factor); - -bool plane_collides(num16 x, num16 z, uint32_t shape); - -//=== Azur shader wrapping the raytracing ===// +/* Azur shader wrapping the raytracing function. */ void cd_raytrace(struct camera const *camera, struct world const *world); +/* Shader configuration function, must be called at initialization and after + any scale change. */ void cd_raytrace_configure(void); //=== Input management ===// @@ -126,15 +177,3 @@ struct input { bool NEXT_LEVEL; bool enter; }; - -//=== 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/game.cc b/src/game.cc new file mode 100644 index 0000000..3210534 --- /dev/null +++ b/src/game.cc @@ -0,0 +1,123 @@ +/* Functions for game-specific mechanics. */ +#include "chaos-drop.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); +} + +bool camera_rolling(struct camera const *camera) +{ + float target_roll = camera->roll_quadrant * M_PI / 2; + float distance = fabsf(target_roll - camera->roll); + + /* Wrap-around case */ + if(camera->roll > 6.0 && camera->roll_quadrant == 0) + distance = fabsf(target_roll - (camera->roll - 2*M_PI)); + + return distance > 0.3; +} + +void camera_roll(struct camera *camera, float snap_factor) +{ + float target_roll = camera->roll_quadrant * M_PI / 2; + float distance_nowrap = target_roll - camera->roll; + float distance_wrap_up = (target_roll + 2*M_PI) - camera->roll; + float distance_wrap_down = (target_roll - 2*M_PI) - camera->roll; + + float mv; + + /* Snap with closest direction */ + if(fabsf(distance_nowrap) < fabsf(distance_wrap_up)) { + if(fabsf(distance_nowrap) < fabsf(distance_wrap_down)) + mv = distance_nowrap * snap_factor; + else + mv = distance_wrap_down * snap_factor; + } + else { + if(fabsf(distance_wrap_down) < fabsf(distance_wrap_down)) + mv = distance_wrap_down * snap_factor; + else + mv = distance_wrap_up * snap_factor; + } + + camera->roll += mv; + if(camera->roll < 0) + camera->roll += 2 * M_PI; + else if(camera->roll > 2 * M_PI) + camera->roll -= 2 * M_PI; + + /* Also rotate the player along with it */ + float cos_mv, sin_mv; + sincosf(-mv, &sin_mv, &cos_mv); + num c = cos_mv, s = sin_mv; + num nx = camera->pos.x * c + camera->pos.z * s; + num nz = -camera->pos.x * s + camera->pos.z * c; + camera->pos.x = nx; + camera->pos.z = nz; +} + +mat3 camera_camera2world_matrix(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; + + 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 = camera_camera2world_matrix(camera); + camera->forward = c2w * vec3(0, 1, 0); + camera->right = c2w * vec3(1, 0, 0); + camera->up = c2w * vec3(0, 0, 1); +} + +bool level_plane_collides(num16 x, num16 z, uint32_t shape) +{ + int cx = ((x + num16(WORLD_SIZE)) * num16(5.0 / 2 / WORLD_SIZE)).ifloor(); + int cz = ((z + num16(WORLD_SIZE)) * num16(5.0 / 2 / WORLD_SIZE)).ifloor(); + + return (unsigned)cx < 5 && (unsigned)cz < 5 + && ((shape >> (5*cz + 4-cx)) & 1); +} diff --git a/src/main.cc b/src/main.cc index b93c744..1575156 100644 --- a/src/main.cc +++ b/src/main.cc @@ -470,7 +470,7 @@ int update(void) } static num const mv_speed = 0.45; - static num const wall = WORLD_SIZE * 0.45; /* margin */ + static num const wall = WORLD_SIZE * 0.9; /* margin */ static float const rot_speed = 0.01; static float const rot_snap = 0.6; static float const rot_max = 0.2; @@ -533,8 +533,9 @@ int update(void) /* Apply plane effect */ if((w->plane->type == ELEMENT_PLANE_DAMAGE || w->plane->type == ELEMENT_PLANE_INVIS) - && plane_collides(num16(camera->pos.x), num16(camera->pos.z), - w->plane->shape)) { + && level_plane_collides(num16(camera->pos.x), num16(camera->pos.z), + w->plane->shape)) + { world_reset(w); reset_frames = 2; } diff --git a/src/raytracing.cc b/src/raytracing.cc index dc286ac..20c18e7 100644 --- a/src/raytracing.cc +++ b/src/raytracing.cc @@ -1,187 +1,76 @@ -#define __BSD_VISIBLE 1 #include "chaos-drop.h" #include #include /* 5-bit channels to RGB565 */ #define RGB(R, G, B) ((R << 11) | (G << 6) | (B)) - -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); -} - -bool camera_rolling(struct camera const *camera) -{ - float target_roll = camera->roll_quadrant * M_PI / 2; - float distance = fabsf(target_roll - camera->roll); - - /* Wrap-around case */ - if(camera->roll > 6.0 && camera->roll_quadrant == 0) - distance = fabsf(target_roll - (camera->roll - 2*M_PI)); - - return distance > 0.3; -} - -void camera_roll(struct camera *camera, float snap_factor) -{ - float target_roll = camera->roll_quadrant * M_PI / 2; - float distance_nowrap = target_roll - camera->roll; - float distance_wrap_up = (target_roll + 2*M_PI) - camera->roll; - float distance_wrap_down = (target_roll - 2*M_PI) - camera->roll; - - float mv; - - /* Snap with closest direction */ - if(fabsf(distance_nowrap) < fabsf(distance_wrap_up)) { - if(fabsf(distance_nowrap) < fabsf(distance_wrap_down)) - mv = distance_nowrap * snap_factor; - else - mv = distance_wrap_down * snap_factor; - } - else { - if(fabsf(distance_wrap_down) < fabsf(distance_wrap_down)) - mv = distance_wrap_down * snap_factor; - else - mv = distance_wrap_up * snap_factor; - } - - camera->roll += mv; - if(camera->roll < 0) - camera->roll += 2 * M_PI; - else if(camera->roll > 2 * M_PI) - camera->roll -= 2 * M_PI; - - /* Also rotate the player along with it */ - float cos_mv, sin_mv; - sincosf(-mv, &sin_mv, &cos_mv); - num c = cos_mv, s = sin_mv; - num nx = camera->pos.x * c + camera->pos.z * s; - num nz = -camera->pos.x * s + camera->pos.z * c; - camera->pos.x = nx; - camera->pos.z = nz; -} - -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); -} - -bool plane_collides(num16 x, num16 z, uint32_t shape) -{ - int cx = ((x + num16(WORLD_SIZE / 2)) * num16(5.0 / WORLD_SIZE)).ifloor(); - int cz = ((z + num16(WORLD_SIZE / 2)) * num16(5.0 / WORLD_SIZE)).ifloor(); - - return (unsigned)cx < 5 && (unsigned)cz < 5 - && ((shape >> (5*cz + 4-cx)) & 1); -} - -using snum = num16; -using svec3 = vec; +/* Color palette for world objects */ +static uint16_t const object_colors[] = { + RGB(3, 4, 8), /* Void/background */ + RGB(3, 4, 8), /* Left wall (-X) */ + RGB(5, 6, 9), /* Top wall (+Z) */ + RGB(7, 8, 11), /* Right wall (+X) */ + RGB(5, 6, 9), /* Bottom wall (-Z) */ + RGB(31, 0, 0), /* ELEMENT_PLANE_DAMAGE */ + RGB(0, 31, 0), /* ELEMENT_PLANE_ARROW */ + RGB(15, 0, 0), /* ELEMENT_PLANE_INVIS */ +}; +/* Objects hit by a ray */ +enum { + VOID = 0, /* Background (no hit) */ + LEFT, TOP, RIGHT, BOTTOM, /* Wall sides */ + OBJECT_PLANE, /* Object plane (from level) */ +}; +/* "Short" vec3: 16-bit-based vectors for faster raytracing */ +using svec3 = vec; svec3 svec3_of_vec3(vec3 v) { - return svec3(snum(v.x), snum(v.y), snum(v.z)); + return svec3(num16(v.x), num16(v.y), num16(v.z)); } -enum object { - /* Wall sides */ - LEFT = 1, TOP, RIGHT, BOTTOM, - /* Object plane */ - OBJECT_PLANE, -}; - +/* Cast a ray from `origin` with direction `rayDir`. Stores the distance to + the collision point in `*t` and the full-precision y-coordinate of that + point in `*collision_y`. Returns the identifier of the object type hit by + the ray. If `secondary` is true, invisible planes are not ignored. */ static int cast_ray(struct world const *world, svec3 const &origin, - svec3 const &rayDir, snum *t, num *collision_y, bool secondary) + svec3 const &rayDir, num16 *t, num *collision_y, bool secondary) { - snum tx_rx_rz, tz_rx_rz; + /* We directly compute intersections between rays and walls by exploiting + the very regular structure of the walls. To find out whether the x or z + wall is hit first, we compute tx and tz (travel distances of the ray + before hitting the x/z walls) and check which is smaller. + + The thing is, tx is Δx / rayDir.x and tz is Δz / rayDir.z. We don't want + to compute both divisions because it's expensive. So instead of tx < tz + we check tx * rayDir.x * rayDir.z < tz * rayDir.x * rayDir.z and only + divide once, when we know which is smaller. */ + num16 tx_rx_rz, tz_rx_rz; + /* Object hit for each wall direction */ int hitx = 0, hitz = 0; - static snum const epsilon = 0.1; + + /* Computing with num16 comes with range issues. Rays that go very straight + hit the x and z walls at more than 128 units away, causing an overflow + on the computation of the y coordinate. We hack around this by + registering background hits if the ray's x/z is smaller than epsilon. */ + static num16 const epsilon = 0.1; + + /* Determine which wall is hit first. */ if(rayDir.x > epsilon) { - tx_rx_rz = (snum(WORLD_SIZE / 2) - origin.x) * rayDir.z; + tx_rx_rz = (num16(WORLD_SIZE) - origin.x) * rayDir.z; hitx = RIGHT; } else if(__builtin_expect(rayDir.x < -epsilon, 1)) { - tx_rx_rz = (snum(-WORLD_SIZE / 2) - origin.x) * rayDir.z; + tx_rx_rz = (num16(-WORLD_SIZE) - origin.x) * rayDir.z; hitx = LEFT; } if(rayDir.z > epsilon) { - tz_rx_rz = (snum(WORLD_SIZE / 2) - origin.z) * rayDir.x; + tz_rx_rz = (num16(WORLD_SIZE) - origin.z) * rayDir.x; hitz = TOP; } else if(__builtin_expect(rayDir.z < -epsilon, 1)) { - tz_rx_rz = (snum(-WORLD_SIZE / 2) - origin.z) * rayDir.x; + tz_rx_rz = (num16(-WORLD_SIZE) - origin.z) * rayDir.x; hitz = BOTTOM; } @@ -190,37 +79,39 @@ static int cast_ray(struct world const *world, svec3 const &origin, if(hitz && (!hitx || (tz_rx_rz < tx_rx_rz) ^ rx_rz_sign)) { if(hitz == TOP) - *t = num16::div_positive(snum(WORLD_SIZE / 2) - origin.z, - rayDir.z); + *t = num16::div_positive(num16(WORLD_SIZE) - origin.z, rayDir.z); else - *t = num16::div_positive(origin.z + snum(WORLD_SIZE / 2), - -rayDir.z); + *t = num16::div_positive(origin.z + num16(WORLD_SIZE), -rayDir.z); hit = hitz; } else if(__builtin_expect(hitx, 1)) { if(hitx == RIGHT) - *t = num16::div_positive(snum(WORLD_SIZE / 2) - origin.x, - rayDir.x); + *t = num16::div_positive(num16(WORLD_SIZE) - origin.x, rayDir.x); else - *t = num16::div_positive(origin.x + snum(WORLD_SIZE / 2), - -rayDir.x); + *t = num16::div_positive(origin.x + num16(WORLD_SIZE), -rayDir.x); hit = hitx; } + /* Compute collision point's y-coordinate in full precision. This only half + works because *t can be incorrect due to an overflow. Computing a more + precise version of *t would be too expensive though, so we settle for + this. */ + *collision_y = hit ? num(origin.y) + num(*t) * num(rayDir.y) : num(192); - /* Compute intersection with object plane */ + /* Determine if there is an intersection with the object plane */ + if(__builtin_expect( world->plane && (*collision_y > world->plane->y - world->depth) && (world->plane->type != ELEMENT_PLANE_INVIS || secondary), 0)) { - snum plane_y = snum(world->plane->y - world->depth); - snum plane_t = (plane_y - origin.y) / rayDir.y; - snum plane_x = origin.x + plane_t * rayDir.x; - snum plane_z = origin.z + plane_t * rayDir.z; + num16 plane_y = num16(world->plane->y - world->depth); + num16 plane_t = (plane_y - origin.y) / rayDir.y; + num16 plane_x = origin.x + plane_t * rayDir.x; + num16 plane_z = origin.z + plane_t * rayDir.z; - if(plane_collides(plane_x, plane_z, world->plane->shape)) { + if(level_plane_collides(plane_x, plane_z, world->plane->shape)) { *t = plane_t; *collision_y = world->plane->y - world->depth; return OBJECT_PLANE; @@ -238,22 +129,22 @@ void render_fragment(struct camera const *camera, struct world const *world, 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; + /* Screen center (in front of the camera) */ + svec3 screen_center = origin + num16(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); - + corresponding to individual pixels */ + svec3 pixel_dy = num16(HALF_SCREEN_HEIGHT) * up / num16(VHEIGHT / 2); + svec3 pixel_dx = num16(HALF_SCREEN_HEIGHT) * right / num16(VHEIGHT / 2); + /* Ray direction at the start of current row */ svec3 rayDir_row = screen_center - origin - + snum(VHEIGHT/2 - y_start) * pixel_dy - + snum(0 - VWIDTH/2) * pixel_dx; + + num16(VHEIGHT/2 - y_start) * pixel_dy + + num16(0 - VWIDTH/2) * pixel_dx; for(int y = y_start; y < y_start + y_height; y++) { svec3 rayDir = rayDir_row; for(int x = 0; x < VWIDTH; x++) { - snum t; + num16 t; num coll_y; int obj = cast_ray(world, origin, rayDir, &t, &coll_y, false); bool darken = false; @@ -264,8 +155,8 @@ void render_fragment(struct camera const *camera, struct world const *world, collision.z = origin.z + t * rayDir.z; /* Mirror covers only part of width of wall */ - bool ok_z = collision.z >= snum(-WORLD_SIZE / 4) - && collision.z < snum(WORLD_SIZE / 4); + bool ok_z = collision.z >= num16(-WORLD_SIZE / 2) + && collision.z < num16(WORLD_SIZE / 2); /* Mirror also does not go on forever */ bool ok_y = world->mirror && coll_y >= (world->mirror->begin - world->depth) @@ -273,7 +164,7 @@ void render_fragment(struct camera const *camera, struct world const *world, if(ok_z && ok_y) { collision.x = origin.x + t * rayDir.x; - collision.y = snum(coll_y); + collision.y = num16(coll_y); rayDir.x = -rayDir.x; obj = cast_ray(world, collision, rayDir, &t, &coll_y,true); darken = true; @@ -281,31 +172,21 @@ void render_fragment(struct camera const *camera, struct world const *world, } } - static uint16_t const colors[] = { - RGB(3, 4, 8), - RGB(3, 4, 8), /* Left */ - RGB(5, 6, 9), /* Top */ - RGB(7, 8, 11), /* Right */ - RGB(5, 6, 9), /* Bottom */ - RGB(31, 0, 0), /* ELEMENT_PLANE_DAMAGE */ - RGB(0, 31, 0), /* ELEMENT_PLANE_ARROW */ - RGB(15, 0, 0), /* ELEMENT_PLANE_INVIS */ - }; - uint16_t color = - colors[obj + (obj == OBJECT_PLANE ? world->plane->type : 0)]; + uint16_t color = object_colors[ + obj + (obj == OBJECT_PLANE ? world->plane->type : 0)]; /* Don't show neons that are too far to avoid flickering */ if(coll_y < 64 && obj != OBJECT_PLANE) { - snum neon_pos = world->neon_position; + num16 neon_pos = world->neon_position; if(obj == TOP || obj == BOTTOM) - neon_pos += world->neon_period * snum(0.5); - snum neon = snum(coll_y) - neon_pos; + neon_pos += world->neon_period * num16(0.5); + num16 neon = num16(coll_y) - neon_pos; neon.v = neon.v & (world->neon_period.v - 1); /* Also make neons larger when they're far to further avoid flickering */ - snum neon_size = 1; + num16 neon_size = 1; if(coll_y > 20) - neon_size += ((snum(coll_y)-snum(20)) * snum(1.0/4)); + neon_size += ((num16(coll_y)-num16(20)) * num16(1.0/4)); if(neon <= neon_size) color = world->text ? RGB(15,15,15) : 0xffff; } diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..0b7910f --- /dev/null +++ b/src/util.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +/* Export libnum symbols */ +using namespace libnum; + +/* Provide basic 3x3 matrix operations */ +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);