Rework R_CanSeeBox a bit to support an eyejitter feature, and make it available throu...
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Sun, 11 Mar 2018 19:24:42 +0000 (19:24 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Sun, 11 Mar 2018 19:24:42 +0000 (19:24 +0000)
Make TraceLineOfSight check if the trace endpos is within a box, not just if fraction is 1 - this makes it much more likely to say an object is visible when the object overlaps walls and other obstacles as the rays only need to reach the box, it doesn't matter much where it goes inside the box.

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12348 d7cf8633-e32d-0410-b094-e92efae38249

gl_rmain.c
model_brush.c
model_shared.h
render.h
server.h
snd_main.c
sv_main.c

index 6a31643..e7cfe47 100644 (file)
@@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "csprogs.h"
 #include "cl_video.h"
 #include "dpsoftrast.h"
+#include "cl_collision.h"
 
 #ifdef SUPPORTD3D
 #include <d3d9.h>
@@ -109,10 +110,12 @@ cvar_t r_drawworld = {0, "r_drawworld","1", "draw world (most static stuff)"};
 cvar_t r_drawviewmodel = {0, "r_drawviewmodel","1", "draw your weapon model"};
 cvar_t r_drawexteriormodel = {0, "r_drawexteriormodel","1", "draw your player model (e.g. in chase cam, reflections)"};
 cvar_t r_cullentities_trace = {0, "r_cullentities_trace", "1", "probabistically cull invisible entities"};
+cvar_t r_cullentities_trace_entityocclusion = { 0, "r_cullentities_trace_entityocclusion", "1", "check for occluding entities such as doors, not just world hull" };
 cvar_t r_cullentities_trace_samples = {0, "r_cullentities_trace_samples", "2", "number of samples to test for entity culling (in addition to center sample)"};
 cvar_t r_cullentities_trace_tempentitysamples = {0, "r_cullentities_trace_tempentitysamples", "-1", "number of samples to test for entity culling of temp entities (including all CSQC entities), -1 disables trace culling on these entities to prevent flicker (pvs still applies)"};
 cvar_t r_cullentities_trace_enlarge = {0, "r_cullentities_trace_enlarge", "0", "box enlargement for entity culling"};
 cvar_t r_cullentities_trace_delay = {0, "r_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"};
+cvar_t r_cullentities_trace_eyejitter = {0, "r_cullentities_trace_eyejitter", "16", "randomly offset rays from the eye by this much to reduce the odds of flickering"};
 cvar_t r_sortentities = {0, "r_sortentities", "0", "sort entities before drawing (might be faster)"};
 cvar_t r_speeds = {0, "r_speeds","0", "displays rendering statistics and per-subsystem timings"};
 cvar_t r_fullbright = {0, "r_fullbright","0", "makes map very bright and renders faster"};
@@ -4351,6 +4354,7 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_draw2d);
        Cvar_RegisterVariable(&r_drawworld);
        Cvar_RegisterVariable(&r_cullentities_trace);
+       Cvar_RegisterVariable(&r_cullentities_trace_entityocclusion);
        Cvar_RegisterVariable(&r_cullentities_trace_samples);
        Cvar_RegisterVariable(&r_cullentities_trace_tempentitysamples);
        Cvar_RegisterVariable(&r_cullentities_trace_enlarge);
@@ -5218,42 +5222,88 @@ static void R_View_UpdateEntityLighting (void)
        }
 }
 
