Inside3D!
     

Chase_Update Update
Goto page 1, 2  Next
 
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Engine Programming
View previous topic :: View next topic  
Author Message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Fri Jan 23, 2009 10:11 am    Post subject: Chase_Update Update Reply with quote

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 Wink 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 Sad 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 Twisted Evil


Last edited by r00k on Sat Jan 24, 2009 8:34 am; edited 2 times in total
Back to top
View user's profile Send private message
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri Jan 23, 2009 4:51 pm    Post subject: Reply with quote

Looks like some high quality research Rook. I look forward to testing this code out vs. MH's code.
Back to top
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Fri Jan 23, 2009 6:31 pm    Post subject: Reply with quote

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 Wink
Also the auto-transparency is a well required asset aswell.
Back to top
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Tue Jan 27, 2009 6:43 am    Post subject: Reply with quote

Well one major flaw is that it doesn't clip against entities Sad

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
View user's profile Send private message
mh



Joined: 12 Jan 2008
Posts: 910

PostPosted: Tue Jan 27, 2009 8:34 pm    Post subject: Reply with quote

r00k wrote:
Well one major flaw is that it doesn't clip against entities Sad

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
View user's profile Send private message Visit poster's website
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Wed Jan 28, 2009 5:12 pm    Post subject: Reply with quote

Razz ya I spent about 5 hours pullin my hair out tweaking stuff, but i'm still not happy with what i've done. Sad 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! Razz
Back to top
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Wed Feb 04, 2009 6:44 pm    Post subject: Reply with quote

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
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Mon Apr 13, 2009 7:51 pm    Post subject: Reply with quote

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
View user's profile Send private message
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Mon May 11, 2009 1:02 am    Post subject: Reply with quote

r00k wrote:
Okay, final working code:


Rook, is this the latest and greatest?
Back to top
View user's profile Send private message
reckless



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

PostPosted: Mon May 11, 2009 12:33 pm    Post subject: Reply with quote

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 Sad
Back to top
View user's profile Send private message
mh



Joined: 12 Jan 2008
Posts: 910

PostPosted: Mon May 11, 2009 12:40 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
reckless



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

PostPosted: Mon May 11, 2009 6:09 pm    Post subject: Reply with quote

neat Smile

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
View user's profile Send private message
reckless



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

PostPosted: Mon May 11, 2009 6:13 pm    Post subject: Reply with quote

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 Wink
Back to top
View user's profile Send private message
r00k



Joined: 13 Nov 2004
Posts: 483

PostPosted: Mon May 11, 2009 7:37 pm    Post subject: Reply with quote

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? Wink
Back to top
View user's profile Send private message
mh



Joined: 12 Jan 2008
Posts: 910

PostPosted: Mon May 11, 2009 8:49 pm    Post subject: Reply with quote

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? Wink

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
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    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