xash3d-fwgs/ref/vk/vk_studio_model.c
Ivan Avdeev 2e1cb8173e vk: studio: use explicitly set RI.currentmodel over entity->model
`thirdperson` mode overrides entity model while leaving entity->model
with the old value. Vk renderer was assuming that those are the same.

Fixes #551
2023-09-15 12:47:53 -04:00

308 lines
10 KiB
C

#include "vk_studio_model.h"
#include "r_speeds.h"
#include "vk_entity_data.h"
#include "vk_logs.h"
#include "xash3d_mathlib.h"
#define MODULE_NAME "studio"
#define LOG_MODULE LogModule_Studio
typedef struct {
const studiohdr_t *studio_header_key;
r_studio_model_info_t info;
} r_studio_model_info_entry_t;
static struct {
#define MAX_STUDIO_MODELS 256
r_studio_model_info_entry_t models[MAX_STUDIO_MODELS];
int models_count;
int submodels_cached_dynamic;
int submodels_cached_static;
} g_studio_cache;
void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel ) {
R_RenderModelDestroy(&submodel->model);
R_GeometryRangeFree(&submodel->geometry_range);
if (submodel->geometries)
Mem_Free(submodel->geometries);
if (submodel->prev_verts)
Mem_Free(submodel->prev_verts);
}
static void studioSubmodelInfoDestroy(r_studio_submodel_info_t *subinfo) {
// Not zero means that something still holds a cached render submodel instance somewhere
ASSERT(subinfo->render_refcount == 0);
while (subinfo->cached_head) {
r_studio_submodel_render_t *render = subinfo->cached_head;
subinfo->cached_head = subinfo->cached_head->_.next;
studioRenderSubmodelDestroy(render);
}
}
void R_StudioCacheClear( void ) {
for (int i = 0; i < g_studio_cache.models_count; ++i) {
r_studio_model_info_t *info = &g_studio_cache.models[i].info;
for (int j = 0; j < info->submodels_count; ++j)
studioSubmodelInfoDestroy(info->submodels + j);
if (info->submodels)
Mem_Free(info->submodels);
}
g_studio_cache.models_count = 0;
g_studio_cache.submodels_cached_dynamic = g_studio_cache.submodels_cached_static = 0;
}
typedef struct {
matrix3x4 mat;
} bone_transform_t;
static struct {
bone_transform_t first[MAXSTUDIOBONES];
bone_transform_t current[MAXSTUDIOBONES];
} gb;
static void studioModelCalcBones(int numbones, const mstudiobone_t *pbone, const mstudioanim_t *panim, int frame, bone_transform_t *out) {
for(int b = 0; b < numbones; b++ ) {
// TODO check pbone->bonecontroller, if the bone can be dynamically controlled by entity
// So far we havent't seen any cases where bonecontroller presence makes static submodels dynamic
float *const adj = NULL;
const float interpolation = 0;
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);
}
}
}
qboolean Vector4CompareEpsilon( const vec4_t vec1, const vec4_t vec2, vec_t epsilon )
{
vec_t ax, ay, az, aw;
ax = fabs( vec1[0] - vec2[0] );
ay = fabs( vec1[1] - vec2[1] );
az = fabs( vec1[2] - vec2[2] );
aw = fabs( vec1[3] - vec2[3] );
if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) && ( aw <= epsilon ))
return true;
return false;
}
static qboolean isBoneSame(int b) {
for (int i = 0; i < 3; ++i)
if (!Vector4CompareEpsilon(gb.first[b].mat[i], gb.current[b].mat[i], 1e-4f))
return false;
return true;
}
/* 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; */
/* } */
static void studioModelProcessBonesAnimations(const model_t *const model, const studiohdr_t *const hdr, r_studio_submodel_info_t *submodels, int submodels_count) {
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); */
/* } */
for (int i = 0; i < hdr->numseq; ++i) {
const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i;
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
studioModelCalcBones(hdr->numbones, pbone, panim, 0, gb.first);
// Compute bones for each frame
for (int frame = 1; frame < pseqdesc->numframes; ++frame) {
studioModelCalcBones(hdr->numbones, pbone, panim, frame, gb.current);
// 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++) {
const byte bone = pvertbone[vi];
subinfo->is_dynamic |= !isBoneSame(bone);
if (subinfo->is_dynamic)
break;
}
} // no use_boneweights
/* if (subinfo->has_bonecontroller && !subinfo->is_dynamic) { */
/* WARN("Submodel %s is static, but can be affected by bonecontroller", subinfo->submodel_key->name); */
/* } */
} // for all submodels
} // for all frames
} // for all sequences
}
// 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;
for (int i = 0; i < hdr->numbodyparts; ++i) {
const mstudiobodyparts_t* const bodypart = (mstudiobodyparts_t *)((byte *)hdr + hdr->bodypartindex) + i;
if (out_submodels) {
DEBUG(" Bodypart %d/%d: %s (nummodels=%d)", i, hdr->numbodyparts - 1, bodypart->name, bodypart->nummodels);
for (int j = 0; j < bodypart->nummodels; ++j) {
const mstudiomodel_t * const submodel = (mstudiomodel_t *)((byte *)hdr + bodypart->modelindex) + j;
DEBUG(" Submodel %d: %s", j, submodel->name);
out_submodels[count++].submodel_key = submodel;
}
} else {
count += bodypart->nummodels;
}
}
return count;
}
const r_studio_model_info_t* R_StudioModelPreload(model_t *mod) {
const studiohdr_t *const hdr = (const studiohdr_t *)gEngine.Mod_Extradata(mod_studio, mod);
ASSERT(g_studio_cache.models_count < MAX_STUDIO_MODELS);
r_studio_model_info_entry_t *entry = &g_studio_cache.models[g_studio_cache.models_count++];
entry->studio_header_key = hdr;
DEBUG("Studio model %p(%s) hdr=%p(%s), sequences=%d:", mod, mod->name, hdr, hdr->name, hdr->numseq);
for (int i = 0; i < hdr->numseq; ++i) {
const mstudioseqdesc_t *const pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + i;
DEBUG(" %d: fps=%f numframes=%d", i, pseqdesc->fps, pseqdesc->numframes);
}
// 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;
DEBUG(" submodels_count: %d", submodels_count);
for (int i = 0; i < submodels_count; ++i) {
const r_studio_submodel_info_t *const subinfo = submodels + i;
is_dynamic |= subinfo->is_dynamic;
//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);
DEBUG(" Submodel %d/%d: name=\"%s\", is_dynamic=%d", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic);
}
entry->info.submodels_count = submodels_count;
entry->info.submodels = submodels;
return &entry->info;
}
const r_studio_model_info_t *getStudioModelInfo(model_t *model) {
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;
}
}
WARN("Studio model \"%s\" wasn't preloaded. How did that happen?", hdr->name);
return R_StudioModelPreload(model);
}
void VK_StudioModelInit(void) {
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);
}
r_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *subinfo) {
const char *mode = "";
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;
}
mode = "new";
} else {
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";
}
subinfo->render_refcount++;
DEBUG("%s: submodel=%p(%s) \"%s\" rendermodel=%p refcount=%d", __FUNCTION__, subinfo->submodel_key, mode, subinfo->submodel_key->name, render, subinfo->render_refcount);
return render;
}
void studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel) {
if (!render_submodel)
return;
ASSERT(render_submodel->_.info->render_refcount > 0);
render_submodel->_.info->render_refcount--;
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);
if (!render_submodel->_.info->is_dynamic)
return;
render_submodel->_.next = render_submodel->_.info->cached_head;
render_submodel->_.info->cached_head = render_submodel;
}