Chase camera fixes

Post tutorials on how to do certain tasks within game or engine code here.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Chase camera fixes

Post by mh »

Here we're going to fix the horrible effects when Quake's chase camera goes inside a solid leaf. You can test these on the start map by setting chase_active 1, going to the wall right behind you, and turning around. This fix will accomplish two things: (1) a chase camera can never go inside a leaf which has different contents to the viewleaf, and (2) fixing the resulting effect when a surface is viewed edge-on and partially clipped by the near plane.

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);
}
There are two major new sections here.

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! :D

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!).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

OK, as promised, here are the fixes. The first one is simple enough, just declare a global qboolean called chase_nodraw in chase.c, then add these lines after the second new for loop in Chase_Update:

Code: Select all

	chase_length = (r_refdef.vieworg[0] - chase_dest[0]) * (r_refdef.vieworg[0] - chase_dest[0]);
	chase_length += (r_refdef.vieworg[1] - chase_dest[1]) * (r_refdef.vieworg[1] - chase_dest[1]);
	chase_length += (r_refdef.vieworg[2] - chase_dest[2]) * (r_refdef.vieworg[2] - chase_dest[2]);

	if (chase_length < 150)
		chase_nodraw = true;
	else
		chase_nodraw = false;
(Declare chase_length as a float wherever suits you.)

You'll notice that I don't bother taking the square root; there's no need to here as we can compare with the squared value just as well.

Now go tp gl_rmain.c, find R_DrawEntitiesOnList, and extern the chase_nodraw variable somewhere, then add this right after case mod_alias:

Code: Select all

			if (currententity == &cl_entities[cl.viewentity] && chase_active.value)
				if (chase_nodraw) break;
And here's the fix for the second one:

Code: Select all

qboolean Chase_Check (vec3_t checkpoint, int viewcontents)
{
	int i;
	vec3_t mins;
	vec3_t maxs;

	// check against world model
	if ((Mod_PointInLeaf (checkpoint, cl.worldmodel))->contents != viewcontents)
		return false;

	// check visedicts - this happens *after* CL_ReadFromServer so the list will be valid
	for (i = 0; i < cl_numvisedicts; i++)
	{
		// retrieve the current entity
		entity_t *e = cl_visedicts[i];

		// don't check against self
		if (e == &cl_entities[cl.viewentity]) continue;

		// derive the bbox
		if (e->model->type == mod_brush && (e->angles[0] || e->angles[1] || e->angles[2]))
		{
			// copied from R_CullBox rotation test for inline bmodels, loop just unrolled
			mins[0] = e->origin[0] - e->model->radius;
			maxs[0] = e->origin[0] + e->model->radius;
			mins[1] = e->origin[1] - e->model->radius;
			maxs[1] = e->origin[1] + e->model->radius;
			mins[2] = e->origin[2] - e->model->radius;
			maxs[2] = e->origin[2] + e->model->radius;
		}
		else
		{
			VectorAdd (e->origin, e->model->mins, mins);
			VectorAdd (e->origin, e->model->maxs, maxs);
		}

		// check against bbox
		if (checkpoint[0] < mins[0]) continue;
		if (checkpoint[1] < mins[1]) continue;
		if (checkpoint[2] < mins[2]) continue;
		if (checkpoint[0] > maxs[0]) continue;
		if (checkpoint[1] > maxs[1]) continue;
		if (checkpoint[2] > maxs[2]) continue;

		// point inside
		return false;
	}

	// it's good now
	return true;
}
Then replace the Mod_PointInLeaf test in the first loop with:

Code: Select all

		if (!Chase_Check (chase_newdest, viewcontents))
And replace each Mod_PointInLeaf test in the second loop with:

Code: Select all

		if (Chase_Check (chase_dest, viewcontents)) nummatches++;
Job done.





Aside from another two remaining things; func_illusionary entities and dead bodies. These two are things that the client generally doesn't know about, so unless we hack the code fairly savagely we're kinda stuck. I think enough has been accomplished for now.


And if you're wondering why I just didn't use the various SV_ functions... three reasons. First is that there's no fun in not figuring this stuff out for yourself, second is that I tried them and they didn't quite work right, third is that they really have no business being called from client-side code anyway.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
MauveBib
Posts: 634
Joined: Thu Nov 04, 2004 1:22 am

