Forum

(WinQuake) Software Quake Transform Interpolation

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

(WinQuake) Software Quake Transform Interpolation

Postby Baker » Tue Jul 06, 2010 10:55 pm

MH made some awesome software renderer (WinQuake) animation interpolation code for models. It is pretty easy to add:

viewtopic.php?t=2387

But after adding, you'll twice as much want movement interpolation to complete the circle.

How good is MH's software renderer interpolation? It is very, very fast ...

timedemo demo1

No animation interpolation @ 320x200 fullscreen my machine: 285 FPS
With animation interpolation @ 320x200 fullscreen my machine: 281 FPS


Anyway, software animation interpolation smooths the model animations, but the movement interpolation smooths the movement.

Movement Interpolation - Tutorial Begin ...

And this is pretty much verbatim derived from MH's DirectQ engine.

1. cl_main.c

Go to CL_RelinkEntities ... I have the necessary additions marked with #ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION

Code: Select all
// if the object wasn't included in the last packet, remove it
      if (ent->msgtime != cl.mtime[0])
      {
#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
         CL_ClearInterpolation (ent);
#endif
         ent->model = NULL;
         continue;
      }


And ... still in CL_RelinkEntities

Code: Select all
         f = frac;
         for (j=0 ; j<3 ; j++)
         {
            delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j];
            if (delta[j] > 100 || delta[j] < -100)
               f = 1;      // assume a teleportation, not a motion
         }

#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
         if (f >= 1) CL_ClearInterpolation (ent);
#endif

      // interpolate the origin and angles
         for (j=0 ; j<3 ; j++)
         {
            ent->origin[j] = ent->msg_origins[1][j] + f*delta[j];

            d = ent->msg_angles[0][j] - ent->msg_angles[1][j];
            if (d > 180)
               d -= 360;
            else if (d < -180)
               d += 360;
            ent->angles[j] = ent->msg_angles[1][j] + f*d;
         }

      }

//      if (!(model = cl.model_precache[ent->modelindex]))
//         Host_Error ("CL_RelinkEntities: bad modelindex");

#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
      CL_EntityInterpolateOrigins (ent);
      CL_EntityInterpolateAngles (ent);
#endif

// rotate binary objects locally
      if (ent->model->flags & EF_ROTATE)


2. Go to render.h and alter the entity structure ... note some of these fields should have already been added to support the animation interpolation.

Code: Select all
   int                  trivial_accept;
   struct mnode_s         *topnode;      // for bmodels, first world node that splits bmodel, or NULL if not split

   int      modelindex;

#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
      float           frame_start_time;
      int            lastpose, currpose;
      int            lastframe;

      // fenix@io.com: model transform interpolation
        float           translate_start_time;
      vec3_t          lastorigin, currorigin;

        float         rotate_start_time;
        vec3_t         lastangles, currangles;

#endif

} entity_t;


3. cl_parse.c above CL_ParseUpdate add this

Code: Select all
#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
void CL_EntityInterpolateOrigins (entity_t *ent)
{
   qboolean no_interpolate=false;


      float timepassed = cl.time - ent->translate_start_time;
      float blend = 0;
      vec3_t delta = {0, 0, 0};

      if (ent->translate_start_time == 0 || timepassed > 1)
      {
         ent->translate_start_time = cl.time;

         VectorCopy (ent->origin, ent->lastorigin);
         VectorCopy (ent->origin, ent->currorigin);
      }

      if (!VectorCompare (ent->origin, ent->currorigin))
      {
         ent->translate_start_time = cl.time;

         VectorCopy (ent->currorigin, ent->lastorigin);
         VectorCopy (ent->origin,  ent->currorigin);

         blend = 0;
      }
      else
      {
         blend = timepassed / 0.1;

         if (cl.paused || blend > 1) blend = 1;
      }

      VectorSubtract (ent->currorigin, ent->lastorigin, delta);

      // use cubic interpolation
      {
         float lastlerp = 2 * (blend * blend * blend) - 3 * (blend * blend) + 1;
         float currlerp = 3 * (blend * blend) - 2 * (blend * blend * blend);

         ent->origin[0] = ent->lastorigin[0] * lastlerp + ent->currorigin[0] * currlerp;
         ent->origin[1] = ent->lastorigin[1] * lastlerp + ent->currorigin[1] * currlerp;
         ent->origin[2] = ent->lastorigin[2] * lastlerp + ent->currorigin[2] * currlerp;
      }


   

}


