Chase camera fixes
-
- Posts: 368
- Joined: Thu Jun 25, 2009 4:45 am
- Location: Michigan
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.
Here's what has evolved on my end.
MH's code will clip against brush models where this does not but its fast.
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);
}
-
- Posts: 368
- Joined: Thu Jun 25, 2009 4:45 am
- Location: Michigan
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?
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?
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.
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
We knew the words, we knew the score, we knew what we were fighting for
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.
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? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
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.
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
We knew the words, we knew the score, we knew what we were fighting for
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.
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.
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;
}
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Neato. 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
We knew the words, we knew the score, we knew what we were fighting for
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:
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.
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);
}
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
... to this:
This will make the camera go through empty areas, water and slime, but not lava.
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;
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;
Haha, spambot knows the red armor location in DM6!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 [...].
QuakeWiki
getButterfly - WordPress Support Services
Roo Holidays
Fear not the dark, but what the dark hides.
getButterfly - WordPress Support Services
Roo Holidays
Fear not the dark, but what the dark hides.
-
- Posts: 2126
- Joined: Sat Nov 25, 2006 1:49 pm