All changes are to the Chase_Update function, so I'm just going to give it in full and discuss how it works afterwards.
Code: Select all
#define NUM_TESTS 64
#define CHASE_DEST_OFFSET 2.0f
void Chase_Update (void)
{
int i;
float dist;
vec3_t forward, up, right;
vec3_t dest, stop;
int best;
int viewcontents;
// if can't see player, reset
AngleVectors (cl.viewangles, forward, right, up);
// calc exact destination
for (i = 0; i < 3; i++)
chase_dest[i] = r_refdef.vieworg[i] - forward[i] * chase_back.value - right[i] * chase_right.value;
chase_dest[2] = r_refdef.vieworg[2] + chase_up.value;
// take the contents of the view leaf
viewcontents = (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel))->contents;
for (best = 0; best < NUM_TESTS; best++)
{
float chase_newdest[3];
chase_newdest[0] = r_refdef.vieworg[0] + (chase_dest[0] - r_refdef.vieworg[0]) * best / NUM_TESTS;
chase_newdest[1] = r_refdef.vieworg[1] + (chase_dest[1] - r_refdef.vieworg[1]) * best / NUM_TESTS;
chase_newdest[2] = r_refdef.vieworg[2] + (chase_dest[2] - r_refdef.vieworg[2]) * best / NUM_TESTS;
// check for a leaf hit with different contents
if ((Mod_PointInLeaf (chase_newdest, cl.worldmodel))->contents != viewcontents)
{
// go back to the previous best as this one is bad
// unless the first one was also bad, (viewleaf contents != viewleaf contents!!!)
if (best > 0)
best--;
else best = NUM_TESTS;
break;
}
}
// certain surfaces can be viewed at an oblique enough angle that they are partially clipped
// by znear, so now we fix that too...
for (; best >= 0; best--)
{
// number of matches
int nummatches = 0;
// adjust
chase_dest[0] = r_refdef.vieworg[0] + (chase_dest[0] - r_refdef.vieworg[0]) * best / NUM_TESTS;
chase_dest[1] = r_refdef.vieworg[1] + (chase_dest[1] - r_refdef.vieworg[1]) * best / NUM_TESTS;
chase_dest[2] = r_refdef.vieworg[2] + (chase_dest[2] - r_refdef.vieworg[2]) * best / NUM_TESTS;
// move x to neg
chase_dest[0] -= CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[0] += CHASE_DEST_OFFSET;
// move x to pos
chase_dest[0] += CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[0] -= CHASE_DEST_OFFSET;
// move y to neg
chase_dest[1] -= CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[1] += CHASE_DEST_OFFSET;
// move y to pos
chase_dest[1] += CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[1] -= CHASE_DEST_OFFSET;
// move z to neg
chase_dest[2] -= CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[2] += CHASE_DEST_OFFSET;
// move z to pos
chase_dest[2] += CHASE_DEST_OFFSET;
if ((Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents == viewcontents) nummatches++;
chase_dest[2] -= CHASE_DEST_OFFSET;
// all tests passed so we're good!
if (nummatches == 6) break;
}
// find the spot the player is looking at
VectorMA (r_refdef.vieworg, 4096, forward, dest);
TraceLine (r_refdef.vieworg, dest, stop);
// calculate pitch to look at the same spot from camera
VectorSubtract (stop, r_refdef.vieworg, stop);
dist = DotProduct (stop, forward);
if (dist < 1)
dist = 1;
r_refdef.viewangles[PITCH] = -atan(stop[2] / dist) / M_PI * 180;
// move towards destination
VectorCopy (chase_dest, r_refdef.vieworg);
}
The first one starts at the normal view origin and gradually edges towards the chase camera origin until it hits a leaf that has different contents to the view leaf. The previous good test was therefore the correct spot where the chase camera position was last inside a valid leaf. I picked 64 tests above, which might seem like a lot, but Mod_PointInLeaf is a fast enough function, and this is peanuts compared to some of what goes on at the server side.
Reduce it to 32 or even 16 if you like, but be warned that camera positioning can get very jerky if you do.
Running with this alone, (put a break statement after the assignation of chase_dest inside the next for loop if you want to test it) will work fine until you start viewing surfaces edge-on or almost-edge on. If you test it you'll see what I mean; I'm certain that there's some correct terminology but I don't know it!
The second section fixes that by building a small bounding box around the camera position and testing up/down/left/right/front/back, edging forward towards the original origin again, until all 6 pass.
Compile and run, but there are two remaining bugs. The first one is that when the camera gets near enough to the original origin, the player model will start to be clipped away. This can be fixed by just not drawing the model at this point; for a nicer effect you could gradually fade it out.
The second one concerns our old friends brush models. I'm not testing against brush models here, so the camera can sometimes go behind an intervening brush model (such as a door). The obvious solution is to just split off the contents tests into a separate function and add a test against the bboxes of all visedicts.
For the first one, assuming a NUM_TESTS of 64, if the camera gets to less than point 18 you probably don't want to draw.
I'll publish code for the second one later (I haven't written it yet!).