pyTweener - new and improved - see below :)

bharling

29-01-2009 14:29:06

Hi folks,
I just thought I'd post a little routine I adapted from here: http://code.google.com/p/tweener/ original in Actionscript.

Its a really simple way to move objects around with nice natural transitions, and not have to worry about updating anything.

http://wiki.python-ogre.org/index.php/CodeSnippits_pyTweener

cheers,

Danaugrs

30-01-2009 17:18:53

Love it! :D
I was just looking for something like that!
I began to think I had to write my own interpolation module sometime ago,
when I realized how code/time consuming creating interpolations manually was.
But I never really implemented the idea.
Very useful indeed!

Cheers,
Danaugrs

bharling

31-01-2009 10:42:51

Glad you like it!

Have written a much improved version which I'll post as soon as I can get to it again, plus added most of the tween types from the AS library.

Should also mention that all the equations come from this clever chap Robert Penner: http://www.robertpenner.com/easing/

I'm wondering whether to make the addTween function more like the AS version, which takes a dictionary of parameters, but I'm not sure I like the syntax:


addTween( myObject, { "position": newPosition, "tweenType":"EaseInOutSin", "time":1.0 } )


however perhaps it could be done with arbitrary keywords in any order:

addTween( myObject, position=newPosition, time=50, tweenType=EASE_OUT_QUAD, rotation=newRotation )

Also perhaps just combine the function and property tweens into one Tween class, and use try: except: to see whether its a property or a function we're dealing with. Finally, it should also be able to detect the initial value you're tweening from without needing to be told ( either by reading the property initially or trying to run the "get" version of the function.)

what do you think?

bharling

01-02-2009 13:09:52

right, I re-wrote it to be more like the AS original and accept a variety of arguments without worrying too much.

so to use it now, its just:

import pyTweener.Tweener as Tweener
...
tweener = Tweener()
...
tweener.addTween( myRocket, setThrust=ogre.Vector3(0,200,0), tweenTime=2.0, tweenType=tweener.IN_OUT_CUBIC, onCompleteFunction=self.rocketLaunched, scale=2.0 )
activeTweens = tweener.getTweensAffectingObject( myobj )
for t in activeTweens:
t.pause()
...

tweener.update( timeSinceLastFrame )


here it is, with more of Penner's easing equations added:

import math

class Tweener:
def __init__(self):
self.currentTweens = []
self.defaultTweenType = self.IN_OUT_QUAD
self.defaultDuration = 1.0

def OUT_EXPO(self, t, b, c, d ):
return b+c if (t==d) else c * (-math.pow(2, -10 * t/d) + 1) + b;

def LINEAR (self, t, b, c, d):
return c*t/d + b

def IN_QUAD (self, t, b, c, d):
t/=d
return c*(t)*t + b

def OUT_QUAD (self, t, b, c, d):
t/=d
return -c *(t)*(t-2) + b

def IN_OUT_QUAD( self, t, b, c, d ):
t/=d/2
if ((t) < 1): return c/2*t*t + b
t-=1
return -c/2 * ((t)*(t-2) - 1) + b

def OUT_IN_QUAD( self, t, b, c, d ):
if (t < d/2):
return self.OUT_QUAD (t*2, b, c/2, d)
return self.IN_QUAD((t*2)-d, b+c/2, c/2)

def IN_CUBIC(self, t, b, c, d):
t/=d
return c*(t)*t*t + b

def OUT_CUBIC(self, t, b, c, d):
t=t/d-1
return c*((t)*t*t + 1) + b

def IN_OUT_CUBIC( self, t, b, c, d):
t/=d/2
if ((t) < 1):
return c/2*t*t*t + b
t-=2
return c/2*((t)*t*t + 2) + b

def OUT_IN_CUBIC( self, t, b, c, d ):
if (t < d/2): return self.OUT_CUBIC (t*2, b, c/2, d)
return self.IN_CUBIC((t*2)-d, b+c/2, c/2, d)

def IN_QUART( self, t, b, c, d):
t/=d
return c*(t)*t*t*t + b

def OUT_QUART( self, t, b, c, d):
t=t/d-1
return -c * ((t)*t*t*t - 1) + b

def IN_OUT_QUART( self, t, b, c, d):
t/=d/2
if (t < 1):
return c/2*t*t*t*t + b
t-=2
return -c/2 * ((t)*t*t*t - 2) + b

def OUT_ELASTIC(self, t, b, c, d): # Not working :(
if (t==0):
return b
t/=d
if t==1:
return b+c
p = period = d*.3
a = amplitude = 1.0
if a < abs(c):
a = c
s = p/4
else:
s = p/(2*math.pi) * math.asin (c/a)

return (a*math.pow(2,-10*t) * math.sin( (t*d-s)*(2*math.PI)/p ) + c + b)


def hasTweens(self):
return len(self.currentTweens) > 0


def addTween(self, obj, **kwargs):
""" addTween( object, **kwargs) -> tweenObject or False

kwargs should include tweenTime and tweenType and
at least one property or function of the object
with the change value.eg:

tweener.addTween( myRocket, throttle=50, setThrust=400, tweenTime=5.0, tweenType=tweener.OUT_QUAD )"""
if kwargs.has_key("tweenTime"):
t_time = kwargs.pop("tweenTime")
else: t_time = self.defaultDuration