-#define MAX_LINEOFSIGHTTRACES 64
-
-static qboolean R_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+qboolean R_CanSeeBox(int numsamples, vec_t eyejitter, vec_t entboxenlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
 {
        int i;
+       vec3_t eyemins, eyemaxs;
        vec3_t boxmins, boxmaxs;
        vec3_t start;
        vec3_t end;
        dp_model_t *model = r_refdef.scene.worldmodel;
+       static vec3_t positions[] = {
+               { 0.5f, 0.5f, 0.5f },
+               { 0.0f, 0.0f, 0.0f },
+               { 0.0f, 0.0f, 1.0f },
+               { 0.0f, 1.0f, 0.0f },
+               { 0.0f, 1.0f, 1.0f },
+               { 1.0f, 0.0f, 0.0f },
+               { 1.0f, 0.0f, 1.0f },
+               { 1.0f, 1.0f, 0.0f },
+               { 1.0f, 1.0f, 1.0f },
+       };
+
+       // sample count can be set to -1 to skip this logic, for flicker-prone objects
+       if (numsamples < 0)
+               return true;
+
+       // view origin is not used for culling in portal/reflection/refraction renders or isometric views
+       if (r_refdef.view.useclipplane || !r_refdef.view.useperspective || r_trippy.integer)
+               return true;
 
-       if (!model || !model->brush.TraceLineOfSight)
+       if (!r_cullentities_trace_entityocclusion.integer && (!model || !model->brush.TraceLineOfSight))
                return true;
 
+       // expand the eye box a little
+       eyemins[0] = eye[0] - eyejitter;
+       eyemaxs[0] = eye[0] + eyejitter;
+       eyemins[1] = eye[1] - eyejitter;
+       eyemaxs[1] = eye[1] + eyejitter;
+       eyemins[2] = eye[2] - eyejitter;
+       eyemaxs[2] = eye[2] + eyejitter;
        // 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];
-
-       // return true if eye is inside enlarged box
-       if (BoxesOverlap(boxmins, boxmaxs, eye, eye))
+       boxmins[0] = (entboxenlarge + 1) * entboxmins[0] - entboxenlarge * entboxmaxs[0];
+       boxmaxs[0] = (entboxenlarge + 1) * entboxmaxs[0] - entboxenlarge * entboxmins[0];
+       boxmins[1] = (entboxenlarge + 1) * entboxmins[1] - entboxenlarge * entboxmaxs[1];
+       boxmaxs[1] = (entboxenlarge + 1) * entboxmaxs[1] - entboxenlarge * entboxmins[1];
+       boxmins[2] = (entboxenlarge + 1) * entboxmins[2] - entboxenlarge * entboxmaxs[2];
+       boxmaxs[2] = (entboxenlarge + 1) * entboxmaxs[2] - entboxenlarge * entboxmins[2];
+
+       // return true if eye overlaps enlarged box
+       if (BoxesOverlap(boxmins, boxmaxs, eyemins, eyemaxs))
                return true;
 
-       // try center
-       VectorCopy(eye, start);
-       VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, end);
-       if (model->brush.TraceLineOfSight(model, start, end))
+       // try specific positions in the box first - note that these can be cached
+       if (r_cullentities_trace_entityocclusion.integer)
+       {
+               for (i = 0; i < sizeof(positions) / sizeof(positions[0]); i++)
+               {
+                       VectorCopy(eye, start);
+                       end[0] = boxmins[0] + (boxmaxs[0] - boxmins[0]) * positions[i][0];
+                       end[1] = boxmins[1] + (boxmaxs[1] - boxmins[1]) * positions[i][1];
+                       end[2] = boxmins[2] + (boxmaxs[2] - boxmins[2]) * positions[i][2];
+                       //trace_t trace = CL_TraceLine(start, end, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, 0.0f, true, false, NULL, true, true);
+                       trace_t trace = CL_Cache_TraceLineSurfaces(start, end, MOVE_NOMONSTERS, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY);
+                       // not picky - if the trace ended anywhere in the box we're good
+                       if (BoxesOverlap(trace.endpos, trace.endpos, boxmins, boxmaxs))
+                               return true;
+               }
+       }
+       else if (model->brush.TraceLineOfSight(model, start, end, boxmins, boxmaxs))
                return true;
 
        // try various random positions
-       for (i = 0;i < numsamples;i++)
+       for (i = 0; i < numsamples; i++)
        {
+               VectorSet(start, lhrandom(eyemins[0], eyemaxs[0]), lhrandom(eyemins[1], eyemaxs[1]), lhrandom(eyemins[2], eyemaxs[2]));
                VectorSet(end, lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2]));
-               if (model->brush.TraceLineOfSight(model, start, end))
+               if (r_cullentities_trace_entityocclusion.integer)
+               {
+                       trace_t trace = CL_TraceLine(start, end, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, 0.0f, true, false, NULL, true, true);
+                       // not picky - if the trace ended anywhere in the box we're good
+                       if (BoxesOverlap(trace.endpos, trace.endpos, boxmins, boxmaxs))
+                               return true;
+               }
+               else if (model->brush.TraceLineOfSight(model, start, end, boxmins, boxmaxs))
                        return true;
        }
 
