Forum

Chase_Update Update

Discuss programming topics for the various GPL'd game engine sources.

Moderator: InsideQC Admins

Chase_Update Update

Postby r00k » Fri Jan 23, 2009 10:11 am

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

Postby Baker » Fri Jan 23, 2009 4:51 pm

Looks like some high quality research Rook. I look forward to testing this code out vs. MH's code.
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby r00k » Fri Jan 23, 2009 6:31 pm

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.
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Postby r00k » Tue Jan 27, 2009 6:43 am

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: Select all
num_tests = max(360,(VectorDistance(r_refdef.vieworg, chase_dest)));
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Postby mh » Tue Jan 27, 2009 8:34 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
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby r00k » Wed Jan 28, 2009 5:12 pm

:P 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! :P
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Postby r00k » Wed Feb 04, 2009 6:44 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
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

Postby r00k » Mon Apr 13, 2009 7:51 pm

Okay, final working code:

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

Postby Baker » Mon May 11, 2009 1:02 am

r00k wrote:Okay, final working code:


Rook, is this the latest and greatest?
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby revelator » Mon May 11, 2009 12:33 pm

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 :(
User avatar
revelator
 
Posts: 2567
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Postby mh » Mon May 11, 2009 12:40 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.
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
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby revelator » Mon May 11, 2009 6:09 pm

neat :)

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.
User avatar
revelator
 
Posts: 2567
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Postby revelator » Mon May 11, 2009 6:13 pm

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

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 ;)
User avatar
revelator
 
Posts: 2567
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Postby r00k » Mon May 11, 2009 7:37 pm

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

Postby mh » Mon May 11, 2009 8:49 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
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Next

Return to Engine Programming

Who is online

Users browsing this forum: No registered users and 1 guest