summaryrefslogtreecommitdiff
path: root/examples/modelview.c
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-09 17:36:12 +0800
committerKonAki <konaki@anokonaki.org>2026-10-09 17:36:12 +0800
commitaf7dafe1d79e39ba2d51a6e3f10990d9e8df3735 (patch)
tree95a8bea4072fa026135330c1445493da6993f276 /examples/modelview.c
parent402304dca923efb39e19a4a093458921a17e63f7 (diff)
Add modelview example and examples target
Diffstat (limited to 'examples/modelview.c')
-rw-r--r--examples/modelview.c372
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;
+}