summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-11 16:29:45 +0800
committerKonAki <konaki@anokonaki.org>2026-10-11 16:29:45 +0800
commite7ab646556188582af37b3064bb765daef3c9aa9 (patch)
treefb5e4b02bcd426b4f7d657479bccf9b8d621c3dc /examples
parent41a0f89f0762f16f27dbac0fa211ecb342e024c6 (diff)
examples: sectorview, walk the house map
Diffstat (limited to 'examples')
-rw-r--r--examples/sectorview.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/examples/sectorview.c b/examples/sectorview.c
new file mode 100644
index 0000000..2816461
--- /dev/null
+++ b/examples/sectorview.c
@@ -0,0 +1,307 @@
+/*
+ * sectorview: walk around a map made of rooms and picture quads
+ *
+ * build: ./build.sh -d -m 3d,sector,font obj/sectorview
+ * run: obj/sectorview (from the folder with examples/)
+ *
+ * click to grab the mouse, tab lets go. w and s walk, a and d strafe, e presses a button close to you.
+ * the map is examples/maps/house.kmap, it is plain text so change it and run again. try moving the watcher
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "konprogram.h"
+#include "kon3d.h"
+#include "konsector.h"
+
+#if !defined(KON_MODULE_3D) || !defined(KON_MODULE_SECTOR) || !defined(KON_MODULE_FONT)
+#error "build with: ./build.sh -d -m 3d,sector,font obj/sectorview"
+#endif
+
+#define MAP_PATH "examples/maps/house.kmap"
+#define MAX_TEXTURES 32
+#define MAX_ENTITIES 64
+#define EYE 0.7f
+#define RADIUS 0.2f
+#define STEP 0.35f
+#define REACH 1.6f
+
+#define COL_SKY 0xFF1B1826u
+#define COL_DIM 0xFF9A93B3u
+#define COL_NOTE 0xFFF3DDA0u
+
+typedef struct state {
+ kon_sectorMap_t *map;
+ kon_sectorMesh_t *mesh;
+ kon_image *images[MAX_TEXTURES];
+ kon_material_t materials[MAX_TEXTURES];
+ kon_image *marker;
+ kon_fp_t input; /* only used for the mouse and keys, the position is ours */
+ kon_vec3_t eye;
+ float feet;
+ int sector;
+ float open[MAX_ENTITIES]; /* how far a door is open, 0 to 1 */
+ int opening[MAX_ENTITIES];
+ char note[64];
+ double time;
+} state_t;
+
+/*** images made in code ***/
+
+static kon_image *makeChecker(uint32_t a, uint32_t b)
+{
+ kon_image *image = kon_createFramebuffer(32, 32);
+
+ if (!image) return NULL;
+ kon_clearFramebuffer(image, a);
+ kon_fillRectangle(image, 0, 0, 16, 16, b);
+ kon_fillRectangle(image, 16, 16, 16, 16, b);
+ return image;
+}
+
+/* the same cross-in-a-box as the markers in hills, it stands in for any picture */
+static kon_image *makeMarker(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;
+}
+
+/* every texture name in the map gets a pastel checker picked from its letters, so two names look different */
+static kon_image *makeTexture(const char *name)
+{
+ static const uint32_t palette[][2] = {
+ { 0xFF3A3558, 0xFF332E4E }, { 0xFF8A5560, 0xFF7A4550 }, { 0xFF5C8F89, 0xFF4C7F79 },
+ { 0xFF9A93B3, 0xFF8A83A3 }, { 0xFF8A7A50, 0xFF7A6A40 }, { 0xFF4A6B58, 0xFF3F5A4A },
+ };
+ unsigned hash = 0;
+ int pick;
+
+ if (strcmp(name, "watcher") == 0) return makeMarker();
+ while (*name) hash = hash * 31u + (unsigned char)*name++;
+ pick = (int)(hash % (sizeof(palette) / sizeof(palette[0])));
+ return makeChecker(palette[pick][0], palette[pick][1]);
+}
+
+/*** moving ***/
+
+/* the point is fine to stand on when it is inside the map, the step up to it is small and there is room for the head */
+static int canStand(const state_t *state, float x, float z, float feet, int hint)
+{
+ int s = kon_sectorAt(state->map, x, z, hint);
+
+ if (s < 0) return 0;
+ if (state->map->sectors[s].floor > feet + STEP) return 0;
+ return state->map->sectors[s].ceiling - state->map->sectors[s].floor > EYE + 0.2f;
+}
+
+static int canStandBody(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 (!canStand(state, x + dx[i] * RADIUS, z + dz[i] * RADIUS, state->feet, state->sector)) return 0;
+ }
+ return 1;
+}
+
+static void walk(state_t *state, float dx, float dz)
+{
+ if (canStandBody(state, state->eye.x + dx, state->eye.z + dz)) {
+ state->eye.x += dx;
+ state->eye.z += dz;
+ } else if (canStandBody(state, state->eye.x + dx, state->eye.z)) {
+ state->eye.x += dx;
+ } else if (canStandBody(state, state->eye.x, state->eye.z + dz)) {
+ state->eye.z += dz;
+ }
+}
+
+/*** program ***/
+
+static int event(kon_program_t *program, const kon_event_t *ev, void *user)
+{
+ state_t *state = user;
+ int i, n, ids[8];
+
+ if (kon_fpEvent(&state->input, program, ev)) return 1;
+ if (ev->type != KON_EVENT_KEY_DOWN || ev->key != KON_KEY_E) return 0;
+
+ /* the map only says what is where, what a button does is decided here: it opens whatever it points at */
+ for (i = 0; i < state->map->entityCount && i < MAX_ENTITIES; i++) {
+ const kon_entity_t *button = &state->map->entities[i];
+ float dx = button->position.x - state->eye.x, dz = button->position.z - state->eye.z;
+
+ if (strcmp(button->kind, "button") != 0 || dx * dx + dz * dz > REACH * REACH) continue;
+ n = kon_entityTargets(state->map, button->target, ids, 8);
+ if (n > 8) n = 8;
+ while (n-- > 0) {
+ if (ids[n] < MAX_ENTITIES) state->opening[ids[n]] = !state->opening[ids[n]];
+ }
+ snprintf(state->note, sizeof(state->note), "pressed, it points at %s", button->target);
+ return 1;
+ }
+ return 0;
+}
+
+static void update(kon_program_t *program, double dt, void *user)
+{
+ state_t *state = user;
+ float fx = sinf(state->input.yaw), fz = -cosf(state->input.yaw);
+ float step = state->input.speed * (float)dt;
+ float dx = (fx * (float)state->input.forward - fz * (float)state->input.strafe) * step;
+ float dz = (fz * (float)state->input.forward + fx * (float)state->input.strafe) * step;
+ int i, s;
+
+ state->time += dt;
+ if (kon_debugConsoleOpen(program->debug) && kon_isMouseCaptured(program->window)) kon_setMouseCapture(program->window, false);
+ state->input.yaw += (float)state->input.turn * state->input.turnSpeed * (float)dt;
+
+ walk(state, dx, dz);
+ s = kon_sectorAt(state->map, state->eye.x, state->eye.z, state->sector);
+ if (s >= 0) {
+ state->sector = s;
+ state->feet += (state->map->sectors[s].floor - state->feet) * (1.0f - expf(-12.0f * (float)dt));
+ }
+ state->eye.y = state->feet + EYE;
+
+ for (i = 0; i < state->map->entityCount && i < MAX_ENTITIES; i++) {
+ float speed = kon_entityFloat(&state->map->entities[i], "speed", 1.0f) * (float)dt;
+
+ if (state->opening[i] && state->open[i] < 1.0f) state->open[i] = fminf(1.0f, state->open[i] + speed);
+ if (!state->opening[i] && state->open[i] > 0.0f) state->open[i] = fmaxf(0.0f, state->open[i] - speed);
+ }
+}
+
+static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user)
+{
+ state_t *state = user;
+ kon_light_t light = kon_lightDefault();
+ kon_camera_t camera = kon_cameraDefault();
+ kon_mat4_t vp;
+ char line[96];
+ int i;
+
+ camera.position = state->eye;
+ camera.target = KON_VEC3(state->eye.x + sinf(state->input.yaw) * cosf(state->input.pitch), state->eye.y + sinf(state->input.pitch),
+ state->eye.z - cosf(state->input.yaw) * cosf(state->input.pitch));
+ camera.fov = state->input.fov;
+ camera.nearZ = 0.05f;
+ camera.farZ = 60.0f;
+ vp = kon_cameraViewProjection(&camera, game->width, game->height);
+
+ kon_clearFramebuffer(game, COL_SKY);
+ 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);
+
+ /* things without a picture of their own are shown as markers, a door slides up as it opens */
+ for (i = 0; i < state->map->entityCount; i++) {
+ const kon_entity_t *e = &state->map->entities[i];
+ float lift = i < MAX_ENTITIES ? state->open[i] * 1.5f : 0.0f;
+
+ if (strcmp(e->kind, "spawn") == 0 || strcmp(e->kind, "light") == 0) continue;
+ kon_drawBillboard(game, vp, KON_VEC3(e->position.x, e->position.y + lift, e->position.z), 0.3f, 0.4f, state->marker, KON_RECT(0, 0, 0, 0), 1.0f, KON_RENDER_DEPTH);
+ }
+
+ kon_fillRectangle(game, game->width / 2 - 3, game->height / 2, 7, 1, COL_NOTE);
+ kon_fillRectangle(game, game->width / 2, game->height / 2 - 3, 1, 7, COL_NOTE);
+ if (program->font) {
+ snprintf(line, sizeof(line), "room %d click to look, e presses buttons", state->sector);
+ kon_drawText(game, program->font, 8, 16, line, COL_DIM);
+ if (state->note[0]) kon_drawText(game, program->font, 8, 30, state->note, COL_NOTE);
+ }
+}
+
+static int init(kon_program_t *program, void *user)
+{
+ state_t *state = user;
+ kon_tilemap_t none = {0};
+ char error[160];
+ int i, spawn = 0;
+
+ (void)program;
+ state->map = kon_sectorLoad(MAP_PATH, error, sizeof(error));
+ if (!state->map) {
+ fprintf(stderr, "%s: %s\n", MAP_PATH, error);
+ return 0;
+ }
+ state->mesh = kon_sectorBuild(state->map);
+ state->marker = makeMarker();
+ if (!state->mesh || !state->marker || state->map->textureCount > MAX_TEXTURES) return 0;
+
+ for (i = 0; i < state->map->textureCount; i++) {
+ state->images[i] = makeTexture(state->map->textures[i]);
+ if (!state->images[i]) return 0;
+ state->materials[i] = kon_materialTexture(state->images[i]);
+ }
+
+ none.tileWidth = none.tileHeight = 1;
+ kon_fpInit(&state->input, &none, 0, 0);
+ state->input.speed = 2.5f;
+
+ for (i = 0; i < state->map->entityCount; i++) {
+ if (strcmp(state->map->entities[i].kind, "spawn") == 0) {
+ spawn = i;
+ break;
+ }
+ }
+ state->sector = -1;
+ if (state->map->entityCount > 0) {
+ const kon_entity_t *e = &state->map->entities[spawn];
+
+ state->eye = e->position;
+ state->input.yaw = e->angle;
+ state->sector = kon_sectorAt(state->map, e->position.x, e->position.z, -1);
+ }
+ if (state->sector < 0) state->sector = 0;
+ state->feet = state->map->sectors[state->sector].floor;
+ return 1;
+}
+
+static void shutdown(kon_program_t *program, void *user)
+{
+ state_t *state = user;
+ int i;
+
+ (void)program;
+ for (i = 0; i < state->map->textureCount; i++) kon_freeFramebuffer(state->images[i]);
+ kon_freeFramebuffer(state->marker);
+ kon_sectorMeshFree(state->mesh);
+ kon_sectorFree(state->map);
+}
+
+int main(void)
+{
+ state_t *state = calloc(1, sizeof(state_t));
+ kon_programConfig_t config = {0};
+ int result;
+
+ if (!state) return 1;
+ config.title = "sectorview";
+ config.gameWidth = 480;
+ config.gameHeight = 270;
+ config.windowWidth = 960;
+ config.windowHeight = 540;
+ config.user = state;
+ config.init = init;
+ config.event = event;
+ config.update = update;
+ config.draw = draw;
+ config.shutdown = shutdown;
+ result = kon_programRun(&config);
+ free(state);
+ return result;
+}