#include "linalg.h" #include "texture_map.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const f3 verts[] = { // clang-format off { 0, 0, 0 }, // 0 front top left { 1, 0, 0 }, // 1 front top right { 0, 1, 0 }, // 2 front bottom left { 1, 1, 0 }, // 3 front bottom right { 0, 0, 1 }, // 4 back top left { 1, 0, 1 }, // 5 back top right { 0, 1, 1 }, // 6 back bottom left { 1, 1, 1 }, // 7 back bottom right // clang-format on }; const size_t shapes[][3] = { // back { 0, 1, 2 }, { 3, 2, 1 }, // bottom { 4, 5, 0 }, { 1, 0, 5 }, // left { 4, 0, 6 }, { 2, 6, 0 }, // front { 5, 4, 7 }, { 6, 7, 4 }, // top { 7, 6, 3 }, { 2, 3, 6 }, // right { 1, 5, 3 }, { 7, 3, 5 }, }; constexpr size_t verts_size = sizeof(verts) / sizeof(verts[0]); constexpr size_t shapes_size = sizeof(shapes) / sizeof(shapes[0]); constexpr int screen_width = 1024; constexpr int screen_height = 1024; constexpr size_t colors_size = 32; uint32_t colors[colors_size] = { 0 }; constexpr size_t textures_size = 1; uint32_t textures[textures_size][64]; const f2 verts_uv_map[verts_size] = { // clang-format off { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, // clang-format on }; const size_t shapes_textures_map[shapes_size] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #ifndef M_PI #define M_PI (3.141529653578) #endif double pi(double v) { return v * M_PI; } #define MAX(L, R) ((L) >= (R) ? (L) : (R)) #define MIN(L, R) ((L) < (R) ? (L) : (R)) void set_px(uint32_t* pixels, int x, int y, uint32_t color) { if ((uint32_t)x >= (size_t)screen_width || (uint32_t)y >= (size_t)screen_height) return; pixels[y * screen_width + x] = color; } void draw_line_impl( uint32_t* pixels, int x0, int y0, int x1, int y1, uint32_t color, bool steep) { if (abs(x0 - x1) < abs(y0 - y1)) { draw_line_impl(pixels, y0, x0, y1, x1, color, true); return; } if (x0 > x1) { draw_line_impl(pixels, x1, y1, x0, y0, color, steep); return; } int dx = x1 - x0; int dy = y1 - y0; int derror2 = abs(dy) * 2; int error2 = 0; int y = y0; for (int x = x0; x <= x1; ++x) { if (steep) { set_px(pixels, y, x, color); } else { set_px(pixels, x, y, color); } error2 += derror2; if (error2 > dx) { y += y1 > y0 ? 1 : -1; error2 -= dx * 2; } } } void draw_line(uint32_t* pixels, i2 p0, i2 p1, uint32_t color) { draw_line_impl(pixels, p0.x, p0.y, p1.x, p1.y, color, false); } void draw_triangle_lines(uint32_t* pixels, i2x3 tri, uint32_t color) { draw_line(pixels, tri.p0, tri.p1, color); draw_line(pixels, tri.p1, tri.p2, color); draw_line(pixels, tri.p2, tri.p0, color); } double signed_triangle_area(int x0, int y0, int x1, int y1, int x2, int y2) { return 0.5 * ((y1 - y0) * (x1 + x0) + (y2 - y1) * (x2 + x1) + (y0 - y2) * (x0 + x2)); } void draw_triangle_filled(uint32_t* pixels, i2x3 tri, uint32_t color) { int x0 = tri.p0.x; int y0 = tri.p0.y; int x1 = tri.p1.x; int y1 = tri.p1.y; int x2 = tri.p2.x; int y2 = tri.p2.y; int rect_x0 = MIN(MIN(x0, x1), x2); int rect_y0 = MIN(MIN(y0, y1), y2); int rect_x1 = MAX(MAX(x0, x1), x2); int rect_y1 = MAX(MAX(y0, y1), y2); int rect_w = rect_x1 - rect_x0; int rect_h = rect_y1 - rect_y0; if (rect_w == 0 || rect_h == 0) return; double area = signed_triangle_area(x0, y0, x1, y1, x2, y2); for (int y = rect_y0; y <= rect_y1; ++y) { for (int x = rect_x0; x <= rect_x1; ++x) { double a0 = signed_triangle_area(x, y, x1, y1, x2, y2) / area; double a1 = signed_triangle_area(x, y, x2, y2, x0, y0) / area; double a2 = signed_triangle_area(x, y, x0, y0, x1, y1) / area; if (a0 < 0 || a1 < 0 || a2 < 0) continue; set_px(pixels, x, y, color); } } } f3 rotate_x(f3 v, double angle) { f3x3 rotation_mx = { // clang-format off 1, 0, 0, 0, cos(angle), -sin(angle), 0, sin(angle), cos(angle), // clang-format on }; return mul_f3_f3x3(v, rotation_mx); } f3 rotate_y(f3 v, double angle) { f3x3 rotation_mx = { // clang-format off cos(angle), 0, sin(angle), 0, 1, 0, -sin(angle), 0, cos(angle), // clang-format on }; return mul_f3_f3x3(v, rotation_mx); } f3 rotate_z(f3 v, double angle) { f3x3 rotation_mx = { // clang-format off cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1, // clang-format on }; return mul_f3_f3x3(v, rotation_mx); } f3 translate(f3 v, double x, double y, double z) { return add_f3(v, (f3) { x, y, z }); } f2 f3_project(f3 v, double focal_length) { focal_length *= -1; return (f2) { (v.x * focal_length) / (v.z + focal_length), (-v.y * focal_length) / (v.z + focal_length), }; } i2 f2_to_screen_i2(f2 v) { return (i2) { (int)(v.x * (screen_width / 4.0) + screen_width / 2.0), (int)(v.y * (screen_width / 4.0) + screen_width / 2.0), }; } f2 f2_to_screen(f2 v) { return (f2) { v.x * (screen_width / 4.0) + screen_width / 2.0, v.y * (screen_width / 4.0) + screen_width / 2.0, }; } void draw_shape(uint32_t* pixels, i2x3 verts, uint32_t color) { double cross_z = (verts.p1.x - verts.p0.x) * (verts.p2.y - verts.p0.y) - (verts.p1.y - verts.p0.y) * (verts.p2.x - verts.p0.x); if (cross_z <= 0) return; draw_triangle_filled(pixels, verts, color); // draw_triangle_lines(pixels, verts, 0xff000000); } double f2x3_signed_area(f2x3 s) { return 0.5 * ((s.p1.y - s.p0.y) * (s.p1.x + s.p0.x) + (s.p2.y - s.p1.y) * (s.p2.x + s.p1.x) + (s.p0.y - s.p2.y) * (s.p0.x + s.p2.x)); } void draw_shape_with_texture(uint32_t* pixels, size_t shape_idx, f2x3 s) { double cross_z = (s.p1.x - s.p0.x) * (s.p2.y - s.p0.y) - (s.p1.y - s.p0.y) * (s.p2.x - s.p0.x); if (cross_z <= 0) return; double rect_x0 = MIN(MIN(s.p0.x, s.p1.x), s.p2.x); double rect_y0 = MIN(MIN(s.p0.y, s.p1.y), s.p2.y); double rect_x1 = MAX(MAX(s.p0.x, s.p1.x), s.p2.x); double rect_y1 = MAX(MAX(s.p0.y, s.p1.y), s.p2.y); double rect_w = rect_x1 - rect_x0; double rect_h = rect_y1 - rect_y0; if (rect_w == 0 || rect_h == 0) return; double area = f2x3_signed_area(s); for (int y = (int)rect_y0; y <= rect_y1; ++y) { for (int x = (int)rect_x0; x <= rect_x1; ++x) { f2 p = { x, y }; double a0 = f2x3_signed_area((f2x3) { p, s.p1, s.p2 }) / area; double a1 = f2x3_signed_area((f2x3) { p, s.p2, s.p0 }) / area; double a2 = f2x3_signed_area((f2x3) { p, s.p0, s.p1 }) / area; if (a0 < 0 || a1 < 0 || a2 < 0) continue; f2 uv = { x / rect_x1, y / rect_y1 }; // uint32_t color // = (uint32_t)(0xff * uv.x) << 0 | (uint32_t)(0xff * uv.y) << // 8; // uint32_t color = colors[shape_idx]; uint32_t color = textures[0][MIN((size_t)(uv.y * 8 * 8 + uv.x * 8), 63)]; set_px(pixels, x, y, 0xff000000 | color); } } } void render(uint32_t* pixels, uint64_t total_ms) { double time_frac = total_ms % 4000 / 4000.0; i2 screen_verts_i2[verts_size]; f2 screen_verts[verts_size]; for (size_t i = 0; i < verts_size; ++i) { f3 v = verts[i]; v = translate(v, -0.5, -0.5, -0.5); // v = rotate_y(v, pi(2) * time_frac); v = rotate_x(v, M_PI * 2 * time_frac); v = rotate_y(v, M_PI * 2 * time_frac); v = rotate_z(v, M_PI * 2 * time_frac); v = translate(v, -1, -1, 0); v = translate(v, 10 * time_frac, 10 * time_frac, -20 * time_frac); screen_verts_i2[i] = f2_to_screen_i2(f3_project(v, 4.0)); screen_verts[i] = f2_to_screen(f3_project(v, 4.0)); } for (size_t shape_idx = 0; shape_idx < shapes_size; ++shape_idx) { i2x3 shape_verts_i2; f2x3 shape_verts; for (size_t vert_idx = 0; vert_idx < 3; ++vert_idx) { ((i2*)&shape_verts_i2)[vert_idx] = screen_verts_i2[shapes[shape_idx][vert_idx]]; ((f2*)&shape_verts)[vert_idx] = screen_verts[shapes[shape_idx][vert_idx]]; } #ifdef DRAW_TEXTURE draw_shape_with_texture(pixels, shape_idx, shape_verts); #elifdef DRAW_LINES draw_triangle_lines(pixels, shape_verts_i2, 0xffffffff); #else draw_shape(pixels, shape_verts_i2, colors[shape_idx]); #endif } } int main(void) { srand((unsigned int)time(nullptr)); TextureMap* texture_map = texture_map_new("texture_map.tga"); for (size_t i = 0; i < textures_size; ++i) { texture_map_read_texture(texture_map, textures[i], (int)i); } texture_map_free(texture_map); for (size_t i = 0; i < colors_size; ++i) { uint32_t color = (uint32_t)((double)rand() / RAND_MAX * 0xffffff); colors[i] = 0xffu << 24 | color; } SDL_Init(SDL_INIT_VIDEO); SDL_Window* window; SDL_Renderer* renderer; SDL_CreateWindowAndRenderer( screen_width, screen_height, 0, &window, &renderer); SDL_Texture* buffer = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, screen_width, screen_height); SDL_SetTextureBlendMode(buffer, SDL_BLENDMODE_BLEND); uint64_t before = SDL_GetTicks64(); while (true) { uint64_t now = SDL_GetTicks64(); uint64_t delta = now - before; (void)delta; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: goto exit_game; case SDL_KEYDOWN: { switch (event.key.keysym.sym) { case SDLK_ESCAPE: goto exit_game; } } } } SDL_RenderClear(renderer); uint32_t* pixels; int row_size; SDL_LockTexture( buffer, &(SDL_Rect) { 0, 0, screen_width, screen_height }, (void**)&pixels, &row_size); row_size /= (int)sizeof(uint32_t); memset( pixels, 0, sizeof(int) * (size_t)screen_width * (size_t)screen_height); render(pixels, now); SDL_UnlockTexture(buffer); SDL_RenderCopy(renderer, buffer, nullptr, nullptr); SDL_RenderPresent(renderer); SDL_Delay(16); before = now; } exit_game:; printf("exitting...\n"); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); }