/* SPDX-License-Identifier: MIT */ /* * konmodel.c * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include "konmodel.h" /*** definition ***/ #define KON_MODEL_VERSION 1 #define KON_MODEL_MAX_COUNT (1 << 24) /*** reading ***/ typedef struct kon_cursor { const uint8_t *data; size_t size, at; int bad; } kon_cursor_t; static const uint8_t *kon_take_(kon_cursor_t *c, size_t n) { const uint8_t *p; if (c->bad || n > c->size - c->at) { c->bad = 1; return NULL; } p = c->data + c->at; c->at += n; return p; } static uint32_t kon_readU32_(kon_cursor_t *c) { const uint8_t *p = kon_take_(c, 4); if (!p) return 0; return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; } static float kon_readFloat_(kon_cursor_t *c) { uint32_t u = kon_readU32_(c); float f; memcpy(&f, &u, sizeof f); return f; } static void kon_readFloats_(kon_cursor_t *c, float *out, size_t n) { size_t i; for (i = 0; i < n; i++) out[i] = kon_readFloat_(c); } static void kon_readName_(kon_cursor_t *c, char *out, size_t n) { const uint8_t *p = kon_take_(c, n); if (!p) return; memcpy(out, p, n); out[n - 1] = 0; } /* the count has to fit in what is left of the file, so a bad count can't make a huge allocation */ static int kon_countFits_(const kon_cursor_t *c, uint32_t count, size_t each) { if (count > KON_MODEL_MAX_COUNT) return 0; return (size_t)count * each <= c->size - c->at; } static void *kon_alloc_(kon_model_t *model, size_t count, size_t each) { void *p; if (count == 0) return NULL; p = calloc(count, each); if (p) model->bytes += count * each; return p; } static int kon_validFloat_(float f) { return f == f && f > -1e30f && f < 1e30f; } static int kon_readMaterial_(kon_cursor_t *c, kon_modelMaterial_t *m) { uint32_t filter; kon_readName_(c, m->texture, KON_MODEL_TEXTURE_SIZE); m->color = kon_readU32_(c); filter = kon_readU32_(c); m->filter = filter ? KON_FILTER_LINEAR : KON_FILTER_NEAREST; return !c->bad; } static int kon_readMesh_(kon_cursor_t *c, kon_model_t *model, kon_modelMesh_t *mesh) { uint32_t material = kon_readU32_(c), vertexCount = kon_readU32_(c), triangleCount = kon_readU32_(c), skinned = kon_readU32_(c); int i; if (c->bad || material >= (uint32_t)model->materialCount || skinned > 1) return 0; if (vertexCount == 0 || vertexCount > 65535 || triangleCount == 0) return 0; if (!kon_countFits_(c, vertexCount, 32 + (skinned ? 20 : 0))) return 0; if (!kon_countFits_(c, triangleCount, 6)) return 0; if (skinned && model->boneCount == 0) return 0; mesh->material = (int)material; mesh->vertexCount = (int)vertexCount; mesh->triangleCount = (int)triangleCount; mesh->vertices = kon_alloc_(model, vertexCount, sizeof *mesh->vertices); mesh->indices = kon_alloc_(model, (size_t)triangleCount * 3, sizeof *mesh->indices); if (skinned) { mesh->joints = kon_alloc_(model, (size_t)vertexCount * 4, 1); mesh->weights = kon_alloc_(model, (size_t)vertexCount * 4, sizeof *mesh->weights); if (!mesh->joints || !mesh->weights) return 0; } if (!mesh->vertices || !mesh->indices) return 0; for (i = 0; i < mesh->vertexCount; i++) { float f[8]; kon_readFloats_(c, f, 8); if (c->bad) return 0; for (int k = 0; k < 8; k++) if (!kon_validFloat_(f[k])) return 0; mesh->vertices[i].position = KON_VEC3(f[0], f[1], f[2]); mesh->vertices[i].normal = KON_VEC3(f[3], f[4], f[5]); mesh->vertices[i].uv = (kon_vec2_t){f[6], f[7]}; } if (skinned) { for (i = 0; i < mesh->vertexCount; i++) { const uint8_t *j = kon_take_(c, 4); if (!j) return 0; for (int k = 0; k < 4; k++) { float w = kon_readFloat_(c); if (!kon_validFloat_(w) || j[k] >= model->boneCount) return 0; mesh->joints[i * 4 + k] = j[k]; mesh->weights[i * 4 + k] = w; } } } for (i = 0; i < mesh->triangleCount * 3; i++) { const uint8_t *p = kon_take_(c, 2); if (!p) return 0; mesh->indices[i] = (uint16_t)(p[0] | p[1] << 8); if (mesh->indices[i] >= mesh->vertexCount) return 0; } return 1; } static int kon_readBone_(kon_cursor_t *c, kon_modelBone_t *bone, int index) { float f[26]; uint32_t parent; int i; kon_readName_(c, bone->name, KON_MODEL_NAME_SIZE); parent = kon_readU32_(c); kon_readFloats_(c, f, 26); if (c->bad) return 0; for (i = 0; i < 26; i++) if (!kon_validFloat_(f[i])) return 0; bone->parent = (int)parent; if (parent == 0xFFFFFFFFu) bone->parent = -1; else if (parent >= (uint32_t)index) return 0; memcpy(bone->inverseBind.m, f, 16 * sizeof(float)); bone->position = KON_VEC3(f[16], f[17], f[18]); bone->rotation = (kon_vec4_t){f[19], f[20], f[21], f[22]}; bone->scale = KON_VEC3(f[23], f[24], f[25]); return 1; } static int kon_readClip_(kon_cursor_t *c, kon_model_t *model, kon_modelClip_t *clip) { uint32_t channelCount; int i; kon_readName_(c, clip->name, KON_MODEL_NAME_SIZE); clip->duration = kon_readFloat_(c); channelCount = kon_readU32_(c); if (c->bad || !kon_validFloat_(clip->duration) || clip->duration < 0.0f) return 0; if (!kon_countFits_(c, channelCount, 12)) return 0; clip->channels = kon_alloc_(model, channelCount, sizeof *clip->channels); if (channelCount && !clip->channels) return 0; clip->channelCount = (int)channelCount; for (i = 0; i < clip->channelCount; i++) { kon_modelChannel_t *ch = &clip->channels[i]; uint32_t bone = kon_readU32_(c), kind = kon_readU32_(c), keyCount = kon_readU32_(c); int width, k; ch->path = (int)(kind & 0xFF); ch->step = (int)((kind >> 8) & 1); if (c->bad || bone >= (uint32_t)model->boneCount || ch->path > KON_MODEL_SCALE || (kind >> 9)) return 0; width = ch->path == KON_MODEL_ROTATION ? 4 : 3; if (keyCount == 0 || !kon_countFits_(c, keyCount, 4 + (size_t)width * 4)) return 0; ch->bone = (int)bone; ch->keyCount = (int)keyCount; ch->times = kon_alloc_(model, keyCount, sizeof(float)); ch->values = kon_alloc_(model, (size_t)keyCount * width, sizeof(float)); if (!ch->times || !ch->values) return 0; kon_readFloats_(c, ch->times, keyCount); kon_readFloats_(c, ch->values, (size_t)keyCount * width); if (c->bad) return 0; for (k = 0; k < ch->keyCount; k++) { if (!kon_validFloat_(ch->times[k]) || (k && ch->times[k] < ch->times[k - 1])) return 0; } for (k = 0; k < ch->keyCount * width; k++) if (!kon_validFloat_(ch->values[k])) return 0; } return 1; } kon_model_t *kon_modelLoad(const uint8_t *data, size_t size) { kon_cursor_t c = {data, size, 0, 0}; kon_model_t *model; const uint8_t *magic; uint32_t version, materials, meshes, bones, clips; int i, maxVertices = 0; if (!data) return NULL; magic = kon_take_(&c, 4); if (!magic || memcmp(magic, "KMDL", 4) != 0) return NULL; version = kon_readU32_(&c); materials = kon_readU32_(&c); meshes = kon_readU32_(&c); bones = kon_readU32_(&c); clips = kon_readU32_(&c); if (c.bad || version != KON_MODEL_VERSION || bones > KON_MODEL_MAX_BONES) return NULL; if (!kon_countFits_(&c, materials, 72) || !kon_countFits_(&c, meshes, 16)) return NULL; if (!kon_countFits_(&c, bones, 32 + 4 + 104) || !kon_countFits_(&c, clips, 40)) return NULL; model = calloc(1, sizeof *model); if (!model) return NULL; model->bytes = sizeof *model; model->materialCount = (int)materials; model->meshCount = (int)meshes; model->boneCount = (int)bones; model->clipCount = (int)clips; model->materials = kon_alloc_(model, materials, sizeof *model->materials); model->meshes = kon_alloc_(model, meshes, sizeof *model->meshes); model->bones = kon_alloc_(model, bones, sizeof *model->bones); model->clips = kon_alloc_(model, clips, sizeof *model->clips); if ((materials && !model->materials) || (meshes && !model->meshes) || (bones && !model->bones) || (clips && !model->clips)) goto fail; for (i = 0; i < model->materialCount; i++) if (!kon_readMaterial_(&c, &model->materials[i])) goto fail; for (i = 0; i < model->boneCount; i++) if (!kon_readBone_(&c, &model->bones[i], i)) goto fail; for (i = 0; i < model->meshCount; i++) { if (!kon_readMesh_(&c, model, &model->meshes[i])) goto fail; if (model->meshes[i].vertexCount > maxVertices) maxVertices = model->meshes[i].vertexCount; } for (i = 0; i < model->clipCount; i++) if (!kon_readClip_(&c, model, &model->clips[i])) goto fail; model->scratch = kon_alloc_(model, (size_t)maxVertices, sizeof *model->scratch); if (maxVertices && !model->scratch) goto fail; model->scratchCount = maxVertices; return model; fail: kon_modelFree(model); return NULL; } void kon_modelFree(kon_model_t *model) { int i, j; if (!model) return; for (i = 0; i < model->meshCount; i++) { free(model->meshes[i].vertices); free(model->meshes[i].indices); free(model->meshes[i].joints); free(model->meshes[i].weights); } for (i = 0; i < model->clipCount; i++) { for (j = 0; j < model->clips[i].channelCount; j++) { free(model->clips[i].channels[j].times); free(model->clips[i].channels[j].values); } free(model->clips[i].channels); } free(model->materials); free(model->meshes); free(model->bones); free(model->clips); free(model->scratch); free(model); } /*** assets ***/ static void *kon_modelAssetLoad_(const uint8_t *data, size_t size, size_t *bytes) { kon_model_t *model = kon_modelLoad(data, size); if (model) *bytes = model->bytes; return model; } static void kon_modelAssetUnload_(void *object) { kon_modelFree((kon_model_t *)object); } static void kon_modelAssetBind_(void *object, kon_assets_t *assets) { kon_modelBindTextures((kon_model_t *)object, assets); } static void kon_modelAssetUnbind_(void *object, kon_assets_t *assets) { kon_modelUnbindTextures((kon_model_t *)object, assets); } static const kon_assetLoader_t kon_modelLoader_ = {"model", ".kmdl", kon_modelAssetLoad_, kon_modelAssetUnload_, kon_modelAssetBind_, kon_modelAssetUnbind_}; int kon_modelRegister(kon_assets_t *assets) { return kon_assetsRegister(assets, &kon_modelLoader_); } void kon_modelBindTextures(kon_model_t *model, kon_assets_t *assets) { int i; if (!model || !assets) return; for (i = 0; i < model->materialCount; i++) { kon_modelMaterial_t *m = &model->materials[i]; if (!m->texture[0] || m->image) continue; m->image = kon_assetImage(assets, m->texture); } } void kon_modelUnbindTextures(kon_model_t *model, kon_assets_t *assets) { int i; if (!model || !assets) return; for (i = 0; i < model->materialCount; i++) { kon_modelMaterial_t *m = &model->materials[i]; if (m->image) kon_assetRelease(assets, m->texture); m->image = NULL; } } /*** animation ***/ int kon_modelFindBone(const kon_model_t *model, const char *name) { int i; if (!model || !name) return -1; for (i = 0; i < model->boneCount; i++) if (strcmp(model->bones[i].name, name) == 0) return i; return -1; } int kon_modelFindClip(const kon_model_t *model, const char *name) { int i; if (!model || !name) return -1; for (i = 0; i < model->clipCount; i++) if (strcmp(model->clips[i].name, name) == 0) return i; return -1; } static kon_vec4_t kon_quatSlerp_(kon_vec4_t a, kon_vec4_t b, float t) { float dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; float wa, wb, len; kon_vec4_t r; if (dot < 0.0f) { dot = -dot; b = (kon_vec4_t){-b.x, -b.y, -b.z, -b.w}; } if (dot > 0.9995f) { wa = 1.0f - t; wb = t; } else { float angle = acosf(dot), s = sinf(angle); wa = sinf((1.0f - t) * angle) / s; wb = sinf(t * angle) / s; } r = (kon_vec4_t){wa * a.x + wb * b.x, wa * a.y + wb * b.y, wa * a.z + wb * b.z, wa * a.w + wb * b.w}; len = sqrtf(r.x * r.x + r.y * r.y + r.z * r.z + r.w * r.w); if (len > 0.0f) r = (kon_vec4_t){r.x / len, r.y / len, r.z / len, r.w / len}; return r; } static kon_mat4_t kon_composeTRS_(kon_vec3_t p, kon_vec4_t q, kon_vec3_t s) { float len = sqrtf(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); float x, y, z, w; kon_mat4_t r = kon_mat4Identity(); if (len > 0.0f) { x = q.x / len; y = q.y / len; z = q.z / len; w = q.w / len; } else { x = y = z = 0.0f; w = 1.0f; } r.m[0] = (1.0f - 2.0f * (y * y + z * z)) * s.x; r.m[1] = 2.0f * (x * y + z * w) * s.x; r.m[2] = 2.0f * (x * z - y * w) * s.x; r.m[4] = 2.0f * (x * y - z * w) * s.y; r.m[5] = (1.0f - 2.0f * (x * x + z * z)) * s.y; r.m[6] = 2.0f * (y * z + x * w) * s.y; r.m[8] = 2.0f * (x * z + y * w) * s.z; r.m[9] = 2.0f * (y * z - x * w) * s.z; r.m[10] = (1.0f - 2.0f * (x * x + y * y)) * s.z; r.m[12] = p.x; r.m[13] = p.y; r.m[14] = p.z; return r; } /* finds the pair of keys around time and how far between them it is. before the first or after the last key it holds that key */ static void kon_findKeys_(const kon_modelChannel_t *ch, float time, int *k0, int *k1, float *t) { int lo = 0, hi = ch->keyCount - 1; if (time <= ch->times[0]) { *k0 = *k1 = 0; *t = 0.0f; return; } if (time >= ch->times[hi]) { *k0 = *k1 = hi; *t = 0.0f; return; } while (hi - lo > 1) { int mid = (lo + hi) / 2; if (ch->times[mid] <= time) lo = mid; else hi = mid; } *k0 = lo; *k1 = hi; *t = (time - ch->times[lo]) / (ch->times[hi] - ch->times[lo]); } void kon_poseRest(const kon_model_t *model, kon_pose_t *pose) { int i; if (!model || !pose) return; for (i = 0; i < model->boneCount; i++) { pose->position[i] = model->bones[i].position; pose->rotation[i] = model->bones[i].rotation; pose->scale[i] = model->bones[i].scale; } } /* the bone in model that has the same name as bone number from of source. the same number is tried first, it is nearly always right */ static int kon_matchBone_(const kon_model_t *model, const kon_model_t *source, int from) { if (source == model) return from; if (from < model->boneCount && strcmp(model->bones[from].name, source->bones[from].name) == 0) return from; return kon_modelFindBone(model, source->bones[from].name); } void kon_poseClip(const kon_model_t *model, const kon_model_t *clipModel, int clip, float time, int loop, kon_pose_t *pose) { const kon_modelClip_t *c; int i; if (!model || !pose) return; if (!clipModel) clipModel = model; if (clip < 0 || clip >= clipModel->clipCount) return; c = &clipModel->clips[clip]; if (!(time == time)) time = 0.0f; if (c->duration > 0.0f) { if (loop) { time = fmodf(time, c->duration); if (time < 0.0f) time += c->duration; } else if (time > c->duration) { time = c->duration; } } if (time < 0.0f) time = 0.0f; for (i = 0; i < c->channelCount; i++) { const kon_modelChannel_t *ch = &c->channels[i]; int bone = kon_matchBone_(model, clipModel, ch->bone); int k0, k1; float t; if (bone < 0) continue; kon_findKeys_(ch, time, &k0, &k1, &t); if (ch->step) t = 0.0f; if (ch->path == KON_MODEL_ROTATION) { const float *a = &ch->values[k0 * 4], *b = &ch->values[k1 * 4]; pose->rotation[bone] = kon_quatSlerp_((kon_vec4_t){a[0], a[1], a[2], a[3]}, (kon_vec4_t){b[0], b[1], b[2], b[3]}, t); } else { const float *a = &ch->values[k0 * 3], *b = &ch->values[k1 * 3]; kon_vec3_t v = kon_vec3Lerp(KON_VEC3(a[0], a[1], a[2]), KON_VEC3(b[0], b[1], b[2]), t); if (ch->path == KON_MODEL_POSITION) pose->position[bone] = v; else pose->scale[bone] = v; } } } void kon_poseBlend(const kon_model_t *model, const kon_pose_t *a, const kon_pose_t *b, float t, const float *mask, kon_pose_t *out) { int i; if (!model || !a || !b || !out) return; for (i = 0; i < model->boneCount; i++) { float w = mask ? t * mask[i] : t; out->position[i] = kon_vec3Lerp(a->position[i], b->position[i], w); out->scale[i] = kon_vec3Lerp(a->scale[i], b->scale[i], w); out->rotation[i] = kon_quatSlerp_(a->rotation[i], b->rotation[i], w); } } int kon_modelMaskBranch(const kon_model_t *model, const char *bone, float *mask) { int root = kon_modelFindBone(model, bone), i; if (root < 0 || !mask) return 0; /* parents always come first, so one pass is enough */ for (i = 0; i < model->boneCount; i++) { int parent = model->bones[i].parent; mask[i] = (i == root || (parent >= 0 && mask[parent] > 0.0f)) ? 1.0f : 0.0f; } return 1; } void kon_poseSkin(const kon_model_t *model, const kon_pose_t *pose, kon_mat4_t *skin) { kon_mat4_t global[KON_MODEL_MAX_BONES]; int i; if (!model || !pose || !skin) return; for (i = 0; i < model->boneCount; i++) { kon_mat4_t local = kon_composeTRS_(pose->position[i], pose->rotation[i], pose->scale[i]); int parent = model->bones[i].parent; global[i] = parent >= 0 ? kon_mat4Mul(global[parent], local) : local; skin[i] = kon_mat4Mul(global[i], model->bones[i].inverseBind); } } void kon_modelSample(const kon_model_t *model, int clip, float time, int loop, kon_mat4_t *skin) { kon_pose_t pose; if (!model || !skin) return; kon_poseRest(model, &pose); kon_poseClip(model, NULL, clip, time, loop, &pose); kon_poseSkin(model, &pose, skin); } /*** drawing ***/ static kon_vec3_t kon_mat4MulDirection_(const kon_mat4_t *m, kon_vec3_t v) { return KON_VEC3(m->m[0] * v.x + m->m[4] * v.y + m->m[8] * v.z, m->m[1] * v.x + m->m[5] * v.y + m->m[9] * v.z, m->m[2] * v.x + m->m[6] * v.y + m->m[10] * v.z); } static void kon_skinMesh_(const kon_modelMesh_t *mesh, const kon_mat4_t *skin, kon_vertex_t *out) { int i, k; for (i = 0; i < mesh->vertexCount; i++) { const kon_vertex_t *v = &mesh->vertices[i]; kon_vec3_t p = KON_VEC3_ZERO, n = KON_VEC3_ZERO; float total = 0.0f; for (k = 0; k < 4; k++) { float w = mesh->weights[i * 4 + k]; const kon_mat4_t *m = &skin[mesh->joints[i * 4 + k]]; if (w == 0.0f) continue; p = kon_vec3Add(p, kon_vec3Scale(kon_mat4MulPoint(*m, v->position), w)); n = kon_vec3Add(n, kon_vec3Scale(kon_mat4MulDirection_(m, v->normal), w)); total += w; } out[i].uv = v->uv; if (total > 0.0f) { out[i].position = p; out[i].normal = kon_vec3Normalize(n); } else { out[i].position = v->position; out[i].normal = v->normal; } } } void kon_modelDraw(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t world, kon_model_t *model, const kon_mat4_t *skin, const kon_light_t *light, kon_renderFlags_t flags) { int i; if (!fb || !model) return; for (i = 0; i < model->meshCount; i++) { const kon_modelMesh_t *mesh = &model->meshes[i]; const kon_modelMaterial_t *source = &model->materials[mesh->material]; kon_material_t material; kon_mesh_t draw; material.texture = source->image; material.color = source->color; material.filter = source->filter; material.mips = NULL; draw.indices = mesh->indices; draw.triangleCount = mesh->triangleCount; draw.vertices = mesh->vertices; if (skin && mesh->weights) { kon_skinMesh_(mesh, skin, model->scratch); draw.vertices = model->scratch; } kon_drawMesh(fb, viewProjection, world, &draw, &material, light, flags); } } /*** writing ***/ #ifdef KONMODEL_WRITER typedef struct kon_sink { uint8_t *data; size_t size, capacity; int bad; } kon_sink_t; static void kon_put_(kon_sink_t *s, const void *src, size_t n) { if (s->bad) return; if (s->size + n > s->capacity) { size_t capacity = s->capacity ? s->capacity : 1024; uint8_t *grown; while (capacity < s->size + n) capacity *= 2; grown = realloc(s->data, capacity); if (!grown) { s->bad = 1; return; } s->data = grown; s->capacity = capacity; } memcpy(s->data + s->size, src, n); s->size += n; } static void kon_putU32_(kon_sink_t *s, uint32_t v) { uint8_t b[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24)}; kon_put_(s, b, 4); } static void kon_putFloat_(kon_sink_t *s, float f) { uint32_t u; memcpy(&u, &f, sizeof u); kon_putU32_(s, u); } static void kon_putName_(kon_sink_t *s, const char *name, size_t n) { char buffer[KON_MODEL_TEXTURE_SIZE]; size_t length; memset(buffer, 0, sizeof buffer); length = strlen(name); if (length > n - 1) length = n - 1; memcpy(buffer, name, length); kon_put_(s, buffer, n); } uint8_t *kon_modelSerialize(const kon_model_t *model, size_t *size) { kon_sink_t s = {NULL, 0, 0, 0}; int i, j, k; if (!model) return NULL; kon_put_(&s, "KMDL", 4); kon_putU32_(&s, KON_MODEL_VERSION); kon_putU32_(&s, (uint32_t)model->materialCount); kon_putU32_(&s, (uint32_t)model->meshCount); kon_putU32_(&s, (uint32_t)model->boneCount); kon_putU32_(&s, (uint32_t)model->clipCount); for (i = 0; i < model->materialCount; i++) { kon_putName_(&s, model->materials[i].texture, KON_MODEL_TEXTURE_SIZE); kon_putU32_(&s, model->materials[i].color); kon_putU32_(&s, model->materials[i].filter == KON_FILTER_LINEAR); } for (i = 0; i < model->boneCount; i++) { const kon_modelBone_t *b = &model->bones[i]; kon_putName_(&s, b->name, KON_MODEL_NAME_SIZE); kon_putU32_(&s, b->parent < 0 ? 0xFFFFFFFFu : (uint32_t)b->parent); for (k = 0; k < 16; k++) kon_putFloat_(&s, b->inverseBind.m[k]); kon_putFloat_(&s, b->position.x); kon_putFloat_(&s, b->position.y); kon_putFloat_(&s, b->position.z); kon_putFloat_(&s, b->rotation.x); kon_putFloat_(&s, b->rotation.y); kon_putFloat_(&s, b->rotation.z); kon_putFloat_(&s, b->rotation.w); kon_putFloat_(&s, b->scale.x); kon_putFloat_(&s, b->scale.y); kon_putFloat_(&s, b->scale.z); } for (i = 0; i < model->meshCount; i++) { const kon_modelMesh_t *m = &model->meshes[i]; int skinned = m->joints && m->weights; kon_putU32_(&s, (uint32_t)m->material); kon_putU32_(&s, (uint32_t)m->vertexCount); kon_putU32_(&s, (uint32_t)m->triangleCount); kon_putU32_(&s, (uint32_t)skinned); for (j = 0; j < m->vertexCount; j++) { const kon_vertex_t *v = &m->vertices[j]; kon_putFloat_(&s, v->position.x); kon_putFloat_(&s, v->position.y); kon_putFloat_(&s, v->position.z); kon_putFloat_(&s, v->normal.x); kon_putFloat_(&s, v->normal.y); kon_putFloat_(&s, v->normal.z); kon_putFloat_(&s, v->uv.x); kon_putFloat_(&s, v->uv.y); } if (skinned) { for (j = 0; j < m->vertexCount; j++) { kon_put_(&s, &m->joints[j * 4], 4); for (k = 0; k < 4; k++) kon_putFloat_(&s, m->weights[j * 4 + k]); } } for (j = 0; j < m->triangleCount * 3; j++) { uint8_t b[2] = {(uint8_t)m->indices[j], (uint8_t)(m->indices[j] >> 8)}; kon_put_(&s, b, 2); } } for (i = 0; i < model->clipCount; i++) { const kon_modelClip_t *c = &model->clips[i]; kon_putName_(&s, c->name, KON_MODEL_NAME_SIZE); kon_putFloat_(&s, c->duration); kon_putU32_(&s, (uint32_t)c->channelCount); for (j = 0; j < c->channelCount; j++) { const kon_modelChannel_t *ch = &c->channels[j]; int width = ch->path == KON_MODEL_ROTATION ? 4 : 3; kon_putU32_(&s, (uint32_t)ch->bone); kon_putU32_(&s, (uint32_t)ch->path | (uint32_t)(ch->step != 0) << 8); kon_putU32_(&s, (uint32_t)ch->keyCount); for (k = 0; k < ch->keyCount; k++) kon_putFloat_(&s, ch->times[k]); for (k = 0; k < ch->keyCount * width; k++) kon_putFloat_(&s, ch->values[k]); } } if (s.bad) { free(s.data); return NULL; } *size = s.size; return s.data; } #endif