/* =========================================================================== Copyright (C) 2024 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_models.cpp -- model loading and caching #include "tr_local.h" #include "tiki.h" #include #define LL(x) x = LittleLong(x) qboolean g_bInfoworldtris = qfalse; static int entityNumIndexes[MAX_ENTITIES]; static int staticModelNumIndexes[4095]; static int R_CullSkelModel(dtiki_t *tiki, refEntity_t *e, skelAnimFrame_t *newFrame, float fScale, float *vLocalOrg); /* ** R_GetModelByHandle */ model_t *R_GetModelByHandle(qhandle_t hModel) { model_t *mod; // out of range gets the default model if (hModel < 1 || hModel >= tr.numModels) { return tr.models[0]; } mod = tr.models[hModel]; return mod; } /* ** R_Model_GetHandle */ dtiki_t *R_Model_GetHandle(qhandle_t handle) { model_t *model = R_GetModelByHandle(handle); if (model->type == MOD_TIKI) { return model->d.tiki; } return NULL; } //=============================================================================== /* ** R_FreeModel */ void R_FreeModel(model_t *mod) { if (mod->type == MOD_TIKI) { ri.CG_EndTiki(mod->d.tiki); } memset(mod, 0, sizeof(*mod)); } /* ** R_AllocModel */ model_t *R_AllocModel(void) { model_t *mod; int i; for (i = 0; i < tr.numModels; i++) { mod = tr.models[i]; if (!mod->name[0]) { break; } } if (i == tr.numModels) { if (i == MAX_MOD_KNOWN) { return NULL; } mod = (model_t*)ri.Hunk_Alloc(sizeof(*tr.models[tr.numModels]), h_low); mod->index = tr.numModels; tr.models[tr.numModels] = mod; tr.numModels++; } else { mod = tr.models[i]; } return mod; } /* ** RE_FreeModels */ void RE_FreeModels(void) { int hModel; for (hModel = 0; hModel < tr.numModels; hModel++) { if (!tr.models[hModel]->name[0]) { continue; } R_FreeModel(tr.models[hModel]); } } /* ** R_RegisterShaders */ void R_RegisterShaders(model_t *mod) { dtiki_t *tiki; int i, j; dtikisurface_t *psurface; shader_t *sh; tiki = mod->d.tiki; for (i = 0; i < tiki->num_surfaces; i++) { psurface = &tiki->surfaces[i]; assert(psurface->numskins <= MAX_TIKI_SHADER); for (j = 0; j < psurface->numskins; j++) { if (psurface->shader[j][0]) { sh = R_FindShader(psurface->shader[j], LIGHTMAP_NONE, qtrue); psurface->hShader[j] = sh->index; } else { psurface->hShader[j] = 0; } } } } /* ** RE_UnregisterServerModel */ void RE_UnregisterServerModel(qhandle_t hModel) { if (hModel < 0 || hModel >= MAX_MOD_KNOWN) { return; } if (tr.models[hModel]->serveronly) { R_FreeModel(tr.models[hModel]); } } /* ** R_RegisterModelInternal */ static qhandle_t R_RegisterModelInternal(const char *name, qboolean bBeginTiki, qboolean use) { model_t *mod; qhandle_t hModel; const char *ptr; if (!name || !*name) { ri.Printf(PRINT_ALL, "RE_RegisterModel: NULL name\n"); return 0; } if (strlen(name) >= 128) { Com_Printf("Model name exceeds MAX_MODEL_NAME\n"); return 0; } // // search the currently loaded models // for (hModel = 1; hModel < tr.numModels; hModel++) { mod = tr.models[hModel]; if (!Q_stricmp(mod->name, name)) { if (mod->type == MOD_BAD) { return 0; } return hModel; } } // allocate a new model_t if ((mod = R_AllocModel()) == NULL) { ri.Printf(PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); return 0; } // only set the name after the model has been successfully loaded Q_strncpyz(mod->name, name, sizeof(mod->name)); // make sure the render thread is stopped R_IssuePendingRenderCommands(); mod->serveronly = qtrue; // // load the files // ptr = strrchr(name, '.'); if (ptr) { ptr++; if (!stricmp(ptr, "spr")) { mod->d.sprite = SPR_RegisterSprite(name); Q_strncpyz(mod->name, name, sizeof(mod->name)); if (mod->d.sprite) { mod->type = MOD_SPRITE; return mod->index; } } else if (!stricmp(ptr, "tik")) { mod->d.tiki = ri.TIKI_RegisterTikiFlags(name, use); Q_strncpyz(mod->name, name, sizeof(mod->name)); if (mod->d.tiki) { mod->type = MOD_TIKI; R_RegisterShaders(mod); if (bBeginTiki) { ri.CG_ProcessInitCommands(mod->d.tiki, NULL); } return mod->index; } } } ri.Printf(PRINT_ERROR, "RE_RegisterModel: Registration failed for '%s'\n", name); mod->type = MOD_BAD; return 0; } /* ** RE_RegisterServerModel */ qhandle_t RE_RegisterServerModel(const char *name) { return R_RegisterModelInternal(name, qtrue, qfalse); } /* ** RE_SpawnEffectModel */ qhandle_t RE_SpawnEffectModel(const char *szModel, vec3_t vPos, vec3_t *axis) { refEntity_t new_entity; memset(&new_entity, 0, sizeof(refEntity_t)); memset(&new_entity.shaderRGBA, 255, sizeof(byte) * 4); VectorCopy(vPos, new_entity.origin); new_entity.scale = 1.0; if (axis) { AxisCopy(axis, new_entity.axis); } new_entity.hModel = R_RegisterModelInternal(szModel, qfalse, qtrue); if (new_entity.hModel) { tr.models[new_entity.hModel]->serveronly = qfalse; ri.CG_ProcessInitCommands(tr.models[new_entity.hModel]->d.tiki, &new_entity); } return new_entity.hModel; } /* ** RE_RegisterModel */ qhandle_t RE_RegisterModel(const char *name) { qhandle_t handle; handle = R_RegisterModelInternal(name, qtrue, qtrue); if (handle) { tr.models[handle]->serveronly = qfalse; } return handle; } //============================================================================= /* =============== R_ModelInit =============== */ void R_ModelInit(void) { model_t *mod; int i; // leave a space for NULL model tr.numModels = 0; mod = R_AllocModel(); Q_strncpyz(mod->name, "** BAD MODEL **", sizeof(mod->name)); mod->type = MOD_BAD; for (i = 0; i < ARRAY_LEN(tr.skel_index); i++) { tr.skel_index[i] = -1; } } /* ================ R_Modellist_f ================ */ void R_Modellist_f(void) { int i; for (i = 1; i < tr.numModels; i++) { ri.Printf(PRINT_ALL, "%s\n", tr.models[i]->name); } } /* ==================== R_ModelRadius ==================== */ float R_ModelRadius(qhandle_t handle) { int j; vec3_t bounds[2]; model_t *model; float radius, maxRadius; vec3_t tmpVec; float w; model = R_GetModelByHandle(handle); switch (model->type) { case MOD_BRUSH: maxRadius = 0.0; VectorCopy(model->bmodel->bounds[0], bounds[0]); VectorCopy(model->bmodel->bounds[1], bounds[1]); for (j = 0; j < 8; j++) { tmpVec[0] = bounds[j & 1 ? 1 : 0][0]; tmpVec[1] = bounds[j & 2 ? 1 : 0][1]; tmpVec[2] = bounds[j & 4 ? 1 : 0][2]; radius = VectorLength(tmpVec); if (maxRadius < radius) { maxRadius = radius; } } break; case MOD_TIKI: return ri.TIKI_GlobalRadius(model->d.tiki); case MOD_SPRITE: maxRadius = model->d.sprite->width * model->d.sprite->scale * 0.5; w = model->d.sprite->height * model->d.sprite->scale * 0.5; if (maxRadius <= w) { maxRadius = w; } break; default: maxRadius = 0.0; } return maxRadius; } /* ==================== R_ModelBounds ==================== */ void R_ModelBounds(qhandle_t handle, vec3_t mins, vec3_t maxs) { model_t *model; model = R_GetModelByHandle(handle); switch (model->type) { default: case MOD_BAD: VectorClear(mins); VectorClear(maxs); break; case MOD_BRUSH: VectorCopy(model->bmodel->bounds[0], mins); VectorCopy(model->bmodel->bounds[1], maxs); break; case MOD_TIKI: ri.TIKI_CalculateBounds(model->d.tiki, 1.0, mins, maxs); break; case MOD_SPRITE: mins[0] = -model->d.sprite->width * model->d.sprite->scale * 0.5; mins[1] = -model->d.sprite->height * model->d.sprite->scale * 0.5; mins[2] = -0.0; maxs[0] = model->d.sprite->width * model->d.sprite->scale * 0.5; maxs[1] = model->d.sprite->height * model->d.sprite->scale * 0.5; maxs[2] = 0.0; break; } } #if 0 // Replaced by TIKI_FindSkelByHeader /* ==================== GetModelPath ==================== */ const char *GetModelPath( skelHeaderGame_t *skelmodel ) { int i; int num; skelcache_t *cache; num = cache_numskel; for( i = 0; i < TIKI_MAX_SKELCACHE; i++ ) { cache = &skelcache[ i ]; if( cache->skel ) { if( cache->skel == skelmodel ) { return cache->path; } num--; if( num < 0 ) { break; } } } return NULL; } #endif /* ==================== GetLodCutoff ==================== */ int GetLodCutoff(skelHeaderGame_t *skelmodel, float lod_val, int renderfx) { lodControl_t *LOD; float f; float fLODCap; LOD = skelmodel->pLOD; if (renderfx & RF_DEPTHHACK) { fLODCap = LOD->maxMetric + (LOD->minMetric - LOD->maxMetric) * r_lodviewmodelcap->value; } else { f = (LOD->minMetric - LOD->maxMetric) * r_lodcap->value + LOD->maxMetric; fLODCap = (lod_val - LOD->maxMetric) * r_lodscale->value + LOD->maxMetric; if (fLODCap > f) { fLODCap = f; } } if (fLODCap >= LOD->minMetric || !r_uselod->integer) { return LOD->curve[0].val; } else if (fLODCap <= LOD->maxMetric) { return LOD->curve[4].val; } else if (fLODCap <= LOD->consts[3].cutoff) { return fLODCap * LOD->consts[3].scale + LOD->consts[3].base; } else if (fLODCap <= LOD->consts[2].cutoff) { return fLODCap * LOD->consts[2].scale + LOD->consts[2].base; } else if (fLODCap <= LOD->consts[1].cutoff) { return fLODCap * LOD->consts[1].scale + LOD->consts[1].base; } else { return fLODCap * LOD->consts[0].scale + LOD->consts[0].base; } } /* =============== R_SaveLODFile =============== */ static void R_SaveLODFile(const char *path, lodControl_t *LOD) { fileHandle_t file = ri.FS_OpenFileWrite(path); if (!file) { ri.Printf(PRINT_WARNING, "SaveLODFile: Failed to open file %s\n", path); return; } ri.FS_Write(LOD, sizeof(lodControl_t), file); } /* ==================== GetToolLodCutoff ==================== */ int GetToolLodCutoff(skelHeaderGame_t *skelmodel, float lod_val) { lodControl_t *LOD; float totalRange; int i; char lodPath[256]; char *ext; LOD = skelmodel->pLOD; totalRange = 0.0; for (i = 0; i < 10; i++) { if (skelmodel->lodIndex[i] > 0) { totalRange = skelmodel->lodIndex[i]; break; } } if (lod_save->integer == 1) { ri.Cvar_Set("lod_save", "0"); Q_strncpyz(lodPath, ri.TIKI_FindSkelByHeader(skelmodel)->path, sizeof(lodPath)); ext = strstr(lodPath, "skd"); strcpy(ext, "lod"); R_SaveLODFile(lodPath, LOD); } if (lod_mesh->modified) { lod_mesh->modified = qfalse; ri.Cvar_Set("lod_minLOD", va("%f", LOD->minMetric)); ri.Cvar_Set("lod_maxLOD", va("%f", LOD->maxMetric)); ri.Cvar_Set("lod_LOD_slider", va("%f", 0.5)); ri.Cvar_Set("lod_curve_0_slider", va("%f", LOD->curve[0].val / totalRange)); ri.Cvar_Set("lod_curve_1_slider", va("%f", LOD->curve[1].val / totalRange)); ri.Cvar_Set("lod_curve_2_slider", va("%f", LOD->curve[2].val / totalRange)); ri.Cvar_Set("lod_curve_3_slider", va("%f", LOD->curve[3].val / totalRange)); ri.Cvar_Set("lod_curve_4_slider", va("%f", LOD->curve[4].val / totalRange)); } ri.Cvar_Set("lod_curve_0_val", va("%f", lod_curve_0_slider->value * totalRange)); ri.Cvar_Set("lod_curve_1_val", va("%f", lod_curve_1_slider->value * totalRange)); ri.Cvar_Set("lod_curve_2_val", va("%f", lod_curve_2_slider->value * totalRange)); ri.Cvar_Set("lod_curve_3_val", va("%f", lod_curve_3_slider->value * totalRange)); ri.Cvar_Set("lod_curve_4_val", va("%f", lod_curve_4_slider->value * totalRange)); LOD->minMetric = lod_minLOD->value; LOD->maxMetric = lod_maxLOD->value; LOD->curve[0].val = lod_curve_0_val->value; LOD->curve[1].val = lod_curve_1_val->value; LOD->curve[2].val = lod_curve_2_val->value; LOD->curve[3].val = lod_curve_3_val->value; LOD->curve[4].val = lod_curve_4_val->value; ri.TIKI_CalcLodConsts(LOD); return GetLodCutoff(skelmodel, lod_val, 0); } /* ============== R_GetTagPositionAndOrientation ============== */ orientation_t R_GetTagPositionAndOrientation(refEntity_t *ent, int tagnum) { int i; orientation_t tag_or, new_or; tag_or = RE_TIKI_Orientation(ent, tagnum); VectorCopy(ent->origin, new_or.origin); for (i = 0; i < 3; i++) { VectorMA(new_or.origin, tag_or.origin[i], ent->axis[i], new_or.origin); } MatrixMultiply(tag_or.axis, ent->axis, new_or.axis); return new_or; } /* ============== RB_DrawSkeletor ============== */ void RB_DrawSkeletor(trRefEntity_t *ent) { // FIXME: Unimplemented (GL2) } surfaceType_t skelSurface = SF_TIKI_SKEL; /* ============== R_AddSkelSurfaces ============== */ void R_AddSkelSurfaces(trRefEntity_t *ent) { dtiki_t *tiki; qboolean personalModel; float tiki_scale; vec3_t tiki_localorigin; vec3_t tiki_worldorigin; skelBoneCache_t *outbones; //int tikiSurfNumOffset; static cvar_t *vmEntity = NULL; skeletor_c *skeletor; float radius; SkelVec3 centroid; skelAnimFrame_t *newFrame; int added; shader_t *shader; dtikisurface_t *dsurf; byte *bsurf; //float range; //int render_count, total_tris; skelSurfaceGame_t *surface; //int skinnum; //float target; Vector newDistance; //vec3_t org; int i; int mesh; int iRadiusCull = 0; int num_tags; tiki = ent->e.tiki; if (!vmEntity) { vmEntity = ri.Cvar_Get("viewmodelentity", "", 0); } R_UpdatePoseInternal(&ent->e); // don't add third_person objects if in a portal personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; outbones = &TIKI_Skel_Bones[TIKI_Skel_Bones_Index]; num_tags = ri.TIKI_GetNumChannels(tiki); if (num_tags + TIKI_Skel_Bones_Index > MAX_SKELBONES) { ri.Printf(PRINT_DEVELOPER, "R_AddSkelSurfaces: too many skeleton models visible on '%s'\n", tiki->a->name); return; } tiki_scale = tiki->load_scale * ent->e.scale; VectorScale(tiki->load_origin, tiki_scale, tiki_localorigin); R_LocalPointToWorld(tiki_localorigin, tiki_worldorigin); radius = R_GetRadius(&ent->e); if (!lod_tool->integer) { iRadiusCull = R_CullPointAndRadius(tiki_worldorigin, radius); if (r_showcull->integer & 2) { switch (iRadiusCull) { case CULL_IN: R_DebugCircle(tiki_worldorigin, radius * 1.2f, 0, 1, 0, 0.5f, qfalse); break; case CULL_CLIP: R_DebugCircle(tiki_worldorigin, radius * 1.2f, 0, 1, 0, 0.5f, qfalse); break; case CULL_OUT: R_DebugCircle(tiki_worldorigin, radius * 1.2f + 32.f, 1, 0.2f, 0.2f, 0.5f, qfalse); break; } } switch (iRadiusCull) { case CULL_IN: tr.pc.c_sphere_cull_md3_in++; break; case CULL_CLIP: tr.pc.c_sphere_cull_md3_clip++; break; case CULL_OUT: tr.pc.c_sphere_cull_md3_out++; break; } } if (tiki->a->bIsCharacter) { if (tr.viewParms.isPortal) { ent->lodpercentage[1] = R_CalcLod(tiki_worldorigin, 92.f / ent->e.scale); } else { ent->lodpercentage[0] = R_CalcLod(tiki_worldorigin, 92.f / ent->e.scale); } } else { if (tr.viewParms.isPortal) { ent->lodpercentage[1] = R_CalcLod(tiki_worldorigin, radius / ent->e.scale); } else { ent->lodpercentage[0] = R_CalcLod(tiki_worldorigin, radius / ent->e.scale); } } newFrame = (skelAnimFrame_t *)ri.Hunk_AllocateTempMemory( sizeof(skelAnimFrame_t) + ri.TIKI_GetNumChannels(tiki) * sizeof(SkelMat4) ); R_GetFrame(&ent->e, newFrame); if (lod_tool->integer || iRadiusCull != CULL_CLIP || R_CullSkelModel(tiki, &ent->e, newFrame, tiki_scale, tiki_localorigin) != CULL_OUT) { // // copy bones position and axis // for (i = 0; i < num_tags; i++) { VectorCopy(newFrame->bones[i][3], outbones->offset); outbones->matrix[0][0] = newFrame->bones[i][0][0]; outbones->matrix[0][1] = newFrame->bones[i][0][1]; outbones->matrix[0][2] = newFrame->bones[i][0][2]; outbones->matrix[0][3] = 0; outbones->matrix[1][0] = newFrame->bones[i][1][0]; outbones->matrix[1][1] = newFrame->bones[i][1][1]; outbones->matrix[1][2] = newFrame->bones[i][1][2]; outbones->matrix[1][3] = 0; outbones->matrix[2][0] = newFrame->bones[i][2][0]; outbones->matrix[2][1] = newFrame->bones[i][2][1]; outbones->matrix[2][2] = newFrame->bones[i][2][2]; outbones->matrix[2][3] = 0; outbones++; } } ri.Hunk_FreeTempMemory(newFrame); ent->e.bonestart = TIKI_Skel_Bones_Index; TIKI_Skel_Bones_Index += num_tags; ent->e.hasMorph = qfalse; // // get the skeletor // skeletor = (skeletor_c *)ri.TIKI_GetSkeletor(tiki, ent->e.entityNumber); // // add morphs // added = ri.SKEL_GetMorphWeightFrame( skeletor, ent->e.frameInfo[0].index, ent->e.frameInfo[0].time, &skeletorMorphCache[skeletorMorphCacheIndex] ); ent->e.morphstart = skeletorMorphCacheIndex; if (added) { // found morphs skeletorMorphCacheIndex += added; ent->e.hasMorph = qtrue; } // // draw all meshes // dsurf = tiki->surfaces; bsurf = &ent->e.surfaces[0]; for (mesh = 0; mesh < tiki->numMeshes; mesh++) { skelHeaderGame_t *skelmodel = ri.TIKI_GetSkel(tiki->mesh[mesh]); if (!skelmodel) { ri.Printf(PRINT_DEVELOPER, "R_AddSkelSurfaces: couldn't get skel model in '%s'\n", tiki->a->name); return; } if (lod_tool->integer && !stricmp(ent->e.tiki->a->name, lod_tikiname->string)) { if (lod_mesh->integer > tiki->numMeshes - 1) { ri.Cvar_Set("lod_mesh", va("%d", tiki->numMeshes - 1)); } if (mesh == lod_mesh->integer) { if (skelmodel->pLOD) { break; } } } // // draw all surfaces // surface = skelmodel->pSurfaces; for (i = 0; i < skelmodel->numSurfaces; i++, dsurf++, bsurf++, surface = surface->pNext) { if (*bsurf & 4) { continue; } shader = NULL; surface->ident = SF_TIKI_SKEL; // use a custom shader if specified if (!(ent->e.customShader) || (ent->e.renderfx & RF_CUSTOMSHADERPASS)) { int iShaderNum = ent->e.skinNum + (*bsurf & 3); if (iShaderNum >= dsurf->numskins) { iShaderNum = 0; } shader = tr.shaders[dsurf->hShader[iShaderNum]]; } else { shader = R_GetShaderByHandle(ent->e.customShader); } if (!personalModel) { if ((*bsurf & 0x40) && (dsurf->numskins > 1)) { int iShaderNum = ent->e.skinNum + (*bsurf & 2); R_AddDrawSurf((surfaceType_t *)surface, tr.shaders[dsurf->hShader[iShaderNum]], 0, 0, 0, 0); R_AddDrawSurf((surfaceType_t *)surface, tr.shaders[dsurf->hShader[iShaderNum + 1]], 0, 0, 0, 0); } else { R_AddDrawSurf((surfaceType_t *)surface, shader, 0, 0, 0, 0); } } if ((ent->e.customShader) && (ent->e.renderfx & RF_CUSTOMSHADERPASS)) { shader = R_GetShaderByHandle(ent->e.customShader); R_AddDrawSurf((surfaceType_t *)surface, shader, 0, 0, 0, 0); } } } // FIXME: setup LOD } /* ============= SkelVertGetNormal ============= */ inline static void SkelVertGetNormal(skeletorVertex_t *vert, skelBoneCache_t *bone, vec3_t out) { out[0] = vert->normal[0] * bone->matrix[0][0] + vert->normal[1] * bone->matrix[1][0] + vert->normal[2] * bone->matrix[2][0]; out[1] = vert->normal[0] * bone->matrix[0][1] + vert->normal[1] * bone->matrix[1][1] + vert->normal[2] * bone->matrix[2][1]; out[2] = vert->normal[0] * bone->matrix[0][2] + vert->normal[1] * bone->matrix[1][2] + vert->normal[2] * bone->matrix[2][2]; } /* ============= SkelMorphGetXyz ============= */ inline static void SkelMorphGetXyz(skeletorMorph_t *morph, int *morphcache, vec3_t out) { VectorMA(out, *morphcache, morph->offset, out); } /* ============= SkelWeightGetXyz ============= */ inline static void SkelWeightGetXyz(skelWeight_t *weight, skelBoneCache_t *bone, vec3_t out) { out[0] += ((weight->offset[0] * bone->matrix[0][0] + weight->offset[1] * bone->matrix[1][0] + weight->offset[2] * bone->matrix[2][0]) + bone->offset[0]) * weight->boneWeight; out[1] += ((weight->offset[0] * bone->matrix[0][1] + weight->offset[1] * bone->matrix[1][1] + weight->offset[2] * bone->matrix[2][1]) + bone->offset[1]) * weight->boneWeight; out[2] += ((weight->offset[0] * bone->matrix[0][2] + weight->offset[1] * bone->matrix[1][2] + weight->offset[2] * bone->matrix[2][2]) + bone->offset[2]) * weight->boneWeight; } /* ============= SkelWeightMorphGetXyz ============= */ inline static void SkelWeightMorphGetXyz(skelWeight_t *weight, skelBoneCache_t *bone, vec3_t totalmorph, vec3_t out) { vec3_t point; VectorAdd(totalmorph, weight->offset, point); out[0] += ((point[0] * bone->matrix[0][0] + point[1] * bone->matrix[1][0] + point[2] * bone->matrix[2][0]) + bone->offset[0]) * weight->boneWeight; out[1] += ((point[0] * bone->matrix[0][1] + point[1] * bone->matrix[1][1] + point[2] * bone->matrix[2][1]) + bone->offset[1]) * weight->boneWeight; out[2] += ((point[0] * bone->matrix[0][2] + point[1] * bone->matrix[1][2] + point[2] * bone->matrix[2][2]) + bone->offset[2]) * weight->boneWeight; } /* ============= RB_SkelMesh ============= */ void RB_SkelMesh(skelSurfaceGame_t *sf) { unsigned int baseIndex, baseVertex; unsigned int render_count; unsigned int indexes; float *outXyz; int16_t *outNormal; skelIndex_t *triangles; skelIndex_t *collapse_map; skeletorVertex_t *newVerts; skeletorMorph_t *morph; skelWeight_t *weight; int vertNum; int morphNum; int weightNum; skelBoneCache_t *bones; skelBoneCache_t *bone; int *morphs; int *morphcache; float scale; dtiki_t *tiki; int mesh; int surf; int i; skelHeaderGame_t *skelmodel; skelSurfaceGame_t *psurface; qboolean bFound; short collapse[TIKI_MAX_VERTEXES]; if (!r_drawentitypoly->integer) { return; } tiki = backEnd.currentEntity->e.tiki; scale = tiki->load_scale * backEnd.currentEntity->e.scale; // // get the mesh associated with the surface // bFound = qfalse; for (mesh = 0; mesh < tiki->numMeshes; mesh++) { skelmodel = ri.TIKI_GetSkel(tiki->mesh[mesh]); psurface = skelmodel->pSurfaces; // find the surface for (surf = 0; surf < skelmodel->numSurfaces; surf++) { if (psurface == sf) { bFound = qtrue; break; } psurface = psurface->pNext; } if (bFound) { break; } } assert(bFound); // // Process LOD // if (skelmodel->pLOD) { float lod_val; int renderfx; lod_val = backEnd.currentEntity->lodpercentage[0]; renderfx = backEnd.currentEntity->e.renderfx; if (sf->numVerts > 3) { skelIndex_t *collapseIndex; int mid, low, high; int lod_cutoff; if (lod_tool->integer && !strcmp(backEnd.currentEntity->e.tiki->a->name, lod_tikiname->string) && mesh == lod_mesh->integer) { lod_cutoff = GetToolLodCutoff(skelmodel, backEnd.currentEntity->lodpercentage[0]); } else { lod_cutoff = GetLodCutoff(skelmodel, backEnd.currentEntity->lodpercentage[0], renderfx); } collapseIndex = sf->pCollapseIndex; if (collapseIndex[2] < lod_cutoff) { return; } low = mid = 3; high = sf->numVerts; while (high >= low) { mid = (low + high) >> 1; if (collapseIndex[mid] < lod_cutoff) { high = mid - 1; if (collapseIndex[mid - 1] >= lod_cutoff) { break; } } else { mid++; low = mid; if (high == mid || collapseIndex[mid] < lod_cutoff) { break; } } } render_count = mid; } else { render_count = sf->numVerts; } if (!render_count) { return; } } else { render_count = sf->numVerts; } indexes = sf->numTriangles * 3; RB_CHECKOVERFLOW(render_count, indexes); collapse_map = sf->pCollapse; triangles = sf->pTriangles; baseIndex = tess.numIndexes; baseVertex = tess.numVertexes; tess.numVertexes += render_count; outXyz = tess.xyz[baseVertex]; outNormal = tess.normal[baseVertex]; newVerts = sf->pVerts; if (render_count == sf->numVerts) { for (i = 0; i < indexes; i++) { tess.indexes[baseIndex + i] = baseVertex + triangles[i]; } entityNumIndexes[backEnd.currentEntity - backEnd.refdef.entities] += indexes; tess.numIndexes += indexes; } else { assert(sf->numVerts < TIKI_MAX_VERTEXES); for (i = 0; i < render_count; i++) { collapse[i] = i; } for (i = render_count; i < sf->numVerts; i++) { collapse[i] = collapse[collapse_map[i]]; } for (i = 0; i < indexes; i += 3) { assert(collapse[triangles[i]] < sf->numVerts); assert(collapse[triangles[i + 1]] < sf->numVerts); assert(collapse[triangles[i + 2]] < sf->numVerts); if (collapse[triangles[i]] == collapse[triangles[i + 1]] || collapse[triangles[i + 1]] == collapse[triangles[i + 2]] || collapse[triangles[i + 2]] == collapse[triangles[i]]) { break; } tess.indexes[baseIndex + i] = baseVertex + collapse[triangles[i]]; tess.indexes[baseIndex + i + 1] = baseVertex + collapse[triangles[i + 1]]; tess.indexes[baseIndex + i + 2] = baseVertex + collapse[triangles[i + 2]]; } entityNumIndexes[backEnd.currentEntity - backEnd.refdef.entities] += indexes; tess.numIndexes += i; } // // just copy the vertexes // bones = &TIKI_Skel_Bones[backEnd.currentEntity->e.bonestart]; morphs = &skeletorMorphCache[backEnd.currentEntity->e.morphstart]; if (backEnd.currentEntity->e.hasMorph) { if (mesh > 0) { for (vertNum = 0; vertNum < render_count; vertNum++) { vec3_t normal; vec3_t out; vec3_t totalmorph; int channelNum; int boneNum; VectorClear(out); VectorClear(outXyz); VectorClear(totalmorph); weight = (skelWeight_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs); morph = (skeletorMorph_t *)((byte *)newVerts + sizeof(skeletorVertex_t)); for (morphNum = 0; morphNum < newVerts->numMorphs; morphNum++) { morphcache = &morphs[morph->morphIndex]; if (*morphcache) { SkelMorphGetXyz(morph, morphcache, totalmorph); } morph++; } if (newVerts->numMorphs) { channelNum = skelmodel->pBones[morph->morphIndex].channel; } else { channelNum = skelmodel->pBones[weight->boneIndex].channel; } boneNum = ri.TIKI_GetLocalChannel(tiki, channelNum); bone = &bones[boneNum]; SkelVertGetNormal(newVerts, bone, normal); for (weightNum = 0; weightNum < newVerts->numWeights; weightNum++) { channelNum = skelmodel->pBones[weight->boneIndex].channel; boneNum = ri.TIKI_GetLocalChannel(tiki, channelNum); bone = &bones[boneNum]; if (!weightNum) { SkelWeightMorphGetXyz(weight, bone, totalmorph, out); } else { SkelWeightGetXyz(weight, bone, out); } weight++; } R_VaoPackNormal(outNormal, normal); VectorScale(out, scale, outXyz); tess.texCoords[baseVertex + vertNum][0] = newVerts->texCoords[0]; tess.texCoords[baseVertex + vertNum][1] = newVerts->texCoords[1]; // FIXME: fill in lightmapST for completeness? newVerts = (skeletorVertex_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs + sizeof(skelWeight_t) * newVerts->numWeights); outXyz += 4; outNormal += 4; } } else { for (vertNum = 0; vertNum < render_count; vertNum++) { vec3_t normal; vec3_t out; vec3_t totalmorph; VectorClear(out); VectorClear(outXyz); VectorClear(totalmorph); weight = (skelWeight_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs); morph = (skeletorMorph_t *)((byte *)newVerts + sizeof(skeletorVertex_t)); for (morphNum = 0; morphNum < newVerts->numMorphs; morphNum++) { morphcache = &morphs[morph->morphIndex]; if (*morphcache) { SkelMorphGetXyz(morph, morphcache, totalmorph); } morph++; } if (newVerts->numMorphs) { bone = &bones[morph->morphIndex]; } else { bone = &bones[weight->boneIndex]; } SkelVertGetNormal(newVerts, bone, normal); for (weightNum = 0; weightNum < newVerts->numWeights; weightNum++) { bone = &bones[weight->boneIndex]; if (!weightNum) { SkelWeightMorphGetXyz(weight, bone, totalmorph, out); } else { SkelWeightGetXyz(weight, bone, out); } weight++; } R_VaoPackNormal(outNormal, normal); VectorScale(out, scale, outXyz); tess.texCoords[baseVertex + vertNum][0] = newVerts->texCoords[0]; tess.texCoords[baseVertex + vertNum][1] = newVerts->texCoords[1]; // FIXME: fill in lightmapST for completeness? newVerts = (skeletorVertex_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs + sizeof(skelWeight_t) * newVerts->numWeights); outXyz += 4; outNormal += 4; } } } else { if (mesh > 0) { for (vertNum = 0; vertNum < render_count; vertNum++) { vec3_t normal; vec3_t out; int channelNum; int boneNum; VectorClear(out); VectorClear(outXyz); weight = (skelWeight_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs); channelNum = skelmodel->pBones[weight->boneIndex].channel; boneNum = ri.TIKI_GetLocalChannel(tiki, channelNum); bone = &bones[boneNum]; SkelVertGetNormal(newVerts, bone, normal); for (weightNum = 0; weightNum < newVerts->numWeights; weightNum++) { channelNum = skelmodel->pBones[weight->boneIndex].channel; boneNum = ri.TIKI_GetLocalChannel(tiki, channelNum); bone = &bones[boneNum]; SkelWeightGetXyz(weight, bone, out); weight++; } R_VaoPackNormal(outNormal, normal); VectorScale(out, scale, outXyz); tess.texCoords[baseVertex + vertNum][0] = newVerts->texCoords[0]; tess.texCoords[baseVertex + vertNum][1] = newVerts->texCoords[1]; // FIXME: fill in lightmapST for completeness? newVerts = (skeletorVertex_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs + sizeof(skelWeight_t) * newVerts->numWeights); outXyz += 4; outNormal += 4; } } else { for (vertNum = 0; vertNum < render_count; vertNum++) { vec3_t normal; vec3_t out; VectorClear(out); VectorClear(outXyz); weight = (skelWeight_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs); bone = &bones[weight->boneIndex]; SkelVertGetNormal(newVerts, bone, normal); for (weightNum = 0; weightNum < newVerts->numWeights; weightNum++) { bone = &bones[weight->boneIndex]; SkelWeightGetXyz(weight, bone, out); weight++; } R_VaoPackNormal(outNormal, normal); VectorScale(out, scale, outXyz); tess.texCoords[baseVertex + vertNum][0] = newVerts->texCoords[0]; tess.texCoords[baseVertex + vertNum][1] = newVerts->texCoords[1]; // FIXME: fill in lightmapST for completeness? newVerts = (skeletorVertex_t *)((byte *)newVerts + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * newVerts->numMorphs + sizeof(skelWeight_t) * newVerts->numWeights); outXyz += 4; outNormal += 4; } } } #if 0 if( backEnd.currentEntity->e.staticModelIndex ) { mstaticModel_t *sm; color4ub_t *out; color3ub_t *in; int cdofs; skdSurface_t *sf2; sm = &tr.world->staticModels[ backEnd.currentEntity->e.staticModelIndex - 1 ]; tess.useStaticModelVertexColors = qtrue; cdofs = 0; sf2 = tiki->surfs; while( sf2 != sf ) { cdofs += sf2->numVerts; sf2 = ( skdSurface_t* )( ( ( byte* )sf2 ) + sf2->ofsEnd ); } in = &tr.world->smColors[ sm->firstVert + cdofs ]; out = tess.vertexColors + baseVertex; for( i = 0; i < sf->numVerts; i++, in++, out++ ) { # if 1 ( *out )[ 0 ] = ( *in )[ 0 ]; ( *out )[ 1 ] = ( *in )[ 1 ]; ( *out )[ 2 ] = ( *in )[ 2 ]; ( *out )[ 3 ] = 255; # elif 0 ( ( int* )out ) = tr.identityLightByte; # else // su44: set it to something special so // I can debug vertex colors rendering ( *out )[ 0 ] = 255; ( *out )[ 1 ] = 0; ( *out )[ 2 ] = 0; ( *out )[ 3 ] = 255; # endif } } //else #endif //{ // // an attemp to fix bizarre vertex colors bug // color4ub_t *col; // // col = &tess.vertexColors[baseVertex]; // for(i = 0; i < sf->numVerts; i++,col++) { // (*col)[0] = 255; // (*col)[1] = 255; // (*col)[2] = 255; // (*col)[3] = 255; // } //} //tess.numVertexes += sf->numVerts; } /* ============= RB_StaticMesh ============= */ void RB_StaticMesh(staticSurface_t *staticSurf) { int i, j; dtiki_t *tiki; skelSurfaceGame_t *surf; int meshNum; skelHeaderGame_t *skelmodel; int render_count; skelIndex_t *collapse_map; skelIndex_t *triangles; int indexes; int baseIndex, baseVertex; short collapse[1000]; if (!r_drawstaticmodelpoly->integer) { return; } assert(backEnd.currentStaticModel); tiki = backEnd.currentStaticModel->tiki; surf = staticSurf->surface; assert(surf->pStaticXyz); if (!surf->pStaticXyz) { return; } meshNum = staticSurf->meshNum; skelmodel = ri.TIKI_GetSkel(tiki->mesh[meshNum]); // // Process LOD // if (skelmodel->pLOD && r_staticlod->integer) { float lod_val; lod_val = backEnd.currentStaticModel->lodpercentage[0]; if (surf->numVerts > 3) { skelIndex_t *collapseIndex; int mid, low, high; int lod_cutoff; if (lod_tool->integer && !strcmp(backEnd.currentStaticModel->tiki->a->name, lod_tikiname->string) && meshNum == lod_mesh->integer) { lod_cutoff = GetToolLodCutoff(skelmodel, backEnd.currentStaticModel->lodpercentage[0]); } else { lod_cutoff = GetLodCutoff(skelmodel, backEnd.currentStaticModel->lodpercentage[0], 0); } collapseIndex = surf->pCollapseIndex; if (collapseIndex[2] < lod_cutoff) { return; } low = mid = 3; high = surf->numVerts; while (high >= low) { mid = (low + high) >> 1; if (collapseIndex[mid] < lod_cutoff) { high = mid - 1; if (collapseIndex[mid - 1] >= lod_cutoff) { break; } } else { mid++; low = mid; if (high == mid || collapseIndex[mid] < lod_cutoff) { break; } } } render_count = mid; } else { render_count = surf->numVerts; } if (!render_count) { return; } } else { render_count = surf->numVerts; } indexes = surf->numTriangles * 3; RB_CHECKOVERFLOW(render_count, surf->numTriangles); collapse_map = surf->pCollapse; triangles = surf->pTriangles; baseIndex = tess.numIndexes; baseVertex = tess.numVertexes; tess.numVertexes += render_count; if (render_count == surf->numVerts) { for (j = 0; j < indexes; j++) { tess.indexes[baseIndex + j] = baseVertex + triangles[j]; } staticModelNumIndexes[backEnd.currentStaticModel - backEnd.refdef.staticModels] += indexes; tess.numIndexes += indexes; } else { for (i = 0; i < render_count; i++) { collapse[i] = i; } for (i = render_count; i < surf->numVerts; i++) { collapse[i] = collapse[collapse_map[i]]; } for (j = 0; j < indexes; j += 3) { if (collapse[triangles[j]] == collapse[triangles[j + 1]] || collapse[triangles[j + 1]] == collapse[triangles[j + 2]] || collapse[triangles[j + 2]] == collapse[triangles[j]]) { break; } tess.indexes[baseIndex + j] = baseVertex + collapse[triangles[j]]; tess.indexes[baseIndex + j + 1] = baseVertex + collapse[triangles[j + 1]]; tess.indexes[baseIndex + j + 2] = baseVertex + collapse[triangles[j + 2]]; } staticModelNumIndexes[backEnd.currentStaticModel - backEnd.refdef.staticModels] += j; tess.numIndexes += j; } for (j = 0; j < render_count; j++) { Vector4Copy(surf->pStaticXyz[j], tess.xyz[baseVertex + j]); Vector4Copy(surf->pStaticNormal[j], tess.normal[baseVertex + j]); tess.texCoords[baseVertex + j][0] = surf->pStaticTexCoords[j][0][0]; tess.texCoords[baseVertex + j][1] = surf->pStaticTexCoords[j][0][1]; tess.lightCoords[baseVertex + j][0] = surf->pStaticTexCoords[j][1][0]; tess.lightCoords[baseVertex + j][1] = surf->pStaticTexCoords[j][1][1]; } if (backEndData->staticModelData) { const size_t offset = backEnd.currentStaticModel->firstVertexData + staticSurf->ofsStaticData * sizeof(color4ub_t); assert(offset < tr.world->numStaticModelData * sizeof(color4ub_t)); assert(offset + render_count * sizeof(color4ub_t) <= tr.world->numStaticModelData * sizeof(color4ub_t)); const color4ub_t *in = (const color4ub_t *)&backEndData->staticModelData[offset]; for (i = 0; i < render_count; i++) { tess.color[baseVertex + i][0] = in[i][0] * 0xffff / 0xff; tess.color[baseVertex + i][1] = in[i][1] * 0xffff / 0xff; tess.color[baseVertex + i][2] = in[i][2] * 0xffff / 0xff; tess.color[baseVertex + i][3] = in[i][3] * 0xffff / 0xff; } } else { for (i = 0; i < render_count; i++) { tess.color[baseVertex + i][0] = 0xffff; tess.color[baseVertex + i][1] = 0xffff; tess.color[baseVertex + i][2] = 0xffff; tess.color[baseVertex + i][3] = 0xffff; } } } /* ============= R_InfoWorldTris_f ============= */ void R_InfoWorldTris_f(void) { int i; g_bInfoworldtris = qtrue; for (i = 0; i < ARRAY_LEN(entityNumIndexes); i++) { entityNumIndexes[i] = 0; } for (i = 0; i < ARRAY_LEN(staticModelNumIndexes); i++) { staticModelNumIndexes[i] = 0; } } /* ============= R_PrintInfoWorldtris ============= */ void R_PrintInfoWorldtris(void) { int i; int numTris; int totalNumTris; dtiki_t *tiki; skelHeaderGame_t *skelmodel; totalNumTris = 0; for (i = 0; i < ARRAY_LEN(entityNumIndexes); i++) { numTris = entityNumIndexes[i] / 3; if (!numTris) { continue; } totalNumTris += numTris; tiki = backEnd.refdef.entities[i].e.tiki; skelmodel = ri.TIKI_GetSkel(tiki->mesh[0]); Com_Printf("ent: %i, tris: %i, %s, version: %i\n", i, numTris, tiki->a->name, skelmodel->version); } Com_Printf("total entity tris: %i\n\n", totalNumTris); totalNumTris = 0; for (i = 0; i < ARRAY_LEN(entityNumIndexes); i++) { numTris = staticModelNumIndexes[i] / 3; if (!numTris) { continue; } totalNumTris += numTris; tiki = backEnd.refdef.staticModels[i].tiki; skelmodel = ri.TIKI_GetSkel(tiki->mesh[0]); Com_Printf("sm: %i, tris: %i, %s, version: %i\n", i, numTris, tiki->a->name, skelmodel->version); } Com_Printf("total static model tris: %i\n\n", totalNumTris); } /* ============= RE_SetFrameNumber ============= */ void RE_SetFrameNumber(int frameNumber) { tr.frame_skel_index = frameNumber; } /* ============= R_UpdatePoseInternal ============= */ void R_UpdatePoseInternal(refEntity_t *model) { if (model->entityNumber != ENTITYNUM_NONE) { if (tr.skel_index[model->entityNumber] == tr.frame_skel_index) { return; } tr.skel_index[model->entityNumber] = tr.frame_skel_index; } ri.TIKI_SetPoseInternal( ri.TIKI_GetSkeletor(model->tiki, model->entityNumber), model->frameInfo, model->bone_tag, model->bone_quat, model->actionWeight ); } /* ============= RE_ForceUpdatePose ============= */ void RE_ForceUpdatePose(refEntity_t *model) { if (model->entityNumber != ENTITYNUM_NONE) { tr.skel_index[model->entityNumber] = tr.frame_skel_index; } ri.TIKI_SetPoseInternal( ri.TIKI_GetSkeletor(model->tiki, model->entityNumber), model->frameInfo, model->bone_tag, model->bone_quat, model->actionWeight ); } /* ============= RE_TIKI_Orientation ============= */ orientation_t RE_TIKI_Orientation(refEntity_t *model, int tagnum) { R_UpdatePoseInternal(model); return ri.TIKI_OrientationInternal(model->tiki, model->entityNumber, tagnum, model->scale); } /* ============= RE_TIKI_IsOnGround ============= */ qboolean RE_TIKI_IsOnGround(refEntity_t *model, int tagnum, float threshold) { R_UpdatePoseInternal(model); return ri.TIKI_IsOnGroundInternal(model->tiki, model->entityNumber, tagnum, threshold); } /* ============= R_GetRadius ============= */ float R_GetRadius(refEntity_t *model) { R_UpdatePoseInternal(model); return ri.GetRadiusInternal(model->tiki, model->entityNumber, model->scale); } /* ============= R_GetFrame ============= */ void R_GetFrame(refEntity_t *model, struct skelAnimFrame_s *newFrame) { R_UpdatePoseInternal(model); ri.GetFrameInternal(model->tiki, model->entityNumber, newFrame); } /* ============= R_DebugSkeleton ============= */ void R_DebugSkeleton(void) { // FIXME: unimplemented (GL2) } /* ============= ProjectRadius ============= */ static float ProjectRadius(float r, const vec3_t location) { Vector separation; float projectedRadius; separation = Vector(tr.viewParms.or.origin) - Vector(location); projectedRadius = separation.length(); return fabs(r) * (100.0 / tr.viewParms.fovX) / projectedRadius; } /* ============= R_CalcLod ============= */ float R_CalcLod(const vec3_t origin, float radius) { return ProjectRadius(radius, origin); } /* ============= R_CullTIKI ============= */ static int R_CullSkelModel(dtiki_t *tiki, refEntity_t *e, skelAnimFrame_t *newFrame, float fScale, float *vLocalOrg) { vec3_t bounds[2]; vec3_t delta; int i; int cull; // FIXME: not working properly return CULL_IN; if (tr.currentEntity->e.renderfx & RF_FRAMELERP) { VectorSubtract(e->origin, e->oldorigin, delta); } else { VectorClear(delta); } for (i = 0; i < 3; i++) { bounds[0][i] = newFrame->bounds[0][i] * fScale + vLocalOrg[i]; bounds[1][i] = newFrame->bounds[1][i] * fScale + vLocalOrg[i]; if (delta[i] > 0) { bounds[1][i] += delta[i]; } else { bounds[0][i] += delta[i]; } } cull = R_CullLocalBox(bounds); if (r_showcull->integer & 1) { float fR, fG, fB; vec3_t vAngles; switch (cull) { case CULL_IN: fR = 0; fG = 1; fB = 0; break; case CULL_CLIP: fR = 1; fG = 1; fB = 0; break; case CULL_OUT: fR = 1; fG = 0.2f; fB = 0.2f; for (i = 0; i < 3; i++) { bounds[0][i] -= 16; bounds[1][i] += 16; } break; } MatrixToEulerAngles(tr.or.axis, vAngles); R_DebugRotatedBBox(tr.or.origin, vAngles, bounds[0], bounds[1], fR, fG, fB, 0.5); } switch (cull) { case CULL_IN: tr.pc.c_box_cull_md3_in++; return CULL_IN; case CULL_CLIP: tr.pc.c_box_cull_md3_clip++; return CULL_CLIP; case CULL_OUT: default: tr.pc.c_box_cull_md3_out++; return CULL_OUT; } } /* ================== R_CountTikiLodTris Computes the number of triangles to be rendered for the given TIKI model based on the given LoD percentage, and passes it back with render_tris. The total number of triangles in the TIKI model are passed back with total_tris. Currently only used for debugging purposes. FIXME: Shares some code with RB_StaticMesh, common parts could be extracted. According to debug symbols, this was originally in tiki_mesh.cpp ================== */ void R_CountTikiLodTris(dtiki_t *tiki, float lodpercentage, int *render_tris, int *total_tris) { *render_tris = 0; *total_tris = 0; int numtris = 0, totaltris = 0; for (int i = 0; i < tiki->numMeshes; i++) { skelHeaderGame_t *skelmodel = ri.TIKI_GetSkel(tiki->mesh[i]); skelSurfaceGame_t *surface = skelmodel->pSurfaces; for (int j = 0; j < skelmodel->numSurfaces; j++) { int render_count = 0; if (skelmodel->pLOD) { int lod_cutoff = GetLodCutoff(skelmodel, lodpercentage, 0); skelIndex_t *collapseIndex = surface->pCollapseIndex; render_count = surface->numVerts; // Determine the number of vertices to be rendered based on the LOD cutoff while (render_count > 0 && collapseIndex[render_count - 1] < lod_cutoff) { render_count--; } } else { // The surf mesh doesn't have a LOD model, so all vertices will be rendered render_count = surface->numVerts; } skelIndex_t *triangles = surface->pTriangles; int indexes = surface->numTriangles * 3; // 3 vertex/index for each tri skelIndex_t *collapse_map = surface->pCollapse; totaltris += surface->numTriangles; skelIndex_t collapse[4096] {}; // Initialize array for collapsed indices int k; for (k = 0; k < render_count; ++k) { collapse[k] = k; } // Map remaining vertices to their collapsed indices using the collapse map for (k = render_count; k < surface->numVerts; ++k) { collapse[k] = collapse[collapse_map[k]]; } // Check the first two collapsed indices of each triangle for (k = 0; k < indexes; k += 3) { if (collapse[triangles[k]] == collapse[triangles[k + 1]]) { // The collapsed indices of the two vertices are the same: // this, and any subsequent tris do not need to be rendered // because the collapsed vertices (for this surface) coincide from here. break; } } surface = surface->pNext; numtris += k / 3; } } *render_tris = numtris; *total_tris = totaltris; } /* ================== R_LerpTag ================== */ int R_LerpTag(orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, float frac, const char *tagName) { // stub return 0; }