spinny-cube/main.c
2026-03-24 22:47:12 +01:00

455 lines
11 KiB
C

#include "linalg.h"
#include "texture_map.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_blendmode.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_video.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
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();
}