void CL_EntityInterpolateAngles (entity_t *ent)
{

      float timepassed = cl.time - ent->rotate_start_time;
      float blend = 0;
      vec3_t delta = {0, 0, 0};

      if (ent->rotate_start_time == 0 || timepassed > 1)
      {
         ent->rotate_start_time = cl.time;

         VectorCopy (ent->angles, ent->lastangles);
         VectorCopy (ent->angles, ent->currangles);
      }

      if (!VectorCompare (ent->angles, ent->currangles))
      {
         ent->rotate_start_time = cl.time;

         VectorCopy (ent->currangles, ent->lastangles);
         VectorCopy (ent->angles,  ent->currangles);

         blend = 0;
      }
      else
      {
         blend = timepassed / 0.1;

         if (cl.paused || blend > 1) blend = 1;
      }

      VectorSubtract (ent->currangles, ent->lastangles, delta);

      // always interpolate along the shortest path
      if (delta[0] > 180) delta[0] -= 360; else if (delta[0] < -180) delta[0] += 360;
      if (delta[1] > 180) delta[1] -= 360; else if (delta[1] < -180) delta[1] += 360;
      if (delta[2] > 180) delta[2] -= 360; else if (delta[2] < -180) delta[2] += 360;

      // get currangles on the shortest path
      VectorAdd (ent->lastangles, delta, delta);

      // use cubic interpolation
      {
         float lastlerp = 2 * (blend * blend * blend) - 3 * (blend * blend) + 1;
         float currlerp = 3 * (blend * blend) - 2 * (blend * blend * blend);

         ent->angles[0] = ent->lastangles[0] * lastlerp + delta[0] * currlerp;
         ent->angles[1] = ent->lastangles[1] * lastlerp + delta[1] * currlerp;
         ent->angles[2] = ent->lastangles[2] * lastlerp + delta[2] * currlerp;
      }
   
}


void CL_ClearInterpolation (entity_t *ent)
{
   ent->frame_start_time = 0;
   ent->lastpose = ent->currpose;

   ent->translate_start_time = 0;
   ent->lastorigin[0] = ent->lastorigin[1] = ent->lastorigin[2] = 0;
   ent->currorigin[0] = ent->currorigin[1] = ent->currorigin[2] = 0;

   ent->rotate_start_time = 0;
   ent->lastangles[0] = ent->lastangles[1] = ent->lastangles[2] = 0;
   ent->currangles[0] = ent->currangles[1] = ent->currangles[2] = 0;
}
#endif


4. Still in cl_parse.c goto CL_ParseUpdate

Code: Select all
   // automatic animation (torches, etc) can be either all together or randomized
      if (model)
            ent->syncbase = (model->synctype == ST_RAND) ? (float)(rand()&0x7fff) / 0x7fff : 0.0;
      else
         forcelink = true;   // hack to make null model players work

#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
      // if the model has changed we must also reset the interpolation data
      // lastpose and currpose are critical as they might be pointing to invalid frames in the new model!!!
      CL_ClearInterpolation (ent);

      // reset frame and skin too...!
      if (!(bits & U_FRAME)) ent->frame = 0;
      if (!(bits & U_SKIN)) ent->skinnum = 0;
#endif


5. For simplicity's sake, open quakedef.h and add #define SUPPORTS_SOFTWARE_ANIM_INTERPOLATION somewhere like the bottom if you want.

Code: Select all
#ifndef GLQUAKE
#define SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
#endif