Post by MauveBib »

inside3d has definately benefitted from the death of quakesrc in terms of engine tutorials :)
Apathy Now!
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Damn, MH! If this is what I think it is, a million thanks!
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I think it might be... :wink:


_____________


I fixed dead bodies by changing the alias model bboxes (on the client side only) to be per-frame rather than a single bbox for the entire model. :D

This info is already loaded in Mod_LoadAliasFrame and Mod_LoadAliasGroup, so it's just a matter of (1) pre-scaling and translating it (change from a trivertx_t to a vec3_t also - lightnormalindex isn't used here, per modelgen.h); (2) retrieving the aliashdr_t from Mod_ExtraData if it's a mod_alias; and (3) using paliashdr->frames[e->frame]->bboxmin and paliashdr->frames[e->frame]->bboxmax instead of e->model->mins and e->model->maxs.

If you do this, and you've implemented the aliasbboxmins/aliasbboxmaxs fix, be sure to remove the scaling and translation from that.

Also watch out for the bug in stock ID code where it has "frame->bboxmax.v = pdaliasframe->bboxmin.v;" - this should be "pdaliasframe->bboxmax.v", of course. :D
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

I haven't exhaustively tested this with all situations, but it works very well with the standard chase_back/chase_up/chase_right values in all the testing I've done.

Very well written!

It can get a little jerky at times if you change the standard defaults greatly, but no doubt I could interpolate the positioning to smooth it out. I want to learn more about controlling the chasecam anyway ;)
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Another option that's occurred to me is that you could change the value of NUM_TESTS; 64 is OK I suppose for the defaults, but like you said if you increase them you'll lose a lot of granularity. Using something based on the distance between r_refdef.vieworg and the original chase_dest would be much better, then perhaps also adding a cvar to allow the user to control how granular they want it to be (say, 0 to 1 with 0 being a reversion to the default behaviour, 1 using the full scale, and points between using a partial scale). Allowing user control over this kind of behaviour is definitely a good thing, IMO.

The main thing I'm unhappy with is that first new for loop; it's a "late-out" loop, so in the majority of non-extreme cases it will run for most or all of it's iterations. Increasing the number of tests for smoother camera positioning could end up hurting a bit.

Using a coarser scale to establish an initial good point and bad point, then a finer scale to position more correctly between the two seems to be the way to go. I don't have my code with me right now, but I think the loop for the finer scale could be written as "early-out".

Another option would be to impose PVS restrictions on the chasecam positions.

The second loop is fine; it looks a little frightening - 6 checks per iteration! - but it will "early-out" in the vast majority of cases - one iteration most of the time, no more than 2 or 3 otherwise.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

I've done a little bit more testing.

It appears that the main source of jerkiness I get is when the player is backed up against a wall with a diagonal ceiling a big chase_up and chase_back value. The start map beginning is one place with such a ceiling, another is the DM6 tunnel to the red armor.

If I move slightly, I get a lot of jerking with the view as if one frame it decides x,y is the right place to be and if I'm forward just a little, it decides x2, y2 is the best place to be.

I can remember walking through early DarkPlaces builds and trying them and LordHavoc had this same issue and then one release it was fixed. I'm more pointing out that it was funny that LH had the chase camera fixed except for diagonal ceilings as well :D

I used chase_up 200 and chase_back 200, it appears using a big value with only 1 of the cvars you still get a smooth transition. I didn't mess with chase_right as honestly I can't think of an obvious use for chase_right at the moment.

Hmmmm ...

Hey I can spot part of the original Quake code even without looking at the source:

Code: Select all

// 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; 
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Interesting...

With chase_back of 512 and chase_up of 256 it's incredibly jerky indeed. Changing NUM_TESTS to 1024 resolves that - very smooth. It seems obvious that distance-based is the way to go with this, so I'm gonna try that out and report back. I'll also try the initial coarse/then fine-tune method and see what happens. All code will be posted, of course, but be warned that it's my D3D engine and not GLQuake at this point; there won't be any rendering code (naturally) but there will be some minor C++isms.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:Interesting...

