#define DEBUG #include #include #include #include #include #include #include /* 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); }