@@ -5302,22 +5352,19 @@ static void R_View_UpdateEntityVisible (void)
                                r_refdef.viewcache.entityvisible[i] = true;
                }
        }
-       if(r_cullentities_trace.integer && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.TraceLineOfSight && !r_refdef.view.useclipplane && !r_trippy.integer)
-               // sorry, this check doesn't work for portal/reflection/refraction renders as the view origin is not useful for culling
+       if (r_cullentities_trace.integer)
        {
                for (i = 0;i < r_refdef.scene.numentities;i++)
                {
                        if (!r_refdef.viewcache.entityvisible[i])
                                continue;
                        ent = r_refdef.scene.entities[i];
-                       if(!(ent->flags & (RENDER_VIEWMODEL | RENDER_WORLDOBJECT | RENDER_NODEPTHTEST)) && !(ent->model && (ent->model->name[0] == '*')))
+                       if (!(ent->flags & (RENDER_VIEWMODEL | RENDER_WORLDOBJECT | RENDER_NODEPTHTEST)) && !(ent->model && (ent->model->name[0] == '*')))
                        {
-                               samples = ent->entitynumber ? r_cullentities_trace_samples.integer : r_cullentities_trace_tempentitysamples.integer;
-                               if (samples < 0)
-                                       continue; // temp entities do pvs only
-                               if(R_CanSeeBox(samples, r_cullentities_trace_enlarge.value, r_refdef.view.origin, ent->mins, ent->maxs))
+                               samples = ent->last_trace_visibility == 0 ? r_cullentities_trace_tempentitysamples.integer : r_cullentities_trace_samples.integer;
+                               if (R_CanSeeBox(samples, r_cullentities_trace_eyejitter.value, 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)
+                               if (ent->last_trace_visibility < realtime - r_cullentities_trace_delay.value)
                                        r_refdef.viewcache.entityvisible[i] = 0;
                        }
                }
index 37e2fd5..9c19c0b 100644 (file)
@@ -70,7 +70,7 @@ static texture_t mod_q1bsp_texture_lava;
 static texture_t mod_q1bsp_texture_slime;
 static texture_t mod_q1bsp_texture_water;
 
-static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end);
+static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs);
 
 void Mod_BrushInit(void)
 {
@@ -1182,11 +1182,11 @@ void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t
        }
 }
 
-static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
+static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs)
 {
        trace_t trace;
        Mod_Q1BSP_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0);
-       return trace.fraction == 1;
+       return trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, acceptmins, acceptmaxs);
 }
 
 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)
@@ -6786,7 +6786,7 @@ static void Mod_Q3BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambie
        //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])
+static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3], double endpos[3])
 {
        double t1, t2;
        double midf, mid[3];
@@ -6836,31 +6836,34 @@ static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p
                // 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);
+               ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side    ], p1, mid, endpos);
                if (ret != 0)
                        return ret;
-               ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2);
+               ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2, endpos);
                if (ret != 1)
                        return ret;
+               VectorCopy(mid, endpos);
                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)
+static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs)
 {
        if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer)
        {
                trace_t trace;
                model->TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0);
-               return trace.fraction == 1;
+               return trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, acceptmins, acceptmaxs);
        }
        else
        {
-               double tracestart[3], traceend[3];
+               double tracestart[3], traceend[3], traceendpos[3];
                VectorCopy(start, tracestart);
                VectorCopy(end, traceend);
-               return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend);
+               VectorCopy(end, traceendpos);
+               Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend, traceendpos);
+               return BoxesOverlap(traceendpos, traceendpos, acceptmins, acceptmaxs);
        }
 }
 
@@ -7230,11 +7233,11 @@ int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const
        return trace.startsupercontents;
 }
 
-qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
+qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs)
 {
        trace_t trace;
        Mod_CollisionBIH_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0);
-       return trace.fraction == 1;
+       return trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, acceptmins, acceptmaxs);
 }
 
 void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask)
index e7e4003..1c18678 100644 (file)
@@ -859,7 +859,7 @@ typedef struct model_brush_s
        void (*AmbientSoundLevelsForPoint)(struct model_s *model, const vec3_t p, unsigned char *out, int outsize);
        void (*RoundUpToHullSize)(struct model_s *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs);
        // trace a line of sight through this model (returns false if the line if sight is definitely blocked)