With chase_back of 512 and chase_up of 256 it's incredibly jerky indeed. Changing NUM_TESTS to 1024 resolves that - very smooth. It seems obvious that distance-based is the way to go with this, so I'm gonna try that out and report back. I'll also try the initial coarse/then fine-tune method and see what happens. All code will be posted, of course, but be warned that it's my D3D engine and not GLQuake at this point; there won't be any rendering code (naturally) but there will be some minor C++isms.
I just tried with up/back 512/256 like above using NUM_TESTS 1024. I still get a lot of jerkiness near the beginning of the start map.

Update: No I don't, it works fine with 1024. I was using VC 2008 unlike normal and didn't properly set the output path.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Ok, based on your above comments, it seems like you recommend checking half a unit per chase_x unit.

Here is a slightly revised version for your consideration that instead of using a fixed number of divides, just checks to see the total distance * 2. About the equivalent but should adjust to whatever the chase camera cvar values are.
#define NUM_TESTS 64 (delete)
#define CHASE_DEST_OFFSET 2.0f
qboolean chase_nodraw;
void Chase_Update (void)
{
int i;
float dist;
vec3_t forward, up, right;
vec3_t dest, stop;
int best;
int viewcontents;
float num_tests;
vec3_t delta;


// if can't see player, reset
AngleVectors (cl.viewangles, forward, right, up);

// calc exact destination
for (i = 0; i < 3; i++)
chase_dest = r_refdef.vieworg - forward * chase_back.value - right * chase_right.value;

chase_dest[2] = r_refdef.vieworg[2] + chase_up.value;

VectorSubtract(chase_dest, r_refdef.vieworg, delta);
num_tests = Length(delta)*2 + 1;


// 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 (!Chase_Check (chase_newdest, 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 (Chase_Check (chase_dest, viewcontents)) nummatches++;
chase_dest[0] += CHASE_DEST_OFFSET;

// move x to pos
chase_dest[0] += CHASE_DEST_OFFSET;
if (Chase_Check (chase_dest, viewcontents)) nummatches++;
chase_dest[0] -= CHASE_DEST_OFFSET;

// move y to neg
chase_dest[1] -= CHASE_DEST_OFFSET;
if (Chase_Check (chase_dest, viewcontents)) nummatches++;
chase_dest[1] += CHASE_DEST_OFFSET;

// move y to pos
chase_dest[1] += CHASE_DEST_OFFSET;
if (Chase_Check (chase_dest, viewcontents)) nummatches++;
chase_dest[1] -= CHASE_DEST_OFFSET;

// move z to neg
chase_dest[2] -= CHASE_DEST_OFFSET;
if (Chase_Check (chase_dest, viewcontents)) nummatches++;
chase_dest[2] += CHASE_DEST_OFFSET;

// move z to pos
chase_dest[2] += CHASE_DEST_OFFSET;
if (Chase_Check (chase_dest, 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);
}


This is at least the best tutorial/code since Rook condensed the anti-wallhack stuff down to a single function way early in the year.

Having a truly fixed chase cam solves so many problems. There are so many things that just aren't right if the chase cam pokes into walls.

It makes demos look stupid, especially if you turn them into a video and upload to YouTube. And a 3rd person view mod is going to act up and not look right.

Plus you have the uber-nice bounding box check and hide the model.

Tutorials like this make me feel really good about this game.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Yup, that's exactly the way I'm just after doing it myself, except instead of multiplying by 2 I used a cvar (value of 0 = revert to old behaviour). I also used an int for num_tests. :D

I tried using a coarser initial scale on the first loop, then fine-tuning between two points, but the coarseness of the scale meant that the old "intervening walls" came back, so we're stuck with using the full scale, but to be honest the performance loss is very low, so no great shakes there.

Here's what I ended up with:

Code: Select all

void Chase_Adjust (vec3_t chase_dest)
{
	// calculate distance between chasecam and original org to establish number of tests we need.
	// an int is good enough here.:)  add a cvar multiplier to this...
	int num_tests = sqrt ((r_refdef.vieworg[0] - chase_dest[0]) * (r_refdef.vieworg[0] - chase_dest[0]) +
					(r_refdef.vieworg[1] - chase_dest[1]) * (r_refdef.vieworg[1] - chase_dest[1]) +
					(r_refdef.vieworg[2] - chase_dest[2]) * (r_refdef.vieworg[2] - chase_dest[2])) * chase_scale.value;

	// take the contents of the view leaf
	int viewcontents = (Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel))->contents;
	int best;

	// move along path from r_refdef.vieworg to chase_dest
	for (best = 0; best < num_tests; best++)
	{
		vec3_t chase_newdest;

		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 (!Chase_Check (chase_newdest, viewcontents))
		{
			// go back to the previous best as this one is bad
			if (best > 1)
				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...
	int chase_vert[] = {0, 0, 1, 1, 2, 2};
	int dest_offset[] = {CHASE_DEST_OFFSET, -CHASE_DEST_OFFSET};

	// move along path from chase_dest to r_refdef.vieworg
	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;

		// run 6 tests: -x/+x/-y/+y/-z/+z
		for (int test = 0; test < 6; test++)
		{
			// adjust, test and put back.
			chase_dest[chase_vert[test]] -= dest_offset[test & 1];
			if (Chase_Check (chase_dest, viewcontents)) nummatches++;
			chase_dest[chase_vert[test]] += dest_offset[test & 1];
		}

		// test result, if all match we're done in here
		if (nummatches == 6) break;
	}

	float chase_length = (r_refdef.vieworg[0] - chase_dest[0]) * (r_refdef.vieworg[0] - chase_dest[0]);
	chase_length += (r_refdef.vieworg[1] - chase_dest[1]) * (r_refdef.vieworg[1] - chase_dest[1]);
	chase_length += (r_refdef.vieworg[2] - chase_dest[2]) * (r_refdef.vieworg[2] - chase_dest[2]);

	if (chase_length < 150)
	{
		chase_nodraw = true;
		chase_alpha = 255;
	}
	else
	{
		chase_nodraw = false;
		chase_alpha = (chase_length - 150);
		if (chase_alpha > 255) chase_alpha = 255;
	}
}


void Chase_Update (void)
{
	int		i;
	float	dist;
	vec3_t	forward, up, right;
	vec3_t	dest, stop;
	vec3_t	chase_dest;

	// 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;

	// don't allow really small or negative scaling values
	if (chase_scale.value < 0.01)
	{
		// revert to default Q1 chasecam behaviour
		// store out alpha and nodraw so that they're valid
		chase_alpha = 255;
		chase_nodraw = false;
	}
	else
	{
		// adjust the chasecam to prevent it going into solid
		Chase_Adjust (chase_dest);
	}

	// store alpha to entity
	cl_entities[cl.viewentity].alphaval = chase_alpha;

	// 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 last thing I want to fix is the sudden transitions from one point to another when there is some small intervening detail geometry, like the metal trim in start.bsp (the horizontal white brick just behind you is another good example); the camera should smoothly move aound the obstacle rather than suddenly snap from one side to the other.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Tomaz
Posts: 67
Joined: Fri Nov 05, 2004 8:21 pm

Post by Tomaz »

Out of curiosity, why isnt there just a traceline from player to the camera postion? And position the camera where there was an intersection ( or slightly in front of it ). Seems like alot smoother and way more optimized than running 1024 leaf tests. And the current code itself could benefit from quite alof of optimizations.

Just throwing out ideas, perhaps mh has thought of something that I have overlooked.
scar3crow
InsideQC Staff
Posts: 1054
Joined: Tue Jan 18, 2005 8:54 pm
Location: Alabama

Post by scar3crow »

why isnt there just a traceline from player to the camera postion? And position the camera where there was an intersection ( or slightly in front of it ).
You know, I always had assumed it did this, but that there were complications, and these complications were far beyond my comprehension.

Sometimes its really weird not being a coder in this community. =)
...and all around me was the chaos of battle and the reek of running blood.... and for the first time in my life I knew true happiness.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Simple reason - I tried it and it didn't quite work (and from the look of the DP chasecam code, the same can be said of LH).

Slightly less simple reason - it uses SV_RecursiveHullCheck, meaning that server-only data is potentially being exposed client-side. OK, the damage is already done with the existing TraceLine, but let's not make it any worse. :wink:
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Post Reply