if kwargs.has_key("tweenType"):
t_type = kwargs.pop("tweenType")
else: t_type = self.defaultTweenType

if kwargs.has_key("onCompleteFunction"):
t_completeFunc = kwargs.pop("onCompleteFunction")
else: t_completeFunc = None

if kwargs.has_key("tweenDelay"):
t_delay = kwargs.pop("tweenDelay")
else: t_delay = 0

tw = Tween( obj, t_time, t_type, t_completeFunc, t_delay, **kwargs )
if tw:
self.currentTweens.append( tw )
return tw

def removeTween( tweenObj ):
if self.currentTweens.contains( tweenObj ):
tweenObj.complete = True
#self.currentTweens.remove( tweenObj )

def getTweensAffectingObject( self, obj ):
tweens = []
for t in self.currentTweens:
if t.target is obj:
tweens.append(t)
return tweens

def removeTweeningFrom( self, obj ):
for t in self.currentTweens:
if t.target is obj:
t.complete = True


def update(self, timeSinceLastFrame):
for t in self.currentTweens:
if not t.complete:
t.update( timeSinceLastFrame )
else:
self.currentTweens.remove(t)

class Tween:
def __init__(self, obj, duration, tweenType, completeFunction, delay, **kwargs):
self.duration = duration
self.delay = delay
self.target = obj
self.tween = tweenType
self.tweenables = kwargs
self.delta = 0
self.completeFunction = completeFunction
self.complete = False
self.tProps = []
self.tFuncs = []
self.paused = self.delay > 0
self.decodeArguments()

def decodeArguments(self):
if len(self.tweenables) == 0:
# nothing to do
print "TWEEN ERROR: No Tweenable properties or functions defined"
self.complete = True
return
for k, v in self.tweenables.items():
# check that its compatible
if not hasattr( self.target, k):
print "TWEEN ERROR: " + str(self.target) + " has no function " + k
self.complete = True
break

prop = func = False
startVal = 0
change = v

try:
startVal = self.target.__dict__[k]
prop = k

except:
func = getattr( self.target, k)
funcName = k

if func:
try:
getFunc = getattr(self.target, funcName.replace("set", "get") )
print "Found getter function"
startVal = getFunc()
except:
# no start value, assume its 0
# but make sure the start and change
# dataTypes match :)
startVal = change * 0
self.tFuncs.append( [ func, startVal, change ] )

if prop:
self.tProps.append( [prop, startVal, change])

def pause( self, numSeconds=-1 ):
self.paused = True
self.delay = numSeconds

def resume( self ):
if self.paused:
self.paused=False

def update(self, ptime):
if self.paused:
if self.delay > 0:
self.delay = max( 0, self.delay - ptime )
if self.delay == 0:
self.paused = False
self.delay = -1
return

self.delta = min(self.delta + ptime, self.duration)

if self.delta == self.duration:
self.complete = True
if self.completeFunction:
self.completeFunction()
return
for prop, start, change in self.tProps:
self.target.__dict__[prop] = self.tween( self.delta, start, change, self.duration )
for func, start, change in self.tFuncs:
func( self.tween( self.delta, start, change, self.duration ) )

def Remove(self):
"""Disables and removes this tween
without calling the complete function"""
self.complete = True

class TweenTestObject:
def __init__(self):
self.pos = 20
self.rot = 50

def setRotation(self, rot):
self.rot = rot

def getRotation(self):
return self.rot

def complete(self):
print "hello im done with tweening"


if __name__=="__main__":
import time
T = Tweener()
tst = TweenTestObject()
T.addTween( tst, setRotation=523.0, tweenTime=2, tweenType=T.OUT_EXPO, pos=-200, tweenDelay=0.4 )
s = time.clock()
while T.hasTweens():
tm = time.clock()
d = tm - s
s = tm
T.update( d )
print tst.getRotation(), tst.pos
time.sleep(0.006)


Be warned the error checking is abysmal, I could really do with some help on that if anyone would like to!
Also its not tested very well, so might go wrong at times.
Guess the license should be the same as for the original caurina tweener which is MIT

I hope to wiki it later when its more complete :)

Danaugrs

01-02-2009 14:09:57

I'm not really fond of the dictionary syntax too, I prefer the second :)
Combining the property and function into one class is a good idea.
I'd really like to help, but after my classes start I will have little if any time to code :(

Guess the license should be the same as for the original caurina tweener which is MIT

It is good to maintain the original license, especially such a nice one :)

So, how can I help? hehe

bharling

03-02-2009 13:15:50

I placed a further updated version on the wiki while ogre3d was down. This new version allows tweens to be modified while they are running via a 'Tweenable' class.

see here: http://wiki.python-ogre.org/index.php/CodeSnippits_pyTweener

Have also tidied it all up a bit, and made a stab at some documentation. I'll only update the wiki version from now on as this is definitely the best so far ;)

zorrito

02-05-2009 18:45:08

You should contact zisforzeh so this is added to the list of ports in the tweener project main page =)
Good work

bharling

04-05-2009 11:55:31

Cool,

I managed to find his email eventually and have notified him

thanks! :)

kyathir

10-05-2009 22:03:48

It looks useful =)

I'm sure that I'll use it many times in the future ^^