diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/sectorview.c | 307 |
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; +} |
