Browse Source

make ActorInterval more efficient

David Rose 23 years ago
parent
commit
e0291f7303
2 changed files with 180 additions and 271 deletions
  1. 166 254
      direct/src/actor/Actor.py
  2. 14 17
      direct/src/interval/ActorInterval.py

+ 166 - 254
direct/src/actor/Actor.py

@@ -442,70 +442,42 @@ class Actor(PandaObject, NodePath):
     
     def getFrameRate(self, animName=None, partName=None):
         """getFrameRate(self, string, string=None)
-        Return duration of given anim name and given part.
+        Return actual frame rate of given anim name and given part.
         If no anim specified, use the currently playing anim.
         If no part specified, return anim durations of first part.
-        NOTE: returns info only for the first LOD"""
-        # use the first LOD
+        NOTE: returns info only for an arbitrary LOD"""
         lodName = self.__animControlDict.keys()[0]
-
-        if (partName == None):
-            partName = self.__animControlDict[lodName].keys()[0]
-    
-        if (animName==None):
-            animName = self.getCurrentAnim(partName)
-
-        # get duration for named part only
-        if (self.__animControlDict[lodName].has_key(partName)):        
-            animControl = self.getAnimControl(animName, partName, lodName)
-            if (animControl != None):
-                return animControl.getFrameRate()
-        else:
-            Actor.notify.warning("no part named %s" % (partName))
-        return None
+        controls = self.getAnimControls(animName, partName)
+        if len(controls) == 0:
+            return None
+        
+        return controls[0].getFrameRate()
     
     def getBaseFrameRate(self, animName=None, partName=None):
         """getBaseFrameRate(self, string, string=None)
-        Return duration of given anim name and given part, unmodified
+        Return frame rate of given anim name and given part, unmodified
         by any play rate in effect.
         """
         lodName = self.__animControlDict.keys()[0]
+        controls = self.getAnimControls(animName, partName)
+        if len(controls) == 0:
+            return None
 
-        if (partName == None):
-            partName = self.__animControlDict[lodName].keys()[0]
-    
-        if (animName==None):
-            animName = self.getCurrentAnim(partName)
-
-        # get duration for named part only
-        if (self.__animControlDict[lodName].has_key(partName)):        
-            animControl = self.getAnimControl(animName, partName, lodName)
-            if (animControl != None):
-                return animControl.getAnim().getBaseFrameRate()
-        else:
-            Actor.notify.warning("no part named %s" % (partName))
-        return None
+        return controls[0].getAnim().getBaseFrameRate()
 
     def getPlayRate(self, animName=None, partName=None):
         """getPlayRate(self, string=None, string=None)
         Return the play rate of given anim for a given part.
         If no part is given, assume first part in dictionary.
         If no anim is given, find the current anim for the part.
-        NOTE: Returns info only for the first LOD"""
+        NOTE: Returns info only for an arbitrary LOD"""
         # use the first lod
         lodName = self.__animControlDict.keys()[0]
-            
-        if (partName==None):
-            partName = self.__animControlDict[lodName].keys()[0]
-
-        if (animName==None):
-            animName = self.getCurrentAnim(partName)
-
-        animControl = self.getAnimControl(animName, partName, lodName)
-        if (animControl != None):
-            return animControl.getPlayRate()
-        else:
+        controls = self.getAnimControls(animName, partName)
+        if len(controls) == 0:
             return None
+
+        return controls[0].getPlayRate()
     
     def setPlayRate(self, rate, animName=None, partName=None):
         """getPlayRate(self, float, string=None, string=None)
@@ -513,104 +485,76 @@ class Actor(PandaObject, NodePath):
         If no part is given, set for all parts in dictionary.
         If no anim is given, find the current anim for the part.
         NOTE: sets play rate on all LODs"""
