/* =========================================================================== 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_staticmodels.cpp -- static model rendering #include "tr_local.h" #include "tiki.h" #define MAX_STATIC_MODELS_SURFS 8192 #define MAX_DISTINCT_STATIC_MODELS 1000 int g_nStaticSurfaces; staticSurface_t g_staticSurfaces[MAX_STATIC_MODELS_SURFS]; qboolean g_bInfostaticmodels = qfalse; /* ============== R_InitStaticModels ============== */ void R_InitStaticModels(void) { cStaticModelUnpacked_t *pSM; char szTemp[1024]; skelBoneCache_t bones[128]; float radius; int i, j, k, l; g_bInfostaticmodels = qfalse; if (tr.overbrightShift) { for (i = 0; i < tr.world->numStaticModelData; i++) { int r, g, b; r = (int)((float)tr.world->staticModelData[i * 4] * tr.overbrightMult); g = (int)((float)tr.world->staticModelData[i * 4 + 1] * tr.overbrightMult); b = (int)((float)tr.world->staticModelData[i * 4 + 2] * tr.overbrightMult); if (r > 0xFF || g > 0xFF || b > 0xFF) { float t; t = 255.0 / (float)Q_max(r, Q_max(g, b)); r = (int)((float)r * t); g = (int)((float)g * t); b = (int)((float)b * t); } tr.world->staticModelData[i * 4] = r; tr.world->staticModelData[i * 4 + 1] = g; tr.world->staticModelData[i * 4 + 2] = b; } } for (i = 0; i < tr.world->numStaticModels; i++) { vec3_t mins, maxs; pSM = &tr.world->staticModels[i]; pSM->bRendered = qfalse; AngleVectorsLeft(pSM->angles, pSM->axis[0], pSM->axis[1], pSM->axis[2]); if (!strnicmp(pSM->model, "models", 6)) { Q_strncpyz(szTemp, pSM->model, sizeof(szTemp)); } else { Com_sprintf(szTemp, sizeof(szTemp), "models/%s", pSM->model); } ri.FS_CanonicalFilename(szTemp); //const bool exists = ri.TIKI_FindTiki(szTemp) != NULL; pSM->tiki = ri.TIKI_RegisterTikiFlags(szTemp, qfalse); if (!pSM->tiki) { ri.Printf(PRINT_WARNING, "^~^~^: Warning: Cannot Load Static Model %s\n", szTemp); continue; } pSM->radius = ri.TIKI_GlobalRadius(pSM->tiki); // // register shaders // for (j = 0; j < pSM->tiki->num_surfaces; j++) { dtikisurface_t *surf = &pSM->tiki->surfaces[j]; for (k = 0; k < surf->numskins; k++) { if (surf->shader[k][0]) { shader_t *sh = R_FindShader(surf->shader[k], -1, !(surf->flags & TIKI_SURF_NOMIPMAPS)); surf->hShader[k] = sh->index; } else { surf->hShader[k] = 0; } } } // prepare the skeleton frame for the static model ri.TIKI_GetSkelAnimFrame(pSM->tiki, bones, &radius, &mins, &maxs); pSM->cull_radius = radius * pSM->tiki->load_scale * pSM->scale; // Suggestion: // It would be cool to have animated static model in the future // Removed in 2.0 // This only leads to issues where pStaticXyz is not initialized // and it can occurs if the TIKI model is registered before the static model //if (exists) { // continue; //} for (j = 0; j < pSM->tiki->numMeshes; j++) { skelHeaderGame_t *skelmodel = ri.TIKI_GetSkel(pSM->tiki->mesh[j]); skelSurfaceGame_t *surf; if (!skelmodel) { ri.Printf(PRINT_WARNING, "^~^~^: Warning: Missing mesh in Static Model %s\n", skelmodel->name); continue; } surf = skelmodel->pSurfaces; for (k = 0; k < skelmodel->numSurfaces; k++, surf = surf->pNext) { byte *buf; byte *p; skelWeight_t *weight; skeletorVertex_t *vert; if (surf->pStaticXyz) { continue; } // allocate static vectors p = buf = (byte *)ri.TIKI_Alloc((sizeof(vec4_t) + sizeof(vec4_t) + sizeof(vec2_t) * 2) * surf->numVerts); surf->pStaticXyz = (vec4_t *)p; p += sizeof(vec4_t) * surf->numVerts; surf->pStaticNormal = (vec4_t *)p; p += sizeof(vec4_t) * surf->numVerts; surf->pStaticTexCoords = (vec2_t(*)[2])p; vert = surf->pVerts; for (l = 0; l < surf->numVerts; l++) { int channel; skelBoneCache_t *bone; weight = (skelWeight_t *)((byte *)vert + sizeof(skeletorVertex_t) + vert->numMorphs * sizeof(skeletorMorph_t)); if (j > 0) { channel = ri.TIKI_GetLocalChannel(pSM->tiki, skelmodel->pBones[weight->boneIndex].channel); } else { channel = weight->boneIndex; } bone = &bones[channel]; surf->pStaticXyz[l][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; surf->pStaticXyz[l][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; surf->pStaticXyz[l][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; surf->pStaticXyz[l][3] = 0.f; surf->pStaticNormal[l][0] = vert->normal[0] * bone->matrix[0][0] + vert->normal[1] * bone->matrix[1][0] + vert->normal[2] * bone->matrix[2][0]; surf->pStaticNormal[l][1] = vert->normal[0] * bone->matrix[0][1] + vert->normal[1] * bone->matrix[1][1] + vert->normal[2] * bone->matrix[2][1]; surf->pStaticNormal[l][2] = vert->normal[0] * bone->matrix[0][2] + vert->normal[1] * bone->matrix[1][2] + vert->normal[2] * bone->matrix[2][2]; surf->pStaticNormal[l][3] = 0.f; surf->pStaticTexCoords[l][0][0] = vert->texCoords[0]; surf->pStaticTexCoords[l][0][1] = vert->texCoords[1]; surf->pStaticTexCoords[l][1][0] = vert->texCoords[0]; surf->pStaticTexCoords[l][1][1] = vert->texCoords[1]; vert = (skeletorVertex_t *)((byte *)vert + sizeof(skeletorVertex_t) + sizeof(skeletorMorph_t) * vert->numMorphs + sizeof(skelWeight_t) * vert->numWeights); } } } } tr.refdef.numStaticModels = tr.world->numStaticModels; tr.refdef.staticModels = tr.world->staticModels; tr.refdef.numStaticModelData = tr.world->numStaticModelData; tr.refdef.staticModelData = tr.world->staticModelData; } /* ============== R_CullStaticModel ============== */ static int R_CullStaticModel(dtiki_t *tiki, float fScale, const vec3_t vLocalOrg) { vec3_t bounds[2]; int i; int cull; for (i = 0; i < 3; i++) { bounds[0][i] = vLocalOrg[i] + tiki->a->mins[i] * fScale; bounds[1][i] = vLocalOrg[i] + tiki->a->maxs[i] * fScale; } cull = R_CullLocalBox(bounds); if (r_showcull->integer & 4) { float fR, fG, fB; vec3_t vAngles; switch (cull) { case CULL_CLIP: fR = 1.0f; fG = 1.0f; fB = 0.0f; break; case CULL_IN: fR = 0.0f; fG = 1.0f; fB = 0.0f; break; case CULL_OUT: fR = 1.0f; fG = 0.2f; fB = 0.2f; for (i = 0; i < 2; i++) { bounds[0][i] = bounds[0][i] - 16.0; bounds[1][i] = bounds[1][i] + 16.0; } break; default: 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_CLIP: tr.pc.c_box_cull_md3_clip++; break; case CULL_IN: tr.pc.c_box_cull_md3_in++; break; case CULL_OUT: tr.pc.c_box_cull_md3_out++; break; } return cull; } /* ============== R_AddStaticModelSurfaces ============== */ void R_AddStaticModelSurfaces(void) { cStaticModelUnpacked_t *SM; int i, j, k; int ofsStaticData; int iRadiusCull; dtiki_t *tiki; float tiki_scale; vec3_t tiki_localorigin; vec3_t tiki_worldorigin; if (!tr.world->numStaticModels) { return; } tr.shiftedIsStatic = (1 << QSORT_STATICMODEL_SHIFT); for (i = 0; i < tr.world->numStaticModels; i++) { SM = &tr.world->staticModels[i]; //if( SM->visCount != tr.visCounts[ tr.visIndex ] ) { // continue; //} tiki = SM->tiki; if (!tiki) { continue; } tr.currentEntityNum = i; tr.shiftedEntityNum = i << QSORT_REFENTITYNUM_SHIFT; R_RotateForStaticModel(SM, &tr.viewParms, &tr.or); ofsStaticData = 0; // get the world position tiki_scale = tiki->load_scale * SM->scale; VectorScale(tiki->load_origin, tiki_scale, tiki_localorigin); R_LocalPointToWorld(tiki_localorigin, tiki_worldorigin); iRadiusCull = R_CullPointAndRadius(tiki_worldorigin, SM->cull_radius); if (r_showcull->integer & 8) { switch (iRadiusCull) { case CULL_IN: R_DebugCircle(tiki_worldorigin, SM->cull_radius * 1.2, 0.0, 1.0, 0.0, 0.5, 0); break; case CULL_OUT: R_DebugCircle(tiki_worldorigin, SM->cull_radius * 1.4 + 16.0, 1.0, 0.2, 0.2, 0.5, 0); break; } } if (iRadiusCull != CULL_OUT && (iRadiusCull != CULL_CLIP || R_CullStaticModel(SM->tiki, tiki_scale, tiki_localorigin) != CULL_OUT)) { dtikisurface_t *dsurf; if (tr.viewParms.isPortal) { SM->lodpercentage[1] = R_CalcLod(tiki_worldorigin, SM->cull_radius / SM->scale); } else { SM->lodpercentage[0] = R_CalcLod(tiki_worldorigin, SM->cull_radius / SM->scale); } // // draw all meshes // dsurf = tiki->surfaces; for (int mesh = 0; mesh < tiki->numMeshes; mesh++) { skelHeaderGame_t *skelmodel = ri.TIKI_GetSkel(tiki->mesh[mesh]); skelSurfaceGame_t *surface; staticSurface_t *s_surface; shader_t *shader; float fDist; vec3_t vDelta; if (!skelmodel) { continue; } // // draw all surfaces // surface = skelmodel->pSurfaces; for (j = 0; j < skelmodel->numSurfaces; j++, ofsStaticData += surface->numVerts, surface = surface->pNext, dsurf++) { if (g_nStaticSurfaces >= MAX_STATIC_MODELS_SURFS) { ri.Printf( PRINT_DEVELOPER, "^~^~^ ERROR: MAX_STATIC_MODELS_SURFS exceeded - surface of '%s' skipped\n", tiki->a->name ); continue; } s_surface = &g_staticSurfaces[g_nStaticSurfaces++]; s_surface->ident = SF_TIKI_STATIC; s_surface->ofsStaticData = ofsStaticData; s_surface->surface = surface; s_surface->meshNum = mesh; shader = tr.shaders[dsurf->hShader[0]]; if (shader->numUnfoggedPasses == 1 && !r_nocull->integer) { switch (shader->stages[0]->alphaGen) { case AGEN_DIST_FADE: if (R_DistanceCullPointAndRadius( shader->fDistNear + shader->fDistRange, tiki_worldorigin, SM->cull_radius ) == CULL_OUT) { continue; } break; case AGEN_ONE_MINUS_DIST_FADE: if (R_DistanceCullPointAndRadius(shader->fDistNear, tiki_worldorigin, SM->cull_radius) == CULL_IN) { continue; } break; case AGEN_TIKI_DIST_FADE: fDist = (shader->fDistNear + shader->fDistRange); VectorSubtract(tiki_worldorigin, tr.viewParms.or.origin, vDelta); if (VectorLengthSquared(vDelta) >= Square(fDist)) { continue; } break; case AGEN_ONE_MINUS_TIKI_DIST_FADE: fDist = (shader->fDistNear + shader->fDistRange); VectorSubtract(tiki_worldorigin, tr.viewParms.or.origin, vDelta); if (VectorLengthSquared(vDelta) <= Square(shader->fDistNear)) { continue; } break; } } SM->bRendered = qtrue; R_AddDrawSurf((surfaceType_t *)s_surface, shader, 0, 0, 0, 0); if (r_showstaticlod->integer) { vec3_t org; int render_count, total_tris; VectorCopy(SM->origin, org); org[2] += 100.0; R_DrawDebugNumber(org, SM->lodpercentage[0], r_showstaticlod->value * 2, 1.0, 1.0, 0.0, 3); org[2] += 125.0; R_CountTikiLodTris(tiki, SM->lodpercentage[0], &render_count, &total_tris); R_DrawDebugNumber(org, render_count, r_showstaticlod->value * 2, 1.0, 1.0, 0.0, 0); } if (r_showstaticbboxes->integer) { vec3_t vMins, vMaxs; for (k = 0; k < 3; k++) { vMins[k] = tiki->a->mins[k] * tiki->load_scale * SM->scale; vMaxs[k] = tiki->a->maxs[k] * tiki->load_scale * SM->scale; } R_DebugRotatedBBox(SM->origin, SM->angles, vMins, vMaxs, 1.0, 0.0, 1.0, 0.75); } } } } } tr.shiftedIsStatic = 0; } /* ============== RB_Static_BuildDLights ============== */ void RB_Static_BuildDLights() { int i; backEnd.currentStaticModel->useSpecialLighting = backEnd.refdef.num_dlights > 0; backEnd.currentStaticModel->numdlights = 0; if (!backEnd.currentStaticModel->useSpecialLighting) { return; } for (i = 0; i < backEnd.refdef.num_dlights && backEnd.currentStaticModel->numdlights != 32; i++) { vec3_t lightorigin, delta; VectorCopy(backEnd.refdef.dlights[i].origin, lightorigin); VectorSubtract(lightorigin, backEnd.currentStaticModel->origin, delta); if (backEnd.refdef.dlights[i].radius * 2.0 >= VectorLength(delta)) { MatrixTransformVectorRight( backEnd.currentStaticModel->axis, delta, backEnd.currentStaticModel->dlights[backEnd.currentStaticModel->numdlights].transformed ); backEnd.currentStaticModel->dlights[backEnd.currentStaticModel->numdlights++].index = i; } } if (!backEnd.currentStaticModel->numdlights) { backEnd.currentStaticModel->useSpecialLighting = qfalse; } } /* ============== R_InfoStaticModels_f ============== */ void R_InfoStaticModels_f(void) { g_bInfostaticmodels = qtrue; } /* ============== R_PrintInfoStaticModels ============== */ void R_PrintInfoStaticModels() { int iRenderCount; int i, j; cStaticModelUnpacked_t *SM; dtiki_t *tikis[MAX_DISTINCT_STATIC_MODELS]; int tiki_count[MAX_DISTINCT_STATIC_MODELS]; cStaticModelUnpacked_t *pSM[MAX_DISTINCT_STATIC_MODELS]; int count; ri.Printf(PRINT_ALL, "Static model info:\n"); ri.Printf(PRINT_ALL, "------------------\n"); count = 0; iRenderCount = 0; for (i = 0; i < tr.world->numStaticModels; i++) { SM = &tr.world->staticModels[i]; if (!SM->bRendered) { continue; } SM->bRendered = qfalse; iRenderCount++; if (!SM->tiki) { ri.Printf(PRINT_ALL, "ERROR: static model with no tiki\n"); continue; } for (j = 0; j < count; j++) { if (SM->tiki == tikis[j]) { tiki_count[j]++; break; } } if (count >= MAX_DISTINCT_STATIC_MODELS) { ri.Printf(PRINT_ALL, "R_PrintInfoStaticModels: MAX_DISTINCT_STATIC_MODELS exceeded - increase and recompile\n"); break; } if (j == count) { tikis[count] = SM->tiki; pSM[count] = SM; tiki_count[count] = 1; count++; } } ri.Printf(PRINT_ALL, "Total static models rendered: %d\n", iRenderCount); for (i = 0; i < count; i++) { skelHeaderGame_t *skelmodel = ri.TIKI_GetSkel(tikis[i]->mesh[0]); ri.Printf(PRINT_ALL, "model: %s, version: %d, count: %d,\n culling min %.1f %.1f %.1f, max %.1f %.1f %.1f, radius %.1f\n", tikis[i]->a->name, skelmodel ? skelmodel->version : -1, tiki_count[i], tikis[i]->a->mins[0], tikis[i]->a->mins[1], tikis[i]->a->mins[2], tikis[i]->a->maxs[0], tikis[i]->a->maxs[1], tikis[i]->a->maxs[2], pSM[i]->cull_radius ); } }