Chase_Update Update
Moderator: InsideQC Admins
16 posts
• Page 1 of 2 • 1, 2
Chase_Update Update
Okay so I was looking at MH's new DirectX quake port and noticed yet another engine that has a better chase_active camera fix than the standard ol Frik's way. Well that got me looking at the source, than at how other engines like DarkPlaces was handling it. Both approaches of each engine left me digging up some old math books from 20 years ago
But I actually have fallen back to using Quake's standard routines to accomplish a smoth chasecam that clips to walls perfectly !
I had to modify this because SV_ClipMoveToEntity would CRASH when connecting to a server online
But this will work for both singleplayer and multiplayer.
And here's the code, should even work in vanilla quake
Makes me consider some client-side culling for things like coronas etc
I had to modify this because SV_ClipMoveToEntity would CRASH when connecting to a server online
And here's the code, should even work in vanilla quake
- Code: Select all
/*
hull_t *CL_HullForEntity (entity_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset)
{
model_t *model;
vec3_t size, hullmins, hullmaxs;
hull_t *hull;
extern hull_t *SV_HullForBox (vec3_t mins, vec3_t maxs);
// decide which clipping hull to use, based on the size
if (ent->model->type == mod_brush)
{ // explicit hulls in the BSP model
model = ent->model;
VectorSubtract (maxs, mins, size);
if (size[0] < 3)
hull = &model->hulls[0];
else if (size[0] <= 32)
hull = &model->hulls[1];
else
hull = &model->hulls[2];
// calculate an offset value to center the origin
VectorSubtract (hull->clip_mins, mins, offset);
VectorAdd (offset, ent->origin, offset);
}
else
{ // create a temp hull from bounding box sizes
VectorSubtract (ent->model->mins, maxs, hullmins);
VectorSubtract (ent->model->maxs, mins, hullmaxs);
hull = SV_HullForBox (hullmins, hullmaxs);
VectorCopy (ent->origin, offset);
}
return hull;
}
/*
==================
Handles selection or creation of a clipping hull, and offseting (and eventually rotation) of the end points
R00k: modified from sv_* for clientside...
==================
*/
trace_t CL_ClipMoveToEntity (entity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
{
trace_t trace;
vec3_t offset, start_l, end_l;
hull_t *hull;
// fill in a default trace
memset (&trace, 0, sizeof(trace_t));
trace.fraction = 1;
trace.allsolid = true;
VectorCopy (end, trace.endpos);
// get the clipping hull
hull = CL_HullForEntity (ent, mins, maxs, offset);
// calculate an offset value to center the origin
VectorSubtract (hull->clip_mins, mins, offset);
VectorAdd (offset, ent->origin, offset);
VectorSubtract (start, offset, start_l);
VectorSubtract (end, offset, end_l);
// trace a line through the apropriate clipping hull
SV_RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace);
// fix trace up by the offset
if (trace.fraction != 1)
VectorAdd (trace.endpos, offset, trace.endpos);
// did we clip the move?
if (trace.fraction < 1 || trace.startsolid)
trace.clent = ent;
return trace;
}
void Chase_Update (void)//Modified From DarkPlaces...
{
trace_t trace;
vec_t camback, camup, distance, forward;
extern cvar_t chase_back;
extern cvar_t chase_up;
#define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2])
camback = bound(0, chase_back.value, 128);
camup = bound(-48, chase_up.value, 96);
AngleVectors(cl.lerpangles, forward, NULL, NULL);
distance = -camback - 8;// trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range)
chase_dest[0] = r_refdef.vieworg[0] + forward[0] * distance;
chase_dest[1] = r_refdef.vieworg[1] + forward[1] * distance;
chase_dest[2] = r_refdef.vieworg[2] + forward[2] * distance + camup;
trace = CL_ClipMoveToEntity (cl_entities, r_refdef.vieworg, vec3_origin, vec3_origin, chase_dest);
VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, r_refdef.vieworg);
}
Makes me consider some client-side culling for things like coronas etc
Last edited by r00k on Sat Jan 24, 2009 8:34 am, edited 2 times in total.
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
Well one major flaw is that it doesn't clip against entities
But on a side note, this is for MH:
I noticed this smooths out the updated origin when clipping in chase_active in DQ1.3
But on a side note, this is for MH:
I noticed this smooths out the updated origin when clipping in chase_active in DQ1.3
- Code: Select all
num_tests = max(360,(VectorDistance(r_refdef.vieworg, chase_dest)));
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
r00k wrote:Well one major flaw is that it doesn't clip against entities
That's one reason I went down the route I did - clipping against inline brush models in particular is very important (not sure if the other model types are really that big a deal).
I've deliberately left my num_tests at a user-configurable level of smoothness - you can always increase the chase_scale cvar to get it even smoother if you want. At least it gives you an option if it's running slowly.
One thing I tried early on that proved to be a bad idea was use a coarse scale to establish two points - a good point and a bad point - and then a finer scale to get the best point in between them. It didn't work because if the scale was coarse enough you could easily get a wall between the camera and the player model (e.g. one of the walls between the skill halls in start.bsp).
I'm also thinking that chase_active 1 could reasonably be considered "cheating" by a lot of people (me included) - it lets you see parts of the map that you wouldn't otherwise. An option here is to move it server-side and use the fat PVS to further limit where the camera can go.
The big thing I'd like to do is to smoothly move the camera around any partially intervening geometry, rather than abruptly switch from one side to another. This would eliminate maybe 99% of the perceived jerkiness with it.
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
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
> get the desired angle,
> trace from the target out to distance chase_back/chase_up
> set the position of impact
> get the direction from where the camera is now to the new pos
> set the distance from origin to new position
> adjust the velocity of the camera self.velocity = dir * dist;
phew!
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
Oh MH, I found something you missed that was causing erratic behavior. Like On the start map if you stand between the rl and the gate towards episode 3, the lava area and the lavaballs leap from underneath, no matter which way you face the camera freaks out,
this fixes that! :O
this fixes that! :O
- Code: Select all
else if ((e->model->type == mod_alias) && (e->model->numframes > 1))//R00k added check for no frames
{
aliashdr_t *hdr = (aliashdr_t *)Mod_Extradata (e->model);
// use per-frame bbox clip tests
VectorAdd (e->origin, hdr->frames[e->frame].bboxmin.v, mins);
VectorAdd (e->origin, hdr->frames[e->frame].bboxmax.v, maxs);
}
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
Okay, final working code:
Doesnt clip at all to entities or brushmodels, but works perfect against the bsp.
- Code: Select all
void Chase_Update (void)
{
int i;
float dist, scale;
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);
}
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. hmm, efficient?
for (scale = 0.9; scale > 0; scale -= 0.0056)//scale 180 times until it fits
{
//Round off the traceline...
LerpVector (r_refdef.vieworg, chase_dest, scale, chase_dest);
if ((Mod_PointInLeaf (r_refdef.vieworg, cl.worldmodel))->contents == (Mod_PointInLeaf (chase_dest, cl.worldmodel))->contents)
break;
}
VectorCopy (chase_dest, r_refdef.vieworg);
}
Doesnt clip at all to entities or brushmodels, but works perfect against the bsp.
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
I've just ported the trace routines from the light executable to the engine, so that seems to be another potentially viable option. Requires quite a bit more work than just copying and pasting a function though, but in tests so far (using them for server-side entity occlusion) they seem considerably more efficient than the engine TraceLine/etc (at the expense of a bit more memory for storage).
I remain of the opinion that clipping the camera against inline bmodels is an essential feature of any implementation.
I remain of the opinion that clipping the camera against inline bmodels is an essential feature of any implementation.
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
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
neat
and i ported parts of yours and rooks code for easy use in unmodified quake code.
it does need some external functions not in normal quake so ill post them here.
above is same as Vectorlength2. shove it in mathlib.c and make a pointer to the function in mathlib.h.
and a few macros
the above macros can be shuffled in common.h
tested and works pretty ok.
and i ported parts of yours and rooks code for easy use in unmodified quake code.
- Code: Select all
/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// chase.c -- chase camera code
#include "quakedef.h"
#define CHASE_DEST_OFFSET 4.0f
cvar_t chase_back = {"chase_back", "100"};
cvar_t chase_up = {"chase_up", "16"};
cvar_t chase_right = {"chase_right", "0"};
cvar_t chase_active = {"chase_active", "0"};
cvar_t chase_scale = {"chase_scale", "1"};
vec3_t chase_dest;
void Chase_Init (void)
{
Cvar_RegisterVariable (&chase_back);
Cvar_RegisterVariable (&chase_up);
Cvar_RegisterVariable (&chase_right);
Cvar_RegisterVariable (&chase_active);
Cvar_RegisterVariable (&chase_scale);
}
void Chase_Clip (vec3_t start, vec3_t end, vec3_t impact)
{
trace_t trace;
memset (&trace, 0, sizeof(trace));
SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace);
VectorCopy (trace.endpos, impact);
}
qboolean Chase_Check (vec3_t checkpoint, int viewcontents)
{
int i;
vec3_t mins;
vec3_t maxs;
// check against world model - going into different contents
if ((Mod_PointInLeaf (checkpoint, cl.worldmodel))->contents != viewcontents) return false;
// check visedicts - this happens *after* CL_ReadFromServer so the list will be valid
// (may not include static entities) (but only checks brush models)
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;
// let's not clip against these types
if (e->model->name[0] != '*') continue;
// because you never know what a mod might try...
if (e->model->type != mod_brush) continue;
// derive the bbox
if (e->angles[0] || e->angles[1] || e->angles[2])
{
// copied from R_CullBox rotation code 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;
}
void Chase_Adjust (vec3_t chase_dest)
{
// certain surfaces can be viewed at an oblique enough angle that they are partially clipped
// by znear, so now we fix that too...
float chase_length;
int chase_vert[] = {0, 0, 1, 1, 2, 2};
int dest_offset[] = {CHASE_DEST_OFFSET, -CHASE_DEST_OFFSET};
// 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;
}
}
// move along path from chase_dest to r_refdef.vieworg
// this one will early-out the vast majority of cases
for (/**/; best >= 0; best--)
{
// number of matches
int test, 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 (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;
}
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]);
}
void Chase_Update (void)
{
int i;
float dist, alphadist;
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
chase_dest[0] = r_refdef.vieworg[0] - forward[0] * chase_back.value - right[0] * chase_right.value;
chase_dest[1] = r_refdef.vieworg[1] - forward[1] * chase_back.value - right[1] * 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)
{
aliashdr_t *tmphdr;
// store alpha
tmphdr = (aliashdr_t *)cl.worldmodel->extradata;
alphadist = Distance(r_refdef.vieworg, chase_dest);
tmphdr->translevel = bound(0.1, (alphadist / chase_back.value), 1);
}
else
{
// adjust the chasecam to prevent it going into solid
Chase_Adjust (chase_dest);
}
// find the spot the player is looking at
VectorMA (r_refdef.vieworg, 4096, forward, dest);
Chase_Clip (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;
}
r_refdef.viewangles[PITCH] = -atan (stop[2] / dist) / M_PI * 180;
// move towards destination
VectorCopy (chase_dest, r_refdef.vieworg);
}
it does need some external functions not in normal quake so ill post them here.
- Code: Select all
float Distance(const vec3_t v, const vec3_t v2)
{
int i;
float length, d;
length = 0;
for (i=0 ; i< 3 ; i++)
{
d = v[i] - v2[i];
length += d*d;
}
length = sqrt ((double)length); // FIXME
return length;
}
above is same as Vectorlength2. shove it in mathlib.c and make a pointer to the function in mathlib.h.
and a few macros
- Code: Select all
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
#define bound(a, b, c) ((a) >= (c) ? (a) : (b) < (a) ? (a) : (b) > (c) ? (c) : (b))
the above macros can be shuffled in common.h
tested and works pretty ok.
-

