summaryrefslogtreecommitdiff
path: root/kon/konmodel.c
diff options
context:
space:
mode:
Diffstat (limited to 'kon/konmodel.c')
-rw-r--r--kon/konmodel.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/kon/konmodel.c b/kon/konmodel.c
new file mode 100644
index 0000000..389d16c
--- /dev/null
+++ b/kon/konmodel.c
@@ -0,0 +1,652 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#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 const kon_assetLoader_t kon_modelLoader_ = {"model", ".kmdl", kon_modelAssetLoad_, kon_modelAssetUnload_};
+
+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;
+ if (!kon_assetGet(assets, m->texture)) continue;
+ m->image = kon_assetImage(assets, m->texture);
+ if (!m->image) kon_assetRelease(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_modelSample(const kon_model_t *model, int clip, float time, int loop, kon_mat4_t *skin) {
+ kon_vec3_t position[KON_MODEL_MAX_BONES], scale[KON_MODEL_MAX_BONES];
+ kon_vec4_t rotation[KON_MODEL_MAX_BONES];
+ kon_mat4_t global[KON_MODEL_MAX_BONES];
+ int i;
+
+ if (!model || !skin) return;
+ for (i = 0; i < model->boneCount; i++) {
+ position[i] = model->bones[i].position;
+ rotation[i] = model->bones[i].rotation;
+ scale[i] = model->bones[i].scale;
+ }
+
+ if (clip >= 0 && clip < model->clipCount) {
+ const kon_modelClip_t *c = &model->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 k0, k1;
+ float t;
+
+ 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];
+ rotation[ch->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) position[ch->bone] = v; else scale[ch->bone] = v;
+ }
+ }
+ }
+
+ for (i = 0; i < model->boneCount; i++) {
+ kon_mat4_t local = kon_composeTRS_(position[i], rotation[i], 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);
+ }
+}
+
+/*** 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;
+ 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