/* * handmade: everything in one file, on the raw libs only * * no program module, no assets, no build script. copy these headers next to this file: * konwinlib.h konsofren.h kontime.h konmath.h konsector.h * and build it (linux): cc -std=c99 -o handmade handmade.c -lX11 -lXext -lXrandr -lm * (windows): cc -std=c99 -o handmade.exe handmade.c -lgdi32 -lm * * the map is text inside this file and the gem is a list of vertices. click to grab the mouse and look around, tab lets go. * w and s walk, a and d strafe, left and right turn, escape quits. which picture goes on which texture name is the table in textures[] */ #define KONSOFREN_3D #define KONWINLIB_IMPLEMENTATION #define KONSOFREN_IMPLEMENTATION #define KONTIME_IMPLEMENTATION #define KONSECTOR_IMPLEMENTATION #include #include #include #include "konwinlib.h" #include "konsofren.h" #include "kontime.h" #include "konmath.h" #include "konsector.h" #define GAME_WIDTH 320 #define GAME_HEIGHT 180 #define EYE 0.8f #define RADIUS 0.25f /*** the map ***/ /* x goes east, z goes south, corners go counter clockwise seen from above. two rooms and a short passage, and a picture on the east wall of the far room */ static const char crypt[] = "kmap 1\n" "v 0 0\n" "v 0 6\n" "v 6 6\n" "v 6 4\n" "v 6 2\n" "v 6 0\n" "v 10 2\n" "v 10 4\n" "v 16 6\n" "v 16 0\n" "sector 0 3 light 0.9 floor stone ceiling stone wall brick : 0 1 2 3 4 5\n" "sector 0 2.2 light 0.5 floor stone ceiling stone wall brick : 4 3 7 6\n" "sector 0 4 light 0.7 floor stone ceiling stone wall plaster : 6 7 8 9\n" "entity spawn 2 0.8 3 0\n" "quad mark 15.95 0.4 2 15.95 0.4 4 15.95 2.4 4 15.95 2.4 2 light 0.8\n"; /*** the gem ***/ #define V(x, y, z, nx, ny, nz) { { x, y, z }, { nx, ny, nz }, { 0, 0 } } static const kon_vertex_t gemVertices[24] = { V(1, 0, 0, 0.577, 0.577, 0.577), V(0, 1, 0, 0.577, 0.577, 0.577), V(0, 0, 1, 0.577, 0.577, 0.577), V(1, 0, 0, 0.577, 0.577, -0.577), V(0, 0, -1, 0.577, 0.577, -0.577), V(0, 1, 0, 0.577, 0.577, -0.577), V(1, 0, 0, 0.577, -0.577, 0.577), V(0, 0, 1, 0.577, -0.577, 0.577), V(0, -1, 0, 0.577, -0.577, 0.577), V(1, 0, 0, 0.577, -0.577, -0.577), V(0, -1, 0, 0.577, -0.577, -0.577), V(0, 0, -1, 0.577, -0.577, -0.577), V(-1, 0, 0, -0.577, 0.577, 0.577), V(0, 0, 1, -0.577, 0.577, 0.577), V(0, 1, 0, -0.577, 0.577, 0.577), V(-1, 0, 0, -0.577, 0.577, -0.577), V(0, 1, 0, -0.577, 0.577, -0.577), V(0, 0, -1, -0.577, 0.577, -0.577), V(-1, 0, 0, -0.577, -0.577, 0.577), V(0, -1, 0, -0.577, -0.577, 0.577), V(0, 0, 1, -0.577, -0.577, 0.577), V(-1, 0, 0, -0.577, -0.577, -0.577), V(0, 0, -1, -0.577, -0.577, -0.577), V(0, -1, 0, -0.577, -0.577, -0.577), }; static const uint16_t gemIndices[24] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; static const kon_mesh_t gem = { gemVertices, gemIndices, 8 }; /*** state ***/ typedef struct state { kon_window_t *window; kon_display_t *display; kon_sectorMap_t *map; kon_sectorMesh_t *mesh; kon_material_t materials[8]; kon_image *mark; float x, z, yaw, pitch; int sector; int forward, strafe, turn; kon_time last, time; } state_t; /*** images made in code ***/ static kon_image *makeMark(void) { kon_image *image = kon_createFramebuffer(32, 40); int i; if (!image) return NULL; kon_clearFramebuffer(image, 0xFFF2A7C3); kon_fillRectangle(image, 2, 2, 28, 36, 0xFF332E4E); for (i = 0; i < 28; i++) { kon_fillRectangle(image, 2 + i, 2 + i * 36 / 28, 1, 1, 0xFFF2A7C3); kon_fillRectangle(image, 2 + i, 37 - i * 36 / 28, 1, 1, 0xFFF2A7C3); } return image; } /* the names the map uses and what they look like. a name that is not in here is grey */ static const struct { const char *name; uint32_t color; } textures[] = { { "stone", 0xFF6C6688 }, { "brick", 0xFF8A5560 }, { "plaster", 0xFFB5AECB }, }; static uint32_t colorOf(const char *name) { size_t i; for (i = 0; i < sizeof(textures) / sizeof(textures[0]); i++) { if (strcmp(name, textures[i].name) == 0) return textures[i].color; } return 0xFF9A93B3; } /*** moving ***/ /* the four corners of the body all have to be inside some room */ static int fits(const state_t *state, float x, float z) { static const float dx[4] = { -1, 1, 1, -1 }, dz[4] = { -1, -1, 1, 1 }; int i; for (i = 0; i < 4; i++) { if (kon_sectorAt(state->map, x + dx[i] * RADIUS, z + dz[i] * RADIUS, state->sector) < 0) return 0; } return 1; } static void walk(state_t *state, float dx, float dz) { if (fits(state, state->x + dx, state->z + dz)) { state->x += dx; state->z += dz; } else if (fits(state, state->x + dx, state->z)) { state->x += dx; } else if (fits(state, state->x, state->z + dz)) { state->z += dz; } state->sector = kon_sectorAt(state->map, state->x, state->z, state->sector); } /*** the loop ***/ static void frame(void *data) { state_t *state = data; kon_framebuffer_t *game = state->display->game; kon_camera_t camera = kon_cameraDefault(); kon_light_t light = kon_lightDefault(); kon_event_t ev; kon_time now = kon_getTime(), dt = now - state->last; float fx = sinf(state->yaw), fz = -cosf(state->yaw), step = 2.0f * (float)dt; kon_mat4_t vp; state->last = now; if (dt > 0.25) dt = 0.25; state->time += dt; while (kon_pollEvent(state->window, &ev)) { int down = ev.type == KON_EVENT_KEY_DOWN; if (ev.type == KON_EVENT_RESIZE) kon_resizeDisplay(state->display, ev.width, ev.height); if (ev.type == KON_EVENT_MOUSE_MOVE && kon_isMouseCaptured(state->window)) { state->yaw += (float)ev.mouseDX * 0.0015f; state->pitch -= (float)ev.mouseDY * 0.0015f; if (state->pitch > 1.2f) state->pitch = 1.2f; if (state->pitch < -1.2f) state->pitch = -1.2f; } if (ev.type == KON_EVENT_MOUSE_DOWN && !kon_isMouseCaptured(state->window)) kon_setMouseCapture(state->window, true); if (ev.type == KON_EVENT_KEY_DOWN && ev.key == KON_KEY_TAB) kon_setMouseCapture(state->window, !kon_isMouseCaptured(state->window)); if (ev.type != KON_EVENT_KEY_DOWN && ev.type != KON_EVENT_KEY_UP) continue; switch (ev.key) { case KON_KEY_W: state->forward = down ? 1 : (state->forward > 0 ? 0 : state->forward); break; case KON_KEY_S: state->forward = down ? -1 : (state->forward < 0 ? 0 : state->forward); break; case KON_KEY_D: state->strafe = down ? 1 : (state->strafe > 0 ? 0 : state->strafe); break; case KON_KEY_A: state->strafe = down ? -1 : (state->strafe < 0 ? 0 : state->strafe); break; case KON_KEY_RIGHT: state->turn = down ? 1 : (state->turn > 0 ? 0 : state->turn); break; case KON_KEY_LEFT: state->turn = down ? -1 : (state->turn < 0 ? 0 : state->turn); break; default: break; } } state->yaw += (float)state->turn * 2.0f * (float)dt; walk(state, (fx * (float)state->forward - fz * (float)state->strafe) * step, (fz * (float)state->forward + fx * (float)state->strafe) * step); camera.position = KON_VEC3(state->x, state->map->sectors[state->sector].floor + EYE, state->z); camera.target = KON_VEC3(state->x + fx * cosf(state->pitch), camera.position.y + sinf(state->pitch), state->z + fz * cosf(state->pitch)); camera.fov = KON_DEG2RAD(60); camera.nearZ = 0.05f; camera.farZ = 40.0f; vp = kon_cameraViewProjection(&camera, game->width, game->height); kon_clearFramebuffer(game, 0xFF1B1826); light.direction = KON_VEC3(-0.3f, -1.0f, -0.2f); light.ambient = 0.5f; kon_sectorDraw(game, &camera, state->mesh, state->materials, state->map->textureCount, &light); kon_drawMesh(game, vp, kon_mat4FromTransform(KON_VEC3(13.0f, 1.2f, 3.5f), KON_VEC3(0.0f, (float)state->time, 0.0f), KON_VEC3(0.4f, 0.6f, 0.4f)), &gem, &state->materials[7], &light, KON_RENDER_DEFAULT); kon_presentDisplay(state->display); kon_blitPixels(state->window, state->display->screen->data, state->display->screen->width, state->display->screen->height); kon_sleep(0.004); } int main(void) { static state_t state; char error[160]; int i; state.map = kon_sectorParse(crypt, error, sizeof(error)); if (!state.map) { fprintf(stderr, "map: %s\n", error); return 1; } state.mesh = kon_sectorBuild(state.map); state.mark = makeMark(); for (i = 0; i < state.map->textureCount && i < 7; i++) { state.materials[i] = strcmp(state.map->textures[i], "mark") == 0 ? kon_materialTexture(state.mark) : kon_materialColor(colorOf(state.map->textures[i])); } state.materials[7] = kon_materialColor(0xFF8FD3C8); if (!state.mesh || !state.mark || !kon_init()) return 1; state.window = kon_createWindow("handmade", 0, 0, GAME_WIDTH * 3, GAME_HEIGHT * 3, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); if (!state.window) return 1; kon_setExitKey(state.window, KON_KEY_ESCAPE); state.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, GAME_WIDTH * 3, GAME_HEIGHT * 3); if (!state.display) return 1; state.x = state.map->entities[0].position.x; state.z = state.map->entities[0].position.z; state.sector = kon_sectorAt(state.map, state.x, state.z, -1); state.last = kon_getTime(); kon_runMainLoop(state.window, frame, &state); kon_freeDisplay(state.display); kon_destroyWindow(state.window); kon_deinit(); kon_sectorMeshFree(state.mesh); kon_sectorFree(state.map); kon_freeFramebuffer(state.mark); return 0; }