/* * sectorview: walk around a map made of rooms and picture quads * * build: ./build.sh -d -m 3d,sector,world,font obj/sectorview * run: obj/sectorview (from the folder with examples/) * * click to grab the mouse, tab lets go, a click after that shoots a ray and the top line says what it hit. w and s walk, a and d strafe, * space jumps, e presses a button close to you. * the door is a box in the world that the program lifts, nothing else about it is built in * 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" #include "konworld.h" #if !defined(KON_MODULE_3D) || !defined(KON_MODULE_SECTOR) || !defined(KON_MODULE_WORLD) || !defined(KON_MODULE_FONT) #error "build with: ./build.sh -d -m 3d,sector,world,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 HEIGHT 1.0f #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_fp_t input; /* only used for the mouse and keys, the position is ours */ kon_world_t world; kon_body_t body; kon_material_t doorMaterial, buttonMaterial; kon_vec3_t eye; float feet; /* what the eyes follow, it catches up with the body after a step */ int door[MAX_ENTITIES]; /* the collider of a door entity, -1 for the others */ 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]); } /*** doors ***/ /* a door is a box across its opening, facing the way the entity does. opening it lifts the box until it is over the head */ static void doorShape(const kon_entity_t *e, kon_vec3_t *min, kon_vec3_t *max) { float width = kon_entityFloat(e, "width", 1.2f) * 0.5f, height = kon_entityFloat(e, "height", 2.2f); int alongZ = fabsf(sinf(e->angle)) > 0.7f; *min = KON_VEC3(e->position.x - (alongZ ? 0.1f : width), e->position.y, e->position.z - (alongZ ? width : 0.1f)); *max = KON_VEC3(e->position.x + (alongZ ? 0.1f : width), e->position.y + height, e->position.z + (alongZ ? width : 0.1f)); } /*** 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_MOUSE_DOWN && kon_isMouseCaptured(program->window)) { static const char *const kinds[] = { "nothing", "a wall", "the floor", "the ceiling", "a collider", "the ground", "a tile" }; kon_rayHit_t hit; kon_vec3_t eye = KON_VEC3(state->body.position.x, state->feet + EYE, state->body.position.z); kon_vec3_t dir = KON_VEC3(sinf(state->input.yaw) * cosf(state->input.pitch), sinf(state->input.pitch), -cosf(state->input.yaw) * cosf(state->input.pitch)); if (kon_worldRay(&state->world, eye, dir, 50.0f, &hit)) snprintf(state->note, sizeof(state->note), "shot %s, %.1f away", kinds[hit.kind], (double)hit.distance); else snprintf(state->note, sizeof(state->note), "shot, hit nothing"); return 1; } if (ev->type == KON_EVENT_KEY_DOWN && ev->key == KON_KEY_SPACE) { kon_bodyJump(&state->body); 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->body.position.x, dz = button->position.z - state->body.position.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; 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; 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); if (state->door[i] >= 0) { kon_collider_t *c = kon_worldCollider(&state->world, state->door[i]); kon_vec3_t min, max; doorShape(&state->map->entities[i], &min, &max); c->min.y = min.y + state->open[i] * (max.y - min.y); } } kon_worldMove(&state->world, &state->body, dx, dz, dt); state->feet += (state->body.position.y - state->feet) * (1.0f - expf(-16.0f * (float)dt)); if (state->body.onGround == 0) state->feet = state->body.position.y; } /* a flat rectangle standing on the entity's position, facing the way it does. the door is a thing the program moves, so this is all the engine knows about it. lift slides it up, the bottom edge is the entity's y (the middle for a small one) */ static void drawPanel(kon_framebuffer_t *fb, kon_mat4_t vp, const kon_entity_t *e, float width, float height, float lift, const kon_material_t *material, const kon_light_t *light) { float rx = -cosf(e->angle) * width * 0.5f, rz = -sinf(e->angle) * width * 0.5f; float bottom = e->position.y + lift - (height < 0.5f ? height * 0.5f : 0.0f); kon_vec3_t normal = KON_VEC3(sinf(e->angle), 0.0f, -cosf(e->angle)); kon_vertex_t bl = { KON_VEC3(e->position.x - rx, bottom, e->position.z - rz), normal, KON_VEC2(0, 1) }; kon_vertex_t br = { KON_VEC3(e->position.x + rx, bottom, e->position.z + rz), normal, KON_VEC2(1, 1) }; kon_vertex_t tr = { KON_VEC3(e->position.x + rx, bottom + height, e->position.z + rz), normal, KON_VEC2(1, 0) }; kon_vertex_t tl = { KON_VEC3(e->position.x - rx, bottom + height, e->position.z - rz), normal, KON_VEC2(0, 0) }; kon_drawTriangle3D(fb, vp, bl, br, tr, material, light, KON_RENDER_DEPTH); kon_drawTriangle3D(fb, vp, bl, tr, tl, material, light, KON_RENDER_DEPTH); } 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; state->eye = KON_VEC3(state->body.position.x, state->feet + EYE, state->body.position.z); 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); for (i = 0; i < state->map->entityCount; i++) { const kon_entity_t *e = &state->map->entities[i]; if (strcmp(e->kind, "door") == 0) { float width = kon_entityFloat(e, "width", 1.2f), height = kon_entityFloat(e, "height", 2.2f); float lift = i < MAX_ENTITIES ? state->open[i] * height : 0.0f; drawPanel(game, vp, e, width, height, lift, &state->doorMaterial, &light); } else if (strcmp(e->kind, "button") == 0) { drawPanel(game, vp, e, 0.2f, 0.2f, 0.0f, &state->buttonMaterial, &light); } } 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 shoots a ray, e presses buttons, space jumps", state->body.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); if (!state->mesh || 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->doorMaterial = kon_materialColor(0xFF8A7A50); state->buttonMaterial = kon_materialColor(0xFFF2A7C3); kon_worldInit(&state->world); state->world.sectors = state->map; for (i = 0; i < MAX_ENTITIES; i++) state->door[i] = -1; for (i = 0; i < state->map->entityCount && i < MAX_ENTITIES; i++) { kon_vec3_t min, max; if (strcmp(state->map->entities[i].kind, "door") != 0) continue; doorShape(&state->map->entities[i], &min, &max); state->door[i] = kon_worldAdd(&state->world, kon_colliderBox(min, max)); } kon_bodyInit(&state->body, 0, 0, 0, RADIUS, HEIGHT); if (state->map->entityCount > 0) { const kon_entity_t *e = &state->map->entities[spawn]; state->body.position = KON_VEC3(e->position.x, e->position.y, e->position.z); state->input.yaw = e->angle; } kon_worldPlace(&state->world, &state->body); state->feet = state->body.position.y; 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_sectorMeshFree(state->mesh); kon_worldFree(&state->world); 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; }