-       qboolean (*TraceLineOfSight)(struct model_s *model, const vec3_t start, const vec3_t end);
+       qboolean (*TraceLineOfSight)(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs);
 
        char skybox[MAX_QPATH];
 
@@ -1240,7 +1240,7 @@ void Mod_CollisionBIH_TraceLine(dp_model_t *model, const struct frameblend_s *fr
 void Mod_CollisionBIH_TraceBox(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask);
 void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask, int skipsupercontentsmask);
 void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask);
-qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end);
+qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end, const vec3_t acceptmins, const vec3_t acceptmaxs);
 int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const vec3_t point);
 int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t point);
 bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out);
index 09782f5..cba8059 100644 (file)
--- a/render.h
+++ b/render.h
@@ -162,6 +162,7 @@ void R_DrawExplosions(void);
 
 int R_CullBox(const vec3_t mins, const vec3_t maxs);
 int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes);
+qboolean R_CanSeeBox(int numsamples, vec_t eyejitter, vec_t entboxenlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs);
 
 #include "r_modules.h"
 
index e0721ca..57eef85 100644 (file)
--- a/server.h
+++ b/server.h
@@ -582,7 +582,7 @@ trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_
 trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask);
 int SV_EntitiesInBox(const vec3_t mins, const vec3_t maxs, int maxedicts, prvm_edict_t **resultedicts);
 
-qboolean SV_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs);
+qboolean SV_CanSeeBox(int numsamples, vec_t eyejitter, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs);
 
 int SV_PointSuperContents(const vec3_t point);
 
index e435485..62807eb 100644 (file)
@@ -1502,7 +1502,7 @@ static void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx)
 
                                if(snd_spatialization_occlusion.integer & 2)
                                        if(!occluded)
-                                               if(cl.worldmodel && cl.worldmodel->brush.TraceLineOfSight && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin))
+                                               if(cl.worldmodel && cl.worldmodel->brush.TraceLineOfSight && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin, ch->origin, ch->origin))
                                                        occluded = true;
                        }
                        if(occluded)
index 85ca4fe..d724aa1 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -81,6 +81,7 @@ cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow
 cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"};
 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_eyejitter = {0, "sv_cullentities_trace_eyejitter", "16", "jitter the eye by this much for each trace"};
 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"};
@@ -493,6 +494,7 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_cullentities_trace_delay);
        Cvar_RegisterVariable (&sv_cullentities_trace_delay_players);
        Cvar_RegisterVariable (&sv_cullentities_trace_enlarge);
+       Cvar_RegisterVariable (&sv_cullentities_trace_eyejitter);
        Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion);
        Cvar_RegisterVariable (&sv_cullentities_trace_prediction);
        Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time);
@@ -1485,12 +1487,14 @@ static void SV_PrepareEntitiesForSending(void)
 
 #define MAX_LINEOFSIGHTTRACES 64
 
-qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+qboolean SV_CanSeeBox(int numtraces, vec_t eyejitter, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
 {
        prvm_prog_t *prog = SVVM_prog;
        float pitchsign;
        float alpha;
        float starttransformed[3], endtransformed[3];
+       float boxminstransformed[3], boxmaxstransformed[3];
+       float localboxcenter[3], localboxextents[3], localboxmins[3], localboxmaxs[3];
        int blocked = 0;
        int traceindex;
        int originalnumtouchedicts;
@@ -1500,12 +1504,20 @@ qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmin
        dp_model_t *model;
        prvm_edict_t *touch;
        static prvm_edict_t *touchedicts[MAX_EDICTS];
+       vec3_t eyemins, eyemaxs, start;
        vec3_t boxmins, boxmaxs;
        vec3_t clipboxmins, clipboxmaxs;
        vec3_t endpoints[MAX_LINEOFSIGHTTRACES];
 
        numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES);
 
+       // jitter the eye location within this box
+       eyemins[0] = eye[0] - eyejitter;
+       eyemaxs[0] = eye[0] + eyejitter;
+       eyemins[1] = eye[1] - eyejitter;
+       eyemaxs[1] = eye[1] + eyejitter;
+       eyemins[2] = eye[2] - eyejitter;
+       eyemaxs[2] = eye[2] + eyejitter;
        // expand the box a little
        boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0];
        boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0];
