2023-07-29 14:32:42 +02:00
|
|
|
#include "vk_studio_model.h"
|
|
|
|
#include "r_speeds.h"
|
2023-08-22 20:23:20 +02:00
|
|
|
#include "vk_entity_data.h"
|
2023-08-29 18:31:57 +02:00
|
|
|
#include "vk_logs.h"
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-24 21:11:53 +02:00
|
|
|
#include "xash3d_mathlib.h"
|
|
|
|
|
2023-07-29 14:32:42 +02:00
|
|
|
#define MODULE_NAME "studio"
|
2023-08-29 18:31:57 +02:00
|
|
|
#define LOG_MODULE LogModule_Studio
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-22 20:23:20 +02:00
|
|
|
typedef struct {
|
|
|
|
const studiohdr_t *studio_header_key;
|
|
|
|
r_studio_model_info_t info;
|
|
|
|
} r_studio_model_info_entry_t;
|
|
|
|
|
2023-07-29 14:32:42 +02:00
|
|
|
static struct {
|
|
|
|
#define MAX_STUDIO_MODELS 256
|
2023-08-22 20:23:20 +02:00
|
|
|
r_studio_model_info_entry_t models[MAX_STUDIO_MODELS];
|
2023-07-29 14:32:42 +02:00
|
|
|
int models_count;
|
2023-08-28 17:58:38 +02:00
|
|
|
|
|
|
|
int submodels_cached_dynamic;
|
|
|
|
int submodels_cached_static;
|
2023-07-29 14:32:42 +02:00
|
|
|
} g_studio_cache;
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel ) {
|
2023-07-29 14:32:42 +02:00
|
|
|
R_RenderModelDestroy(&submodel->model);
|
|
|
|
R_GeometryRangeFree(&submodel->geometry_range);
|
2023-08-25 19:44:53 +02:00
|
|
|
if (submodel->geometries)
|
|
|
|
Mem_Free(submodel->geometries);
|
2023-08-28 18:59:54 +02:00
|
|
|
if (submodel->prev_verts)
|
|
|
|
Mem_Free(submodel->prev_verts);
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
static void studioSubmodelInfoDestroy(r_studio_submodel_info_t *subinfo) {
|
2023-08-28 17:58:38 +02:00
|
|
|
// Not zero means that something still holds a cached render submodel instance somewhere
|
|
|
|
ASSERT(subinfo->render_refcount == 0);
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
while (subinfo->cached_head) {
|
|
|
|
r_studio_submodel_render_t *render = subinfo->cached_head;
|
|
|
|
subinfo->cached_head = subinfo->cached_head->_.next;
|
|
|
|
studioRenderSubmodelDestroy(render);
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
2023-08-25 21:25:07 +02:00
|
|
|
}
|
2023-08-22 20:23:20 +02:00
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
void R_StudioCacheClear( void ) {
|
2023-08-25 19:22:19 +02:00
|
|
|
for (int i = 0; i < g_studio_cache.models_count; ++i) {
|
|
|
|
r_studio_model_info_t *info = &g_studio_cache.models[i].info;
|
2023-08-25 21:25:07 +02:00
|
|
|
|
|
|
|
for (int j = 0; j < info->submodels_count; ++j)
|
|
|
|
studioSubmodelInfoDestroy(info->submodels + j);
|
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
if (info->submodels)
|
|
|
|
Mem_Free(info->submodels);
|
2023-08-24 21:11:53 +02:00
|
|
|
}
|
2023-08-25 19:22:19 +02:00
|
|
|
g_studio_cache.models_count = 0;
|
2023-08-28 17:58:38 +02:00
|
|
|
|
|
|
|
g_studio_cache.submodels_cached_dynamic = g_studio_cache.submodels_cached_static = 0;
|
2023-08-24 21:11:53 +02:00
|
|
|
}
|
|
|
|
|
2023-09-12 19:43:46 +02:00
|
|
|
typedef struct {
|
|
|
|
matrix3x4 mat;
|
|
|
|
} bone_transform_t;
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-09-12 19:43:46 +02:00
|
|
|
static struct {
|
|
|
|
bone_transform_t first[MAXSTUDIOBONES];
|
|
|
|
bone_transform_t current[MAXSTUDIOBONES];
|
2023-08-25 19:22:19 +02:00
|
|
|
} gb;
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-09-12 19:43:46 +02:00
|
|
|
static void studioModelCalcBones(int numbones, const mstudiobone_t *pbone, const mstudioanim_t *panim, int frame, bone_transform_t *out) {
|
2023-08-25 19:22:19 +02:00
|
|
|
for(int b = 0; b < numbones; b++ ) {
|
|
|
|
// TODO check pbone->bonecontroller, if the bone can be dynamically controlled by entity
|
2023-09-12 19:43:46 +02:00
|
|
|
// So far we havent't seen any cases where bonecontroller presence makes static submodels dynamic
|
2023-08-25 19:22:19 +02:00
|
|
|
float *const adj = NULL;
|
|
|
|
const float interpolation = 0;
|
2023-09-12 19:43:46 +02:00
|
|
|
vec4_t q;
|
|
|
|
vec3_t pos;
|
|
|
|
R_StudioCalcBoneQuaternion( frame, interpolation, pbone + b, panim + b, adj, q );
|
|
|
|
R_StudioCalcBonePosition( frame, interpolation, pbone + b, panim + b, adj, pos );
|
|
|
|
|
|
|
|
matrix3x4 bonematrix;
|
|
|
|
Matrix3x4_FromOriginQuat(bonematrix, q, pos);
|
|
|
|
if (pbone[b].parent >= 0) {
|
|
|
|
Matrix3x4_ConcatTransforms(out[b].mat, out[pbone[b].parent].mat, bonematrix);
|
|
|
|
} else {
|
|
|
|
Matrix3x4_Copy(out[b].mat, bonematrix);
|
|
|
|
}
|
2023-08-25 19:22:19 +02:00
|
|
|
}
|
|
|
|
}
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
qboolean Vector4CompareEpsilon( const vec4_t vec1, const vec4_t vec2, vec_t epsilon )
|
|
|
|
{
|
|
|
|
vec_t ax, ay, az, aw;
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
ax = fabs( vec1[0] - vec2[0] );
|
|
|
|
ay = fabs( vec1[1] - vec2[1] );
|
|
|
|
az = fabs( vec1[2] - vec2[2] );
|
|
|
|
aw = fabs( vec1[3] - vec2[3] );
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) && ( aw <= epsilon ))
|
|
|
|
return true;
|
|
|
|
return false;
|
2023-08-24 21:11:53 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
static qboolean isBoneSame(int b) {
|
2023-09-12 19:43:46 +02:00
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
if (!Vector4CompareEpsilon(gb.first[b].mat[i], gb.current[b].mat[i], 1e-4f))
|
|
|
|
return false;
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-09-12 19:43:46 +02:00
|
|
|
/* static qboolean canBoneBeControlled(const mstudiobone_t* pbone, int b) { */
|
|
|
|
/* pbone += b; */
|
|
|
|
/* for (int i = 0; i < COUNTOF(pbone->bonecontroller); ++i) { */
|
|
|
|
/* if (pbone->bonecontroller[i] >= 0) */
|
|
|
|
/* return true; */
|
|
|
|
/* } */
|
|
|
|
/* return false; */
|
|
|
|
/* } */
|
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
static void studioModelProcessBonesAnimations(const model_t *const model, const studiohdr_t *const hdr, r_studio_submodel_info_t *submodels, int submodels_count) {
|
2023-09-12 19:43:46 +02:00
|
|
|
const mstudiobone_t* const pbone = (mstudiobone_t *)((byte *)hdr + hdr->boneindex);
|
|
|
|
|
|
|
|
/* for (int i = 0; i < hdr->numbones; ++i) { */
|
|
|
|
/* const mstudiobone_t* const bone = pbone + i; */
|
|
|
|
/* INFO(" Bone %i: %s", i, bone->name); */
|
|
|
|
/* } */
|
|
|
|
|
2023-07-29 14:32:42 +02:00
|
|
|
for (int i = 0; i < hdr->numseq; ++i) {
|
|
|
|
const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i;
|
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
const mstudioanim_t* const panim = gEngine.R_StudioGetAnim( (studiohdr_t*)hdr, (model_t*)model, (mstudioseqdesc_t*)pseqdesc );
|
|
|
|
|
|
|
|
// Compute the first frame bones to compare with
|
2023-09-12 19:43:46 +02:00
|
|
|
studioModelCalcBones(hdr->numbones, pbone, panim, 0, gb.first);
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
// Compute bones for each frame
|
|
|
|
for (int frame = 1; frame < pseqdesc->numframes; ++frame) {
|
2023-09-12 19:43:46 +02:00
|
|
|
studioModelCalcBones(hdr->numbones, pbone, panim, frame, gb.current);
|
2023-08-25 19:22:19 +02:00
|
|
|
|
|
|
|
// Compate bones for each submodel
|
|
|
|
for (int si = 0; si < submodels_count; ++si) {
|
|
|
|
r_studio_submodel_info_t *const subinfo = submodels + si;
|
|
|
|
|
|
|
|
// Once detected as dynamic, there's no point in checking further
|
|
|
|
if (subinfo->is_dynamic)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const mstudiomodel_t *const submodel = subinfo->submodel_key;
|
|
|
|
const qboolean use_boneweights = FBitSet(hdr->flags, STUDIO_HAS_BONEWEIGHTS) && submodel->blendvertinfoindex != 0 && submodel->blendnorminfoindex != 0;
|
|
|
|
|
|
|
|
if (use_boneweights) {
|
|
|
|
const mstudioboneweight_t *const pvertweight = (mstudioboneweight_t *)((byte *)hdr + submodel->blendvertinfoindex);
|
|
|
|
for(int vi = 0; vi < submodel->numverts; vi++) {
|
|
|
|
for (int bi = 0; bi < 4; ++bi) {
|
|
|
|
const int8_t bone = pvertweight[vi].bone[bi];
|
|
|
|
if (bone == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
subinfo->is_dynamic |= !isBoneSame(bone);
|
|
|
|
if (subinfo->is_dynamic)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (subinfo->is_dynamic)
|
|
|
|
break;
|
|
|
|
} // for submodel verts
|
|
|
|
|
|
|
|
} /* use_boneweights */ else {
|
|
|
|
const byte *const pvertbone = ((const byte *)hdr + submodel->vertinfoindex);
|
|
|
|
for(int vi = 0; vi < submodel->numverts; vi++) {
|
2023-09-12 19:43:46 +02:00
|
|
|
const byte bone = pvertbone[vi];
|
|
|
|
subinfo->is_dynamic |= !isBoneSame(bone);
|
|
|
|
if (subinfo->is_dynamic)
|
|
|
|
break;
|
2023-08-25 19:22:19 +02:00
|
|
|
}
|
|
|
|
} // no use_boneweights
|
2023-09-12 19:43:46 +02:00
|
|
|
|
|
|
|
/* if (subinfo->has_bonecontroller && !subinfo->is_dynamic) { */
|
|
|
|
/* WARN("Submodel %s is static, but can be affected by bonecontroller", subinfo->submodel_key->name); */
|
|
|
|
/* } */
|
2023-08-25 19:22:19 +02:00
|
|
|
} // for all submodels
|
|
|
|
} // for all frames
|
|
|
|
} // for all sequences
|
|
|
|
}
|
2023-08-24 21:11:53 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
// Get submodels count and/or fill submodels array
|
|
|
|
static int studioModelGetSubmodels(const studiohdr_t *hdr, r_studio_submodel_info_t *out_submodels) {
|
|
|
|
int count = 0;
|
2023-08-24 21:11:53 +02:00
|
|
|
for (int i = 0; i < hdr->numbodyparts; ++i) {
|
|
|
|
const mstudiobodyparts_t* const bodypart = (mstudiobodyparts_t *)((byte *)hdr + hdr->bodypartindex) + i;
|
2023-08-25 19:22:19 +02:00
|
|
|
if (out_submodels) {
|
2023-08-29 18:31:57 +02:00
|
|
|
DEBUG(" Bodypart %d/%d: %s (nummodels=%d)", i, hdr->numbodyparts - 1, bodypart->name, bodypart->nummodels);
|
2023-08-25 19:22:19 +02:00
|
|
|
for (int j = 0; j < bodypart->nummodels; ++j) {
|
|
|
|
const mstudiomodel_t * const submodel = (mstudiomodel_t *)((byte *)hdr + bodypart->modelindex) + j;
|
2023-08-29 18:31:57 +02:00
|
|
|
DEBUG(" Submodel %d: %s", j, submodel->name);
|
2023-08-25 19:22:19 +02:00
|
|
|
out_submodels[count++].submodel_key = submodel;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
count += bodypart->nummodels;
|
2023-08-24 21:11:53 +02:00
|
|
|
}
|
|
|
|
}
|
2023-08-25 19:22:19 +02:00
|
|
|
return count;
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 16:18:31 +02:00
|
|
|
const r_studio_model_info_t* R_StudioModelPreload(model_t *mod) {
|
2023-07-29 14:32:42 +02:00
|
|
|
const studiohdr_t *const hdr = (const studiohdr_t *)gEngine.Mod_Extradata(mod_studio, mod);
|
|
|
|
|
|
|
|
ASSERT(g_studio_cache.models_count < MAX_STUDIO_MODELS);
|
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
r_studio_model_info_entry_t *entry = &g_studio_cache.models[g_studio_cache.models_count++];
|
|
|
|
entry->studio_header_key = hdr;
|
|
|
|
|
2023-09-15 18:47:53 +02:00
|
|
|
DEBUG("Studio model %p(%s) hdr=%p(%s), sequences=%d:", mod, mod->name, hdr, hdr->name, hdr->numseq);
|
2023-08-25 19:22:19 +02:00
|
|
|
for (int i = 0; i < hdr->numseq; ++i) {
|
|
|
|
const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i;
|
2023-08-29 18:31:57 +02:00
|
|
|
DEBUG(" %d: fps=%f numframes=%d", i, pseqdesc->fps, pseqdesc->numframes);
|
2023-08-25 19:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get submodel array
|
|
|
|
const int submodels_count = studioModelGetSubmodels(hdr, NULL);
|
|
|
|
r_studio_submodel_info_t *submodels = Mem_Calloc(vk_core.pool, sizeof(*submodels) * submodels_count);
|
|
|
|
studioModelGetSubmodels(hdr, submodels);
|
|
|
|
|
|
|
|
studioModelProcessBonesAnimations(mod, hdr, submodels, submodels_count);
|
|
|
|
|
|
|
|
qboolean is_dynamic = false;
|
2023-08-29 18:31:57 +02:00
|
|
|
DEBUG(" submodels_count: %d", submodels_count);
|
2023-08-25 19:22:19 +02:00
|
|
|
for (int i = 0; i < submodels_count; ++i) {
|
|
|
|
const r_studio_submodel_info_t *const subinfo = submodels + i;
|
|
|
|
is_dynamic |= subinfo->is_dynamic;
|
2023-09-12 19:43:46 +02:00
|
|
|
//DEBUG(" Submodel %d/%d: name=\"%s\", is_dynamic=%d has_bonecontroller=%d", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic, subinfo->has_bonecontroller);
|
2023-08-29 18:31:57 +02:00
|
|
|
DEBUG(" Submodel %d/%d: name=\"%s\", is_dynamic=%d", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic);
|
2023-08-25 19:22:19 +02:00
|
|
|
}
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-25 19:22:19 +02:00
|
|
|
entry->info.submodels_count = submodels_count;
|
|
|
|
entry->info.submodels = submodels;
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-09-07 16:18:31 +02:00
|
|
|
return &entry->info;
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 16:18:31 +02:00
|
|
|
const r_studio_model_info_t *getStudioModelInfo(model_t *model) {
|
2023-08-22 20:23:20 +02:00
|
|
|
const studiohdr_t *const hdr = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, model );
|
|
|
|
|
|
|
|
for (int i = 0; i < g_studio_cache.models_count; ++i) {
|
|
|
|
r_studio_model_info_entry_t *const entry = g_studio_cache.models + i;
|
|
|
|
if (entry->studio_header_key == hdr) {
|
|
|
|
return &entry->info;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-07 16:18:31 +02:00
|
|
|
WARN("Studio model \"%s\" wasn't preloaded. How did that happen?", hdr->name);
|
|
|
|
|
|
|
|
return R_StudioModelPreload(model);
|
2023-08-22 20:23:20 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
void VK_StudioModelInit(void) {
|
2023-08-28 17:58:38 +02:00
|
|
|
R_SPEEDS_METRIC(g_studio_cache.submodels_cached_static, "submodels_cached_static", kSpeedsMetricCount);
|
|
|
|
R_SPEEDS_METRIC(g_studio_cache.submodels_cached_dynamic, "submodels_cached_dynamic", kSpeedsMetricCount);
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
r_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *subinfo) {
|
2023-09-14 19:02:01 +02:00
|
|
|
const char *mode = "";
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
r_studio_submodel_render_t *render = NULL;
|
|
|
|
if (subinfo->cached_head) {
|
|
|
|
render = subinfo->cached_head;
|
|
|
|
if (subinfo->is_dynamic) {
|
|
|
|
subinfo->cached_head = render->_.next;
|
|
|
|
render->_.next = NULL;
|
|
|
|
}
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-09-14 19:02:01 +02:00
|
|
|
mode = "new";
|
2023-08-28 17:58:38 +02:00
|
|
|
} else {
|
2023-09-14 19:02:01 +02:00
|
|
|
render = Mem_Calloc(vk_core.pool, sizeof(*render));
|
|
|
|
render->_.info = subinfo;
|
|
|
|
|
|
|
|
if (!subinfo->is_dynamic) {
|
|
|
|
subinfo->cached_head = render;
|
|
|
|
++g_studio_cache.submodels_cached_static;
|
|
|
|
} else {
|
|
|
|
++g_studio_cache.submodels_cached_dynamic;
|
|
|
|
}
|
|
|
|
|
|
|
|
mode = "cached";
|
2023-08-28 17:58:38 +02:00
|
|
|
}
|
2023-08-25 21:25:07 +02:00
|
|
|
|
2023-08-28 17:58:38 +02:00
|
|
|
subinfo->render_refcount++;
|
2023-09-15 18:47:53 +02:00
|
|
|
DEBUG("%s: submodel=%p(%s) \"%s\" rendermodel=%p refcount=%d", __FUNCTION__, subinfo->submodel_key, mode, subinfo->submodel_key->name, render, subinfo->render_refcount);
|
2023-08-25 21:25:07 +02:00
|
|
|
return render;
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
void studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel) {
|
|
|
|
if (!render_submodel)
|
|
|
|
return;
|
2023-07-29 14:32:42 +02:00
|
|
|
|
2023-08-28 17:58:38 +02:00
|
|
|
ASSERT(render_submodel->_.info->render_refcount > 0);
|
|
|
|
render_submodel->_.info->render_refcount--;
|
|
|
|
|
2023-09-14 19:02:01 +02:00
|
|
|
const r_studio_submodel_info_t* const subinfo = render_submodel->_.info;
|
|
|
|
DEBUG("%s: submodel=%p(%s) rendermodel=%p refcount=%d", __FUNCTION__, subinfo->submodel_key, subinfo->submodel_key->name, render_submodel, subinfo->render_refcount);
|
|
|
|
|
2023-08-25 21:25:07 +02:00
|
|
|
if (!render_submodel->_.info->is_dynamic)
|
|
|
|
return;
|
|
|
|
|
|
|
|
render_submodel->_.next = render_submodel->_.info->cached_head;
|
|
|
|
render_submodel->_.info->cached_head = render_submodel;
|
2023-07-29 14:32:42 +02:00
|
|
|
}
|