revelator - Posts: 2567
- Joined: Thu Jan 24, 2008 12:04 pm
- Location: inside tha debugger
oh btw if your engine doesnt have alpha entity support (standard quake doesnt) you can safely skip this codebit
just comment it out it works fine without it
- Code: Select all
aliashdr_t *tmphdr;
// store alpha
tmphdr = (aliashdr_t *)cl.worldmodel->extradata;
alphadist = Distance(r_refdef.vieworg, chase_dest);
tmphdr->translevel = bound(0.1, (alphadist / chase_back.value), 1);
just comment it out it works fine without it
-

revelator - Posts: 2567
- Joined: Thu Jan 24, 2008 12:04 pm
- Location: inside tha debugger
- Code: Select all
void LerpVector (const vec3_t from, const vec3_t to, float frac, vec3_t out)
{
out[0] = from[0] + frac * (to[0] - from[0]);
out[1] = from[1] + frac * (to[1] - from[1]);
out[2] = from[2] + frac * (to[2] - from[2]);
}
Nice! I agree with MH that for completeness it should clip against inline bmodels etc. though for instance when playing singleplayer with monsters around me and grenades coming at me etc, at times my camera pov jumps around, which was frustrating. Maybe a delay or just a interval transition from here --> to there when/if the camera is to be clipped. So for instance, if im facing a monster and an entity passes 10 units behind me going in a perpendicular path it wont jerk my camera for that split second the entity is in range but instead exponentially push and slide back ?? Or is this what the "scale" is supposed to do?
- r00k
- Posts: 1110
- Joined: Sat Nov 13, 2004 10:39 pm
r00k wrote:Nice! I agree with MH that for completeness it should clip against inline bmodels etc. though for instance when playing singleplayer with monsters around me and grenades coming at me etc, at times my camera pov jumps around, which was frustrating. Maybe a delay or just a interval transition from here --> to there when/if the camera is to be clipped. So for instance, if im facing a monster and an entity passes 10 units behind me going in a perpendicular path it wont jerk my camera for that split second the entity is in range but instead exponentially push and slide back ?? Or is this what the "scale" is supposed to do?
I think inline bmodels only is good enough, no need to clip against any other model type.
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
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
16 posts
• Page 1 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 1 guest
