/* * 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, the wheel or 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 #include #include #include #include "konprogram.h" #include "konmodel.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 typedef struct viewer { kon_program_t *program; const char *name; kon_model_t *model; kon_light_t light; 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; } viewer_t; static void say(viewer_t *viewer, const char *text) { printf("%s\n", text); kon_debugLog(viewer->program->debug, "%s", text); } static void sayClip(viewer_t *viewer) { char text[128]; const kon_model_t *model = viewer->model; if (model->clipCount == 0) { say(viewer, "this model has no clips"); return; } if (viewer->blendClip >= 0 && viewer->amount > 0.0f) { snprintf(text, sizeof text, "playing %s blended with %s (%.2f)", model->clips[viewer->clip].name, model->clips[viewer->blendClip].name, viewer->amount); } else { snprintf(text, sizeof text, "playing %s", model->clips[viewer->clip].name); } say(viewer, 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) { viewer_t *viewer = user; int i; (void)argc; (void)argv; for (i = 0; i < viewer->model->clipCount; i++) { kon_debugLog(debug, "%d %s %.2fs", i, viewer->model->clips[i].name, viewer->model->clips[i].duration); } if (viewer->model->clipCount == 0) kon_debugLog(debug, "no clips"); } static void cmdClip(kon_debug_t *debug, void *user, int argc, char **argv) { viewer_t *viewer = user; int clip; if (argc < 2) { kon_debugLog(debug, "clip , see clips"); return; } clip = findClip(viewer->model, argv[1]); if (clip < 0) { kon_debugLog(debug, "no clip called %s", argv[1]); return; } viewer->clip = clip; viewer->time = 0.0f; sayClip(viewer); } static void cmdBlend(kon_debug_t *debug, void *user, int argc, char **argv) { viewer_t *viewer = user; int clip; if (argc < 2) { kon_debugLog(debug, "blend [amount 0 to 1], or blend off"); return; } if (strcmp(argv[1], "off") == 0) { viewer->blendClip = -1; sayClip(viewer); return; } clip = findClip(viewer->model, argv[1]); if (clip < 0) { kon_debugLog(debug, "no clip called %s", argv[1]); return; } viewer->blendClip = clip; viewer->amount = argc > 2 ? (float)atof(argv[2]) : 0.5f; if (viewer->amount < 0.0f) viewer->amount = 0.0f; if (viewer->amount > 1.0f) viewer->amount = 1.0f; sayClip(viewer); } /* 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(viewer_t *viewer) { const kon_model_t *model = viewer->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); } viewer->center = kon_vec3Scale(kon_vec3Add(lo, hi), 0.5f); viewer->size = kon_vec3Length(kon_vec3Sub(hi, lo)); viewer->floorY = lo.y; } static void drawFloor(viewer_t *viewer, kon_framebuffer_t *game, kon_mat4_t viewProjection) { float step = viewer->size / 10.0f; int i; for (i = -5; i <= 5; i++) { kon_vec3_t a = KON_VEC3(viewer->center.x + i * step, viewer->floorY, viewer->center.z - 5 * step); kon_vec3_t b = KON_VEC3(viewer->center.x + i * step, viewer->floorY, viewer->center.z + 5 * step); kon_vec3_t c = KON_VEC3(viewer->center.x - 5 * step, viewer->floorY, viewer->center.z + i * step); kon_vec3_t d = KON_VEC3(viewer->center.x + 5 * step, viewer->floorY, viewer->center.z + i * step); kon_drawLine3D(game, viewProjection, a, b, 0xFF4A5566); kon_drawLine3D(game, viewProjection, c, d, 0xFF4A5566); } } static void animate(viewer_t *viewer, kon_pose_t *pose) { const kon_model_t *model = viewer->model; kon_poseRest(model, pose); if (model->clipCount == 0) return; kon_poseClip(model, NULL, viewer->clip, viewer->time, 1, pose); if (viewer->blendClip >= 0 && viewer->amount > 0.0f) { kon_pose_t other; float durationA = model->clips[viewer->clip].duration, durationB = model->clips[viewer->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(viewer->time, durationA) / durationA : 0.0f; kon_poseRest(model, &other); kon_poseClip(model, NULL, viewer->blendClip, phase * durationB, 1, &other); kon_poseBlend(model, pose, &other, viewer->amount, NULL, pose); } } static int event(kon_program_t *program, const kon_event_t *ev, void *user) { viewer_t *viewer = user; (void)program; if (ev->type == KON_EVENT_MOUSE_DOWN && ev->mouseButton == KON_MOUSE_LEFT) { viewer->dragging = 1; viewer->dragX = ev->mouseX; viewer->dragY = ev->mouseY; } else if (ev->type == KON_EVENT_MOUSE_UP && ev->mouseButton == KON_MOUSE_LEFT) { viewer->dragging = 0; } else if (ev->type == KON_EVENT_MOUSE_MOVE && viewer->dragging) { viewer->yaw -= (float)(ev->mouseX - viewer->dragX) * 0.01f; viewer->pitch += (float)(ev->mouseY - viewer->dragY) * 0.01f; if (viewer->pitch > 1.5f) viewer->pitch = 1.5f; if (viewer->pitch < -1.5f) viewer->pitch = -1.5f; viewer->dragX = ev->mouseX; viewer->dragY = ev->mouseY; } else if (ev->type == KON_EVENT_MOUSE_WHEEL) { viewer->distance *= powf(0.9f, (float)ev->wheel); } else if (ev->type == KON_EVENT_KEY_DOWN) { if (ev->key == KON_KEY_SPACE && viewer->model->clipCount) { viewer->clip = (viewer->clip + 1) % viewer->model->clipCount; viewer->time = 0.0f; sayClip(viewer); } if (ev->key == KON_KEY_F) viewer->flat = !viewer->flat; if (ev->key == KON_KEY_L) viewer->lit = !viewer->lit; if (ev->key == KON_KEY_W) viewer->wire = !viewer->wire; if (ev->key == KON_KEY_P) viewer->paused = !viewer->paused; if (ev->key == KON_KEY_O) viewer->ortho = !viewer->ortho; if (ev->key == KON_KEY_UP) viewer->distance *= 0.9f; if (ev->key == KON_KEY_DOWN) viewer->distance /= 0.9f; if (ev->key == KON_KEY_LEFT) viewer->speed *= 0.8f; if (ev->key == KON_KEY_RIGHT) viewer->speed *= 1.25f; } else { return 0; } return 1; } static void update(kon_program_t *program, double dt, void *user) { viewer_t *viewer = user; (void)program; if (!viewer->paused) viewer->time += (float)dt * viewer->speed; if (viewer->spin) viewer->yaw += (float)dt * 0.5f; } static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) { viewer_t *viewer = user; kon_pose_t pose; kon_mat4_t skin[KON_MODEL_MAX_BONES], viewProjection; kon_camera_t camera = kon_cameraDefault(); kon_renderFlags_t flags = KON_RENDER_DEFAULT; (void)program; camera.target = viewer->center; camera.position = kon_vec3Add(viewer->center, kon_vec3Scale(KON_VEC3(cosf(viewer->pitch) * sinf(viewer->yaw), sinf(viewer->pitch), cosf(viewer->pitch) * cosf(viewer->yaw)), viewer->distance)); camera.nearZ = viewer->size * 0.02f; camera.farZ = viewer->size * 20.0f; camera.orthoSize = viewer->distance * 0.4f; if (viewer->ortho) camera.projection = KON_PROJECTION_ORTHOGRAPHIC; viewProjection = kon_cameraViewProjection(&camera, game->width, game->height); animate(viewer, &pose); kon_poseSkin(viewer->model, &pose, skin); if (viewer->flat) flags |= KON_RENDER_FLAT; if (viewer->wire) flags |= KON_RENDER_WIREFRAME; drawFloor(viewer, game, viewProjection); kon_modelDraw(game, viewProjection, kon_mat4Identity(), viewer->model, skin, viewer->lit ? &viewer->light : NULL, flags); } static int init(kon_program_t *program, void *user) { viewer_t *viewer = user; int type = kon_modelRegister(program->assets); viewer->program = program; viewer->model = type >= 0 ? kon_assetGetAs(program->assets, viewer->name, type) : NULL; if (!viewer->model) { fprintf(stderr, "modelview: could not load %s: %s\n", viewer->name, 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", viewer->name); return 0; } kon_debugCommand(program->debug, "clips", "list the clips of the model", cmdClips, viewer); kon_debugCommand(program->debug, "clip", "clip ", cmdClip, viewer); kon_debugCommand(program->debug, "blend", "blend [amount], blend off", cmdBlend, viewer); kon_debugVar(program->debug, "flat", KON_VAR_BOOL, &viewer->flat); kon_debugVar(program->debug, "light", KON_VAR_BOOL, &viewer->lit); kon_debugVar(program->debug, "wire", KON_VAR_BOOL, &viewer->wire); kon_debugVar(program->debug, "spin", KON_VAR_BOOL, &viewer->spin); kon_debugVar(program->debug, "speed", KON_VAR_FLOAT, &viewer->speed); kon_debugVar(program->debug, "amount", KON_VAR_FLOAT, &viewer->amount); viewer->light = kon_lightDefault(); viewer->lit = 1; viewer->speed = 1.0f; viewer->blendClip = -1; viewer->yaw = 0.8f; viewer->pitch = 0.25f; measure(viewer); viewer->distance = viewer->size * 0.9f; sayClip(viewer); return 1; } int main(int argc, char **argv) { viewer_t viewer = {0}; kon_programConfig_t config = {0}; viewer.name = argc > 1 ? argv[1] : "models/fox.kmdl"; config.title = "modelview"; config.gameWidth = 640; config.gameHeight = 360; config.windowWidth = 960; config.windowHeight = 540; config.packPath = argc > 2 ? argv[2] : "obj/data.kpk"; config.user = &viewer; config.init = init; config.event = event; config.update = update; config.draw = draw; return kon_programRun(&config); }