From ad6536bdabe3d4a70285c84d1796e05b785fc435 Mon Sep 17 00:00:00 2001 From: odweta Date: Wed, 14 Oct 2026 02:52:30 +0200 Subject: [PATCH] add rotating 3d cube --- include/dangerzone/engine.h | 8 +- include/dangerzone/render/misc.h | 25 +++- src/engine/render/misc.c | 91 +++++++++++- src/examples/3d.c | 231 +++++++++++++++++++++++++++++++ src/examples/CMakeLists.txt | 4 + 5 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 src/examples/3d.c diff --git a/include/dangerzone/engine.h b/include/dangerzone/engine.h index e532845..92b2049 100644 --- a/include/dangerzone/engine.h +++ b/include/dangerzone/engine.h @@ -9,6 +9,10 @@ typedef enum { OVERDRAW } framebuffer_e; +typedef struct { + double x, y; +} v2; + typedef struct { SDL_Event event; bool is_running; @@ -17,9 +21,9 @@ typedef struct { SDL_Rect dirty_rect; uint16_t width, height; // base layer ~ layer 0 - uint32_t *framebuffer_base; + uint32_t *framebuffer_base; // layer on top of that ~ layer 1 - uint32_t *framebuffer_overdraw; + uint32_t *framebuffer_overdraw; } game_state_t; extern game_state_t *game_state; diff --git a/include/dangerzone/render/misc.h b/include/dangerzone/render/misc.h index 5662a46..122ca82 100644 --- a/include/dangerzone/render/misc.h +++ b/include/dangerzone/render/misc.h @@ -4,16 +4,33 @@ #include -void +void put_pixel( framebuffer_e framebuffer, - unsigned int x, - unsigned int y, + unsigned int x, + unsigned int y, uint32_t color ); -void +void clear_framebuffer( framebuffer_e framebuffer, uint32_t color ); + +void +put_line( + v2 a, + v2 b, + framebuffer_e framebuffer, + uint32_t color +); + +void +put_triangle( + v2 a, + v2 b, + v2 c, + framebuffer_e framebuffer, + uint32_t color +); diff --git a/src/engine/render/misc.c b/src/engine/render/misc.c index 88dbe8e..6485a53 100644 --- a/src/engine/render/misc.c +++ b/src/engine/render/misc.c @@ -6,6 +6,8 @@ #include "internal/util/error.h" #include +#include +#include // helper function to return the correct pointer to a framebuffer for drawing selection uint32_t* @@ -23,11 +25,11 @@ get_framebuffer( } // alter a pixel at an imaginary x & y coordinates -void +void put_pixel( framebuffer_e framebuffer, - unsigned int x, - unsigned int y, + unsigned int x, + unsigned int y, uint32_t color ) { uint32_t *current_framebuffer = get_framebuffer(framebuffer); @@ -55,7 +57,7 @@ clear_framebuffer( uint32_t color ) { uint32_t *current_framebuffer = get_framebuffer(framebuffer); - + for (size_t i = 0; i < (size_t)game_state->width * game_state->height; ++i) { @@ -69,3 +71,84 @@ clear_framebuffer( game_state->height ); } + +void +normalize_v2( + v2 input, + v2 *output +) { + double min = (input.x > input.y) ? input.y : input.x; + + for (int i = min; i > 1; i--) { + if ((int)input.x % (int)min == 0 && (int)input.y % (int)min == 0) { + input.x /= min; + input.y /= min; + + break; + } + } + + output->x = input.x; + output->y = input.y; +} + +// subtract vector b from vector a +void +sub_v2( + v2 a, + v2 b, + v2 *dest +) { + dest->x = b.x - a.x; + dest->y = b.y - a.y; +} + +// draw a line between two points with a given color +void +put_line( + v2 a, v2 b, + framebuffer_e fb, + uint32_t color +) { + int x0 = (int)round(a.x); + int y0 = (int)round(a.y); + int x1 = (int)round(b.x); + int y1 = (int)round(b.y); + + int dx = abs(x1 - x0); + int sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0); + int sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + + while (1) { + put_pixel(fb, x0, y0, color); + + if (x0 == x1 && y0 == y1) + break; + + int e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } +} + +// draw a frame of a triangle with a given color +void +put_triangle( + v2 a, + v2 b, + v2 c, + framebuffer_e framebuffer, + uint32_t color +) { + put_line(a, b, framebuffer, color); + put_line(b, c, framebuffer, color); + put_line(a, c, framebuffer, color); +} diff --git a/src/examples/3d.c b/src/examples/3d.c new file mode 100644 index 0000000..7d85365 --- /dev/null +++ b/src/examples/3d.c @@ -0,0 +1,231 @@ +#include +#include + +#include +#include +#include +#include + +// a point in 3D space +typedef struct +{ + double x, y, z; +} v3; + +// a triangle with 3 points in 3D space +typedef struct +{ + v3 p[3]; +} tri; + +// a grouping of triangles - can desribe any 3D object +typedef struct +{ + tri *tris; +} mesh; + +typedef struct +{ + float m[4][4]; +} mat4x4; + +#define PI 3.1415926535 + +void multiply_matrix_vector(v3 input, v3 *output, mat4x4 m) +{ + output->x = input.x * m.m[0][0] + input.y * m.m[1][0] + input.z * m.m[2][0] + m.m[3][0]; + output->y = input.x * m.m[0][1] + input.y * m.m[1][1] + input.z * m.m[2][1] + m.m[3][1]; + output->z = input.x * m.m[0][2] + input.y * m.m[1][2] + input.z * m.m[2][2] + m.m[3][2]; + double w = input.x * m.m[0][3] + input.y * m.m[1][3] + input.z * m.m[2][3] + m.m[3][3]; + + if (w != 0) + { + output->x /= w; + output->y /= w; + output->z /= w; + } +} + +#define ASSERT(_e) do { if (!(_e)) { fprintf(stderr, "assert failed for %s\n", #_e); exit(1); } } while (0) + +struct { + mesh m; + mat4x4 mproj; + double near, far, fov; + unsigned int screen_width, screen_height; +} state; + +// a unit-sized cube mesh defined with triangles +#define CUBE_TRI_AMOUNT 12 + +tri cube_mesh[CUBE_TRI_AMOUNT] = +{ + // south + {{ {0,0,0}, {0,1,0}, {1,1,0} }}, + {{ {0,0,0}, {1,1,0}, {1,0,0} }}, + // west + {{ {1,0,0}, {1,1,0}, {1,1,1} }}, + {{ {1,0,0}, {1,1,1}, {1,0,1} }}, + // north + {{ {1,0,1}, {1,1,1}, {0,1,1} }}, + {{ {1,0,1}, {0,1,1}, {0,0,1} }}, + // east + {{ {0,0,1}, {0,1,1}, {0,1,0} }}, + {{ {0,0,1}, {0,1,0}, {0,0,0} }}, + // top + {{ {0,1,0}, {0,1,1}, {1,1,1} }}, + {{ {0,1,0}, {1,1,1}, {1,1,0} }}, + // bottom + {{ {0,0,1}, {0,0,0}, {1,0,0} }}, + {{ {0,0,1}, {1,0,0}, {1,0,1} }}, +}; + +void +game_init() +{ + state.m.tris = cube_mesh; + + memset(&state.mproj, 0, sizeof(state.mproj)); + + state.near = 0.1; + state.far = 1000000.0; + state.fov = 90.0; + state.screen_width = 500; + state.screen_height = 500; + + // aspect ratio + double a = (float)state.screen_height / (float)state.screen_width; + + // fov tangent + double fov_rad = 1.0 / tanf(state.fov * 0.5 / 180.0 * PI); + + state.mproj.m[0][0] = a * fov_rad; + state.mproj.m[1][1] = fov_rad; + state.mproj.m[2][2] = state.far / (state.far - state.near); + state.mproj.m[3][2] = (-state.far * state.near) / (state.far - state.near); + state.mproj.m[2][3] = 1.0; +} + +void +client_poll_events() +{ + SDL_Event ev = game_state->event; + + if (ev.type == SDL_EVENT_QUIT) { + game_state->is_running = false; + } + + if (ev.type == SDL_EVENT_KEY_DOWN) { + switch (ev.key.key) { + case SDLK_Q: + game_state->is_running = false; + break; + default: + break; + } + } +} + + +void +client_update_framebuffer() +{ + clear_framebuffer(BASE, 0x000000); + clear_framebuffer(OVERDRAW, 0x000000); + + mat4x4 mrotz = { 0 }, mrotx = { 0 }; + + double theta = SDL_GetTicks() / 1000.0; + + // Rotation Z + mrotz.m[0][0] = cosf(theta); + mrotz.m[0][1] = sinf(theta); + mrotz.m[1][0] = -sinf(theta); + mrotz.m[1][1] = cosf(theta); + mrotz.m[2][2] = 1; + mrotz.m[3][3] = 1; + + // Rotation X + mrotx.m[0][0] = 1; + mrotx.m[1][1] = cosf(theta * 0.5f); + mrotx.m[1][2] = sinf(theta * 0.5f); + mrotx.m[2][1] = -sinf(theta * 0.5f); + mrotx.m[2][2] = cosf(theta * 0.5f); + mrotx.m[3][3] = 1; + + for (int i = 0; i < CUBE_TRI_AMOUNT; i++) + { + // project from 3D to 2D + tri triangle = state.m.tris[i]; + for (int k = 0; k < 3; k++) { + triangle.p[k].x -= 0.5; + triangle.p[k].y -= 0.5; + triangle.p[k].z -= 0.5; + } + tri projected, translated, rotatedz, rotatedzx; + + multiply_matrix_vector(triangle.p[0], &rotatedz.p[0], mrotz); + multiply_matrix_vector(triangle.p[1], &rotatedz.p[1], mrotz); + multiply_matrix_vector(triangle.p[2], &rotatedz.p[2], mrotz); + + multiply_matrix_vector(rotatedz.p[0], &rotatedzx.p[0], mrotx); + multiply_matrix_vector(rotatedz.p[1], &rotatedzx.p[1], mrotx); + multiply_matrix_vector(rotatedz.p[2], &rotatedzx.p[2], mrotx); + + translated = rotatedzx; + translated.p[0].z = rotatedzx.p[0].z + 3.0; + translated.p[1].z = rotatedzx.p[1].z + 3.0; + translated.p[2].z = rotatedzx.p[2].z + 3.0; + + multiply_matrix_vector(translated.p[0], &projected.p[0], state.mproj); + multiply_matrix_vector(translated.p[1], &projected.p[1], state.mproj); + multiply_matrix_vector(translated.p[2], &projected.p[2], state.mproj); + + // scale into view + projected.p[0].x += 1.0; + projected.p[0].y += 1.0; + + projected.p[1].x += 1.0; + projected.p[1].y += 1.0; + + projected.p[2].x += 1.0; + projected.p[2].y += 1.0; + + + projected.p[0].x *= 0.5 * state.screen_width; + projected.p[0].y *= 0.5 * state.screen_height; + + projected.p[1].x *= 0.5 * state.screen_width; + projected.p[1].y *= 0.5 * state.screen_height; + + projected.p[2].x *= 0.5 * state.screen_width; + projected.p[2].y *= 0.5 * state.screen_height; + + + put_triangle( + (v2){.x=projected.p[0].x, .y=projected.p[0].y}, + (v2){.x=projected.p[1].x, .y=projected.p[1].y}, + (v2){.x=projected.p[2].x, .y=projected.p[2].y}, + OVERDRAW, 0xFFFFFF + ); + } +} + + +int main() +{ + game_init(); + + if (engine_init( + state.screen_width, state.screen_height, "3D demo", + client_poll_events, client_update_framebuffer + )) { + engine_start(); + + engine_stop(); + + return 0; + } else { + return 1; + } +} diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 64be64c..0d90a92 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -5,5 +5,9 @@ add_executable(game_2048 2048.c) target_link_libraries(game_2048 PRIVATE dangerzone_engine) target_link_libraries(game_2048 PRIVATE m) +add_executable(game_3d 3d.c) +target_link_libraries(game_3d PRIVATE dangerzone_engine) +target_link_libraries(game_3d PRIVATE m) + #add_executable(game_window window.c) #target_link_libraries(game_window PRIVATE dangerzone_engine)