diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-09 17:36:12 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-09 17:36:12 +0800 |
| commit | af7dafe1d79e39ba2d51a6e3f10990d9e8df3735 (patch) | |
| tree | 95a8bea4072fa026135330c1445493da6993f276 /examples/modelview.c | |
| parent | 402304dca923efb39e19a4a093458921a17e63f7 (diff) | |
Add modelview example and examples target
Diffstat (limited to 'examples/modelview.c')
| -rw-r--r-- | examples/modelview.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/examples/modelview.c b/examples/modelview.c new file mode 100644 index 0000000..29daa78 --- /dev/null +++ b/examples/modelview.c @@ -0,0 +1,372 @@ +/* + * modelview: shows a .kmdl model and plays and blends its animations + * + * build: ./build.sh -d -m model,image,debug examples + * run: obj/modelview [models/fox.kmdl] [pack file] + * models: obj/konmodel art/models/Fox.glb assets/models/fox.kmdl -t models/ + * + * mouse drag turns the camera, up and down zoom, space picks the next clip, + * left and right change the speed, f flat, l light, w wireframe, p pause + * with the developer build the console (`) knows clips, clip, blend, and the variables flat light wire spin speed amount + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "konwinlib.h" +#include "konsofren.h" +#include "kontime.h" +#include "konpack.h" +#include "konassets.h" +#include "konmodel.h" +#include "konfont.h" +#include "kondebug.h" + +#if !defined(KON_MODULE_MODEL) || !defined(KON_MODULE_IMAGE) || !defined(KON_MODULE_DEBUG) +#error "build with: ./build.sh -d -m model,image,debug examples" +#endif + +#define GAME_WIDTH 640 +#define GAME_HEIGHT 360 + +typedef struct program { + kon_window_t *window; + kon_display_t *display; + kon_pack_t *pack; + kon_assets_t *assets; + kon_font_t *font; + kon_debug_t *debug; + kon_model_t *model; + const char *modelName; + kon_light_t light; + kon_time last; + + kon_vec3_t center; + float size, floorY, yaw, pitch, distance; + int dragging, dragX, dragY; + int ortho; + + int clip, blendClip; + float time, speed, amount; + int paused, spin, flat, lit, wire; +} program_t; + +static void say(program_t *program, const char *text) +{ + printf("%s\n", text); + kon_debugLog(program->debug, "%s", text); +} + +static void sayClip(program_t *program) +{ + char text[128]; + const kon_model_t *model = program->model; + + if (model->clipCount == 0) { + say(program, "this model has no clips"); + return; + } + if (program->blendClip >= 0 && program->amount > 0.0f) { + snprintf(text, sizeof text, "playing %s blended with %s (%.2f)", model->clips[program->clip].name, model->clips[program->blendClip].name, program->amount); + } else { + snprintf(text, sizeof text, "playing %s", model->clips[program->clip].name); + } + say(program, text); +} + +static int findClip(const kon_model_t *model, const char *word) +{ + int i; + + if (word[0] >= '0' && word[0] <= '9') { + i = atoi(word); + return i < model->clipCount ? i : -1; + } + return kon_modelFindClip(model, word); +} + +static void cmdClips(kon_debug_t *debug, void *user, int argc, char **argv) +{ + program_t *program = user; + int i; + + (void)argc; (void)argv; + for (i = 0; i < program->model->clipCount; i++) { + kon_debugLog(debug, "%d %s %.2fs", i, program->model->clips[i].name, program->model->clips[i].duration); + } + if (program->model->clipCount == 0) kon_debugLog(debug, "no clips"); +} + +static void cmdClip(kon_debug_t *debug, void *user, int argc, char **argv) +{ + program_t *program = user; + int clip; + + if (argc < 2) { + kon_debugLog(debug, "clip <name or number>, see clips"); + return; + } + clip = findClip(program->model, argv[1]); + if (clip < 0) { + kon_debugLog(debug, "no clip called %s", argv[1]); + return; + } + program->clip = clip; + program->time = 0.0f; + sayClip(program); +} + +static void cmdBlend(kon_debug_t *debug, void *user, int argc, char **argv) +{ + program_t *program = user; + int clip; + + if (argc < 2) { + kon_debugLog(debug, "blend <name or number> [amount 0 to 1], or blend off"); + return; + } + if (strcmp(argv[1], "off") == 0) { + program->blendClip = -1; + sayClip(program); + return; + } + clip = findClip(program->model, argv[1]); + if (clip < 0) { + kon_debugLog(debug, "no clip called %s", argv[1]); + return; + } + program->blendClip = clip; + program->amount = argc > 2 ? (float)atof(argv[2]) : 0.5f; + if (program->amount < 0.0f) program->amount = 0.0f; + if (program->amount > 1.0f) program->amount = 1.0f; + sayClip(program); +} + +/* the bounds of the first frame of the first clip, so the camera fits even if the model is far from the origin */ +static void measure(program_t *program) +{ + const kon_model_t *model = program->model; + kon_mat4_t skin[KON_MODEL_MAX_BONES]; + kon_vec3_t lo = KON_VEC3(1e30f, 1e30f, 1e30f), hi = KON_VEC3(-1e30f, -1e30f, -1e30f); + int m, i, k; + + kon_modelSample(model, model->clipCount ? 0 : -1, 0.0f, 0, skin); + for (m = 0; m < model->meshCount; m++) { + const kon_modelMesh_t *mesh = &model->meshes[m]; + + for (i = 0; i < mesh->vertexCount; i++) { + kon_vec3_t p = mesh->vertices[i].position; + + if (mesh->weights) { + kon_vec3_t sum = KON_VEC3_ZERO; + for (k = 0; k < 4; k++) { + float w = mesh->weights[i * 4 + k]; + if (w > 0.0f) sum = kon_vec3Add(sum, kon_vec3Scale(kon_mat4MulPoint(skin[mesh->joints[i * 4 + k]], p), w)); + } + p = sum; + } + if (p.x < lo.x) lo.x = p.x; + if (p.y < lo.y) lo.y = p.y; + if (p.z < lo.z) lo.z = p.z; + if (p.x > hi.x) hi.x = p.x; + if (p.y > hi.y) hi.y = p.y; + if (p.z > hi.z) hi.z = p.z; + } + } + if (lo.x > hi.x) { + lo = KON_VEC3(-1, -1, -1); + hi = KON_VEC3(1, 1, 1); + } + program->center = kon_vec3Scale(kon_vec3Add(lo, hi), 0.5f); + program->size = kon_vec3Length(kon_vec3Sub(hi, lo)); + program->floorY = lo.y; +} + +static void drawFloor(program_t *program, kon_mat4_t viewProjection) +{ + float step = program->size / 10.0f; + int i; + + for (i = -5; i <= 5; i++) { + kon_vec3_t a = KON_VEC3(program->center.x + i * step, program->floorY, program->center.z - 5 * step); + kon_vec3_t b = KON_VEC3(program->center.x + i * step, program->floorY, program->center.z + 5 * step); + kon_vec3_t c = KON_VEC3(program->center.x - 5 * step, program->floorY, program->center.z + i * step); + kon_vec3_t d = KON_VEC3(program->center.x + 5 * step, program->floorY, program->center.z + i * step); + + kon_drawLine3D(program->display->game, viewProjection, a, b, 0xFF4A5566); + kon_drawLine3D(program->display->game, viewProjection, c, d, 0xFF4A5566); + } +} + +static void animate(program_t *program, kon_pose_t *pose) +{ + const kon_model_t *model = program->model; + + kon_poseRest(model, pose); + if (model->clipCount == 0) return; + kon_poseClip(model, NULL, program->clip, program->time, 1, pose); + + if (program->blendClip >= 0 && program->amount > 0.0f) { + kon_pose_t other; + float durationA = model->clips[program->clip].duration, durationB = model->clips[program->blendClip].duration; + /* both clips are at the same point of their cycle, so walking into running keeps the feet in step */ + float phase = durationA > 0.0f ? fmodf(program->time, durationA) / durationA : 0.0f; + + kon_poseRest(model, &other); + kon_poseClip(model, NULL, program->blendClip, phase * durationB, 1, &other); + kon_poseBlend(model, pose, &other, program->amount, NULL, pose); + } +} + +static void frame(void *data) +{ + program_t *program = data; + kon_display_t *display = program->display; + kon_event_t ev; + kon_time now, dt; + kon_pose_t pose; + kon_mat4_t skin[KON_MODEL_MAX_BONES], viewProjection; + kon_camera_t camera; + kon_renderFlags_t flags = KON_RENDER_DEFAULT; + + while (kon_pollEvent(program->window, &ev)) { + if (kon_debugFeedEvent(program->debug, &ev)) continue; + + if (ev.type == KON_EVENT_RESIZE) { + kon_resizeDisplay(display, ev.width, ev.height); + } else if (ev.type == KON_EVENT_MOUSE_DOWN && ev.mouseButton == KON_MOUSE_LEFT) { + program->dragging = 1; + program->dragX = ev.mouseX; + program->dragY = ev.mouseY; + } else if (ev.type == KON_EVENT_MOUSE_UP && ev.mouseButton == KON_MOUSE_LEFT) { + program->dragging = 0; + } else if (ev.type == KON_EVENT_MOUSE_MOVE && program->dragging) { + program->yaw -= (float)(ev.mouseX - program->dragX) * 0.01f; + program->pitch += (float)(ev.mouseY - program->dragY) * 0.01f; + if (program->pitch > 1.5f) program->pitch = 1.5f; + if (program->pitch < -1.5f) program->pitch = -1.5f; + program->dragX = ev.mouseX; + program->dragY = ev.mouseY; + } else if (ev.type == KON_EVENT_KEY_DOWN) { + if (ev.key == KON_KEY_SPACE && program->model->clipCount) { + program->clip = (program->clip + 1) % program->model->clipCount; + program->time = 0.0f; + sayClip(program); + } + if (ev.key == KON_KEY_F) program->flat = !program->flat; + if (ev.key == KON_KEY_L) program->lit = !program->lit; + if (ev.key == KON_KEY_W) program->wire = !program->wire; + if (ev.key == KON_KEY_P) program->paused = !program->paused; + if (ev.key == KON_KEY_O) program->ortho = !program->ortho; + if (ev.key == KON_KEY_UP) program->distance *= 0.9f; + if (ev.key == KON_KEY_DOWN) program->distance /= 0.9f; + if (ev.key == KON_KEY_LEFT) program->speed *= 0.8f; + if (ev.key == KON_KEY_RIGHT) program->speed *= 1.25f; + } + } + + now = kon_getTime(); + dt = now - program->last; + program->last = now; + if (!program->paused) program->time += (float)dt * program->speed; + if (program->spin) program->yaw += (float)dt * 0.5f; + + camera = kon_cameraDefault(); + camera.target = program->center; + camera.position = kon_vec3Add(program->center, kon_vec3Scale(KON_VEC3(cosf(program->pitch) * sinf(program->yaw), sinf(program->pitch), cosf(program->pitch) * cosf(program->yaw)), program->distance)); + camera.nearZ = program->size * 0.02f; + camera.farZ = program->size * 20.0f; + camera.orthoSize = program->distance * 0.4f; + if (program->ortho) camera.projection = KON_PROJECTION_ORTHOGRAPHIC; + viewProjection = kon_cameraViewProjection(&camera, display->game->width, display->game->height); + + animate(program, &pose); + kon_poseSkin(program->model, &pose, skin); + + if (program->flat) flags |= KON_RENDER_FLAT; + if (program->wire) flags |= KON_RENDER_WIREFRAME; + + kon_clearFramebuffer(display->game, 0xFF1C2028); + drawFloor(program, viewProjection); + kon_modelDraw(display->game, viewProjection, kon_mat4Identity(), program->model, skin, program->lit ? &program->light : NULL, flags); + + kon_presentDisplay(display); + kon_debugFrame(program->debug); + if (kon_debugQuitRequested(program->debug)) kon_setShouldClose(program->window, true); + kon_debugDraw(program->debug, display->screen); + kon_blitPixels(program->window, display->screen->data, display->screen->width, display->screen->height); + kon_sleep(0.008); +} + +int main(int argc, char **argv) +{ + program_t program = {0}; + const char *name = argc > 1 ? argv[1] : "models/fox.kmdl"; + int type; + + program.pack = kon_packOpen(argc > 2 ? argv[2] : "obj/data.kpk", NULL); + program.assets = kon_assetsCreate(program.pack, "assets"); + type = kon_modelRegister(program.assets); + program.model = kon_assetGet(program.assets, name); + if (!program.model || type < 0 || kon_assetGetAs(program.assets, name, type) != program.model) { + fprintf(stderr, "modelview: could not load %s: %s\n", name, program.model ? "it is not a kmdl file" : kon_assetsError(program.assets)); + fprintf(stderr, "run it from the project folder with assets/%s there (./build.sh -d for the loose files), or give it a pack\n", name); + return 1; + } + kon_modelBindTextures(program.model, program.assets); + program.modelName = name; + + if (!kon_init()) return 1; + program.window = kon_createWindow("modelview", 0, 0, 960, 540, KON_WINDOW_CENTER | KON_WINDOW_RESIZABLE); + if (!program.window) return 1; +#ifdef KON_DEVELOPER + kon_setExitKey(program.window, KON_KEY_F10); +#else + kon_setExitKey(program.window, KON_KEY_ESCAPE); +#endif + program.display = kon_createDisplay(GAME_WIDTH, GAME_HEIGHT, 960, 540); + if (!program.display) return 1; + + { + kon_blob_t *ttf = kon_assetBlob(program.assets, "font.ttf"); + + if (ttf) program.font = kon_loadFont(ttf->data, ttf->size, 14.0f); + if (!program.font) program.font = kon_loadBuiltinFont(1); + } + program.debug = kon_debugCreate(program.font); + kon_debugSetAssets(program.debug, program.assets); + kon_debugCommand(program.debug, "clips", "list the clips of the model", cmdClips, &program); + kon_debugCommand(program.debug, "clip", "clip <name or number>", cmdClip, &program); + kon_debugCommand(program.debug, "blend", "blend <clip> [amount], blend off", cmdBlend, &program); + kon_debugVar(program.debug, "flat", KON_VAR_BOOL, &program.flat); + kon_debugVar(program.debug, "light", KON_VAR_BOOL, &program.lit); + kon_debugVar(program.debug, "wire", KON_VAR_BOOL, &program.wire); + kon_debugVar(program.debug, "spin", KON_VAR_BOOL, &program.spin); + kon_debugVar(program.debug, "speed", KON_VAR_FLOAT, &program.speed); + kon_debugVar(program.debug, "amount", KON_VAR_FLOAT, &program.amount); + + program.light = kon_lightDefault(); + program.lit = 1; + program.speed = 1.0f; + program.blendClip = -1; + program.yaw = 0.8f; + program.pitch = 0.25f; + measure(&program); + program.distance = program.size * 0.9f; + program.last = kon_getTime(); + sayClip(&program); + + kon_runMainLoop(program.window, frame, &program); + + kon_debugDestroy(program.debug); + kon_freeFont(program.font); + kon_modelUnbindTextures(program.model, program.assets); + kon_assetsDestroy(program.assets); + if (program.pack) kon_packClose(program.pack); + kon_freeDisplay(program.display); + kon_destroyWindow(program.window); + kon_deinit(); + return 0; +} |
