From be3efe70441bd3fc7f52dc2c54c458c836a49ef8 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 3 Oct 2022 21:36:38 +1000 Subject: [PATCH] Make automatic LOD work on client-networked map objects --- qcsrc/client/csqcmodel_hooks.qc | 16 +++-- qcsrc/client/csqcmodel_hooks.qh | 2 + qcsrc/common/mapobjects/models.qc | 110 ++++++++++++++++-------------- 3 files changed, 70 insertions(+), 58 deletions(-) diff --git a/qcsrc/client/csqcmodel_hooks.qc b/qcsrc/client/csqcmodel_hooks.qc index 7ddfaa5f9..0565c9d86 100644 --- a/qcsrc/client/csqcmodel_hooks.qc +++ b/qcsrc/client/csqcmodel_hooks.qc @@ -23,7 +23,9 @@ .int lodmodelindex0; .int lodmodelindex1; .int lodmodelindex2; -void CSQCPlayer_LOD_Apply(entity this, bool isplayer) +.float loddistance1; +.float loddistance2; +void CSQCModel_LOD_Apply(entity this, bool isplayer) { int detailreduction = ((isplayer) ? autocvar_cl_playerdetailreduction : autocvar_cl_modeldetailreduction); @@ -48,7 +50,7 @@ void CSQCPlayer_LOD_Apply(entity this, bool isplayer) precache_model(s); _setmodel(this, s); if(this.modelindex) - this.lodmodelindex1 = this.modelindex; + this.lodmodelindex2 = this.lodmodelindex1 = this.modelindex; } s = strcat(substring(modelname, 0, strlen(modelname)-4), "_lod2", substring(modelname, -4, 4)); @@ -77,11 +79,13 @@ void CSQCPlayer_LOD_Apply(entity this, bool isplayer) else { float distance = vlen(((isplayer) ? this.origin : NearestPointOnBox(this, view_origin)) - view_origin); // TODO: perhaps it should just use NearestPointOnBox all the time, player hitbox can potentially be huge + float dist1 = (this.loddistance1 > 0) ? this.loddistance1 : autocvar_cl_loddistance1; + float dist2 = (this.loddistance2 > 0) ? this.loddistance2 : autocvar_cl_loddistance2; float f = (distance * current_viewzoom + 100.0) * detailreduction; f *= 1.0 / bound(0.01, view_quality, 1); - if(f > autocvar_cl_loddistance2) + if(f > dist2) this.modelindex = this.lodmodelindex2; - else if(f > autocvar_cl_loddistance1) + else if(f > dist1) this.modelindex = this.lodmodelindex1; else this.modelindex = this.lodmodelindex0; @@ -669,7 +673,7 @@ void CSQCModel_Hook_PreDraw(entity this, bool isplayer) if((this.isplayermodel & ISPLAYER_MODEL) && this.drawmask) // this checks if it's a player MODEL! { CSQCPlayer_ModelAppearance_Apply(this, (this.isplayermodel & ISPLAYER_LOCAL)); - CSQCPlayer_LOD_Apply(this, true); + CSQCModel_LOD_Apply(this, true); if(!isplayer) { @@ -748,7 +752,7 @@ void CSQCModel_Hook_PreDraw(entity this, bool isplayer) } } else - CSQCPlayer_LOD_Apply(this, false); + CSQCModel_LOD_Apply(this, false); CSQCModel_AutoTagIndex_Apply(this); diff --git a/qcsrc/client/csqcmodel_hooks.qh b/qcsrc/client/csqcmodel_hooks.qh index 165008ee9..d7ccc5213 100644 --- a/qcsrc/client/csqcmodel_hooks.qh +++ b/qcsrc/client/csqcmodel_hooks.qh @@ -48,4 +48,6 @@ const int MF_TRACER3 = BIT(7); // purple trail void CSQCModel_Effects_Apply(entity this); +void CSQCModel_LOD_Apply(entity this, bool isplayer); + void CSQCModel_Hook_PreDraw(entity this, bool isplayer); diff --git a/qcsrc/common/mapobjects/models.qc b/qcsrc/common/mapobjects/models.qc index 85a8ab843..f342ebb25 100644 --- a/qcsrc/common/mapobjects/models.qc +++ b/qcsrc/common/mapobjects/models.qc @@ -1,5 +1,9 @@ #include "models.qh" +#ifdef CSQC + #include +#endif + #ifdef SVQC #include #include @@ -69,13 +73,21 @@ void g_model_dropbyspawnflags(entity this) } } +void g_clientmodel_think(entity this) +{ + this.nextthink = time; + if(this.oldorigin != this.origin) + this.SendFlags |= BIT(1); + this.oldorigin = this.origin; +} + void g_clientmodel_dropbyspawnflags(entity this) { vector o0; o0 = this.origin; g_model_dropbyspawnflags(this); if(this.origin != o0) - this.SendFlags |= 2; + this.SendFlags |= BIT(1); } bool g_clientmodel_genericsendentity(entity this, entity to, int sf) @@ -151,42 +163,48 @@ bool g_clientmodel_genericsendentity(entity this, entity to, int sf) return true; } - -#define G_MODEL_INIT(ent,sol) \ - if(ent.geomtype && autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ - if(!ent.scale) ent.scale = ent.modelscale; \ - SetBrushEntityModel(ent,true); \ - ent.use = g_model_setcolormaptoactivator; \ - InitializeEntity(ent, g_model_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ - if(!ent.solid) ent.solid = (sol); \ +void g_model_init(entity ent, float sol) +{ + if(ent.geomtype && autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); + if(!ent.scale) ent.scale = ent.modelscale; + SetBrushEntityModel(ent,true); + ent.use = g_model_setcolormaptoactivator; + InitializeEntity(ent, g_model_dropbyspawnflags, INITPRIO_DROPTOFLOOR); + if(!ent.solid) ent.solid = (sol); else if(ent.solid < 0) ent.solid = SOLID_NOT; +} -#define G_CLIENTMODEL_INIT(ent,sol) \ - if(ent.geomtype && autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ - if(!ent.scale) ent.scale = ent.modelscale; \ - SetBrushEntityModel(ent,true); \ - ent.use = g_clientmodel_use; \ - InitializeEntity(ent, g_clientmodel_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ - if(!ent.solid) ent.solid = (sol); \ - else if(ent.solid < 0) ent.solid = SOLID_NOT; \ - if(!ent.bgmscriptsustain) ent.bgmscriptsustain = 1; \ - else if(ent.bgmscriptsustain < 0) ent.bgmscriptsustain = 0; \ - Net_LinkEntity(ent, true, 0, g_clientmodel_genericsendentity); \ +void g_clientmodel_init(entity ent, float sol) +{ + if(ent.geomtype && autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); + if(!ent.scale) ent.scale = ent.modelscale; + SetBrushEntityModel(ent,true); + ent.use = g_clientmodel_use; + setthink(ent, g_clientmodel_think); + ent.nextthink = time; + ent.oldorigin = ent.origin; // don't run an initial double update + InitializeEntity(ent, g_clientmodel_dropbyspawnflags, INITPRIO_DROPTOFLOOR); + if(!ent.solid) ent.solid = (sol); + else if(ent.solid < 0) ent.solid = SOLID_NOT; + if(!ent.bgmscriptsustain) ent.bgmscriptsustain = 1; + else if(ent.bgmscriptsustain < 0) ent.bgmscriptsustain = 0; + Net_LinkEntity(ent, true, 0, g_clientmodel_genericsendentity); ent.default_solid = sol; +} // non-solid model entities: -spawnfunc(misc_gamemodel) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // model entity -spawnfunc(misc_clientmodel) { this.angles_x = -this.angles.x; G_CLIENTMODEL_INIT(this, SOLID_NOT) } // model entity -spawnfunc(misc_models) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // DEPRECATED old compat entity with confusing name, do not use +spawnfunc(misc_gamemodel) { this.angles_x = -this.angles.x; g_model_init(this, SOLID_NOT); } // model entity +spawnfunc(misc_clientmodel) { this.angles_x = -this.angles.x; g_clientmodel_init(this, SOLID_NOT); } // model entity +spawnfunc(misc_models) { this.angles_x = -this.angles.x; g_model_init(this, SOLID_NOT); } // DEPRECATED old compat entity with confusing name, do not use // non-solid brush entities: -spawnfunc(func_illusionary) { G_MODEL_INIT (this, SOLID_NOT) } // Q1 name (WARNING: MISPREDICTED) -spawnfunc(func_clientillusionary) { G_CLIENTMODEL_INIT(this, SOLID_NOT) } // brush entity +spawnfunc(func_illusionary) { g_model_init(this, SOLID_NOT); } // Q1 name (WARNING: MISPREDICTED) +spawnfunc(func_clientillusionary) { g_clientmodel_init(this, SOLID_NOT); } // brush entity // solid brush entities -spawnfunc(func_wall) { G_MODEL_INIT (this, SOLID_BSP) } // Q1 name -spawnfunc(func_clientwall) { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // brush entity (WARNING: MISPREDICTED) -spawnfunc(func_static) { G_MODEL_INIT (this, SOLID_BSP) } // DEPRECATED old alias name from some other game +spawnfunc(func_wall) { g_model_init(this, SOLID_BSP); } // Q1 name +spawnfunc(func_clientwall) { g_clientmodel_init(this, SOLID_BSP); } // brush entity (WARNING: MISPREDICTED) +spawnfunc(func_static) { g_model_init(this, SOLID_BSP); } // DEPRECATED old alias name from some other game #elif defined(CSQC) .float alpha; .float scale; @@ -243,30 +261,7 @@ void Ent_Wall_Draw(entity this) fld = origin; this.(fld) = this.saved; - if(this.lodmodelindex1) - { - if(autocvar_cl_modeldetailreduction <= 0) - { - if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2) - this.modelindex = this.lodmodelindex2; - else if(autocvar_cl_modeldetailreduction <= -1) - this.modelindex = this.lodmodelindex1; - else - this.modelindex = this.lodmodelindex0; - } - else - { - float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin); - f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction; - f *= 1.0 / bound(0.01, view_quality, 1); - if(this.lodmodelindex2 && f > this.loddistance2) - this.modelindex = this.lodmodelindex2; - else if(f > this.loddistance1) - this.modelindex = this.lodmodelindex1; - else - this.modelindex = this.lodmodelindex0; - } - } + CSQCModel_LOD_Apply(this, false); InterpolateOrigin_Do(this); @@ -343,10 +338,21 @@ NET_HANDLE(ENT_CLIENT_WALL, bool isnew) this.lodmodelindex1 = ReadShort(); this.loddistance2 = ReadShort(); this.lodmodelindex2 = ReadShort(); + this.modelindex = this.lodmodelindex0; + vector pmin = this.mins, pmax = this.maxs; + setmodelindex(this, this.modelindex); // this retrieves the .model key and sets mins/maxs/absmin/absmax + setsize(this, pmin, pmax); + // if there's no second LOD model, fall back to the first + // avoids using the high quality model at a distance + if(!this.lodmodelindex2 && this.lodmodelindex1) + this.lodmodelindex2 = this.lodmodelindex1; } else { this.modelindex = ReadShort(); + vector pmin = this.mins, pmax = this.maxs; + setmodelindex(this, this.modelindex); // this retrieves the .model key and sets mins/maxs/absmin/absmax + setsize(this, pmin, pmax); this.loddistance1 = 0; this.loddistance2 = 0; } -- 2.39.2