@@ -1519,8 +1531,8 @@ qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmin
                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);
+       VectorCopy(eyemins, clipboxmins);
+       VectorCopy(eyemaxs, clipboxmaxs);
        for (traceindex = 0;traceindex < numtraces;traceindex++)
        {
                clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]);
@@ -1566,9 +1578,10 @@ qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmin
 
        for (traceindex = 0;traceindex < numtraces;traceindex++)
        {
+               VectorSet(start, lhrandom(eyemins[0], eyemaxs[0]), lhrandom(eyemins[1], eyemaxs[1]), lhrandom(eyemins[2], eyemaxs[2]));
                // check world occlusion
                if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight)
-                       if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex]))
+                       if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, start, endpoints[traceindex], boxmins, boxmaxs))
                                continue;
                for (touchindex = 0;touchindex < numtouchedicts;touchindex++)
                {
@@ -1581,9 +1594,22 @@ qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmin
                                Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1);
                                Matrix4x4_Invert_Simple(&imatrix, &matrix);
                                // see if the ray hits this entity
-                               Matrix4x4_Transform(&imatrix, eye, starttransformed);
+                               Matrix4x4_Transform(&imatrix, start, starttransformed);
                                Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed);
-                               if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed))
+                               Matrix4x4_Transform(&imatrix, boxmins, boxminstransformed);
+                               Matrix4x4_Transform(&imatrix, boxmaxs, boxmaxstransformed);
+                               // transform the AABB to local space
+                               VectorMAM(0.5f, boxminstransformed, 0.5f, boxmaxstransformed, localboxcenter);
+                               localboxextents[0] = fabs(boxmaxstransformed[0] - localboxcenter[0]);
+                               localboxextents[1] = fabs(boxmaxstransformed[1] - localboxcenter[1]);
+                               localboxextents[2] = fabs(boxmaxstransformed[2] - localboxcenter[2]);
+                               localboxmins[0] = localboxcenter[0] - localboxextents[0];
+                               localboxmins[1] = localboxcenter[1] - localboxextents[1];
+                               localboxmins[2] = localboxcenter[2] - localboxextents[2];
+                               localboxmaxs[0] = localboxcenter[0] + localboxextents[0];
+                               localboxmaxs[1] = localboxcenter[1] + localboxextents[1];
+                               localboxmaxs[2] = localboxcenter[2] + localboxextents[2];
+                               if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed, localboxmins, localboxmaxs))
                                {
                                        blocked++;
                                        break;
@@ -1703,7 +1729,7 @@ static void SV_MarkWriteEntityStateToClient(entity_state_t *s)
                                {
                                        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))
+                                               if(SV_CanSeeBox(samples, sv_cullentities_trace_eyejitter.value, 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] =
@@ -1786,7 +1812,7 @@ static void SV_AddCameraEyes(void)
                for(k = 0; k < sv.writeentitiestoclient_numeyes; ++k)
                if(eye_levels[k] <= MAX_EYE_RECURSION)
                {
-                       if(SV_CanSeeBox(sv_cullentities_trace_samples.integer, sv_cullentities_trace_enlarge.value, sv.writeentitiestoclient_eyes[k], mi, ma))
+                       if(SV_CanSeeBox(sv_cullentities_trace_samples.integer, sv_cullentities_trace_eyejitter.value, sv_cullentities_trace_enlarge.value, sv.writeentitiestoclient_eyes[k], mi, ma))
                        {
                                eye_levels[sv.writeentitiestoclient_numeyes] = eye_levels[k] + 1;
                                VectorCopy(camera_origins[j], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
@@ -1847,7 +1873,7 @@ static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, size
                vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value);
                vec3_t predeye;
                VectorMA(eye, predtime, PRVM_serveredictvector(camera, velocity), predeye);
-               if (SV_CanSeeBox(1, 0, eye, predeye, predeye))
+               if (SV_CanSeeBox(1, 0, 0, eye, predeye, predeye))
                {
                        VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
                        sv.writeentitiestoclient_numeyes++;