traza/main.c

818 lines
20 KiB
C

#define DEBUG
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <math.h>
#include <SDL2/SDL.h>
/* cursed struct definitions, thanks bx */
#define TYPES_IMPL
#include "types.h"
#define W 800
#define H 600
#define PI 3.14159265358979323844
int32_t zbuffer[W*H] = {INT32_MIN};
int wireframe = 1;
void
die(char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
const rgb WHITE = {255, 255, 255, 255};
const rgb BLACK = {0, 0, 0, 255};
const rgb RED = {255, 60, 40, 255};
const rgb GREEN = {40, 255, 70, 255};
const rgb BLUE = {50, 70, 255, 255};
typedef struct {
SDL_Renderer *renderer;
SDL_Window *window;
SDL_Texture *tex;
rgb *canvas;
} sdl_state;
typedef struct {
uint32_t vertex_num;
uint32_t tri_num; /* for loops */
uint32_t index_num;
vec3 *vertices;
uint32_t *indices;
} mesh;
camera
new_camera(scalar znear, vec3 pos, vec3 up, scalar yaw, scalar pitch)
{
camera ret = {
.znear = znear,
.pos = pos,
.up = up,
.yaw = yaw,
.pitch = pitch
};
update_camera(&ret);
return ret;
}
mesh
new_mesh(
uint32_t vertex_num,
uint32_t tri_num,
uint32_t index_num,
vec3 vertices[],
uint32_t indices[]
)
{
mesh ret;
ret.vertex_num = vertex_num;
ret.tri_num = tri_num;
ret.index_num = index_num;
ret.vertices = malloc(sizeof(vec3) * vertex_num);
for (uint32_t i = 0; i < vertex_num; i++)
ret.vertices[i] = vertices[i];
ret.indices = malloc(sizeof(uint32_t) * index_num);
for (uint32_t i = 0; i < index_num; i++)
ret.indices[i] = indices[i];
return ret;
}
mesh
load_mesh(char *path)
{
mesh ret;
FILE *f = fopen(path, "r");
if (f == NULL)
die("load_mesh: couldn't open model");
/* Vertices */
fread(&ret.vertex_num, 4, 1, f);
ret.vertices = malloc(sizeof(vec3) * ret.vertex_num);
for (uint32_t i = 0; i < ret.vertex_num; i++) {
fread(&ret.vertices[i].x, 8, 1, f);
fread(&ret.vertices[i].y, 8, 1, f);
fread(&ret.vertices[i].z, 8, 1, f);
}
/* Indices */
fread(&ret.index_num, 4, 1, f);
if (ret.index_num % 3 != 0) {
free(ret.vertices);
die("load_mesh: invalid number of indices, should be a multiple of 3");
}
ret.tri_num = ret.index_num / 3;
ret.indices = malloc(sizeof(uint32_t) * ret.index_num);
for (uint32_t i = 0; i < ret.index_num; i++)
fread(&ret.indices[i], 4, 1, f);
return ret;
}
mesh
copy_mesh(mesh orig)
{
return new_mesh(
orig.vertex_num,
orig.tri_num,
orig.index_num,
orig.vertices,
orig.indices
);
}
void free_mesh(mesh mesh) { free(mesh.vertices); free(mesh.indices); }
#define max(x, y) ((x) > (y) ? (x) : (y))
#define min(x, y) ((x) < (y) ? (x) : (y))
#define swap(x, y) do { \
(x) ^= (y); \
(y) ^= (x); \
(x) ^= (y); \
} while (0)
#define clamp(x, min, max) (((x) >= (min)) ? ((x) <= (max)) ? (x) : (max) : (min))
void
swapd(scalar *a, scalar *b)
{
scalar tmp = *a;
*a = *b;
*b = tmp;
}
void clear_canvas(rgb canvas[], rgb colour);
sdl_state init_sdl(void);
void free_sdl(sdl_state state);
void render(sdl_state state);
void plot_rgb(rgb canvas[], int32_t x, int32_t y, rgb colour);
void draw_line(rgb canvas[], rgb c, vec2 p0, vec2 p1);
vec2 project(camera c, vec3 v);
sdl_state
init_sdl(void)
{
SDL_Init(SDL_INIT_VIDEO);
sdl_state ret;
ret.window = SDL_CreateWindow(
"Game",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
W,
H,
// TODO: when we have a proper event loop, we can
// add resizing windows.
/* SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN); */
SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_SHOWN
);
ret.renderer = SDL_CreateRenderer(ret.window, -1, SDL_RENDERER_SOFTWARE);
SDL_SetRenderDrawColor(ret.renderer, 0, 0, 0, 255);
SDL_RenderClear(ret.renderer);
SDL_RenderPresent(ret.renderer);
ret.tex = SDL_CreateTexture(ret.renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STATIC,
W, H);
ret.canvas = malloc(sizeof(rgb) * W * H);
clear_canvas(ret.canvas, (rgb){0, 0, 0, 255});
/* SDL_SetWindowGrab(ret.window, SDL_TRUE); */
SDL_SetRelativeMouseMode(SDL_TRUE);
return ret;
}
void
free_sdl(sdl_state state)
{
SDL_DestroyRenderer(state.renderer);
SDL_DestroyWindow(state.window);
SDL_DestroyTexture(state.tex);
free(state.canvas);
SDL_Quit();
}
uint32_t
rgb_to_int(rgb c)
{
return (((uint32_t)c.a << 24)
| ((uint32_t)c.r << 16)
| ((uint32_t)c.g << 8)
| ((uint32_t)c.b));
}
/* display the contents of the canvas */
void
render(sdl_state state)
{
uint32_t pixels[H*W];
for (size_t y = 0; y < H; y++) {
for (size_t x = 0; x < W; x++) {
pixels[x + y * W] = rgb_to_int(state.canvas[x + y * W]);
}
}
SDL_UpdateTexture(state.tex, NULL, pixels, W * sizeof(uint32_t));
/* present the texture */
SDL_RenderCopy(state.renderer, state.tex, NULL, NULL);
SDL_RenderPresent(state.renderer);
}
void
plot_rgb(rgb canvas[], int32_t x, int32_t y, rgb colour)
{
if (x < W && y < H && x >= 0 && y >= 0)
canvas[x + y * W] = colour;
}
void
clear_canvas_trailing(rgb canvas[], rgb colour)
{
for (size_t i = 0; i < W * H; i++)
if ((rand()&31)==0) canvas[i] = colour;
}
void
clear_canvas(rgb canvas[], rgb colour)
{
for (size_t i = 0; i < W * H; i += 2) {
canvas[i] = colour;
canvas[i+1] = colour;
}
}
void
draw_line(rgb canvas[], rgb c, vec2 p0, vec2 p1)
{
int32_t x0 = (int32_t)p0.x,
x1 = (int32_t)p1.x,
y0 = (int32_t)p0.y,
y1 = (int32_t)p1.y;
if (x0 > x1) {
swap(x0, x1);
swap(y0, y1);
}
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
int s = y1 > y0 ? 1 : -1;
int eps = 0;
if (dy < dx) {
int y = y0;
for (int x = x0; x <= x1; x++) {
plot_rgb(canvas, x, y, c);
eps += dy;
if ((eps * 2) >= dx) {
y += s;
eps -= dx;
}
}
}
else {
if (y0 >= y1) {
swap(y0, y1);
swap(x0, x1);
}
int x = x0;
for (int y = y0; y <= y1; y++) {
plot_rgb(canvas, x, y, c);
eps += dx;
if ((eps * 2) >= dy) {
x += s;
eps -= dy;
}
}
}
}
void
draw_horizontal_line(
rgb canvas[],
rgb c,
int32_t x0,
int32_t x1,
int32_t z0,
int32_t z1,
int32_t y
)
{
if (y < 0) return;
if (x0 < 0) x0 = 0;
if (x1 >= W) x1 = W - 1;
if (y >= H) return;
int32_t z = z0;
int32_t dz = abs(z0 - z1);
int32_t dx = abs(x0 - x1);
int32_t z_change = z0 < z1 ? 1 : -1;
int32_t eps = 0;
if (dx < 1) return;
for (int32_t x = x0; x <= x1; x++) {
/* doing bresenham in the z-axis to find the points where we
* should/shouldn't draw */
eps += dz;
while (eps > 0) {
eps -= dx;
z += z_change;
}
size_t i = x + y * W;
if (zbuffer[i] < z) {
canvas[i] = c;
zbuffer[i] = z;
}
}
}
void
draw_triangle(rgb canvas[], rgb col, triangle2 tri)
{
draw_line(canvas, col, tri.a, tri.b);
draw_line(canvas, col, tri.b, tri.c);
draw_line(canvas, col, tri.c, tri.a);
}
void
fill_bottom_flat_triangle(rgb canvas[], rgb c, triangle3 tri)
{
if (tri.b.x > tri.c.x)
swapd(&tri.b.x, &tri.c.x);
/* tri.a x
* / \
* / \
* tri.b x_____x tri.c
*/
int32_t ax = (int32_t)tri.a.x,
bx = (int32_t)tri.b.x,
cx = (int32_t)tri.c.x,
ay = (int32_t)tri.a.y,
by = (int32_t)tri.b.y,
cy = (int32_t)tri.c.y,
az = (int32_t)(tri.a.z*10000),
bz = (int32_t)(tri.b.z*10000),
cz = (int32_t)(tri.c.z*10000);
assert(ay <= cy);
assert(ay <= by);
assert(bx <= cx);
int32_t dx_right = abs(cx - ax);
int32_t dy_right = abs(cy - ay);
int32_t dz_right = abs(cz - az);
int32_t dx_left = abs(bx - ax);
int32_t dy_left = abs(by - ay);
int32_t dz_left = abs(bz - az);
int32_t eps_rx = 0;
int32_t eps_lx = 0;
int32_t eps_rz = 0;
int32_t eps_lz = 0;
int32_t xr = ax;
int32_t xl = ax;
int32_t zr = az;
int32_t zl = az;
int32_t xr_change = ax < cx ? 1 : -1;
int32_t xl_change = ax < bx ? 1 : -1;
int32_t zr_change = az < cz ? 1 : -1;
int32_t zl_change = az < bz ? 1 : -1;
for (int32_t y = ay; y < by; y++) {
eps_rx += dx_right;
eps_lx += dx_left;
eps_rz += dz_right;
eps_lz += dz_left;
while (eps_rx > 0) {
eps_rx -= dy_right;
xr += xr_change;
}
while (eps_lx > 0) {
eps_lx -= dy_left;
xl += xl_change;
}
while (eps_rz > 0) {
eps_rz -= dy_right;
zr += zr_change;
}
while (eps_lz > 0) {
eps_lz -= dy_left;
zl += zl_change;
}
draw_horizontal_line(canvas, c, xl, xr, zl, zr, y);
}
}
void
fill_top_flat_triangle(rgb canvas[], rgb c, triangle3 tri)
{
if (tri.a.x > tri.b.x)
swapd(&tri.a.x, &tri.b.x);
/* tri.a x_____x tri.b
* \ /
* \ /
* tri.c x
*/
int32_t ax = (int32_t)tri.a.x,
bx = (int32_t)tri.b.x,
cx = (int32_t)tri.c.x,
ay = (int32_t)tri.a.y,
by = (int32_t)tri.b.y,
cy = (int32_t)tri.c.y,
az = (int32_t)(tri.a.z*10000),
bz = (int32_t)(tri.b.z*10000),
cz = (int32_t)(tri.c.z*10000);
assert(cy >= ay);
assert(cy >= by);
assert(ax <= bx);
int32_t dx_right = abs(cx - bx);
int32_t dy_right = abs(cy - by);
int32_t dz_right = abs(cz - bz);
int32_t dx_left = abs(cx - ax);
int32_t dy_left = abs(cy - ay);
int32_t dz_left = abs(cz - az);
int32_t eps_rx = 0;
int32_t eps_lx = 0;
int32_t eps_rz = 0;
int32_t eps_lz = 0;
int32_t xr = cx;
int32_t xl = cx;
int32_t zr = cz;
int32_t zl = cz;
int32_t xr_change = cx < bx ? 1 : -1;
int32_t xl_change = cx < ax ? 1 : -1;
int32_t zr_change = cx < bz ? 1 : -1;
int32_t zl_change = cx < az ? 1 : -1;
int32_t y;
for (y = cy-1; y > by; y--) {
eps_rx += dx_right;
eps_lx += dx_left;
eps_rz += dz_right;
eps_lz += dz_left;
while (eps_rx > 0) {
eps_rx -= dy_right;
xr += xr_change;
}
while (eps_lx > 0) {
eps_lx -= dy_left;
xl += xl_change;
}
while (eps_rz > 0) {
eps_rz -= dy_right;
zr += zr_change;
}
while (eps_lz > 0) {
eps_lz -= dy_left;
zl += zl_change;
}
draw_horizontal_line(canvas, c, xl, xr, zl, zr, y);
}
/* We're missing one line, but setting the loop conditional to be y >= by
* messes up the drawing.
*/
draw_horizontal_line(canvas, c, xl, xr, zl, zr, y);
}
/* http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html */
void
fill_triangle(rgb canvas[], rgb c, triangle3 tri)
{
/* tri.a <= tri.b <= tri.c */
if (tri.b.y < tri.a.y) {
swapd(&tri.b.y, &tri.a.y);
swapd(&tri.b.x, &tri.a.x);
}
if (tri.c.y < tri.a.y) {
swapd(&tri.c.y, &tri.a.y);
swapd(&tri.c.x, &tri.a.x);
}
if (tri.c.y < tri.b.y) {
swapd(&tri.c.y, &tri.b.y);
swapd(&tri.c.x, &tri.b.x);
}
/* simple solutions */
if ((int32_t)tri.b.y == (int32_t)tri.c.y) {
fill_bottom_flat_triangle(canvas, c, tri);
}
else if ((int32_t)tri.a.y == (int32_t)tri.b.y) {
fill_top_flat_triangle(canvas, c, tri);
}
else {
vec3 trid = {
(tri.a.x + (tri.b.y - tri.a.y) / (tri.c.y - tri.a.y) * (tri.c.x - tri.a.x)),
tri.b.y,
tri.b.z};
if (tri.a.y > tri.b.y) {
swapd(&tri.a.y, &tri.b.y);
swapd(&tri.a.x, &tri.b.x);
}
fill_top_flat_triangle(canvas, c, (triangle3) {
tri.b, trid, tri.c
});
fill_bottom_flat_triangle(canvas, c, (triangle3) {
tri.a, tri.b, trid
});
}
}
vec2
midpoint(vec2 a, vec2 b)
{
return (vec2) {
(a.x + b.x) / 2,
(a.y + b.y) / 2
};
}
vec3
view_matricize(camera c, vec3 v)
{
mat4x4 view = lookat(c);
vec4 tmp = vec4_mat4x4_mul(vec3_to_vec4(v), view);
return (vec3) {.x = tmp.x, .y = tmp.y, .z = tmp.z};
}
vec2
project(camera c, vec3 u)
{
scalar r = c.znear / (u.z);
return (vec2){(W / 2) * (u.x * r + 1), (H / 2) * (r * (-u.y) + 1) * (800.0 / 600.0)};
}
triangle3
project_triangle(camera cam, triangle3 tri)
{
vec2 a = project(cam, tri.a);
vec2 b = project(cam, tri.b);
vec2 c = project(cam, tri.c);
return (triangle3) {
.a = (vec3){a.x,a.y,tri.a.z},
.b = (vec3){b.x,b.y,tri.b.z},
.c = (vec3){c.x,c.y,tri.c.z},
};
}
/* saves canvas as a P6 image */
void
screenshot(rgb canvas[])
{
time_t t = time(NULL);
char output[64];
strftime(output, sizeof(output), "screenshot-%Y%m%d-%H%M%S.ppm", localtime(&t));
FILE *out = fopen(output, "w");
fprintf(out, "P6\n%d %d\n255\n", W, H);
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
rgb cur = canvas[x + y * W];
fprintf(out, "%c%c%c", cur.r, cur.g, cur.b);
}
}
fclose(out);
fprintf(stderr, "Saved %s\n", output);
}
void
draw_mesh(rgb canvas[], rgb col, camera c, mesh mesh)
{
for (uint32_t i = 0; i < mesh.tri_num; i++) {
vec3 v0 = mesh.vertices[mesh.indices[3*i ]],
v1 = mesh.vertices[mesh.indices[3*i+1]],
v2 = mesh.vertices[mesh.indices[3*i+2]];
vec3 norm = vec3_normalize(
vec3_cross(vec3_sub(v1, v0), vec3_sub(v2, v0))
);
scalar x = vec3_dot(vec3_sub(v0, c.pos), norm);
vec3 us[3] = {view_matricize(c, v0), view_matricize(c, v1), view_matricize(c, v2)};
int skip_triangle = 0;
for (int i = 0; i < 3; i++) {
if (us[i].z > 0.0)
skip_triangle = 1;
}
if (!skip_triangle) {
if (wireframe) {
if (x < 0) {
triangle3 projected = project_triangle(c, (triangle3) {
us[0], us[1], us[2]
});
draw_triangle(canvas, col, (triangle2){
(vec2){projected.a.x,projected.a.y},
(vec2){projected.b.x,projected.b.y},
(vec2){projected.c.x,projected.c.y}});
}
}
else {
if (x < 0) {
scalar col_mod = clamp(
vec3_dot(norm, vec3_normalize((vec3){2,2,2})),
0.0, 1.0
);
fill_triangle(canvas,
(rgb) {
.r = col.r * col_mod,
.g = col.g * col_mod,
.b = col.b * col_mod
},
project_triangle(c, (triangle3) {
us[0], us[1], us[2]
}));
}
}
}
}
}
scalar
to_deg(scalar deg)
{
return deg * 2 * PI / 360.0;
}
mat3x3
x_rotation(scalar deg)
{
return (mat3x3) {
.nums =
{{1, 0, 0},
{0, cos(to_deg(deg)), -sin(to_deg(deg))},
{0, sin(to_deg(deg)), cos(to_deg(deg))}}
};
}
mat3x3
y_rotation(scalar deg)
{
return (mat3x3) {
.nums =
{{ cos(to_deg(deg)), 0, sin(to_deg(deg))},
{ 0, 1, 0},
{-sin(to_deg(deg)), 0, cos(to_deg(deg))}}
};
}
mat3x3
z_rotation(scalar deg)
{
return (mat3x3) {
.nums =
{{cos(to_deg(deg)), -sin(to_deg(deg)), 0},
{sin(to_deg(deg)), cos(to_deg(deg)), 0},
{0, 0, 1}}
};
}
/* #include "other.h" */
int
main(void)
{
sdl_state state = init_sdl();
camera cam = new_camera(-1.0, (vec3){0, 1, 30}, UP, PI, 0.0);
SDL_Event event;
/* const mesh iso = load_mesh("resources/iso.mod"); */
/* mesh iso_copy = copy_mesh(iso); */
const mesh plane = load_mesh("resources/plane.mod");
mesh plane_copy = copy_mesh(plane);
const mesh monkey = load_mesh("resources/monkey.mod");
mesh monkey_copy = copy_mesh(monkey);
uint64_t t = 0;
scalar rot_speed = 0.6;
for (;;) {
/* clear z-buffer */
for (size_t i = 0; i < W*H; i++) zbuffer[i] = INT32_MIN;
scalar elapsed, start = SDL_GetPerformanceCounter();
mat3x3 rotation = mat3x3_mul(
mat3x3_mul(
z_rotation(1.0 * t * rot_speed),
y_rotation(1.2 * t * rot_speed)),
x_rotation(1.3 * t * rot_speed));
clear_canvas(state.canvas, (rgb){7, 7, 7, 255});
/* for (size_t i = 0; i < iso.vertex_num; i++) { */
/* iso_copy.vertices[i] = vec3_mat3x3_mul(iso.vertices[i], rotation); */
/* } */
for (size_t i = 0; i < monkey.vertex_num; i++) {
monkey_copy.vertices[i].y = monkey.vertices[i].y + sin(t*0.01) * 2;
}
draw_mesh(state.canvas, WHITE, cam, plane_copy);
/* draw_mesh(state.canvas, WHITE, cam, iso_copy); */
draw_mesh(state.canvas, WHITE, cam, monkey_copy);
render(state);
/* continuous keyboard events */
const uint8_t *keyboard = SDL_GetKeyboardState(NULL);
scalar velocity = 0.1;
if (keyboard[SDL_SCANCODE_W]) {
cam.pos = vec3_sub(cam.pos, vec3_scalar_mul(cam.front, velocity));
}
if (keyboard[SDL_SCANCODE_S]) {
cam.pos = vec3_add(cam.pos, vec3_scalar_mul(cam.front, velocity));
}
if (keyboard[SDL_SCANCODE_A]) {
cam.pos = vec3_sub(cam.pos, vec3_scalar_mul(cam.right, velocity));
}
if (keyboard[SDL_SCANCODE_D]) {
cam.pos = vec3_add(cam.pos, vec3_scalar_mul(cam.right, velocity));
}
if (keyboard[SDL_SCANCODE_SPACE]) {
cam.pos = vec3_add(cam.pos, vec3_scalar_mul(UP, velocity));
}
if (keyboard[SDL_SCANCODE_LCTRL]) {
cam.pos = vec3_sub(cam.pos, vec3_scalar_mul(UP, velocity));
}
/* other events */
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.scancode) {
case SDL_SCANCODE_F3:
if (!event.key.repeat) wireframe = !wireframe;
break;
case SDL_SCANCODE_F6:
if (!event.key.repeat) screenshot(state.canvas);
break;
default:
break;
}
break;
case SDL_MOUSEMOTION:
cam.yaw += -event.motion.xrel * 0.001;
cam.pitch += event.motion.yrel * 0.001;
break;
case SDL_QUIT:
goto quit;
}
}
update_camera(&cam);
elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0;
SDL_Delay(clamp(16.666f - elapsed, 0, 1000));
t++;
}
quit:
/* free_mesh(iso); */
/* free_mesh(iso_copy); */
free_mesh(plane);
free_mesh(plane_copy);
free_mesh(monkey);
free_mesh(monkey_copy);
free_sdl(state);
}