/* * 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 #include #include #include #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; }