chaos-drop/src/raytracing.cc

330 lines
10 KiB
C++

#define __BSD_VISIBLE 1
#include "chaos-drop.h"
#include <stdio.h>
#include <math.h>
/* 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<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,
/* Object plane */
OBJECT_PLANE,
};
static int cast_ray(struct world const *world, svec3 const &origin,
svec3 const &rayDir, snum *t, num *collision_y, bool secondary)
{
snum tx_rx_rz, tz_rx_rz;
int hitx = 0, hitz = 0;
static snum const epsilon = 0.1;
if(rayDir.x > epsilon) {
tx_rx_rz = (snum(WORLD_SIZE / 2) - 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;
hitx = LEFT;
}
if(rayDir.z > epsilon) {
tz_rx_rz = (snum(WORLD_SIZE / 2) - 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;
hitz = BOTTOM;
}
int rx_rz_sign = (rayDir.x < 0) ^ (rayDir.z < 0);
int hit = 0;
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);
else
*t = num16::div_positive(origin.z + snum(WORLD_SIZE / 2),
-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);
else
*t = num16::div_positive(origin.x + snum(WORLD_SIZE / 2),
-rayDir.x);
hit = hitx;
}
*collision_y = hit ? num(origin.y) + num(*t) * num(rayDir.y) : num(192);
/* Compute intersection with 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;
if(plane_collides(plane_x, plane_z, world->plane->shape)) {
*t = plane_t;
*collision_y = world->plane->y - world->depth;
return OBJECT_PLANE;
}
}
return hit;
}
void render_fragment(struct camera const *camera, struct world const *world,
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;
for(int y = y_start; y < y_start + y_height; y++) {
svec3 rayDir = rayDir_row;
for(int x = 0; x < VWIDTH; x++) {
snum t;
num coll_y;
int obj = cast_ray(world, origin, rayDir, &t, &coll_y, false);
bool darken = false;
if(__builtin_expect(
world->mirror && (obj == LEFT || obj == RIGHT), 0)) {
svec3 collision;
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);
/* Mirror also does not go on forever */
bool ok_y = world->mirror
&& coll_y >= (world->mirror->begin - world->depth)
&& coll_y < (world->mirror->end - world->depth);
if(ok_z && ok_y) {
collision.x = origin.x + t * rayDir.x;
collision.y = snum(coll_y);
rayDir.x = -rayDir.x;
obj = cast_ray(world, collision, rayDir, &t, &coll_y,true);
darken = true;
rayDir.x = -rayDir.x;
}
}
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)];
/* Don't show neons that are too far to avoid flickering */
if(coll_y < 64 && obj != OBJECT_PLANE) {
snum 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.v = neon.v & (world->neon_period.v - 1);
/* Also make neons larger when they're far to further avoid
flickering */
snum neon_size = 1;
if(coll_y > 20)
neon_size += ((snum(coll_y)-snum(20)) * snum(1.0/4));
if(neon <= neon_size)
color = world->text ? RGB(15,15,15) : 0xffff;
}
else if(__builtin_expect(coll_y >= 64, 0)) {
if(coll_y > 128)
color = (color & 0xc718) >> 3;
else if(coll_y > 96)
color = (color & 0xe79c) >> 2;
else
color = (color & 0xf7de) >> 1;
}
if(darken)
color = (color & 0xf7de) >> 1;
*fragment++ = color;
rayDir += pixel_dx;
}
rayDir_row -= pixel_dy;
}
}