A simple class that produces vectors interpolating between two given vectors in a given amount of time. The vectors may be any dimention, so long as they can be parsed as a sequence. The interpolation defaults to a linear interpolation, but two parameters are provided to modify it into various smooth interpolations.

Background

I had been playing around with the LinearInterpolator and SmoothInterpolator functions here in the cookbook, and after fixing a couple (serious) bugs, I made some other heavy modifications to suit my needs. Finally, I decided to post my updates.

I don't have Numeric, and as it is getting rather old anyway I have puttered along without it. You might find some speed improvements by changing the list comprehensions into Numeric.arrays. The following apply:

This is the module. You can run it through pydoc to get nice HTML documentation.

R"""interpolator.pyPerform a simple interpolation between two points of any dimention, withoutthe use of Numeric.2007 Michael Thomas GreerReleased to the Public Domain"""classInterpolator( object ):
R""" The line actor. Returns successive points along a line until completely traversed. Once traversed this class can do nothing more. Care has been taken to fix errors due to floating-point arithmetic. implementation notes Following the docs, some simple math must be done to get the movement desired. The first thing to remember is that we cannot change the FPS, which is to say, we cannot change the speed of the program. So all our manipulations work by modifying the step size between the start and stop vectors (which, in other words, is the apparent speed of line traversal). For the default linear interpolation, the step size remains constant: step = dx *( 1.0 /FPS ) /seconds where dx represents the total distance to traverse for each dimention. For non-linear interpolations, we must factor in our shape, which is specified relative to the linear interpolation speed. The shape is a simple power function which gives us a nice, spikey curve with a vertical asymptote at the midpoint. The function is: factor = shape *(closeness_to_asymptote **(shape -1.0)) where closeness_to_asymptote is a number in the range [0.0, 1.0]; 0.0 being at either end of the line and 1.0 being at the asymptote. (The location of the asymptote, or "middle", is modifiable.) The final calculation is to modify each vector position by (step *factor). """#---------------------------------------------------------------------------def__init__(
self,
start =None,
stop =None,
seconds =None,
fps =None,
shape =1.0,
middle =0.5
):
R""" Create a new interpolator to produce timed vectors along a line. arguments start - The initial vector. If no vectors are specified the object is treated as a placeholder object and does absolutely nothing but return the two-dimentional vector (0, 0). stop - The final vector. If no final vector is specified the object is treated as a placeholder object and does absolutely nothing but return the 'start' vector. seconds - The number of seconds you wish the interpolation to take. fps - The current number of frames per second. If you specify seconds you must specify the FPS, otherwise a ValueError exception is raised with the message "You must specify both 'seconds' and 'fps'." shape - Modify the interpolation as non-linear. First some quick information to explain: - The total time to traverse the line is constant (in other words, this function cannot take more or less 'seconds' worth of vectors than you specify). - For the same reason, the number of vectors produced by this function (for a given value of 'seconds') is constant. - Hence, the -speed- of a vector is defined wholly by the distance between it the previously produced vector. - In a linear interpolation, the distance between vectors (or again, the speed of each vector) is constant; every vector has the same speed. - In a shaped interpolation, the speed of individual vectors is modified: some are faster and some are slower. The shape is the number of times greater than linear speed the middle vector travels. A shape of 1.0 is a linear interpolation. A shape of 2.0 has slower vectors at the end and a vector in the middle traveling twice as fast as it would in a linear interpolation. A shape of 0.5 travels half as fast. There really isn't any upper- limit, but zero is the lower. You'll get a ValueError if you try any value less-than or equal-to zero. middle - The location of the "middle vector" along the line, expressed as a value from 0.0 (at 'start') to 1.0 (at 'stop'). """self._sec =-1self._length =0if start isNone: start = (0, 0)
if stop isNone: self.stop = start
else:
if (seconds isNone) or (fps isNone):
raiseValueError( "You must specify both 'seconds' and 'fps'" )
if shape &lt;=0.0:
raiseValueError( "The 'shape' argument must have value > 0.0" )
ifnot (0.0&lt;= middle &lt;=1.0):
raiseValueError( "The 'middle' argument must be in range [0.0, 1.0]" )
self.stop = stop
self.diff = [ b -a for a, b inzip( start, stop ) ]
self.inc =1.0/ fps
self.step = [ a *self.inc /seconds for a inself.diff ]
self._pos = start
self._sec = seconds
self.seconds = seconds
self.shape = shape
self.mid = middle
self.maxs = [ max( a, b ) for a, b inzip( start, stop ) ]
self.mins = [ min( a, b ) for a, b inzip( start, stop ) ]
self._length =None#---------------------------------------------------------------------------defnext( self ):
R""" Calculate the location of the next vector in the line. The 'start' vector cannot be a "next" vector. (This is actually rather convenient if you think about it.) That said, if your interpolation is set up right, the first "next" vector might actually be in the same location as the 'start' vector... Care is taken that the 'stop' vector is always the final vector. returns The next vector or None if all done. """defd( a, b, c ):
if b ==0.0: return c
else: return a /b
ifself._sec >=0.0:
ifself.shape ==1.0: factor =1.0else:
percent =1.0-(self._sec /self.seconds) # percent completeif percent &lt; 0.95:
if percent >self.mid: k = d( (1.0-percent), (1.0-self.mid), 1.0 )
else: k = d( percent, self.mid, 0.0 )
if k in [0.0, 1.0]: factor = k *self.shape
else: factor =pow( k, self.shape -1.0 ) *self.shape
else:
# The final 5% of the line is calculated linearly to avoid any# 'jump' or 'snap' artifacts caused by FPU arithmetic errors. ifself.mid isnotNone:
self.diff = [ b -a for a, b inzip( self._pos, self.stop ) ]
self.step = [ a *self.inc /self._sec for a inself.diff ]
self.mid =None
factor =1.0self._pos =tuple(
[ min( max(
a +(step *factor),
mina ), maxa )
for a, step, mina, maxa inzip( self._pos, self.step, self.mins, self.maxs )
]
)
self._sec -=self.inc
returnself._pos
else:
self._pos =self.stop
returnNone#---------------------------------------------------------------------------def_get_pos( self ):
R"""Return the current vector's location."""returnself._pos
#---------------------------------------------------------------------------def_get_length( self ):
R""" Returns the length of the line. The line's length is not calculated until first required. """frommathimport sqrt
ifself._length isNone:
sum=0for a inself.diff: sum+= a *a
self._length = sqrt( sum )
returnself._length
#---------------------------------------------------------------------------
pos =property( _get_pos, doc='The location of the current vector. Read-only.' )
length =property( _get_length, doc='The length of the line. Read-only.' )
# end interpolator

test.py

Here is a simple test program you can use to play around with it.

Click anywhere on the display to cause the colored squares to re-center themselves around the spot where you clicked, using a one-and-a-half-second interpolation.

The blue square is interpolated linearly.

The orange square has a smooth shape of 2.0.

The purple square also has a smooth shape of 2.0, but the fastest part is at the end instead of the middle of the line.

The red square has a smooth shape of 4.0, and the fastest part is at the beginning instead of the middle of the line.

The yellow square has a smooth shape of 0.5, meaning that it is half-speed in the middle and fast at the ends.