Animation trees

This commit is contained in:
Mid 2025-08-10 16:09:13 +03:00
parent cc37e17046
commit 295c882100
5 changed files with 388 additions and 76 deletions

161
src/k3.c
View File

@ -294,12 +294,12 @@ struct k3Mesh *k3MdlGetMeshes(struct k3Mdl *mdl, size_t *count) {
return mdl->meshes;
}
void k3MdlAddAnim(struct k3Mdl *mdl, struct k3Animation *anim) {
void k3MdlAddAnim(struct k3Mdl *mdl, struct k3AnimationFountain *anim) {
mdl->anims = realloc(mdl->anims, sizeof(*mdl->anims) * (mdl->animCount + 1));
mdl->anims[mdl->animCount++] = anim;
}
struct k3Animation *k3MdlGetAnim(struct k3Mdl *mdl, uint16_t id) {
struct k3AnimationFountain *k3MdlGetAnim(struct k3Mdl *mdl, uint16_t id) {
for(size_t i = 0; i < mdl->animCount; i++) {
if(mdl->anims[i]->id == id) {
return mdl->anims[i];
@ -311,6 +311,16 @@ size_t k3MdlGetBoneCount(struct k3Mdl *mdl) {
return mdl->boneCount;
}
static size_t bonenames_len(size_t boneCount, const char *names) {
const char *bn = names;
size_t sz = 0;
for(int b = 0; b < boneCount; b++) {
sz += strlen(bn) + 1;
bn += strlen(bn) + 1;
}
return sz;
}
struct k3Mdl *k3MdlCopySubs(struct k3Mdl *src) {
struct k3Mdl *dst = calloc(1, sizeof(*dst));
@ -331,9 +341,14 @@ struct k3Mdl *k3MdlCopySubs(struct k3Mdl *src) {
dst->boneCount = src->boneCount;
dst->invBind = malloc(sizeof(*dst->invBind) * dst->boneCount);
memcpy(dst->invBind, src->invBind, sizeof(dst->invBind) * dst->boneCount);
memcpy(dst->invBind, src->invBind, sizeof(*dst->invBind) * dst->boneCount);
dst->boneParents = malloc(sizeof(*dst->boneParents) * dst->boneCount);
memcpy(dst->boneParents, src->boneParents, sizeof(*dst->boneParents) * dst->boneCount);
if(src->boneNames) {
size_t sz = bonenames_len(src->boneCount, src->boneNames);
dst->boneNames = calloc(1, sz);
memcpy(dst->boneNames, src->boneNames, sz);
}
dst->animCount = src->animCount;
dst->anims = malloc(sizeof(*dst->anims) * dst->animCount);
@ -360,65 +375,113 @@ void k3MdlSetDebugName(struct k3Mdl *mdl, const char *name) {
k3Log(k3_DEBUG, "Model name: %s", name);
}
static void anim_update(struct k3Animator *anim) {
float frame = anim->time * anim->base->fps;
static void qwfun(struct k3Mdl *mdl, size_t b, float *output) {
for(size_t c = b + 1; c < mdl->boneCount; c++) {
if(mdl->boneParents[c] == b) {
output[c] = 1;
qwfun(mdl, c, output);
}
}
}
void k3MdlQueryWeights(struct k3Mdl *mdl, size_t boneIdx, float *output) {
memset(output, 0, sizeof(*output) * mdl->boneCount);
size_t f0 = floorf(frame);
size_t f1 = (f0 + 1);
float alpha = frame - f0;
f0 %= anim->base->frameCount;
f1 %= anim->base->frameCount;
for(size_t b = 0; b < anim->base->boneCount; b++) {
vec4 t0, t1;
glm_vec4_copy(anim->base->frames[anim->base->boneCount * f0 + b].translation, t0);
glm_vec4_copy(anim->base->frames[anim->base->boneCount * f1 + b].translation, t1);
versor r0, r1;
glm_quat_copy(anim->base->frames[anim->base->boneCount * f0 + b].rotation, r0);
glm_quat_copy(anim->base->frames[anim->base->boneCount * f1 + b].rotation, r1);
vec4 t;
glm_vec4_lerp(t0, t1, alpha, t);
versor r;
glm_quat_slerp(r0, r1, alpha, r);
mat4 m = GLM_MAT4_IDENTITY_INIT;
glm_quat_mat4(r, m);
glm_vec4_copy(t, m[3]);
uint8_t parent = anim->base->boneParents[b];
if(parent != 0xFF) {
glm_mat4_mul(anim->inter[parent], m, anim->inter[b]);
} else {
glm_mat4_copy(m, anim->inter[b]);
if(boneIdx < mdl->boneCount) {
qwfun(mdl, boneIdx, output);
}
}
for(size_t b = 0; b < anim->base->boneCount; b++) {
glm_mat4_mul(anim->inter[b], anim->base->invBind[b], anim->inter[b]);
intmax_t k3MdlGetBoneFromName(struct k3Mdl *mdl, const char *targetName) {
if(!mdl->boneNames) {
return -1;
}
const char *name = mdl->boneNames;
for(size_t b = 0; b < mdl->boneCount; b++) {
if(!strcmp(name, targetName)) {
return b;
}
name = name + strlen(name) + 1;
}
return -1;
}
void k3MdlSetBoneNames(struct k3Mdl *mdl, const char *names) {
size_t sz = bonenames_len(mdl->boneCount, names);
mdl->boneNames = malloc(sz);
memcpy(mdl->boneNames, names, sz);
}
void k3AnimatorInit(struct k3Animator *this, struct k3Mdl *mdl) {
this->mdl = mdl;
size_t boneCount = k3MdlGetBoneCount(this->mdl);
this->bones = _mm_malloc(boneCount * sizeof(*this->bones), 16);
for(size_t b = 0; b < boneCount; b++) {
this->bones[b].translation[0] = 0;
this->bones[b].translation[1] = 0;
this->bones[b].translation[2] = 0;
this->bones[b].translation[3] = 1;
this->bones[b].rotation[0] = 0;
this->bones[b].rotation[1] = 0;
this->bones[b].rotation[2] = 0;
this->bones[b].rotation[3] = 1;
}
}
void k3AnimatorSet(struct k3Animator *anim, float time) {
if(!anim->inter) anim->inter = _mm_malloc(sizeof(mat4) * anim->base->boneCount * anim->base->frameCount, 16);
anim->time = time;
k3AnimationUpdate(anim->anim, anim->time);
if(!anim->loop) {
time = fmaxf(time, 0);
time = fminf(time, (float) (anim->base->frameCount - 1) / anim->base->fps);
if(!anim->bones) {
anim->bones = _mm_malloc(sizeof(*anim->bones) * anim->anim->bones, 16);
memset(anim->bones, 0, sizeof(*anim->bones) * anim->anim->bones);
}
anim->time = time;
if(!anim->anim->cache) {
return;
}
anim_update(anim);
mat4 *mats = alloca(sizeof(*mats) * anim->anim->bones);
memset(mats, 0, sizeof(*mats) * anim->anim->bones);
for(size_t b = 0; b < anim->anim->bones; b++) {
mat4 m = GLM_MAT4_IDENTITY_INIT;
glm_quat_mat4(anim->anim->cache[b].rotation, m);
glm_vec4_copy(anim->anim->cache[b].translation, m[3]);
uint8_t parent = anim->mdl->boneParents[b];
if(parent != 0xFF) {
glm_mat4_mul(mats[parent], m, mats[b]);
} else {
glm_mat4_copy(m, mats[b]);
}
}
for(size_t b = 0; b < anim->anim->bones; b++) {
glm_mat4_mul(mats[b], anim->mdl->invBind[b], mats[b]);
}
for(size_t b = 0; b < anim->anim->bones; b++) {
vec4 t;
mat4 r;
vec3 s;
glm_decompose(mats[b], t, r, s);
t[3] = 1;
glm_vec4_copy(t, anim->bones[b].translation);
versor q;
glm_mat4_quat(r, q);
glm_quat_copy(q, anim->bones[b].rotation);
}
}
void k3AnimatorStep(struct k3Animator *anim, float dt) {
k3AnimatorSet(anim, anim->time + dt);
}
struct k3Tex *k3TexCreate(enum k3TexType type) {
struct k3Tex *ret = calloc(1, sizeof(*ret));
@ -1499,7 +1562,7 @@ static void forward_subpass(mat4 projection, mat4 view, int transparent, int lig
enable_glsl_tangents(glsl, mdl);
}
if(k3IsSoftSkinning) {
if(k3IsSoftSkinning && bones) {
apply_cpu_skinning(mdl, bones);
}
@ -1694,7 +1757,7 @@ void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull) {
enable_glsl_bones(glsl, mdl, bones);
}
if(k3IsSoftSkinning) {
if(k3IsSoftSkinning && bones) {
apply_cpu_skinning(mdl, bones);
}
@ -1940,7 +2003,7 @@ static void pass_irregular(int passnum, mat4 mainproj, mat4 maincam, mat4 lightp
enable_glsl_bones(glsl, mdl, bones);
}
if(k3IsSoftSkinning) {
if(k3IsSoftSkinning && bones) {
apply_cpu_skinning(mdl, bones);
}

View File

@ -5,6 +5,7 @@
#include<cglm/vec3.h>
#include<cglm/mat4.h>
#include<cglm/quat.h>
#include"k3anim.h"
#ifdef k3_MULTITHREAD
#include<pthread.h>
@ -101,27 +102,17 @@ struct k3Mat {
} passes[1];
};
struct k3AnimationBone {
vec4 translation;
versor rotation;
};
struct k3Animation {
struct k3AnimationBone *frames;
mat4 *invBind;
uint8_t *boneParents;
uint16_t fps;
uint16_t frameCount;
uint16_t boneCount;
uint16_t id;
};
struct k3Animator {
float time;
int loop;
struct k3Mdl *mdl;
mat4 *inter;
struct k3AnimationBone *bones;
struct k3Animation *base;
struct k3Animation *anim;
double time;
bool playing;
double playStartTime;
};
struct k3Offscreen;
@ -170,14 +161,16 @@ struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *
void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos);
void k3MdlAddMesh(struct k3Mdl*, struct k3Mat*, uint16_t idxStart, uint16_t idxNumber);
struct k3Mesh *k3MdlGetMeshes(struct k3Mdl*, size_t *count);
void k3MdlAddAnim(struct k3Mdl*, struct k3Animation*);
struct k3Animation *k3MdlGetAnim(struct k3Mdl*, uint16_t id);
void k3MdlAddAnim(struct k3Mdl*, struct k3AnimationFountain*);
struct k3AnimationFountain *k3MdlGetAnim(struct k3Mdl*, uint16_t id);
size_t k3MdlGetBoneCount(struct k3Mdl*);
void k3MdlSetDebugName(struct k3Mdl*, const char*);
struct k3Mdl *k3MdlCopySubs(struct k3Mdl*);
void k3MdlSetDebugName(struct k3Mdl*, const char*);
void k3MdlQueryWeights(struct k3Mdl*, size_t bone, float *output);
intmax_t k3MdlGetBoneFromName(struct k3Mdl*, const char*);
void k3MdlSetBoneNames(struct k3Mdl*, const char*);
void k3AnimatorInit(struct k3Animator*, struct k3Mdl*);
void k3AnimatorSet(struct k3Animator*, float);
void k3AnimatorStep(struct k3Animator*, float);

View File

@ -71,9 +71,10 @@ struct k3Mdl {
size_t boneCount;
mat4 *invBind;
uint8_t *boneParents;
char *boneNames;
uint16_t animCount;
struct k3Animation **anims;
struct k3AnimationFountain **anims;
vec3 aabb[2];

185
src/k3anim.c Normal file
View File

@ -0,0 +1,185 @@
#include"k3anim.h"
#include<cglm/mat4.h>
#include<cglm/quat.h>
#include<stdint.h>
#include<stddef.h>
static uint32_t lcg() {
static uint32_t asdf = 0;
asdf = asdf * 1664525 + 1013904223;
return asdf;
}
static void power_iter(mat4 M, int iterations, float eps, vec4 ev) {
// Random starting vector
uint32_t rng = lcg();
ev[0] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[1] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[2] = (rng & 0xFF) / 255.f;
rng >>= 8;
ev[3] = rng / 255.f;
for(int i = 0; i < iterations; i++) {
vec4 k;
glm_mat4_mulv(M, ev, k);
glm_vec4_normalize_to(k, ev);
}
}
static void quat_to_mat(versor v, mat4 m) {
for(int y = 0; y < 4; y++) {
for(int x = 0; x < 4; x++) {
m[y][x] = v[y] * v[x];
}
}
}
bool k3AnimationUpdate(struct k3Animation *anim, float time) {
if(anim->op == k3_ANIM_BASIC) {
struct k3AnimationBasic *basic = (void*) anim;
struct k3AnimationFountain *fountain = basic->fountain;
if(!anim->cache) {
anim->bones = fountain->bones;
anim->cache = _mm_malloc(sizeof(*anim->cache) * anim->bones, 16);
}
float frame = time * fountain->fps;
if(!basic->loop) {
frame = fminf(frame, fountain->frameCount - 1);
}
size_t f0 = floorf(frame);
size_t f1 = (f0 + 1);
float alpha = frame - f0;
f0 %= fountain->frameCount;
f1 %= fountain->frameCount;
for(size_t b = 0; b < fountain->bones; b++) {
vec4 t0, t1;
glm_vec4_copy(fountain->frames[fountain->bones * f0 + b].translation, t0);
glm_vec4_copy(fountain->frames[fountain->bones * f1 + b].translation, t1);
versor r0, r1;
glm_quat_copy(fountain->frames[fountain->bones * f0 + b].rotation, r0);
glm_quat_copy(fountain->frames[fountain->bones * f1 + b].rotation, r1);
glm_vec4_lerp(t0, t1, alpha, basic->cache[b].translation);
glm_quat_slerp(r0, r1, alpha, basic->cache[b].rotation);
}
return true;
} else if(anim->op == k3_ANIM_BLEND) {
struct k3AnimationBlend *blend = (void*) anim;
if(blend->children == 0) return false;
if(!blend->cache || blend->bones != blend->subs[0]->bones) {
if(blend->cache) _mm_free(blend->cache);
blend->bones = blend->subs[0]->bones;
blend->cache = _mm_malloc(blend->bones * sizeof(*blend->cache), 16);
}
for(size_t c = 0; c < blend->children; c++) {
if(!k3AnimationUpdate(blend->subs[c], blend->offsets[c] + time * blend->speeds[c])) return false;
if(blend->subs[c]->bones != blend->bones) return false;
}
for(size_t b = 0; b < blend->bones; b++) {
float weightSum = 0;
vec4 translationSum = GLM_VEC4_ZERO_INIT;
mat4 rotationSum = GLM_MAT4_ZERO_INIT;
for(size_t c = 0; c < blend->children; c++) {
float w = blend->weights[c * blend->bones + b];
weightSum += w;
glm_vec4_muladds(blend->subs[c]->cache[b].translation, w, translationSum);
mat4 rm;
quat_to_mat(blend->subs[c]->cache[b].rotation, rm);
glm_mat4_scale(rm, w);
glm_vec4_add(rotationSum[0], rm[0], rotationSum[0]);
glm_vec4_add(rotationSum[1], rm[1], rotationSum[1]);
glm_vec4_add(rotationSum[2], rm[2], rotationSum[2]);
glm_vec4_add(rotationSum[3], rm[3], rotationSum[3]);
}
glm_vec4_scale(translationSum, 1.0f / weightSum, blend->cache[b].translation);
vec4 dominantEigenVector;
power_iter(rotationSum, 64, 1e-5f, dominantEigenVector);
glm_vec4_normalize(dominantEigenVector);
glm_quat_copy((float*) dominantEigenVector, blend->cache[b].rotation);
}
return true;
} else if(anim->op == k3_ANIM_ADD) {
struct k3AnimationAdd *add = (void*) anim;
if(!k3AnimationUpdate(add->sub1, time)) return false;
if(!k3AnimationUpdate(add->sub2, time)) return false;
if(!add->cache || add->bones != add->sub1->bones) {
if(add->cache) _mm_free(add->cache);
add->bones = add->sub1->bones;
add->cache = _mm_malloc(add->bones * sizeof(*add->cache), 16);
}
versor norot = {0, 0, 0, 1};
for(size_t b = 0; b < add->bones; b++) {
glm_vec4_copy(add->sub1->cache[b].translation, add->cache[b].translation);
glm_vec4_muladds(add->sub2->cache[b].translation, add->scale, add->cache[b].translation);
versor o;
glm_quat_nlerp(norot, add->sub2->cache[b].rotation, add->scale, o);
glm_quat_mul(o, add->sub1->cache[b].rotation, add->cache[b].rotation);
}
return true;
}
return false;
}
void k3AnimationFree(struct k3Animation *anim) {
if(!anim) return;
if(anim->ref != 0) {
anim->ref--;
return;
}
if(anim->op == k3_ANIM_BLEND) {
struct k3AnimationBlend *blend = (void*) anim;
for(size_t c = 0; c < blend->children; c++) {
k3AnimationFree(blend->subs[c]);
}
free(blend->subs);
free(blend->weights);
free(blend->offsets);
free(blend->speeds);
} else if(anim->op == k3_ANIM_ADD) {
struct k3AnimationAdd *add = (void*) anim;
k3AnimationFree(add->sub1);
k3AnimationFree(add->sub2);
}
_mm_free(anim->cache);
free(anim);
}

70
src/k3anim.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include<cglm/mat4.h>
#include<cglm/quat.h>
#include<stdint.h>
#include<stddef.h>
struct k3AnimationBone {
vec4 translation;
versor rotation;
};
enum k3AnimationOp {
k3_ANIM_BASIC,
k3_ANIM_BLEND,
k3_ANIM_ADD,
};
struct k3AnimationFountain {
float fps;
size_t frameCount;
size_t bones;
uint16_t id;
_Alignas(16) struct k3AnimationBone frames[];
};
struct k3Animation {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
};
struct k3AnimationBasic {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
struct k3AnimationFountain *fountain;
bool loop;
};
struct k3AnimationBlend {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
size_t children;
float *offsets;
float *speeds;
float *weights;
struct k3Animation **subs;
};
struct k3AnimationAdd {
enum k3AnimationOp op;
size_t bones;
struct k3AnimationBone *cache;
size_t ref;
struct k3Animation *sub1;
struct k3Animation *sub2;
float scale;
};
bool k3AnimationUpdate(struct k3Animation *anim, float time);
void k3AnimationFree(struct k3Animation *anim);