chaos-drop/src/raytracing.cc

244 lines
7.2 KiB
C++

#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);
}