330 lines
10 KiB
C++
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;
|
|
}
|
|
}
|