-        # make a list of partNames for loop below
-        for lodName in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[lodName]
-            if (partName==None):
-                partNames = animControlDict.keys()
-            else:
-                partNames = []
-                partNames.append(partName)
-
-            # for each part in list, set play rate on given or current anim
-            for thisPart in partNames:
-                if (animName==None):
-                    thisAnim = self.getCurrentAnim(thisPart)
-                else:
-                    thisAnim = animName
-                animControl = self.getAnimControl(thisAnim, thisPart, lodName)
-                if (animControl != None):
-                    animControl.setPlayRate(rate)
+        for control in self.getAnimControls(animName, partName):
+            control.setPlayRate(rate)
 
     def getDuration(self, animName=None, partName=None):
         """getDuration(self, string, string=None)
         Return duration of given anim name and given part.
         If no anim specified, use the currently playing anim.
         If no part specified, return anim duration of first part.
-        NOTE: returns info for first LOD only"""
+        NOTE: returns info for arbitrary LOD"""
         lodName = self.__animControlDict.keys()[0]
-        if (partName == None):
-            partName = self.__animControlDict[lodName].keys()[0]
-
-        if (animName==None):
-            animName = self.getCurrentAnim(partName)          
-
-        # get duration for named part only
-        if (self.__animControlDict[lodName].has_key(partName)):        
-            animControl = self.getAnimControl(animName, partName, lodName)
-            if (animControl != None):
-                return (animControl.getNumFrames() / \
-                        animControl.getFrameRate())
-        else:
-            Actor.notify.warning("no part named %s" % (partName))
+        controls = self.getAnimControls(animName, partName)
+        if len(controls) == 0:
+            return None
 
-        return None
+        animControl = controls[0]
+        return (animControl.getNumFrames() / animControl.getFrameRate())
 
     def getNumFrames(self, animName=None, partName=None):
         """ getNumFrames(animName, partName)
         """
         lodName = self.__animControlDict.keys()[0]
-        if (partName == None):
-            partName = self.__animControlDict[lodName].keys()[0]
-        if (animName == None):
-            animName = self.getCurrentAnim(partName)
-        if (self.__animControlDict[lodName].has_key(partName)):
-            animControl = self.getAnimControl(animName, partName, lodName)
-            if (animControl != None):
-                return animControl.getNumFrames()
-            else:
-                Actor.notify.error('no anim control!')
-        else:
-            Actor.notify.warning('no part named: %s' % (partName))
+        controls = self.getAnimControls(animName, partName)
+        if len(controls) == 0:
+            return None
+
+        return controls[0].getNumFrames()
         
     def getCurrentAnim(self, partName=None):
         """getCurrentAnim(self, string=None)
-        Return the anim current playing on the actor. If part not
-        specified return current anim of first part in dictionary.
-        NOTE: only returns info for the first LOD"""
-        lodName = self.__animControlDict.keys()[0]
-        if (partName==None):
-            partName = self.__animControlDict[lodName].keys()[0]
+        Return the anim currently playing on the actor. If part not
+        specified return current anim of an arbitrary part in dictionary.
+        NOTE: only returns info for an arbitrary LOD"""
+        lodName, animControlDict = self.__animControlDict.items()[0]
+        if partName == None:
+            partName, animDict = animControlDict.items()[0]
+        else:
+            animDict = animControlDict.get(partName)
+            if animDict == None:
+                # part was not present
+                Actor.notify.warning("couldn't find part: %s" % (partName))
+                return None
 
         # loop through all anims for named part and find if any are playing
