Chase camera fixes

Post tutorials on how to do certain tasks within game or engine code here.
MDave
Posts: 76
Joined: Mon Dec 17, 2007 7:08 pm

Post by MDave »

It could be worth checking out how quake 3 manages to fix this old age problem :) Seems to be smaller code, although I haven't checked if it depends on a lot of other systems built in quake 3.
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

Thank you for the tutorial, it has been useful.


There is a chase cam bug I can't figure out how to fix.

Enable the chase cam and backup all the way to the wall and your chase cam goes inside your player.How would one fix that so it doesn't go inside the player?
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Team Xlink wrote:Thank you for the tutorial, it has been useful.


There is a chase cam bug I can't figure out how to fix.

Enable the chase cam and backup all the way to the wall and your chase cam goes inside your player.How would one fix that so it doesn't go inside the player?

There are going to be instances when the camera has to go inside the player because it doesn't go inside the wall and there isn't anywhere else for the camera to be.

For example, what if you are in an elevator only exactly the right size to contain the player?

That isn't a bug. It is up to you to decide what to do if the camera goes into the player model, but at least it isn't going outside the wall.

In DirectQ and Qrack, they make the player transparent (alpha level less than 1). In DarkPlaces, I don't remember ... I think it just lets the camera go inside the player. Option #3 is, of course, to not render the player if the camera goes inside the player.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Here's what has evolved on my end.

Code: Select all

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

	// if can't see player, reset
	AngleVectors (cl.lerpangles, 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;

	// 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 = max(1, DotProduct(stop, forward));

	if ( dist < 1 ) 
	{
		dist = 1;	// should never happen
	}
  
	r_refdef.viewangles[PITCH] = -180 / M_PI * atan2( stop[2], dist );

	TraceLine (r_refdef.vieworg, chase_dest, stop);

	if (stop[0] != 0 || stop[1] != 0 || stop[2] != 0)
	{
		VectorCopy (stop, chase_dest);//update the camera destination to where we hit the wall

		alphadist = VecLength2(r_refdef.vieworg, chase_dest);		
		alpha = bound(0.1,(alphadist / chase_back.value), 1);
		
		cl_entities[cl.viewentity].transparency = alpha;

		//R00k, this prevents the camera from poking into the wall by rounding off the traceline...
		LerpVector (r_refdef.vieworg, chase_dest, 0.9f, chase_dest);
	}

	VectorCopy (chase_dest, r_refdef.vieworg);	
}
MH's code will clip against brush models where this does not but its fast.
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

Well, I find that it doesn't create a noticeable slowdown for me.

This is a great tutorial.

Also, Tomaz was in IRC last night and there was a conversation and he mentioned the idea of a chasecam falling behind the player and then catching up to the player.

You could always do this via qc by using a stuffcmd for the chase_back commands, but is it possible via engine?
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I actually think it's quite important to clip against brush models, and I wouldn't have put this tutorial out if I hadn't got that part working.

OK, so ammo boxes and the like can be ignored, but doors at least need to be included. The way I see it is that not clipping against doors could let you see some of what's behind a door, and besides - it looks unnatural.

Also, the thing with the view going inside the player - that's not a bug, it's a feature.

Joking aside, if we're clipping against walls then the view has to go somewhere if you back up against a wall. The choices are inside the wall, inside the player or somewhere inbetween.

Somewhere inbetween is right out; the player model is not defined by the actual verts and tris in the model, it's defined by a bounding box, which would virtually be in contact with the wall in such a situation. There is no inbetween.

