traza/main.c

685 lines
16 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 */
#include "types.h"
#define W 800
#define H 600
#define PI 3.14159265358979323844
int wireframe = 0;
void
die(char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
typedef struct {
unsigned char r, g, b, a;
} rgb;
const rgb WHITE = {255, 255, 255, 255};
const rgb BLACK = {0, 0, 0, 255};
const rgb RED = {255, 0, 0, 255};
const rgb GREEN = {0, 255, 0, 255};
const rgb BLUE = {0, 0, 255, 255};
typedef struct {
SDL_Renderer *renderer;
SDL_Window *window;
SDL_Texture *tex;
rgb *canvas;
} sdl_state;
typedef struct {
vec2 a, b, c;
} triangle2;
typedef struct {
vec3 a, b, c;
} triangle3;
typedef struct {
uint32_t vertex_num;
uint32_t tri_num; /* for loops */
uint32_t index_num;
vec3 *vertices;
uint32_t *indices;
} mesh;
typedef struct {
double nums[3][3];
} matrix3x3;
vec3
vec3_matrix3x3_multiply(vec3 v, matrix3x3 r)
{
return (vec3) {
.x = (v.x * r.nums[0][0]) + (v.y * r.nums[0][1]) + (v.z * r.nums[0][2]),
.y = (v.x * r.nums[1][0]) + (v.y * r.nums[1][1]) + (v.z * r.nums[1][2]),
.z = (v.x * r.nums[2][0]) + (v.y * r.nums[2][1]) + (v.z * r.nums[2][2])
};
}
camera
new_camera(double range, vec3 pos, vec3 target, vec3 up)
{
if (pos.x == target.x &&
pos.y == target.y &&
pos.z == target.z)
die("cannot create camera with same target and position");
camera ret = {
.range = range,
.pos = pos,
.target = target,
.up = up,
};
ret.dir = vec3_normalize(vec3_sub(pos, target));
ret.right = vec3_normalize(vec3_cross(up, ret.dir));
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;
}
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(double *a, double *b)
{
double 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});
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 eps = 0;
if (y1 > y0) {
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++;
eps -= dx;
}
}
}
else {
int x = x0;
for (int y = y0; y <= y1; y++) {
plot_rgb(canvas, x, y, c);
eps += dx;
if ((eps * 2) >= dy) {
x++;
eps -= dy;
}
}
}
}
else {
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--;
eps -= dx;
}
}
}
else {
swap(x0, x1);
swap(y0, y1);
int x = x0;
for (int y = y0; y <= y1; y++) {
plot_rgb(canvas, x, y, c);
eps += dx;
if ((eps * 2) >= dy) {
x--;
eps -= dy;
}
}
}
}
}
void
draw_horizontal_line(rgb canvas[], rgb c, int32_t x0, int32_t x1, int32_t y)
{
if (y < 0) return;
if (x0 < 0) x0 = 0;
if (x1 >= W) x1 = W - 1;
if (y >= H) return;
for (int32_t x = x0; x <= x1; x++)
canvas[x + y * W] = c;
}
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, triangle2 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;
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 dx_left = abs(bx - ax);
int32_t dy_left = abs(by - ay);
int32_t eps_r = 0;
int32_t eps_l = 0;
int32_t xr = ax;
int32_t xl = ax;
int32_t xr_change = ax < cx ? 1 : -1;
int32_t xl_change = ax < bx ? 1 : -1;
for (int32_t y = ay; y < by; y++) {
eps_r += dx_right;
eps_l += dx_left;
while (eps_r > 0) {
eps_r -= dy_right;
xr += xr_change;
}
while (eps_l > 0) {
eps_l -= dy_left;
xl += xl_change;
}
draw_horizontal_line(canvas, c, xl, xr, y);
}
}
void
fill_top_flat_triangle(rgb canvas[], rgb c, triangle2 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;
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 dx_left = abs(cx - ax);
int32_t dy_left = abs(cy - ay);
int32_t eps_r = 0;
int32_t eps_l = 0;
int32_t xr = cx;
int32_t xl = cx;
int32_t xr_change = cx < bx ? 1 : -1;
int32_t xl_change = cx < ax ? 1 : -1;
int32_t y;
for (y = cy; y > by; y--) {
eps_r += dx_right;
eps_l += dx_left;
while (eps_r > 0) {
eps_r -= dy_right;
xr += xr_change;
}
while (eps_l > 0) {
eps_l -= dy_left;
xl += xl_change;
}
draw_horizontal_line(canvas, c, xl, xr, 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, y);
}
/* http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html */
void
fill_triangle(rgb canvas[], rgb c, triangle2 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 {
vec2 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};
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, (triangle2) {
tri.b, trid, tri.c
});
fill_bottom_flat_triangle(canvas, c, (triangle2) {
tri.a, tri.b, trid
});
}
}
vec2
midpoint(vec2 a, vec2 b)
{
return (vec2) {
(a.x + b.x) / 2,
(a.y + b.y) / 2
};
}
vec2
project(camera c, vec3 v)
{
double r = 200 / (v.z + c.range);
return (vec2){W / 2 + r * v.x, H / 2 + r * v.y};
}
triangle2
project_triangle(camera c, triangle3 tri)
{
return (triangle2) {
.a = project(c, tri.a),
.b = project(c, tri.b),
.c = project(c, tri.c),
};
}
/* 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++) {
if (wireframe)
draw_triangle(canvas, col, project_triangle(c, (triangle3) {
mesh.vertices[mesh.indices[3*i ]],
mesh.vertices[mesh.indices[3*i+1]],
mesh.vertices[mesh.indices[3*i+2]],
}));
else
fill_triangle(canvas, col, project_triangle(c, (triangle3) {
mesh.vertices[mesh.indices[3*i ]],
mesh.vertices[mesh.indices[3*i+1]],
mesh.vertices[mesh.indices[3*i+2]],
}));
}
}
matrix3x3
matrix3x3_multiply(matrix3x3 r, matrix3x3 s)
{
#define a r.nums
#define b s.nums
return (matrix3x3) {
.nums =
{{a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2]},
{a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2]},
{a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2]}}
};
#undef a
#undef b
}
double
to_deg(double deg)
{
return deg * 2 * PI / 360.0;
}
matrix3x3
x_rotation(double deg)
{
return (matrix3x3) {
.nums =
{{1, 0, 0},
{0, cos(to_deg(deg)), -sin(to_deg(deg))},
{0, sin(to_deg(deg)), cos(to_deg(deg))}}
};
}
matrix3x3
y_rotation(double deg)
{
return (matrix3x3) {
.nums =
{{ cos(to_deg(deg)), 0, sin(to_deg(deg))},
{ 0, 1, 0},
{-sin(to_deg(deg)), 0, cos(to_deg(deg))}}
};
}
matrix3x3
z_rotation(double deg)
{
return (matrix3x3) {
.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(180, (vec3){0, 0, 3}, (vec3){0, 0, 0}, UP);
print_camera(&cam);
SDL_Event event;
vec3 vertices[8] = {
/* front */
{0, 0, 0},
{100, 0, 0},
{100, 100, 0},
{0, 100, 0},
/* back */
{0, 0, 100},
{100, 0, 100},
{100, 100, 100},
{0, 100, 100},
};
uint32_t indices[3 * 12] = {
/* front */
0, 1, 2,
2, 3, 0,
/* back */
4, 5, 6,
6, 7, 4,
/* left */
0, 4, 7,
7, 3, 0,
/* right */
1, 5, 6,
6, 2, 1,
/* top */
0, 1, 5,
5, 4, 0,
/* bottom */
3, 2, 6,
6, 7, 3,
};
const mesh box = new_mesh(8, 12, 3 * 12, vertices, indices);
mesh box_copy = new_mesh(8, 12, 3 * 12, vertices, indices);
uint64_t t = 0;
for (;;) {
double elapsed, start = SDL_GetPerformanceCounter();
matrix3x3 m = matrix3x3_multiply(
matrix3x3_multiply(
z_rotation(1.2 * t),
y_rotation(1.1 * t)),
x_rotation(1.0 * t));
int x, y;
uint32_t buttons = SDL_GetMouseState(&x, &y);
if (buttons & SDL_BUTTON(2))
screenshot(state.canvas);
else if (buttons & (SDL_BUTTON(1)))
wireframe = !wireframe;
clear_canvas(state.canvas, BLACK);
for (size_t i = 0; i < box.vertex_num; i++) {
box_copy.vertices[i] = vec3_matrix3x3_multiply(box.vertices[i], m);
}
draw_mesh(state.canvas, WHITE, cam, box_copy);
render(state);
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
break;
elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0;
SDL_Delay(clamp(16.666f - elapsed, 0, 1000));
t++;
}
free_mesh(box);
free_mesh(box_copy);
free_sdl(state);
}