-        if (self.__animControlDict[lodName].has_key(partName)):
-            for animName in self.__animControlDict[lodName][partName].keys():
-                if (self.getAnimControl(animName, partName, lodName).isPlaying()):
-                    return animName
-        else:
-            Actor.notify.warning("no part named %s" % (partName))
+        for animName, anim in animDict.items():
+            if isinstance(anim[1], AnimControl) and anim[1].isPlaying():
+                return animName
 
         # we must have found none, or gotten an error
         return None
 
     def getCurrentFrame(self, animName=None, partName=None):
         """getCurrentAnim(self, string=None)
-        Return the anim current playing on the actor. If part not
-        specified return current anim of first part in dictionary.
-        NOTE: only returns info for the first LOD"""
-        if animName == None:
-            animName = self.getCurrentAnim(partName)
-
-        lodName = self.__animControlDict.keys()[0]
-        if (partName==None):
-            partName = self.__animControlDict[lodName].keys()[0]
-
-        # check the part name
-        if (self.__animControlDict[lodName].has_key(partName)):
-            animControl = self.getAnimControl(animName, partName, lodName)
-            return animControl.getFrame()
+        Return the current frame number of the anim current playing on
+        the actor. If part not specified return current anim of first
+        part in dictionary.
+        NOTE: only returns info for an arbitrary LOD"""
+        lodName, animControlDict = self.__animControlDict.items()[0]
+        if partName == None:
+            partName, animDict = animControlDict.items()[0]
         else:
-            Actor.notify.warning("no part named %s" % (partName))
+            animDict = animControlDict.get(partName)
+            if animDict == None:
+                # part was not present
+                Actor.notify.warning("couldn't find part: %s" % (partName))
+                return None
+
+        # loop through all anims for named part and find if any are playing
+        for animName, anim in animDict.items():
+            if isinstance(anim[1], AnimControl) and anim[1].isPlaying():
+                return anim[1].getFrame()
 
         # we must have found none, or gotten an error
         return None
@@ -941,53 +885,20 @@ class Actor(PandaObject, NodePath):
         Stop named animation on the given part of the actor.
         If no name specified then stop all animations on the actor.
         NOTE: stops all LODs"""
-        for thisLod in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[thisLod]
-            # assemble lists of parts and anims
-            if (partName == None):
-                partNames = animControlDict.keys()
-            else:
-                partNames = [partName]
-            if (animName == None):
-                animNames = animControlDict[partNames[0]].keys()
-            else:
-                animNames = [animName]
-
-            # loop over all parts
-            for thisPart in partNames:
-                for thisAnim in animNames:
-                    # only stop if it's bound
-                    if isinstance(animControlDict[thisPart][thisAnim][1],
-                                  AnimControl):
-                        animControlDict[thisPart][thisAnim][1].stop()
+        for control in self.getAnimControls(animName, partName):
+            control.stop()
         
     def play(self, animName, partName=None, fromFrame=None, toFrame=None):
         """play(self, string, string=None)
         Play the given animation on the given part of the actor.
         If no part is specified, try to play on all parts. NOTE:
         plays over ALL LODs"""
-        for thisLod in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[thisLod]
-            if (partName == None):
-                # play all parts
-                for thisPart in animControlDict.keys():            
-                    animControl = self.getAnimControl(animName, thisPart,
-                                                        thisLod)
-                    if (animControl != None):
-                        if (fromFrame == None):
-                            animControl.play()
-                        else:
-                            animControl.play(fromFrame, toFrame)
-
-            else:
-                animControl = self.getAnimControl(animName, partName,
-                                                    thisLod)
-                if (animControl != None):
-                    if (fromFrame == None):
-                        animControl.play()
-                    else:
-                        animControl.play(fromFrame, toFrame)
-
+        if fromFrame == None:
+            for control in self.getAnimControls(animName, partName):
+                control.play(restart)
+        else:
+            for control in self.getAnimControls(animName, partName):
+                control.play(restart, fromFrame, toFrame)
 
     def loop(self, animName, restart=1, partName=None,
              fromFrame=None, toFrame=None):
@@ -996,27 +907,12 @@ class Actor(PandaObject, NodePath):
         restarting at zero frame if requested. If no part name
         is given then try to loop on all parts. NOTE: loops on
         all LOD's"""
