Index | FAQ | Search | Memberlist | Usergroups | Profile | Log in to check your private messages | Log in | Register

Chase_Update Update Goto page 1, 2 Next

| Inside3d Forums Forum Index -> Engine Programming

View previous topic :: View next topic
Author | Message
--- | ---
r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Fri Jan 23, 2009 10:11 am Post subject: 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

Code:
/* 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

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Fri Jan 23, 2009 4:51 pm Post subject: |

Looks like some high quality research Rook. I look forward to testing this code out vs. MH's code.

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Fri Jan 23, 2009 6:31 pm Post subject: |

CL_MovecliptoEntity is pretty much a TraceBox but without having to change too much within the engine. And for me less obscure changes less bugs Also the auto-transparency is a well required asset aswell.

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Tue Jan 27, 2009 6:43 am Post subject: |

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

Code:
num_tests = max(360,(VectorDistance(r_refdef.vieworg, chase_dest)));

Back to top |

mh Joined: 12 Jan 2008 Posts: 910 |

Posted: Tue Jan 27, 2009 8:34 pm Post subject: |

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.
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Wed Jan 28, 2009 5:12 pm Post subject: |

ya I spent about 5 hours pullin my hair out tweaking stuff, but i'm still not happy with what i've done. Coding a chasecamera in quakeC would work just as well since a PF_traceline uses an sv_move to determine the impact. > 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!

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Wed Feb 04, 2009 6:44 pm Post subject: |

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

Code:
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);
}

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Mon Apr 13, 2009 7:51 pm Post subject: |

Okay, final working code:

Code:
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.

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Mon May 11, 2009 1:02 am Post subject: |

r00k wrote:
Okay, final working code:

Rook, is this the latest and greatest?

Back to top |

reckless Joined: 24 Jan 2008 Posts: 390 Location: inside tha debugger |

Posted: Mon May 11, 2009 12:33 pm Post subject: |

i think it is ? the lerp code might make it a bit hard to implement in other engines hmm ill see if i can mix something up for standard glquake bit busy atm finding a new job

Back to top |

mh Joined: 12 Jan 2008 Posts: 910 |

Posted: Mon May 11, 2009 12:40 pm Post subject: |

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.
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines

Back to top |

reckless Joined: 24 Jan 2008 Posts: 390 Location: inside tha debugger |

Posted: Mon May 11, 2009 6:09 pm Post subject: |

neat and i ported parts of yours and rooks code for easy use in unmodified quake code.

Code:
/* 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:
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:
#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.

Back to top |

reckless Joined: 24 Jan 2008 Posts: 390 Location: inside tha debugger |

Posted: Mon May 11, 2009 6:13 pm Post subject: |

oh btw if your engine doesnt have alpha entity support (standard quake doesnt) you can safely skip this codebit

Code:
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

Back to top |

r00k Joined: 13 Nov 2004 Posts: 483 |

Posted: Mon May 11, 2009 7:37 pm Post subject: |

Code:
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?

Back to top |

mh Joined: 12 Jan 2008 Posts: 910 |

Posted: Mon May 11, 2009 8:49 pm Post subject: |

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.
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines

Back to top |

Display posts from previous:

| Inside3d Forums Forum Index -> Engine Programming | All times are GMT Goto page 1, 2 Next
Page 1 of 2

Jump to:

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2004 phpBB Group