// structural? (div0) no, game code should not be allowed to differentiate between structural and detail
// trigger? (div0) no, as these are always solid anyway, and that's all that matters for trigger brushes
#define SUPERCONTENTS_LIQUIDSMASK (SUPERCONTENTS_LAVA | SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER)
+#define SUPERCONTENTS_VISBLOCKERMASK SUPERCONTENTS_OPAQUE
/*
#define SUPERCONTENTS_DEADMONSTER 0x00000000
}
}
+#define MAX_LINEOFSIGHTTRACES 64
+
+static qboolean R_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+{
+ int i;
+ vec3_t boxmins, boxmaxs;
+ vec3_t start;
+ vec3_t end;
+ dp_model_t *model = r_refdef.scene.worldmodel;
+
+ if (!model || !model->brush.TraceLineOfSight)
+ return true;
+
+ // expand the box a little
+ boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0];
+ boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0];
+ boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1];
+ boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1];
+ boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2];
+ boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2];
+
+ // try center
+ VectorCopy(eye, start);
+ VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, end);
+ if (model->brush.TraceLineOfSight(model, start, end))
+ return true;
+
+ // try various random positions
+ for (i = 0;i < numsamples;i++)
+ {
+ VectorSet(end, lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2]));
+ if (model->brush.TraceLineOfSight(model, start, end))
+ return true;
+ }
+
+ return false;
+}
+
+
static void R_View_UpdateEntityVisible (void)
{
int i, renderimask;
ent = r_refdef.scene.entities[i];
if(r_refdef.viewcache.entityvisible[i] && !(ent->effects & EF_NODEPTHTEST) && !(ent->flags & (RENDER_VIEWMODEL + RENDER_NOCULL)) && !(ent->model && (ent->model->name[0] == '*')))
{
- if(Mod_CanSeeBox_Trace(r_cullentities_trace_samples.integer, r_cullentities_trace_enlarge.value, r_refdef.scene.worldmodel, r_refdef.view.origin, ent->mins, ent->maxs))
+ if(R_CanSeeBox(r_cullentities_trace_samples.integer, r_cullentities_trace_enlarge.value, r_refdef.view.origin, ent->mins, ent->maxs))
ent->last_trace_visibility = realtime;
if(ent->last_trace_visibility < realtime - r_cullentities_trace_delay.value)
r_refdef.viewcache.entityvisible[i] = 0;
cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "selects different tracebrush bsp recursion algorithms (for debugging purposes only)"};
cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."};
cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"};
+cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"};
static texture_t mod_q1bsp_texture_solid;
static texture_t mod_q1bsp_texture_sky;
Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush);
Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower);
Cvar_RegisterVariable(&mod_q3bsp_nolightmaps);
+ Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes);
memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid));
strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name));
mnode_t *node, *nodestack[1024];
if (!model->brush.num_pvsclusters)
return -1;
- node = model->brush.data_nodes;
+ node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
for (;;)
{
#if 1
mnode_t *node, *nodestack[1024];
if (!model->brush.num_pvsclusters)
return true;
- node = model->brush.data_nodes;
+ node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
for (;;)
{
#if 1
mnode_t *node, *nodestack[1024];
if (!model->brush.num_leafs)
return true;
- node = model->brush.data_nodes;
+ node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
for (;;)
{
#if 1
mnode_t *node, *nodestack[1024];
if (!model->brush.num_leafs)
return true;
- node = model->brush.data_nodes;
+ node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
for (;;)
{
#if 1
}
}
-static int Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3])
-{
- double t1, t2;
- double midf, mid[3];
- int ret, side;
-
- // check for empty
- while (node->plane)
- {
- // find the point distances
- mplane_t *plane = node->plane;
- if (plane->type < 3)
- {
- t1 = p1[plane->type] - plane->dist;
- t2 = p2[plane->type] - plane->dist;
- }
- else
- {
- t1 = DotProduct (plane->normal, p1) - plane->dist;
- t2 = DotProduct (plane->normal, p2) - plane->dist;
- }
-
- if (t1 < 0)
- {
- if (t2 < 0)
- {
- node = node->children[1];
- continue;
- }
- side = 1;
- }
- else
- {
- if (t2 >= 0)
- {
- node = node->children[0];
- continue;
- }
- side = 0;
- }
-
- midf = t1 / (t1 - t2);
- VectorLerp(p1, midf, p2, mid);
-
- // recurse both sides, front side first
- // return 2 if empty is followed by solid (hit something)
- // do not return 2 if both are solid or both empty,
- // or if start is solid and end is empty
- // as these degenerate cases usually indicate the eye is in solid and
- // should see the target point anyway
- ret = Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ], p1, mid);
- if (ret != 0)
- return ret;
- ret = Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2);
- if (ret != 1)
- return ret;
- return 2;
- }
- return ((mleaf_t *)node)->clusterindex < 0;
-}
-
static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
{
- // this function currently only supports same size start and end
- double tracestart[3], traceend[3];
- VectorCopy(start, tracestart);
- VectorCopy(end, traceend);
- return Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend) != 2;
+ trace_t trace;
+ model->TraceLine(model, 0, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK);
+ return trace.fraction == 1;
}
static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(dp_model_t *model, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz)
p = pnext;
}
// now recalculate the node bounding boxes from the leafs
- Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes);
+ Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode);
}
/*
static void Mod_Q1BSP_MakePortals(void)
{
portalchain = NULL;
- Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes);
+ Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode);
Mod_Q1BSP_FinalizePortals();
}
static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p)
{
mnode_t *node;
- node = model->brush.data_nodes;
+ node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
while (node->plane)
node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
if (((mleaf_t *)node)->clusterindex >= 0)
}
if (!merge)
memset(pvsbuffer, 0, bytes);
- Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes);
+ Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode);
return bytes;
}
// textures and memory belong to the main model
mod->texturepool = NULL;
mod->mempool = NULL;
- mod->brush.TraceLineOfSight = NULL;
mod->brush.GetPVS = NULL;
mod->brush.FatPVS = NULL;
mod->brush.BoxTouchingPVS = NULL;
mod->firstmodelsurface = bm->firstface;
mod->nummodelsurfaces = bm->numfaces;
+ // set node/leaf parents for this submodel
+ Mod_Q1BSP_LoadNodes_RecursiveSetParent(mod->brush.data_nodes + mod->brushq1.hulls[0].firstclipnode, NULL);
+
// make the model surface list (used by shadowing/lighting)
mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int);
Mod_MakeSortedSurfaces(mod);
//Con_Printf("result: ambient %f %f %f diffuse %f %f %f diffusenormal %f %f %f\n", ambientcolor[0], ambientcolor[1], ambientcolor[2], diffusecolor[0], diffusecolor[1], diffusecolor[2], diffusenormal[0], diffusenormal[1], diffusenormal[2]);
}
+static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3])
+{
+ double t1, t2;
+ double midf, mid[3];
+ int ret, side;
+
+ // check for empty
+ while (node->plane)
+ {
+ // find the point distances
+ mplane_t *plane = node->plane;
+ if (plane->type < 3)
+ {
+ t1 = p1[plane->type] - plane->dist;
+ t2 = p2[plane->type] - plane->dist;
+ }
+ else
+ {
+ t1 = DotProduct (plane->normal, p1) - plane->dist;
+ t2 = DotProduct (plane->normal, p2) - plane->dist;
+ }
+
+ if (t1 < 0)
+ {
+ if (t2 < 0)
+ {
+ node = node->children[1];
+ continue;
+ }
+ side = 1;
+ }
+ else
+ {
+ if (t2 >= 0)
+ {
+ node = node->children[0];
+ continue;
+ }
+ side = 0;
+ }
+
+ midf = t1 / (t1 - t2);
+ VectorLerp(p1, midf, p2, mid);
+
+ // recurse both sides, front side first
+ // return 2 if empty is followed by solid (hit something)
+ // do not return 2 if both are solid or both empty,
+ // or if start is solid and end is empty
+ // as these degenerate cases usually indicate the eye is in solid and
+ // should see the target point anyway
+ ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ], p1, mid);
+ if (ret != 0)
+ return ret;
+ ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2);
+ if (ret != 1)
+ return ret;
+ return 2;
+ }
+ return ((mleaf_t *)node)->clusterindex < 0;
+}
+
+static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
+{
+ if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer)
+ {
+ trace_t trace;
+ model->TraceLine(model, 0, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK);
+ return trace.fraction == 1;
+ }
+ else
+ {
+ double tracestart[3], traceend[3];
+ VectorCopy(start, tracestart);
+ VectorCopy(end, traceend);
+ return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend);
+ }
+}
+
static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe)
{
int i;
mod->TraceLine = Mod_Q3BSP_TraceLine;
mod->TracePoint = Mod_Q3BSP_TracePoint;
mod->PointSuperContents = Mod_Q3BSP_PointSuperContents;
- mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight;
+ mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight;
mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents;
mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents;
mod->brush.GetPVS = Mod_Q1BSP_GetPVS;
// textures and memory belong to the main model
mod->texturepool = NULL;
mod->mempool = NULL;
- mod->brush.TraceLineOfSight = NULL;
mod->brush.GetPVS = NULL;
mod->brush.FatPVS = NULL;
mod->brush.BoxTouchingPVS = NULL;
void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend);
void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend);
-// utility
-qboolean Mod_CanSeeBox_Trace(int numsamples, float t, dp_model_t *model, vec3_t eye, vec3_t minsX, vec3_t maxsX);
-
#endif // MODEL_SHARED_H
}
server_connectfloodaddress_t;
+#define MAX_CLIENTNETWORKEYES 256
+
typedef struct server_s
{
/// false if only a net client
int writeentitiestoclient_cliententitynumber;
int writeentitiestoclient_clientnumber;
sizebuf_t *writeentitiestoclient_msg;
- vec3_t writeentitiestoclient_testeye;
+ vec3_t writeentitiestoclient_eyes[MAX_CLIENTNETWORKEYES];
+ int writeentitiestoclient_numeyes;
int writeentitiestoclient_pvsbytes;
unsigned char writeentitiestoclient_pvs[MAX_MAP_LEAFS/8];
entity_state_t writeentitiestoclient_sendstates[MAX_EDICTS];
trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask);
trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask);
+qboolean SV_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs);
+
int SV_PointSuperContents(const vec3_t point);
void SV_FlushBroadcastMessages(void);
cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"};
cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"};
cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"};
+cvar_t sv_cullentities_trace_prediction_time = {0, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"};
+cvar_t sv_cullentities_trace_entityocclusion = {0, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"};
cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "1", "number of samples to test for entity culling"};
cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"};
cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"};
Cvar_RegisterVariable (&sv_cullentities_trace_delay);
Cvar_RegisterVariable (&sv_cullentities_trace_delay_players);
Cvar_RegisterVariable (&sv_cullentities_trace_enlarge);
+ Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion);
Cvar_RegisterVariable (&sv_cullentities_trace_prediction);
+ Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time);
Cvar_RegisterVariable (&sv_cullentities_trace_samples);
Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra);
Cvar_RegisterVariable (&sv_cullentities_trace_samples_players);
}
}
+#define MAX_LINEOFSIGHTTRACES 64
+
+qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+{
+ float pitchsign;
+ float alpha;
+ float starttransformed[3], endtransformed[3];
+ int blocked = 0;
+ int traceindex;
+ int originalnumtouchedicts;
+ int numtouchedicts = 0;
+ int touchindex;
+ matrix4x4_t matrix, imatrix;
+ dp_model_t *model;
+ prvm_edict_t *touch;
+ prvm_edict_t *touchedicts[MAX_EDICTS];
+ unsigned int modelindex;
+ vec3_t boxmins, boxmaxs;
+ vec3_t clipboxmins, clipboxmaxs;
+ vec3_t endpoints[MAX_LINEOFSIGHTTRACES];
+
+ numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES);
+
+ // expand the box a little
+ boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0];
+ boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0];
+ boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1];
+ boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1];
+ boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2];
+ boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2];
+
+ VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]);
+ for (traceindex = 1;traceindex < numtraces;traceindex++)
+ VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2]));
+
+ // calculate sweep box for the entire swarm of traces
+ VectorCopy(eye, clipboxmins);
+ VectorCopy(eye, clipboxmaxs);
+ for (traceindex = 0;traceindex < numtraces;traceindex++)
+ {
+ clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]);
+ clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]);
+ clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]);
+ clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]);
+ clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]);
+ clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]);
+ }
+
+ // get the list of entities in the sweep box
+ if (sv_cullentities_trace_entityocclusion.integer)
+ numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ // iterate the entities found in the sweep box and filter them
+ originalnumtouchedicts = numtouchedicts;
+ numtouchedicts = 0;
+ for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++)
+ {
+ touch = touchedicts[touchindex];
+ if (touch->fields.server->solid != SOLID_BSP)
+ continue;
+ modelindex = (unsigned int)touch->fields.server->modelindex;
+ if (!modelindex)
+ continue;
+ if (modelindex >= MAX_MODELS)
+ continue; // error?
+ model = sv.models[(int)touch->fields.server->modelindex];
+ if (!model->brush.TraceLineOfSight)
+ continue;
+ // skip obviously transparent entities
+ alpha = PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.alpha)->_float;
+ if (alpha && alpha < 1)
+ continue;
+ if ((int)touch->fields.server->effects & EF_ADDITIVE)
+ continue;
+ touchedicts[numtouchedicts++] = touch;
+ }
+
+ // now that we have a filtered list of "interesting" entities, fire each
+ // ray against all of them, this gives us an early-out case when something
+ // is visible (which it often is)
+
+ for (traceindex = 0;traceindex < numtraces;traceindex++)
+ {
+ // check world occlusion
+ if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight)
+ if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex]))
+ continue;
+ for (touchindex = 0;touchindex < numtouchedicts;touchindex++)
+ {
+ touch = touchedicts[touchindex];
+ modelindex = (unsigned int)touch->fields.server->modelindex;
+ model = sv.models[(int)touch->fields.server->modelindex];
+ // get the entity matrix
+ pitchsign = (model->type == mod_alias) ? -1 : 1;
+ Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ // see if the ray hits this entity
+ Matrix4x4_Transform(&imatrix, eye, starttransformed);
+ Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed);
+ if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed))
+ {
+ blocked++;
+ break;
+ }
+ }
+ // check if the ray was blocked
+ if (touchindex < numtouchedicts)
+ continue;
+ // return if the ray was not blocked
+ return true;
+ }
+
+ // no rays survived
+ return false;
+}
+
void SV_MarkWriteEntityStateToClient(entity_state_t *s)
{
int isbmodel;
: sv_cullentities_trace_samples.integer;
float enlarge = sv_cullentities_trace_enlarge.value;
- qboolean visible = TRUE;
-
if(samples > 0)
{
- do
- {
- if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
- break; // directly visible from the server's view
-
- if(sv_cullentities_trace_prediction.integer)
- {
- vec3_t predeye;
-
- // get player velocity
- float predtime = bound(0, host_client->ping, 0.2); // / 2
- // sorry, no wallhacking by high ping please, and at 200ms
- // ping a FPS is annoying to play anyway and a player is
- // likely to have changed his direction
- VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye);
- if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there...
- {
- if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
- break; // directly visible from the predicted view
- }
- else
- {
- //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
- }
- }
-
- // when we get here, we can't see the entity
- visible = false;
- }
- while(0);
-
- if(visible)
+ int eyeindex;
+ for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++)
+ if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+ break;
+ if(eyeindex < sv.writeentitiestoclient_numeyes)
svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] =
realtime + (
s->number <= svs.maxclients
entity_state_t *s;
prvm_edict_t *camera;
qboolean success;
+ vec3_t eye;
// if there isn't enough space to accomplish anything, skip it
if (msg->cursize + 25 > maxsize)
sv.writeentitiestoclient_stats_culled_trace = 0;
sv.writeentitiestoclient_stats_visibleentities = 0;
sv.writeentitiestoclient_stats_totalentities = 0;
+ sv.writeentitiestoclient_numeyes = 0;
-// find the client's PVS
- // the real place being tested from
+ // get eye location
+ sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
camera = PRVM_EDICT_NUM( client->clientcamera );
- VectorAdd(camera->fields.server->origin, clent->fields.server->view_ofs, sv.writeentitiestoclient_testeye);
+ VectorAdd(camera->fields.server->origin, clent->fields.server->view_ofs, eye);
sv.writeentitiestoclient_pvsbytes = 0;
+ // get the PVS values for the eye location, later FatPVS calls will merge
if (sv.worldmodel && sv.worldmodel->brush.FatPVS)
- sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_testeye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), false);
+ sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, eye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0);
- sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
+ // add the eye to a list for SV_CanSeeBox tests
+ VectorCopy(eye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
+ sv.writeentitiestoclient_numeyes++;
+
+ // calculate predicted eye origin for SV_CanSeeBox tests
+ if (sv_cullentities_trace_prediction.integer)
+ {
+ vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value);
+ vec3_t predeye;
+ VectorMA(eye, predtime, camera->fields.server->velocity, predeye);
+ if (SV_CanSeeBox(1, 0, eye, predeye, predeye))
+ {
+ VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
+ sv.writeentitiestoclient_numeyes++;
+ }
+ //if (!sv.writeentitiestoclient_useprediction)
+ // Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
+ }
+
+ // TODO: check line of sight to portal entities and add them to PVS
sv.sententitiesmark++;