-        for thisLod in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[thisLod]
-            if (partName == None):
-                # loop all parts
-                for thisPart in animControlDict.keys():
-                    animControl = self.getAnimControl(animName, thisPart,
-                                                      thisLod)
-                    if (animControl != None):
-                        if (fromFrame == None):
-                            animControl.loop(restart)
-                        else:
-                            animControl.loop(restart, fromFrame, toFrame)
-            else:
-                # loop a specific part
-                animControl = self.getAnimControl(animName, partName,
-                                                  thisLod)
-                if (animControl != None):
-                    if (fromFrame == None):
-                        animControl.loop(restart)
-                    else:
-                        animControl.loop(restart, fromFrame, toFrame)
+        if fromFrame == None:
+            for control in self.getAnimControls(animName, partName):
+                control.loop(restart)
+        else:
+            for control in self.getAnimControls(animName, partName):
+                control.loop(restart, fromFrame, toFrame)
 
     def pingpong(self, animName, fromFrame, toFrame, restart=1, partName=None):
         """pingpong(self, string, fromFrame, toFrame, int=1, string=None)
@@ -1024,42 +920,16 @@ class Actor(PandaObject, NodePath):
         restarting at zero frame if requested. If no part name
         is given then try to loop on all parts. NOTE: loops on
         all LOD's"""
-        for thisLod in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[thisLod]
-            if (partName == None):
-                # loop all parts
-                for thisPart in animControlDict.keys():
-                    animControl = self.getAnimControl(animName, thisPart,
-                                                        thisLod)
-                    if (animControl != None):
-                        animControl.pingpong(restart, fromFrame, toFrame)
-            else:
-                # loop a specific part
-                animControl = self.getAnimControl(animName, partName,
-                                                    thisLod)
-                if (animControl != None):
-                    animControl.pingpong(restart, fromFrame, toFrame)
+        for control in self.getAnimControls(animName, partName):
+            control.pingpong(restart, fromFrame, toFrame)
         
-    def pose(self, animName, frame, partName=None):
+    def pose(self, animName, frame, partName=None, lodName=None):
         """pose(self, string, int, string=None)
         Pose the actor in position found at given frame in the specified
         animation for the specified part. If no part is specified attempt
-        to apply pose to all parts. NOTE: poses all LODs"""
-        for thisLod in self.__animControlDict.keys():
-            animControlDict = self.__animControlDict[thisLod]
-            if (partName==None):
-                # pose all parts
-                for thisPart in animControlDict.keys():
-                    animControl = self.getAnimControl(animName, thisPart,
-                                                        thisLod)
-                    if (animControl != None):
-                        animControl.pose(frame)
-            else:
-                # pose a specific part
-                animControl = self.getAnimControl(animName, partName,
-                                                    thisLod)
-                if (animControl != None):
-                    animControl.pose(frame)
+        to apply pose to all parts."""
+        for control in self.getAnimControls(animName, partName, lodName):
+            control.pose(frame)
 
     def enableBlend(self, partName = None):
         """Enables blending of multiple animations simultaneously.
@@ -1106,32 +976,8 @@ class Actor(PandaObject, NodePath):
         animations; it only makes sense to call this after a previous
         call to enableBlend().
         """
-        if lodName == None:
-            for lodName, controlDict in self.__animControlDict.items():
-                if partName == None:
-                    for part in controlDict.keys():
-                        ac = self.getAnimControl(animName, part, lodName)
-                        if ac != None:
-                            ac.getPart().setControlEffect(ac, effect)
-                else:
-                    ac = self.getAnimControl(animName, partName, lodName)
-                    if ac != None:
-                        ac.getPart().setControlEffect(ac, effect)
-        else:
-            if partName == None:
-                controlDict = self.__animControlDict.get(lodName)
-                if controlDict != None:
-                    for part in controlDict.keys():
-                        ac = self.getAnimControl(animName, part, lodName)
-                        if ac != None:
-                            ac.getPart().setControlEffect(ac, effect)
-                else:
-                    Actor.notify.warning("couldn't find lod: %s" % (lodName))
-            else:
-                ac = self.getAnimControl(animName, partName, lodName)
-                if ac != None:
-                    ac.getPart().setControlEffect(ac, effect)
-            
+        for control in self.getAnimControls(animName, partName, lodName):
+            control.getPart().setControlEffect(control, effect)
 
     def getAnimControl(self, animName, partName, lodName="lodRoot"):
         """getAnimControl(self, string, string, string="lodRoot")