Combined with the software renderer model animatiion tutorial, you get pretty much the equivalent of OpenGL version of the QER tutorial ( http://www.quakewiki.net/archives/qer/tutorial4.html )

Related:

Software Quake MDL Frame Interpolation
Nasty interpolation bug - and fix!

[edit = removed cvar remnant]
Last edited by Baker on Tue Jul 06, 2010 11:48 pm, edited 1 time in total.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby mh » Tue Jul 06, 2010 11:38 pm

Somebody buy that man a large beer. :D

Image

I'm thinking that in the last step the reset of frame and skin should probably be done outside of the interpolation #ifdef. It seems like a valid precaution just in case the frame and skin for the baseline are invalid for the new model, and the QC doesn't send updates for either or both of them. This is something that could possibly happen even in stock ID software Quake. :evil:

Also, all of the code here is also good for GLQuake and is a more sensible way of doing it than in the old interpolation tutorial. This would include the clearing of the interpolation data on a model change (I've seen GLQuake crash - very occasionally - with the old code on account of not having that step). You don't need to pollute R_RotateForEntity and you don't need special case handling for the viewmodel this way. It's just cleaner.
Last edited by mh on Tue Jul 06, 2010 11:42 pm, edited 1 time in total.
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 JasonX » Tue Jul 06, 2010 11:40 pm

Where can we see the animation interpolation tut? I'm sorry if i'm being stupid. :cry:
JasonX
 
Posts: 411
Joined: Tue Apr 21, 2009 2:08 pm

Postby mh » Tue Jul 06, 2010 11:43 pm

JasonX wrote:Where can we see the animation interpolation tut? I'm sorry if i'm being stupid. :cry:


Click on the link that says "Software Quake MDL Frame Interpolation":



:wink: :lol:
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 Baker » Wed Jul 07, 2010 12:02 am

mh wrote:I'm thinking that in the last step the reset of frame and skin should probably be done outside of the interpolation #ifdef. It seems like a valid precaution just in case the frame and skin for the baseline are invalid for the new model, and the QC doesn't send updates for either or both of them.


After reading this, I was about to modify the tutorial slightly to adjust it accordingly, but if I do so it makes the changes less clear because the above has the changed blocked in #ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION ... #endif

So rather than edit the post ...

Suggested modification ...

Code: Select all
   // automatic animation (torches, etc) can be either all together or randomized
      if (model)
            ent->syncbase = (model->synctype == ST_RAND) ? (float)(rand()&0x7fff) / 0x7fff : 0.0;
      else
         forcelink = true;   // hack to make null model players work

#ifdef SUPPORTS_SOFTWARE_ANIM_INTERPOLATION
      // if the model has changed we must also reset the interpolation data
      // lastpose and currpose are critical as they might be pointing to invalid frames in the new model!!!
      CL_ClearInterpolation (ent);
#endif

#if 1 // MH suggestion:  Add this to the engine anyway, the frame and skin cannot be relied upon if the model has changed and if those are not sent, set them to frame 0 and skin 0.
      // reset frame and skin too...!
      if (!(bits & U_FRAME)) ent->frame = 0;
      if (!(bits & U_SKIN)) ent->skinnum = 0;
#endif // End MH suggestion

The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby jonyroger » Thu Aug 19, 2010 4:02 am

These is one of the great information which you can share with us. All code here is also good for GLQuake and is a more reasonable to do so in the old tutorial interpolation. This includes compensation data interpolation in a model of change (I saw GLQuake crash - very rarely - with the old code because of not having this). You do not need to pollute R_Rotate For Entity and you do not need special procedures to see the model in this way.
jonyroger
 
Posts: 3
Joined: Thu Aug 19, 2010 3:53 am

Postby Sajt » Thu Aug 19, 2010 4:10 am

*sniff* Markov would be proud!
F. A. Špork, an enlightened nobleman and a great patron of art, had a stately Baroque spa complex built on the banks of the River Labe.
Sajt
 
Posts: 1215
Joined: Sat Oct 16, 2004 3:39 am


Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest