From afa49424839562edc89020c1b376135b9b44e6c9 Mon Sep 17 00:00:00 2001 From: KonAki Date: Sun, 11 Oct 2026 16:56:18 +0800 Subject: examples: handmade, one file on the raw libs with a map and a model in the source --- examples/handmade.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 examples/handmade.c (limited to 'examples') diff --git a/examples/handmade.c b/examples/handmade.c new file mode 100644 index 0000000..072106e --- /dev/null +++ b/examples/handmade.c @@ -0,0 +1,235 @@ +/* + * 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. w and s walk, a and d strafe, left and right turn, escape quits + */ + +#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; + 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; +} + +static uint32_t colorOf(const char *name) +{ + if (!strcmp(name, "stone")) return 0xFF6C6688; + if (!strcmp(name, "brick")) return 0xFF8A5560; + if (!strcmp(name, "plaster")) return 0xFFB5AECB; + 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_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, camera.position.y, state->z + fz); + 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; +} -- cgit