diff --git a/direct/src/interval/AnimInterval.py b/direct/src/interval/AnimInterval.py index 03ad2b3a21..130a614838 100644 --- a/direct/src/interval/AnimInterval.py +++ b/direct/src/interval/AnimInterval.py @@ -7,6 +7,15 @@ class AnimInterval(Interval): # Name counter animNum = 1 # Class methods + # Interval used to play an animation. If loop = 0, animation is + # played only once and the pose of the anim depends on t passed + # into the setT method every frame. If loop = 1, the animation is + # started and plays on its own and stopped when t > duration or + # IVAL_STOP event occurs. If no duration is specified, interval + # duration defaults to be equal to the length of the animation. + # Note: if loop == 0 and duration > anim duration then the animation + # will play once and then nothing will happen for the remainder of the + # interval def __init__(self, animControl, loop = 0, duration = 0.0, name=None): """__init__(name) """ diff --git a/direct/src/interval/FunctionInterval.py b/direct/src/interval/FunctionInterval.py index 3b6d8b58ec..4cc137efc1 100644 --- a/direct/src/interval/FunctionInterval.py +++ b/direct/src/interval/FunctionInterval.py @@ -18,9 +18,12 @@ class FunctionInterval(Interval): name = 'FunctionInterval-%d' % FunctionInterval.functionIntervalNum FunctionInterval.functionIntervalNum += 1 # Initialize superclass - # Set openEnded true if calls after end time cause interval - # function to be called + # Set openEnded true if IVAL_INIT calls after end time cause interval + # function to be called. If false, IVAL_INIT calls have no effect + # Event, Accept, Ignore intervals default to fOpenEnded = 0 + # Parent, Pos, Hpr, etc intervals default to fOpenEnded = 1 Interval.__init__(self, name, duration = 0.0, openEnded = openEnded) + def updateFunc(self, t, event = IVAL_NONE): """ updateFunc(t, event) Go to time t @@ -44,25 +47,29 @@ class EventInterval(FunctionInterval): ### FunctionInterval subclass for accepting hooks ### class AcceptInterval(FunctionInterval): # Initialization - def __init__(self, dirObj, event, function, name): + def __init__(self, dirObj, event, function, name = None): """__init__(dirObj, event, function, name) """ def acceptFunc(dirObj = dirObj, event = event, function = function): - print "accepting..." dirObj.accept(event, function) + # Determine name + if (name == None): + name = 'Accept-' + event # Create function interval FunctionInterval.__init__(self, acceptFunc, name = name, openEnded = 0) -### FunctionInterval subclass for throwing events ### +### FunctionInterval subclass for ignoring events ### class IgnoreInterval(FunctionInterval): # Initialization - def __init__(self, dirObj, event, name): + def __init__(self, dirObj, event, name = None): """__init__(dirObj, event, name) """ def ignoreFunc(dirObj = dirObj, event = event): - print "ignoring..." dirObj.ignore(event) + # Determine name + if (name == None): + name = 'Ignore-' + event # Create function interval FunctionInterval.__init__(self, ignoreFunc, name = name, openEnded = 0) @@ -188,7 +195,7 @@ class PosHprInterval(FunctionInterval): FunctionInterval.__init__(self, posHprFunc, name = name) class PosHprScaleInterval(FunctionInterval): - # PosHprInterval counter + # PosHprScaleInterval counter posHprScaleIntervalNum = 1 # Initialization def __init__(self, nodePath, pos, hpr, scale, duration = 0.0, @@ -212,19 +219,49 @@ class PosHprScaleInterval(FunctionInterval): """ SAMPLE CODE + from IntervalGlobal import * ### Using lambdas and functions ### # Using a lambda i1 = FunctionInterval(lambda: base.transitions.fadeOut()) i2 = FunctionInterval(lambda: base.transitions.fadeIn()) -# Using a function -def printHello(): - print 'hello' -i3 = FunctionInterval(printHello) +def caughtIt(): + print 'Caught here-is-an-event' + +class DummyAcceptor(DirectObject): + pass + +da = DummyAcceptor() +i3 = AcceptInterval(da, 'here-is-an-event', caughtIt) + +i4 = EventInterval('here-is-an-event') + +i5 = IgnoreInterval(da, 'here-is-an-event') + +# Using a function +def printDone(): + print 'done' + +i6 = FunctionInterval(printDone) + # Create track -t1 = Track([(0.0, i1), (2.0, i2), (4.0, i3)], name = 'demo') +t1 = Track([ + # Fade out + (0.0, i1), + # Fade in + (2.0, i2), + # Accept event + (4.0, i3), + # Throw it, + (5.0, i4), + # Ignore event + (6.0, i5), + # Throw event again and see if ignore worked + (7.0, i4), + # Print done + (8.0, i6)], name = 'demo') # Play track t1.play() diff --git a/direct/src/interval/Interval.py b/direct/src/interval/Interval.py index dd36cb61a8..4d895b95b0 100644 --- a/direct/src/interval/Interval.py +++ b/direct/src/interval/Interval.py @@ -16,8 +16,7 @@ class Interval(DirectObject): notify = directNotify.newCategory("Interval") #notify.setDebug(1) - # special methods - + # Class methods def __init__(self, name, duration, openEnded = 1): """__init__(name, duration) """ @@ -65,9 +64,13 @@ class Interval(DirectObject): self.prev_t = self.curr_t def updateFunc(self, t, event = IVAL_NONE): + """ updateFunc(t, event) + """ + # Subclasses define this function pass def setTHook(self, t): + # Used by control panel to update scale pass def setFinalT(self): @@ -79,7 +82,7 @@ class Interval(DirectObject): """ play(t0, duration) """ # Kill ongoing play task - self.stop() + taskMgr.removeTasksNamed(self.name + '-play') # Start new one self.offset = t0 self.startT = self.clock.getFrameTime() @@ -93,12 +96,15 @@ class Interval(DirectObject): # Otherwise use min of interval duration and offset + play duration self.endTime = min(self.duration, self.offset + duration) assert(t0 <= self.endTime) + # Spawn task taskMgr.spawnMethodNamed(self.__playTask, self.name + '-play') def stop(self): """ stop() """ + # Kill task taskMgr.removeTasksNamed(self.name + '-play') + # And stop freerunning (e.g. sound and anim) intervals self.setT(self.curr_t, event = IVAL_STOP) return self.curr_t @@ -108,6 +114,7 @@ class Interval(DirectObject): t = self.clock.getFrameTime() te = self.offset + ((t - self.startT) * self.scale) if (te < self.endTime): + # If first call, init intervals if (self.firstTime): self.setT(te, event = IVAL_INIT) self.firstTime = 0 @@ -127,6 +134,9 @@ class Interval(DirectObject): return (space + self.name + ' dur: %.2f' % self.duration) def popupControls(self): + """ popupControls() + Popup control panel for interval. + """ import fpformat import string # I moved this here because Toontown does not ship Tk @@ -146,11 +156,14 @@ class Interval(DirectObject): # INIT interval s.setT(es.get(), event = IVAL_INIT) es.onPress = onPress + # To make sure you stop free running intervals es.onRelease = lambda s=self: s.stop() + # To update scale and execute intervals with IVAL_INIT es.onReturnRelease = lambda s=self, es = es: s.setT(es.get(), event = IVAL_INIT) es.pack(expand = 1, fill = X) f = Frame(tl) + # Jump to start and end def toStart(s=self, es=es): s.stop() s.setT(0.0, event = IVAL_INIT) @@ -158,6 +171,7 @@ class Interval(DirectObject): s.stop() s.setT(s.getDuration(), event = IVAL_INIT) jumpToStart = Button(tl, text = '<<', command = toStart) + # Stop/play buttons stop = Button(tl, text = 'Stop', command = lambda s=self: s.stop()) play = Button( @@ -169,11 +183,11 @@ class Interval(DirectObject): stop.pack(side = LEFT, expand = 1, fill = X) jumpToEnd.pack(side = LEFT, expand = 1, fill = X) f.pack(expand = 1, fill = X) - # Add hook to update slider on changes in curr_t + # Add function to update slider during setT calls def update(t,es=es): es.set(t, fCommand = 0) self.setTHooks.append(update) - # Clear out hook on destroy + # Clear out function on destroy def onDestroy(e, s=self, u=update): if u in s.setTHooks: s.setTHooks.remove(u) diff --git a/direct/src/interval/IntervalTest.py b/direct/src/interval/IntervalTest.py index 0333a7fdeb..41ae8efe07 100644 --- a/direct/src/interval/IntervalTest.py +++ b/direct/src/interval/IntervalTest.py @@ -72,3 +72,102 @@ def handleWaterDone(): print 'water is done' messenger.accept('water-is-done', 1, handleWaterDone) + + +### Using lambdas and functions ### +# Using a lambda +i1 = FunctionInterval(lambda: base.transitions.fadeOut()) +i2 = FunctionInterval(lambda: base.transitions.fadeIn()) + +def caughtIt(): + print 'Caught here-is-an-event' + +class DummyAcceptor(DirectObject): + pass + +da = DummyAcceptor() +i3 = AcceptInterval(da, 'here-is-an-event', caughtIt) + +i4 = EventInterval('here-is-an-event') + +i5 = IgnoreInterval(da, 'here-is-an-event') + +# Using a function +def printDone(): + print 'done' + +i6 = FunctionInterval(printDone) + +# Create track +t1 = Track([ + # Fade out + (0.0, i1), + # Fade in + (2.0, i2), + # Accept event + (4.0, i3), + # Throw it, + (5.0, i4), + # Ignore event + (6.0, i5), + # Throw event again and see if ignore worked + (7.0, i4), + # Print done + (8.0, i6)], name = 'demo') + +print(t1) + +### Specifying interval start times during track construction ### +# Interval start time can be specified relative to three different points: +# PREVIOUS_END +# PREVIOUS_START +# TRACK_START + +startTime = 0.0 +def printStart(): + global startTime + startTime = globalClock.getFrameTime() + print 'Start' + +def printPreviousStart(): + global startTime + currTime = globalClock.getFrameTime() + print 'PREVIOUS_END %0.2f' % (currTime - startTime) + +def printPreviousEnd(): + global startTime + currTime = globalClock.getFrameTime() + print 'PREVIOUS_END %0.2f' % (currTime - startTime) + +def printTrackStart(): + global startTime + currTime = globalClock.getFrameTime() + print 'TRACK_START %0.2f' % (currTime - startTime) + +i1 = FunctionInterval(printStart) +# Just to take time +i2 = LerpPosInterval(camera, 2.0, Point3(0,10,5)) +# This will be relative to end of camera move +i3 = FunctionInterval(printPreviousEnd) +# Just to take time +i4 = LerpPosInterval(camera, 2.0, Point3(0,0,5)) +# This will be relative to the start of the camera move +i5 = FunctionInterval(printPreviousStart) +# This will be relative to track start +i6 = FunctionInterval(printTrackStart) +# Create the track, if you don't specify offset type in tuple it defaults to +# relative to TRACK_START (first entry below) +t2 = Track([(0.0, i1), # i1 start at t = 0, duration = 0.0 + (1.0, i2, TRACK_START), # i2 start at t = 1, duration = 2.0 + (2.0, i3, PREVIOUS_END), # i3 start at t = 5, duration = 0.0 + (1.0, i4, PREVIOUS_END), # i4 start at t = 6, duration = 2.0 + (3.0, i5, PREVIOUS_START), # i5 start at t = 9, duration = 0.0 + (10.0, i6, TRACK_START)], # i6 start at t = 10, duration = 0.0 + name = 'startTimeDemo') + +print(t2) + +# Play tracks +# mtrack.play() +# t1.play() +# t2.play() diff --git a/direct/src/interval/LerpInterval.py b/direct/src/interval/LerpInterval.py index af7b9c24ce..80468fbb96 100644 --- a/direct/src/interval/LerpInterval.py +++ b/direct/src/interval/LerpInterval.py @@ -9,18 +9,20 @@ class LerpInterval(Interval): def __init__(self, name, duration, functorFunc, blendType='noBlend'): """__init__(name, duration, functorFunc, blendType) """ + # Record instance variables self.lerp = None self.functorFunc = functorFunc self.blendType = self.getBlend(blendType) + # Initialize superclass Interval.__init__(self, name, duration) def updateFunc(self, t, event = IVAL_NONE): """ updateFunc(t, event) """ - # First check to see if we need to create the lerp + # Check to see if we need to create the lerp if (event == IVAL_INIT): self.lerp = Lerp.Lerp(self.functorFunc(), self.duration, self.blendType) - # Now evaluate the lerp + # Evaluate the lerp if its been created if self.lerp: self.lerp.setT(t) def getBlend(self, blendType): @@ -51,7 +53,6 @@ class LerpPosInterval(LerpInterval): def functorFunc(self=self, node=node, pos=pos, startPos=startPos, other=other): import PosLerpFunctor - assert(not node.isEmpty()) if (other != None): # lerp wrt other @@ -227,7 +228,8 @@ class LerpPosHprScaleInterval(LerpInterval): LerpInterval.__init__(self, name, duration, functorFunc, blendType) # Class used to execute a function over time. Function can access fromData -# and toData to perform blend +# and toData to perform blend. If fromData and toData not specified, will +# execute the given function passing in values ranging from 0 to 1 class LerpFunctionInterval(Interval): # Interval counter lerpFunctionIntervalNum = 1 @@ -252,21 +254,24 @@ class LerpFunctionInterval(Interval): """ updateFunc(t, event) """ # Evaluate the function - if (t == self.duration): + if (t >= self.duration): # Set to end value self.function(self.toData) else: - # In the middle of the lerp, compute appropriate value + # In the middle of the lerp, compute appropriate blended value try: bt = self.blendType(t/self.duration) data = (self.fromData * (1 - bt)) + (self.toData * bt) + # Evaluate function self.function(data) except ZeroDivisionError: + # Zero duration, just use endpoint self.function(self.toData) def getBlend(self, blendType): """__getBlend(self, string) Return the C++ blend class corresponding to blendType string """ + # Note, this is temporary until blend functions get exposed import LerpBlendHelpers def easeIn(t): x = t*t diff --git a/direct/src/interval/MultiTrack.py b/direct/src/interval/MultiTrack.py index 3ee170190a..8b99c788f4 100644 --- a/direct/src/interval/MultiTrack.py +++ b/direct/src/interval/MultiTrack.py @@ -4,26 +4,26 @@ from Interval import * from Track import * class MultiTrack(Interval): - + # Name counter multiTrackNum = 1 - - # special methods - + # Class methods def __init__(self, trackList, name=None): """__init__(trackList, name) """ - if (name == None): - n = 'MultiTrack-%d' % MultiTrack.multiTrackNum - MultiTrack.multiTrackNum = MultiTrack.multiTrackNum + 1 - else: - n = name + # Record track list self.tlist = trackList + if (name == None): + name = 'MultiTrack-%d' % MultiTrack.multiTrackNum + MultiTrack.multiTrackNum = MultiTrack.multiTrackNum + 1 + # Duration is max of all track durations duration = self.__computeDuration() - Interval.__init__(self, n, duration) - + # Initialize superclass + Interval.__init__(self, name, duration) + + # Access track at given index def __getitem__(self, item): return self.tlist[item] - + def __computeDuration(self): """ __computeDuration() Returns the duration of the longest Track @@ -43,11 +43,15 @@ class MultiTrack(Interval): tEnd = track.getDuration() # Compare time with track's end times if (t > tEnd): + # If t > tEnd, only call if just crossing over + # or this is an IVAL_INIT event if (self.prev_t < tEnd) or (event == IVAL_INIT): track.setT(t, event) else: + # Update track track.setT(t, event) + # Print out representation of MultiTrack def __repr__(self, indent=0): """ __repr__(indent) """ diff --git a/direct/src/interval/SoundInterval.py b/direct/src/interval/SoundInterval.py index 70bf507084..5149136291 100644 --- a/direct/src/interval/SoundInterval.py +++ b/direct/src/interval/SoundInterval.py @@ -7,36 +7,43 @@ class SoundInterval(Interval): # Name counter soundNum = 1 # Class methods - def __init__(self, sound, loop=0, name=None): + # Create a sound interval + # If loop = 0, sound will play once, duration of the interval + # equals the duration of the sound + # If loop = 1, the sound will loop for the specified duration + # If no duration is specified, sound will loop for the duration + # of the sound, i.e. it will only play once.....usually, there + # seems to be some timing in the audio such that the stop doesn't + # kill the looping sound until the next time around if duration + # of the interval equals duration of the sound + def __init__(self, sound, loop = 0, duration = 0.0, name = None): """__init__(sound, loop, name) """ + # Record instance variables self.sound = sound self.loop = loop - self.isPlaying = 0 + # If no duration given use sound's duration as interval's duration + if duration == 0.0: + duration = self.sound.length() # Generate unique name if necessary if (name == None): name = 'Sound-%d' % SoundInterval.soundNum SoundInterval.soundNum += 1 - # Calculate duration - duration = self.sound.length() # Initialize superclass Interval.__init__(self, name, duration) - def updateFunc(self, t, event = IVAL_NONE): """ updateFunc(t, event) Go to time t """ - if (t == self.duration) or (event == IVAL_STOP): - # Stop sound if necessary - if (self.isPlaying == 1): - self.isPlaying = 0 - AudioManager.stop(self.sound) + # Update sound based on current time + if (t >= self.getDuration()) or (event == IVAL_STOP): + # If end of sound reached or stop event received, stop sound + AudioManager.stop(self.sound) elif (event == IVAL_INIT): - # Set flag - self.isPlaying = 1 - # If its within a 20th of a second of the start, + # IVAL_INIT event, start new sound + # If its within a 10th of a second of the start, # start at the beginning - if (t < 0.05): + if (t < 0.1): t = 0.0 # Start sound AudioManager.play(self.sound, t) diff --git a/direct/src/interval/Track.py b/direct/src/interval/Track.py index 37e0ff2950..217aa45115 100644 --- a/direct/src/interval/Track.py +++ b/direct/src/interval/Track.py @@ -1,7 +1,6 @@ """Track module: contains the Track class""" from Interval import * - import types PREVIOUS_END = 1 @@ -33,9 +32,12 @@ class Track(Interval): # Initialize superclass Interval.__init__(self, name, duration) + # Access interval at given index def __getitem__(self, item): return self.ilist[item] + # Create a list of this track's intervals, recording time + # and time type (relative to track start, previous start, previous end def __buildIlist(self, intervalList): self.ilist = [] for i in intervalList: @@ -43,39 +45,42 @@ class Track(Interval): self.ilist.append([i, 0.0, PREVIOUS_END, 0.0, 0.0]) elif (isinstance(i, types.ListType) or isinstance(i, types.TupleType)): - t0 = i[0] + itime = i[0] ival = i[1] try: type = i[2] except IndexError: type = TRACK_START - self.ilist.append([ival, t0, type, 0.0, 0.0]) + self.ilist.append([ival, itime, type, 0.0, 0.0]) else: print 'Track.__buildIlist: Invalid intervallist entry' + # Compute duration of the track and precompute start and end time of + # each interval def __computeDuration(self): """ __computeDuration() """ duration = 0.0 prev = None for idata in self.ilist: - ival = idata[IDATA_IVAL] - t0 = idata[IDATA_TIME] - type = idata[IDATA_TYPE] - assert(t0 >= 0.0) + ival = idata[IDATA_IVAL] + itime = idata[IDATA_TIME] + type = idata[IDATA_TYPE] + assert(itime >= 0.0) # Compute fill time, time between end of last interval and - # start of this one - fillTime = t0 + # start of this one. Depend on interval type + fillTime = itime if (type == PREVIOUS_END): pass elif (type == PREVIOUS_START): if (prev != None): - fillTime = t0 - prev.getDuration() + fillTime = itime - prev.getDuration() elif (type == TRACK_START): - fillTime = t0 - duration + fillTime = itime - duration else: Interval.notify.error( 'Track.__computeDuration(): unknown type: %d' % type) + # Check for overlap if (fillTime < 0.0): Interval.notify.error( 'Track.__computeDuration(): overlap detected') @@ -83,22 +88,25 @@ class Track(Interval): idata[IDATA_START] = duration + fillTime # Compute end time of interval idata[IDATA_END] = idata[IDATA_START] + ival.getDuration() - # Keep track of current duration + # Keep track of cumulative duration duration = idata[IDATA_END] prev = ival return duration - def setIntervalStartTime(self, name, t0, type=TRACK_START): - """ setIntervalStartTime(name, t0, type) + def setIntervalStartTime(self, name, itime, type=TRACK_START): + """ setIntervalStartTime(name, itime, type) """ found = 0 + # Check for interval in current interval list for idata in self.ilist: + # If found, update time and type if (idata[IDATA_IVAL].getName() == name): - idata[IDATA_TIME] = t0 + idata[IDATA_TIME] = itime idata[IDATA_TYPE] = type found = 1 break if (found): + # And recompute duration self.duration = self.__computeDuration() else: Interval.notify.warning( @@ -107,6 +115,7 @@ class Track(Interval): def getIntervalStartTime(self, name): """ getIntervalStartTime(name) """ + # Search for interval of given name for idata in self.ilist: if (idata[IDATA_IVAL].getName() == name): return idata[IDATA_START] @@ -117,6 +126,7 @@ class Track(Interval): def __getIntervalStartTime(self, interval): """ __getIntervalStartTime(interval) """ + # Search for given interval for idata in self.ilist: if (idata[IDATA_IVAL] == interval): return idata[IDATA_START] @@ -127,6 +137,7 @@ class Track(Interval): def getIntervalEndTime(self, name): """ getIntervalEndTime(name) """ + # Search for interval of given name for idata in self.ilist: if (idata[IDATA_IVAL].getName() == name): return idata[IDATA_END] @@ -152,8 +163,7 @@ class Track(Interval): currentInterval = None # First entry, re-init instance variables if (event == IVAL_INIT): - # Initialize prev_t to max t of track - #self.prev_t = self.getDuration() + # Initialize prev_t to 0.0 self.prev_t = 0.0 # Clear record of currentInterval self.currentInterval = None @@ -166,7 +176,7 @@ class Track(Interval): # start of an interval ((prev_t > tStart) and (t < tStart)) # then execute that interval at its start value for ival, itime, itype, tStart, tEnd in self.ilist: - # Compare time with ival's start/end times + # Compare time with each ival's start/end times if (t < tStart): if (self.prev_t > tStart) and (event != IVAL_STOP): # We just crossed the start of this interval @@ -200,11 +210,13 @@ class Track(Interval): # Record current interval (may be None) self.currentInterval = currentInterval + # Create a printable representation of the track def __repr__(self, indent=0): """ __repr__(indent) """ str = Interval.__repr__(self, indent) + '\n' - for idata in self.ilist: + for idata in self.ilist: + # Tack on start and end time for this interval str = (str + idata[IDATA_IVAL].__repr__(indent+1) + (' start: %0.2f end: %0.2f' % (idata[IDATA_START], idata[IDATA_END])) + '\n'