Inside the wall is out because you would need to know the dimensions of the wall and how it related to everthing else in the world otherwise you risk visual artefacts (and it's the whole point of this code to remove these, so that would defeat the purpose).

So inside the player it is. Turning the player model transparent when this happens is a compromise, because in cases where the view is so close behind the player (and this applies to even using the view inside the wall) the player model will actually occlude most of the view. This way you still get to see somthing.

Another option might be to do what Tomb Raider does and swing the view around to in front of the player. To my mind that kind of thing goes well beyond the scope of what is supposed to be a first-person shooter. It's an exercise for you to do yourself if you want it.

So yes, it was a deliberate choice, and yes, I was completely aware of what the options were and what was good and bad about each of them when I wrote that code.
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 morphed MH's code in a CL_Visible_To_Client function.

A faked SV_ClipMove_To_Entity might be better (it appears this is what at least FuhQuake and other modern QW engines do) ... which apparently QW engines have due to the need for prediction.

But after looking at Rook's and my own personal testing, what is the failure of TraceLine?

Now first, I see with this comments that it doesn't test against brush models and other entities.

Is it otherwise reliable? Do past engines get the camera stuck in the wall because of TraceLine being bad or because they don't update the camera position based on the result or is it a combination of both?

Just looking to understand this more.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

My main problem with TraceLine is that it doesn't work against brush models. The fact that you could back up to a door and have the camera go through the door, showing you the other side of the door, was for me a deal-breaker from the outset.

A secondary problem was that it made calls to server-side code. Now, in stock NetQuake this isn't actually a problem as the client and the server share models, so it's the very same data to begin with. But from the perspective of keeping the client and the server as separate as possible it's a no-go zone. However there's no reason why one couldn't duplicate the server-side functions on the client and make a CL_TraceLine, CL_RecursiveHullCheck and CL_HullPointContents to handle that.

But it still doesn't work against brush models. And if you're going to do something like this you need to do it right, otherwise not at all. Think of a map likep ne_tower where maybe 50% of what you see on-screen is brush models and you can imagine how bad things can get. Duplicating the other movement functions on the client is not going to happen, because then you need to handle sv.edicts and ent.v structs in a manner that's not going to break. So your options are to create a mess or find a different approach.

Thinking a little further about the whole chase cam matter, it seems to me that an alternative aproach might be to make use of PVS data for setting an initial bounds. I'd need to think more about that and write some experimental code before I could say that it's a viable alternative though.
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 was looking through R00k's version and only saw a Traceline and I guess I was under the impression that the traceline was part of the reason that the chasecam poked into walls.

But apparently not.

Still the brush model thing makes a big difference.

Either way, I'll post the raw function I made of your chase cam fix called CL_Visible_To_Client.

Some of the things I'd like to experiment around with in are highly dependent on knowing whether or not a given entity can be seen.

Code: Select all

qboolean Still_Visible (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;

	  // don't check against players
	  if (e->modelindex == cl_modelindex[mi_player])
		  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;
}

qboolean CL_Visible_To_Client (vec3_t viewer, vec3_t seen)
{
   // 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 ((viewer[0] - seen[0]) * (viewer[0] - seen[0]) +
               (viewer[1] - seen[1]) * (viewer[1] - seen[1]) +
               (viewer[2] - seen[2]) * (viewer[2] - seen[2])));

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

   // move along path from viewer to seen
   for (best = 0; best < num_tests; best++)
   {
      vec3_t step_to_entity;

      step_to_entity[0] = viewer[0] + (seen[0] - viewer[0]) * best / num_tests;
      step_to_entity[1] = viewer[1] + (seen[1] - viewer[1]) * best / num_tests;
      step_to_entity[2] = viewer[2] + (seen[2] - viewer[2]) * best / num_tests;

      // check for a leaf hit with different contents
      if (!Still_Visible (step_to_entity, viewcontents))
      {
			return false;
      }
   }

   return true;
} 
Operating on the idea that traceline is faster, someone can have the traceline "pre-screen" a quick test and follow that up with a more detailed test. Most entities would fail the traceline visibility test, leaving far fewer entities to check with the more precise visibility check.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Neato. :D It would be really great to get a solid client-side entity visibility test that runs in software. I'm currently doing stuff with occlusion queries; they're fine once you figure out how to avoid stalling the pipeline with them, but there's a frame of latency in the results (which is mostly OK but sometimes gives you blinky), they require hardware support (which is pretty much ubiquitous but I'd still prefer to not have the requirement), and there's an awful lot of messing about with the way Quake reuses entity slots (which is most definitely NOT OK).
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
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Post by mankrip »

Really good. I just implemented the latest version of this fix in Makaqu, and I'm thinking of using some of its code to dinamically position the crosshair on the screen.

The only other engine I knew that had the chasecam fully fixed is ToChriS 1.67, but it's code seems to require a lot of reenginering all over the renderer.

And dunno if this is interesting, but here's the hackish workaround that I had implemented in the latest releases of Makaqu:

Code: Select all

	if (sv.active)
	{
		// hack to prevent the camera from seeing through solid objects.
		// not all solid objects are detected, and it crashes on demo play (hence the "if (sv.active)").
		// start, mins, maxs, end, nomonsters, ignore_ent
		trace1 = SV_Move (chase_origin1, vec3_origin, vec3_origin, chase_dest1, false, G_EDICT(cl.viewentity));
		trace2 = SV_Move (chase_origin2, vec3_origin, vec3_origin, chase_dest2, false, G_EDICT(cl.viewentity));
		trace3 = SV_Move (chase_origin3, vec3_origin, vec3_origin, chase_dest3, false, G_EDICT(cl.viewentity));
		trace4 = SV_Move (chase_origin4, vec3_origin, vec3_origin, chase_dest4, false, G_EDICT(cl.viewentity));
	}
	else
	{
		memset (&trace1, 0, sizeof(trace1));
		memset (&trace2, 0, sizeof(trace2));
		memset (&trace3, 0, sizeof(trace3));
		memset (&trace4, 0, sizeof(trace4));
		SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, chase_origin1, chase_dest1, &trace1);
		SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, chase_origin2, chase_dest2, &trace2);
		SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, chase_origin3, chase_dest3, &trace3);
		SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, chase_origin4, chase_dest4, &trace4);
	}
About the "not all solid objects" comment: In the start map, the floor brush entity that covers the start of the ladder that leads to the portal to Shub-Niggurath's pit is an example of a solid brush entity that's not detected by this workaround.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Post by mankrip »

I just noticed that the chase camera doesn't go through liquid surfaces, and that if you use noclip to walk inside the solid areas of the map, the chase camera refuses to go into empty areas.

To fix these, go into Chase_Check and change this

Code: Select all

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

Code: Select all

	// check against world model
	#define CHASE_CONTENTS (0x1<<(CONTENTS_EMPTY*-1)|0x1<<(CONTENTS_WATER*-1)|0x1<<(CONTENTS_SLIME*-1))
	if (!(0x1 << ((Mod_PointInLeaf (checkpoint, cl.worldmodel))->contents * -1) & (0x1<<(viewcontents*-1)|CHASE_CONTENTS)))
		return false;
This will make the camera go through empty areas, water and slime, but not lava.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
Chip
Posts: 575
Joined: Wed Jan 21, 2009 9:12 am
Location: Dublin, Ireland
Contact:

Post by Chip »

seopackages wrote:[...] the other is red tunnel DM6 armor. If I move a bit, I have a lot of contractions with a view of the property [...].
Haha, spambot knows the red armor location in DM6!
QuakeWiki
getButterfly - WordPress Support Services
Roo Holidays

Fear not the dark, but what the dark hides.
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Post by frag.machine »

Let's pit him against frikbot!
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
Post Reply