Tuesday, June 19, 2012

Polarity, a game made in 48h for the Bivouac Urbain 2012

Last weekend our team went to a game jam in Quebec city called "Bivouac Urbain", where contestants had to create a game in 48 hours. The theme this year was "All against one, one against all". We decided to play with concepts related to magnetism, where the player can activate some polarity to attract or repulse objects.  

It got an award as the game with the best controls, and most fluid gameplay experience!




If you're interested, take a look at this video: http://www.youtube.com/watch?v=VJcQxmsfEoA&feature=player_embedded

Sunday, October 9, 2011

Useful math functions

While writing gameplay, AI, shaders, or any kind of programming involving spacial transformations or even any kind of interpolation between values (position, speed, color, sound volume, etc), we may be tempted to go the easy way and use linear interpolation, which often results in terrible looking animations.

I have rassembled over time some very useful functions that I use all the time, most of them being a generalisation of Robert Penner's easing equations.



float linear(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return x;
}
     
float inPow(float x, float p)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return pow(x, p);
}
     
float outPow(float x, float p)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
           
   int sign = p % 2 == 0 ? -1 : 1;
   return (sign * (pow(x - 1f, p) + sign));
}
     
float inOutPow(float x, float p)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
           
   x *= 2;
   if (x < 1) return inPow(x, p) / 2;
           
   int sign = p % 2 == 0 ? -1 : 1;
   return (sign / 2.0f * (pow(x - 2f, p) + sign * 2f));
}
     
float inSin(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return -cos(x * (PI / 2.0f)) + 1;
}
     
float outSin(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return sin(x *(PI / 2.0f ));
}
     
float inOutSin(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return -0.5f * (cos(PI * x) - 1.0f);
}
     
float inExp(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return pow(2.0f, 10.0f * (x - 1.0f));
}
     
float outExp(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return -pow(2, -10 * x) + 1;
}
     
float inOutExp(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return x < 0.5f ? 0.5f * pow(2f, 10f * (2f*x - 1f)) :
      0.5f * (-pow(2f, 10f * (-2f*x + 1f)) + 2f);
}
     
float inCirc(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return -(sqrt(1f - x * x) - 1f);
}
     
float outCirc(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return sqrt(1f - (x - 1f) * (x - 1f));
}
     
float inOutCirc(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return x < 1f ? -0.5f * (sqrt(1f - x * x) - 1f) :
      0.5f * (sqrt(1f - ((2f * x) - 2f) * ((2f * x)- 2f)) + 1f);
}
     
float rebound(float x)

   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
           
   if(x < (1f / 2.75f)) return 1f - 7.5625f * x * x;
   else if(x < (2f / 2.75f)) return 1f - (7.5625f * (x - 1.5f / 2.75f) * 
      (x - 1.5f / 2.75f) + 0.75f);
   else if(x < (2.5f / 2.75f)) return 1f - (7.5625f * (x - 2.25f / 2.75f) * 
      (x - 2.25f / 2.75f) + 0.9375f);
   else return 1f - (7.5625f * (x - 2.625f / 2.75f) * (x - 2.625f / 2.75f) + 
      0.984375f);
}
     
float inBack(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return x * x * ((1.70158f + 1f) * x - 1.70158f);
}
     
float outBack(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return (x - 1f) * (x - 1f) * ((1.70158f + 1f) * (x - 1f) + 1.70158f) + 1f;
}
     
float inOutBack(float x)
{
   if(x < 0.0f) return 0.0f;
   if(x > 1.0f) return 1.0f;
   return x < 0.5f ? 0.5f * (4f * x * x * ((2.5949f + 1f) * 2f * x - 2.5949f)) :
      0.5f * ((2f * x - 2f) * (2f * x - 2f) * ((2.5949f + 1f) * (2f * x - 2f) +
      2.5949f) + 2f);
}
     
float impulse(float x, float k) //k controls the stretching of the func
{
   float h = k*x;
   return h*exp(1.0f-h);
}
     
float expPulse( float x, float k, float n )
{
   return exp(-k*pow(x,n));
}


There is a few things to notice about these functions. They are all normalized between [0, 1] in x, and almost all normalized (all but in/out Back) between [0, 1] in y. And if x < 0 they return 0, if x > 1 they return 1. This makes it very easy to interpolate any kind of value (floats, vectors, matrix, colors, etc), and the fact that they are valid even when the x value is not in their comprised domain [0, 1] makes it easy to combine several of them on a timeline.

One of the most recurring use of these functions is to interpolate a value a with a value b in x seconds with a delay d, relative to time t using a function f. This translates into this generic function:


y = f((t - d) / x) * (b - a) + a


Some Examples

1) Move an object from the point posA to posB in 10 seconds, rapidly at first, with a rapidly decreasing speed.


vec3 posA(10, 10, 0);
vec3 posB(0, 0, 0);
...

void update()
{    
   position = outPow(time / 10.0f, 2.0f) * (posB - posA) + posA;
}


2) A mechanical object that falls and rebound on the ground, wait down some time, goes up slowly, and wait up some time before falling again.

void update()
{
   float time = mod(time, 8.0f); //loop every 8 sec
           
   position.y = (1.0f - rebound(time / 2.0f) +   //falls and rebound for 2 sec
                 outSin((time - 4.0f) / 2.0f)) * //ascend for 2 sec after 4 sec
                 (maxHeight - minHeight) + minHeight;
}