#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; } }