@@ -1139,26 +985,90 @@ class Actor(PandaObject, NodePath):
         a given anim and part. Return the animControl if present,
         or None otherwise
         """
-        if (self.__animControlDict.has_key(lodName)):
-            animControlDict = self.__animControlDict[lodName]
-            if (animControlDict.has_key(partName)):
-                if (animControlDict[partName].has_key(animName)):
-                    # make sure the anim is bound first
-                    self.bindAnim(animName, partName, lodName)
-                    return animControlDict[partName][animName][1]
-                else:
-                    # anim was not present
-                    Actor.notify.warning("couldn't find anim: %s" % (animName))
-            else:
-                # part was not present
-                Actor.notify.warning("couldn't find part: %s" % (partName))
+        animControlDict = self.__animControlDict.get(lodName)
+        # if this assertion fails, named lod was not present
+        assert(animControlDict != None)
+
+        animDict = animControlDict.get(partName)
+        if animDict == None:
+            # part was not present
+            Actor.notify.warning("couldn't find part: %s" % (partName))
         else:
-            # lod was not present
-            Actor.notify.warning("couldn't find lod: %s" % (lodName))
-            assert(0)
+            anim = animDict.get(animName)
+            if anim == None:
+                # anim was not present
+                Actor.notify.warning("couldn't find anim: %s" % (animName))
+            else:
+                # bind the animation first if we need to
+                if not isinstance(anim[1], AnimControl):
+                    self.__bindAnimToPart(animName, partName, lodName)
+                return anim[1]
             
         return None
+        
+    def getAnimControls(self, animName=None, partName=None, lodName=None):
+        """getAnimControls(self, string, string=None, string=None)
+
+        Returns a list of the AnimControls that represent the given
+        animation for the given part and the given lod.  If animName
+        is omitted, the currently-playing animation (or all
+        currently-playing animations) is returned.  If partName is
+        omitted, all parts are returned.  If lodName is omitted, all
+        LOD's are returned.
+        """
+        controls = []
 
+        # build list of lodNames and corresponding animControlDicts
+        # requested.
+        if lodName == None:
+            # Get all LOD's
+            animControlDictItems = self.__animControlDict.items()
+        else:
+            animControlDict = self.__animControlDict.get(lodName)
+            if animControlDict == None:
+                Actor.notify.warning("couldn't find lod: %s" % (lodName))
+                animControlDictItems = []
+            else:
+                animControlDictItems = [(lodName, animControlDict)]
+
+        for lodName, animControlDict in animControlDictItems:
+            # Now, build the list of partNames and the corresponding
+            # animDicts.
+            if partName == None:
+                # Get all parts
+                animDictItems = animControlDict.items()
+            else:
+                # Get a specific part
+                animDict = animControlDict.get(partName)
+                if animDict == None:
+                    # part was not present
+                    Actor.notify.warning("couldn't find part: %s" % (partName))
+                    animDictItems = []
+                else:
+                    animDictItems = [(partName, animDict)]
+                
+            if animName == None:
+                # get all playing animations
+                for thisPart, animDict in animDictItems:
+                    for anim in animDict.values():
+                        if isinstance(anim[1], AnimControl) and anim[1].isPlaying():
+                            controls.append(anim[1])
+            else:
+                # get the named animation only. 
+                for thisPart, animDict in animDictItems:
+                    anim = animDict.get(animName)
+                    if anim == None:
+                        # anim was not present
+                        Actor.notify.warning("couldn't find anim: %s" % (animName))
+                    else:
+                        # bind the animation first if we need to
+                        if not isinstance(anim[1], AnimControl):
+                            if self.__bindAnimToPart(animName, thisPart, lodName):
+                                controls.append(anim[1])
+                        else:
+                            controls.append(anim[1])
+
+        return controls
             
     def loadModel(self, modelPath, partName="modelRoot", lodName="lodRoot", copy = 1):
         """loadModel(self, string, string="modelRoot", string="lodRoot",
@@ -1327,6 +1237,8 @@ class Actor(PandaObject, NodePath):
         # enough to preload, fetch from disk :(
         animPath = self.__animControlDict[lodName][partName][animName][0]
         anim = loader.loadModelOnce(animPath)
+        if anim == None:
+            return None
         animBundle = \
                    (anim.find("**/+AnimBundleNode").node()).getBundle()
 

+ 14 - 17
direct/src/interval/ActorInterval.py

@@ -32,9 +32,11 @@ class ActorInterval(Interval.Interval):
         # Record class specific variables
         self.actor = actor
         self.animName = animName
+        self.controls = self.actor.getAnimControls(self.animName)
+        assert(len(self.controls) > 0)
         self.loopAnim = loop
-        self.frameRate = self.actor.getBaseFrameRate(self.animName) * playRate
-        self.numFrames = self.actor.getNumFrames(self.animName)
+        self.frameRate = self.controls[0].getAnim().getBaseFrameRate() * playRate
+        self.numFrames = self.controls[0].getNumFrames()
         # Compute start time
         self.startTime = startTime
         # If no name specified, use id as name
@@ -85,10 +87,13 @@ class ActorInterval(Interval.Interval):
         # Calc integer frame number
         frame = self.calcFrame(t)
         # Pose anim
-        self.actor.pose(self.animName, frame)
-        # Print debug information
-        self.notify.debug('goToT() - %s pose to frame: %d' %
-                          (self.name,frame))
+
+        # We use our pre-computed list of animControls for
+        # efficiency's sake, rather than going through the relatively
+        # expensive Actor interface every frame.
+        for control in self.controls:
+            control.pose(frame)
+            
         return frame
 
     def updateFunc(self, t, event=Interval.IVAL_NONE):
@@ -100,15 +105,11 @@ class ActorInterval(Interval.Interval):
             return
         # Update animation based upon current time
         # Pose or stop anim
-        if (t >= self.getDuration()):
+        if (t >= self.duration):
             self.actor.stop(self.animName)
-            frame = self.goToT(self.getDuration())
+            frame = self.goToT(self.duration)
             if self.loopAnim:
                 self.ignore(self.stopEvent)
-            # Print debug information
-            self.notify.debug(
-                'updateFunc() - %s stoping at frame: ' % self.name +
-                '%d Num frames: %d' % (frame, self.numFrames))
         elif self.loopAnim == 1:
             if event == Interval.IVAL_INIT:
                 # Pose anim
@@ -116,10 +117,6 @@ class ActorInterval(Interval.Interval):
                 # And start loop, restart flag says continue from current frame
                 self.actor.loop(self.animName, restart=0)
                 self.acceptOnce(self.stopEvent, self.actor.stop)
-                # Print debug information
-                self.notify.debug(
-                    'updateFunc() - IVAL_INIT %s looping anim' %
-                    self.name)
         else:
             # Pose anim
             self.goToT(t)
@@ -162,7 +159,7 @@ class LerpAnimInterval(Interval.Interval):
             return
 
         # First, normalize t into the range 0 .. 1, and apply the blendType.
-        t = self.blendType(float(t) / self.getDuration())
+        t = self.blendType(float(t) / self.duration)
 
         # Then compute the current weight based on the time elapsed so far.
         w = self.startWeight + t * self.deltaWeight