Просмотр исходного кода

enable intra-frame animation interpolation

David Rose 19 лет назад
Родитель
Сommit
731ab7b576

+ 68 - 13
direct/src/actor/Actor.py

@@ -1149,6 +1149,67 @@ class Actor(DirectObject, NodePath):
         for control in self.getAnimControls(animName, partName, lodName):
             control.pose(frame)
 
+    def setBlend(self, animBlend = None, frameBlend = None,
+                 blendType = None, partName = None):
+        """
+        Changes the way the Actor handles blending of multiple
+        different animations, and/or interpolation between consecutive
+        frames.
+
+        The animBlend and frameBlend parameters are boolean flags.
+        You may set either or both to True or False.  If you do not
+        specify them, they do not change from the previous value.
+        
+        When animBlend is True, multiple different animations may
+        simultaneously be playing on the Actor.  This means you may
+        call play(), loop(), or pose() on multiple animations and have
+        all of them contribute to the final pose each frame.
+
+        In this mode (that is, when animBlend is True), starting a
+        particular animation with play(), loop(), or pose() does not
+        implicitly make the animation visible; you must also call
+        setControlEffect() for each animation you wish to use to
+        indicate how much each animation contributes to the final
+        pose.
+
+        The frameBlend flag is unrelated to playing multiple
+        animations.  It controls whether the Actor smoothly
+        interpolates between consecutive frames of its animation (when
+        the flag is True) or holds each frame until the next one is
+        ready (when the flag is False).  The default value of
+        frameBlend is controlled by the interpolate-frames Config.prc
+        variable.
+
+        In either case, you may also specify blendType, which controls
+        the precise algorithm used to blend two or more different
+        matrix values into a final result.  Different skeleton
+        hierarchies may benefit from different algorithms.  The
+        default blendType is controlled by the anim-blend-type
+        Config.prc variable.
+        """
+        bundles = []
+        
+        for lodName, bundleDict in self.__partBundleDict.items():
+            if partName == None:
+                for partBundle in bundleDict.values():
+                    bundles.append(partBundle.node().getBundle())
+
+            else:
+                truePartName = self.__subpartDict.get(partName, [partName])[0]
+                partBundle = bundleDict.get(truePartName)
+                if partBundle != None:
+                    bundles.append(partBundle.node().getBundle())
+                else:
+                    Actor.notify.warning("Couldn't find part: %s" % (partName))
+
+        for bundle in bundles:
+            if blendType != None:
+                bundle.setBlendType(blendType)
+            if animBlend != None:
+                bundle.setAnimBlendFlag(animBlend)
+            if frameBlend != None:
+                bundle.setFrameBlendFlag(frameBlend)
+
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
         """
         Enables blending of multiple animations simultaneously.
@@ -1161,25 +1222,19 @@ class Actor(DirectObject, NodePath):
         animation visible; you must also call setControlEffect() for
         each animation you wish to use to indicate how much each
         animation contributes to the final pose.
+
+        This method is deprecated.  You should use setBlend() instead.
         """
-        for lodName, bundleDict in self.__partBundleDict.items():
-            if partName == None:
-                for partBundle in bundleDict.values():
-                    partBundle.node().getBundle().setBlendType(blendType)
-            else:
-                truePartName = self.__subpartDict.get(partName, [partName])[0]
-                partBundle = bundleDict.get(truePartName)
-                if partBundle != None:
-                    partBundle.node().getBundle().setBlendType(blendType)
-                else:
-                    Actor.notify.warning("Couldn't find part: %s" % (partName))
+        self.setBlend(animBlend = True, blendType = blendType, partName = partName)
 
     def disableBlend(self, partName = None):
         """
         Restores normal one-animation-at-a-time operation after a
         previous call to enableBlend().
+
+        This method is deprecated.  You should use setBlend() instead.
         """
-        self.enableBlend(PartBundle.BTSingle, partName)
+        self.setBlend(animBlend = False, partName = partName)
 
     def setControlEffect(self, animName, effect,
                          partName = None, lodName = None):
@@ -1187,7 +1242,7 @@ class Actor(DirectObject, NodePath):
         Sets the amount by which the named animation contributes to
         the overall pose.  This controls blending of multiple
         animations; it only makes sense to call this after a previous
-        call to enableBlend().
+        call to setBlend(animBlend = True).
         """        
         for control in self.getAnimControls(animName, partName, lodName):
             control.getPart().setControlEffect(control, effect)

+ 7 - 0
panda/src/chan/config_chan.cxx

@@ -83,6 +83,13 @@ PRC_DESC("Set this false to disable reading of compressed animation channels, "
          "might want to do this would be to speed load time when you don't "
          "care about what the animation looks like."));
 
+ConfigVariableBool interpolate_frames
+("interpolate-frames", false,
+PRC_DESC("Set this true to interpolate character animations between frames, "
+         "or false to hold each frame until the next one is ready.  This can "
+         "also be changed on a per-character basis with "
+         "PartBundle::set_frame_blend_flag()."));
+
 
 ConfigureFn(config_chan) {
   AnimBundle::init_type();

+ 1 - 0
panda/src/chan/config_chan.h

@@ -30,5 +30,6 @@ NotifyCategoryDecl(chan, EXPCL_PANDA, EXPTP_PANDA);
 EXPCL_PANDA extern ConfigVariableBool compress_channels;
 EXPCL_PANDA extern ConfigVariableInt compress_chan_quality;
 EXPCL_PANDA extern ConfigVariableBool read_compressed_channels;
+EXPCL_PANDA extern ConfigVariableBool interpolate_frames;
 
 #endif

+ 109 - 27
panda/src/chan/movingPartMatrix.cxx

@@ -69,7 +69,7 @@ get_blend_value(const PartBundle *root) {
     // No channel is bound; supply the default value.
     _value = _initial_value;
 
-  } else if (cdata->_blend.size() == 1) {
+  } else if (cdata->_blend.size() == 1 && !cdata->_frame_blend_flag) {
     // A single value, the normal case.
     AnimControl *control = (*cdata->_blend.begin()).first;
 
@@ -85,10 +85,11 @@ get_blend_value(const PartBundle *root) {
     }
 
   } else {
-    // A blend of two or more values.
+    // A blend of two or more values, either between multiple
+    // different animations, or between consecutive frames of the same
+    // animation (or both).
 
     switch (cdata->_blend_type) {
-    case PartBundle::BT_single:
     case PartBundle::BT_linear:
       {
         // An ordinary, linear blend.
@@ -107,8 +108,18 @@ get_blend_value(const PartBundle *root) {
           if (channel != (ChannelType *)NULL) {
             ValueType v;
             channel->get_value(control->get_frame(), v);
-            
-            _value += v * effect;
+
+            if (!cdata->_frame_blend_flag) {
+              // Hold the current frame until the next one is ready.
+              _value += v * effect;
+            } else {
+              // Blend between successive frames.
+              float frac = (float)control->get_frac();
+              _value += v * (effect * (1.0f - frac));
+
+              channel->get_value(control->get_next_frame(), v);
+              _value += v * (effect * frac);
+            }
             net += effect;
           }
         }
@@ -144,15 +155,35 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             ValueType v;
-            channel->get_value_no_scale_shear(control->get_frame(), v);
             LVecBase3f iscale, ishear;
-            channel->get_scale(control->get_frame(), iscale);
-            channel->get_shear(control->get_frame(), ishear);
+            channel->get_value_no_scale_shear(frame, v);
+            channel->get_scale(frame, iscale);
+            channel->get_shear(frame, ishear);
             
-            _value += v * effect;
-            scale += iscale * effect;
-            shear += ishear * effect;
+            if (!cdata->_frame_blend_flag) {
+              // Hold the current frame until the next one is ready.
+              _value += v * effect;
+              scale += iscale * effect;
+              shear += ishear * effect;
+            } else {
+              // Blend between successive frames.
+              float frac = (float)control->get_frac();
+              float e0 = effect * (1.0f - frac);
+              _value += v * e0;
+              scale += iscale * e0;
+              shear += ishear * e0;
+
+              int next_frame = control->get_next_frame();
+              channel->get_value_no_scale_shear(next_frame, v);
+              channel->get_scale(next_frame, iscale);
+              channel->get_shear(next_frame, ishear);
+              float e1 = effect * frac;
+              _value += v * e1;
+              scale += iscale * e1;
+              shear += ishear * e1;
+            }
             net += effect;
           }
         }
@@ -193,16 +224,41 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             LVecBase3f iscale, ihpr, ipos, ishear;
-            channel->get_scale(control->get_frame(), iscale);
-            channel->get_hpr(control->get_frame(), ihpr);
-            channel->get_pos(control->get_frame(), ipos);
-            channel->get_shear(control->get_frame(), ishear);
+            channel->get_scale(frame, iscale);
+            channel->get_hpr(frame, ihpr);
+            channel->get_pos(frame, ipos);
+            channel->get_shear(frame, ishear);
             
-            scale += iscale * effect;
-            hpr += ihpr * effect;
-            pos += ipos * effect;
-            shear += ishear * effect;
+            if (!cdata->_frame_blend_flag) {
+              // Hold the current frame until the next one is ready.
+              scale += iscale * effect;
+              hpr += ihpr * effect;
+              pos += ipos * effect;
+              shear += ishear * effect;
+            } else {
+              // Blend between successive frames.
+              float frac = (float)control->get_frac();
+              float e0 = effect * (1.0f - frac);
+
+              scale += iscale * e0;
+              hpr += ihpr * e0;
+              pos += ipos * e0;
+              shear += ishear * e0;
+
+              int next_frame = control->get_next_frame();
+              channel->get_scale(next_frame, iscale);
+              channel->get_hpr(next_frame, ihpr);
+              channel->get_pos(next_frame, ipos);
+              channel->get_shear(next_frame, ishear);
+              float e1 = effect * frac;
+
+              scale += iscale * e1;
+              hpr += ihpr * e1;
+              pos += ipos * e1;
+              shear += ishear * e1;
+            }
             net += effect;
           }
         }
@@ -241,17 +297,43 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             LVecBase3f iscale, ipos, ishear;
             LQuaternionf iquat;
-            channel->get_scale(control->get_frame(), iscale);
-            channel->get_quat(control->get_frame(), iquat);
-            channel->get_pos(control->get_frame(), ipos);
-            channel->get_shear(control->get_frame(), ishear);
+            channel->get_scale(frame, iscale);
+            channel->get_quat(frame, iquat);
+            channel->get_pos(frame, ipos);
+            channel->get_shear(frame, ishear);
             
-            scale += iscale * effect;
-            quat += iquat * effect;
-            pos += ipos * effect;
-            shear += ishear * effect;
+            if (!cdata->_frame_blend_flag) {
+              // Hold the current frame until the next one is ready.
+              scale += iscale * effect;
+              quat += iquat * effect;
+              pos += ipos * effect;
+              shear += ishear * effect;
+
+            } else {
+              // Blend between successive frames.
+              float frac = (float)control->get_frac();
+              float e0 = effect * (1.0f - frac);
+
+              scale += iscale * e0;
+              quat += iquat * e0;
+              pos += ipos * e0;
+              shear += ishear * e0;
+
+              int next_frame = control->get_next_frame();
+              channel->get_scale(next_frame, iscale);
+              channel->get_quat(next_frame, iquat);
+              channel->get_pos(next_frame, ipos);
+              channel->get_shear(next_frame, ishear);
+              float e1 = effect * frac;
+
+              scale += iscale * e1;
+              quat += iquat * e1;
+              pos += ipos * e1;
+              shear += ishear * e1;
+            }
             net += effect;
           }
         }

+ 12 - 2
panda/src/chan/movingPartScalar.cxx

@@ -54,7 +54,7 @@ get_blend_value(const PartBundle *root) {
     // No channel is bound; supply the default value.
     _value = _initial_value;
 
-  } else if (cdata->_blend.size() == 1) {
+  } else if (cdata->_blend.size() == 1 && !cdata->_frame_blend_flag) {
     // A single value, the normal case.
     AnimControl *control = (*cdata->_blend.begin()).first;
 
@@ -87,7 +87,17 @@ get_blend_value(const PartBundle *root) {
         ValueType v;
         channel->get_value(control->get_frame(), v);
         
-        _value += v * effect;
+        if (!cdata->_frame_blend_flag) {
+          // Hold the current frame until the next one is ready.
+          _value += v * effect;
+        } else {
+          // Blend between successive frames.
+          float frac = (float)control->get_frac();
+          _value += v * (effect * (1.0f - frac));
+
+          channel->get_value(control->get_next_frame(), v);
+          _value += v * (effect * frac);
+        }
         net += effect;
       }
     }

+ 83 - 7
panda/src/chan/partBundle.I

@@ -17,11 +17,31 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::set_blend_type
+//       Access: Published
+//  Description: Defines the algorithm that is used when blending
+//               multiple frames or multiple animations together, when
+//               either anim_blend_flag or frame_blend_flag is set
+//               to true.
+//
+//               See partBundle.h for a description of the meaning of
+//               each of the BlendType values.
+////////////////////////////////////////////////////////////////////
+INLINE void PartBundle::
+set_blend_type(PartBundle::BlendType bt) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+  CDWriter cdata(_cycler);
+  cdata->_blend_type = bt;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::get_blend_type
 //       Access: Published
-//  Description: Returns the way the character responds to multiple
-//               animations being bound simultaneously.
+//  Description: Returns the algorithm that is used when blending
+//               multiple frames or multiple animations together, when
+//               either anim_blend_flag or frame_blend_flag is set
+//               to true.
 ////////////////////////////////////////////////////////////////////
 INLINE PartBundle::BlendType PartBundle::
 get_blend_type() const {
@@ -29,6 +49,62 @@ get_blend_type() const {
   return cdata->_blend_type;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::get_anim_blend_flag
+//       Access: Published
+//  Description: Returns whether the character allows multiple
+//               different animations to be bound simultaneously.  See
+//               set_anim_blend_flag().
+////////////////////////////////////////////////////////////////////
+INLINE bool PartBundle::
+get_anim_blend_flag() const {
+  CDReader cdata(_cycler);
+  return cdata->_anim_blend_flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::set_frame_blend_flag
+//       Access: Published
+//  Description: Specifies whether the character interpolates (blends)
+//               between two sequential frames of an active animation,
+//               showing a smooth intra-frame motion, or whether it
+//               holds each frame until the next frame is ready,
+//               showing precisely the specified animation.
+//
+//               When this value is false, the character holds each
+//               frame until the next is ready.  When this is true,
+//               the character will interpolate between two
+//               consecutive frames of animation for each frame the
+//               animation is onscreen, according to the amount of
+//               time elapsed between the frames.
+//
+//               The default value of this flag is determined by the
+//               interpolate-frames Config.prc variable.
+//
+//               Use set_blend_type() to change the algorithm that the
+//               character uses to interpolate matrix positions.
+////////////////////////////////////////////////////////////////////
+INLINE void PartBundle::
+set_frame_blend_flag(bool frame_blend_flag) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+  CDWriter cdata(_cycler);
+  cdata->_frame_blend_flag = frame_blend_flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::get_frame_blend_flag
+//       Access: Published
+//  Description: Returns whether the character interpolates (blends)
+//               between two sequential animation frames, or whether
+//               it holds the current frame until the next one is
+//               ready.  See set_frame_blend_flag().
+////////////////////////////////////////////////////////////////////
+INLINE bool PartBundle::
+get_frame_blend_flag() const {
+  CDReader cdata(_cycler);
+  return cdata->_frame_blend_flag;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::get_node
 //       Access: Published
@@ -50,11 +126,11 @@ get_node() const {
 //               Zero indicates the animation does not affect the
 //               character, and one means it does.
 //
-//               If the blend_type is not BT_single (see
-//               set_blend_type()), it is possible to have multiple
-//               AnimControls in effect simultaneously.  In this case,
-//               the effect is a weight that indicates the relative
-//               importance of each AnimControl to the final
+//               If the _anim_blend_flag is not false (see
+//               set_anim_blend_flag()), it is possible to have
+//               multiple AnimControls in effect simultaneously.  In
+//               this case, the effect is a weight that indicates the
+//               relative importance of each AnimControl to the final
 //               animation.
 ////////////////////////////////////////////////////////////////////
 void PartBundle::

+ 100 - 32
panda/src/chan/partBundle.cxx

@@ -22,16 +22,24 @@
 #include "animControl.h"
 #include "config_chan.h"
 #include "bitArray.h"
-
+#include "string_utils.h"
 #include "indent.h"
 #include "datagram.h"
 #include "datagramIterator.h"
 #include "bamReader.h"
 #include "bamWriter.h"
+#include "configVariableEnum.h"
 
 TypeHandle PartBundle::_type_handle;
 
 
+static ConfigVariableEnum<PartBundle::BlendType> anim_blend_type
+("anim-blend-type", PartBundle::BT_normalized_linear,
+ PRC_DESC("The default blend type to use for blending animations between "
+          "frames, or between multiple animations.  See interpolate-frames, "
+          "and also PartBundle::set_anim_blend_flag() and "
+          "PartBundle::set_frame_blend_flag()."));
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::Copy Constructor
@@ -71,37 +79,33 @@ make_copy() const {
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PartBundle::set_blend_type
+//     Function: PartBundle::set_anim_blend_flag
 //       Access: Published
 //  Description: Defines the way the character responds to multiple
-//               set_control_effect()).  By default, the blend_type is
-//               BT_single, which disallows multiple animations.  In
-//               BT_single mode, it is not necessary to explicitly set
-//               the control_effect when starting an animation;
-//               starting the animation will implicitly remove the
-//               control_effect from the previous animation and set it
-//               on the current one.
+//               calls to set_control_effect()).  By default, this
+//               flag is set false, which disallows multiple
+//               animations.  When this flag is false, it is not
+//               necessary to explicitly set the control_effect when
+//               starting an animation; starting the animation will
+//               implicitly remove the control_effect from the
+//               previous animation and set it on the current one.
 //
-//               However, if the blend_type is set to any other value,
-//               the control_effect must be explicitly set via
-//               set_control_effect() whenever an animation is to
-//               affect the character.
-//
-//               See partBundle.h for a description of the meaning of
-//               each of the BlendType values.
+//               However, if this flag is set true, the control_effect
+//               must be explicitly set via set_control_effect()
+//               whenever an animation is to affect the character.
 ////////////////////////////////////////////////////////////////////
 void PartBundle::
-set_blend_type(BlendType bt) {
+set_anim_blend_flag(bool anim_blend_flag) {
   nassertv(Thread::get_current_pipeline_stage() == 0);
 
   CDLockedReader cdata(_cycler);
-  if (cdata->_blend_type != bt) {
+  if (cdata->_anim_blend_flag != anim_blend_flag) {
     CDWriter cdataw(_cycler, cdata);
-    cdataw->_blend_type = bt;
+    cdataw->_anim_blend_flag = anim_blend_flag;
 
-    if (cdataw->_blend_type == BT_single && cdataw->_blend.size() > 1) {
-      // If we just changed to a single blend type, i.e. no blending,
-      // we should eliminate all the AnimControls other than the
+    if (!anim_blend_flag && cdataw->_blend.size() > 1) {
+      // If we just changed to disallow animation blending, we should
+      // eliminate all the AnimControls other than the
       // most-recently-added one.
 
       nassertv(cdataw->_last_control_set != NULL);
@@ -245,6 +249,12 @@ update() {
   {
     CDReader cdata(_cycler, current_thread);
     anim_changed = cdata->_anim_changed;
+
+    if (cdata->_frame_blend_flag) {
+      // If the intra-frame blend flag is on, we will just assume the
+      // animation changes every time we call update().
+      anim_changed = true;
+    }
   }
   bool any_changed = do_update(this, NULL, false, anim_changed, current_thread);
 
@@ -300,9 +310,10 @@ control_activated(AnimControl *control) {
   nassertv(control->get_part() == this);
 
   CDLockedReader cdata(_cycler);
-  // If (and only if) our blend type is BT_single, which means no
-  // blending, then starting an animation implicitly enables it.
-  if (cdata->_blend_type == BT_single) {
+
+  // If (and only if) our anim_blend_flag is false, then starting an
+  // animation implicitly enables it.
+  if (!cdata->_anim_blend_flag) {
     CDWriter cdataw(_cycler, cdata);
     do_set_control_effect(control, 1.0f, cdataw);
   }
@@ -330,9 +341,9 @@ do_set_control_effect(AnimControl *control, float effect, CData *cdata) {
   } else {
     // Otherwise we define it.
 
-    // If we currently have BT_single, we only allow one AnimControl
-    // at a time.  Stop all of the other AnimControls.
-    if (cdata->_blend_type == BT_single) {
+    // If anim_blend_flag is false, we only allow one AnimControl at a
+    // time.  Stop all of the other AnimControls.
+    if (!cdata->_anim_blend_flag) {
       clear_and_stop_intersecting(control, cdata);
     }
 
@@ -388,9 +399,9 @@ recompute_net_blend(CData *cdata) {
 //  Description: Removes and stops all the currently activated
 //               AnimControls that animate some joints also animated
 //               by the indicated AnimControl.  This is a special
-//               internal function that's only called when _blend_type
-//               is BT_single, to automatically stop all the other
-//               currently-executing animations.
+//               internal function that's only called when
+//               _anim_blend_flag is false, to automatically stop all
+//               the other currently-executing animations.
 ////////////////////////////////////////////////////////////////////
 void PartBundle::
 clear_and_stop_intersecting(AnimControl *control, CData *cdata) {
@@ -470,7 +481,9 @@ register_with_read_factory()
 ////////////////////////////////////////////////////////////////////
 PartBundle::CData::
 CData() {
-  _blend_type = BT_single;
+  _blend_type = anim_blend_type;
+  _anim_blend_flag = false;
+  _frame_blend_flag = interpolate_frames;
   _last_control_set = NULL;
   _net_blend = 0.0f;
   _anim_changed = false;
@@ -503,3 +516,58 @@ CycleData *PartBundle::CData::
 make_copy() const {
   return new CData(*this);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::BlendType output operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+ostream &
+operator << (ostream &out, PartBundle::BlendType blend_type) {
+  switch (blend_type) {
+    case PartBundle::BT_linear:
+      return out << "linear";
+
+    case PartBundle::BT_normalized_linear:
+      return out << "normalized_linear";
+
+    case PartBundle::BT_componentwise:
+      return out << "componentwise";
+
+    case PartBundle::BT_componentwise_quat:
+      return out << "componentwise_quat";
+  }
+  
+  chan_cat->error()
+    << "Invalid BlendType value: " << (int)blend_type << "\n";
+  nassertr(false, out);
+  return out;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::BlendType input operator
+//  Description:
+////////////////////////////////////////////////////////////////////
+istream &
+operator >> (istream &in, PartBundle::BlendType &blend_type) {
+  string word;
+  in >> word;
+
+  if (cmp_nocase_uh(word, "linear") == 0) {
+    blend_type = PartBundle::BT_linear;
+
+  } else if (cmp_nocase_uh(word, "normalized_linear") == 0) {
+    blend_type = PartBundle::BT_normalized_linear;
+
+  } else if (cmp_nocase_uh(word, "componentwise") == 0) {
+    blend_type = PartBundle::BT_componentwise;
+
+  } else if (cmp_nocase_uh(word, "componentwise_quat") == 0) {
+    blend_type = PartBundle::BT_componentwise_quat;
+
+  } else {
+    chan_cat->error()
+      << "Invalid BlendType string: " << word << "\n";
+    blend_type = PartBundle::BT_linear;
+  }
+  return in;
+}

+ 14 - 12
panda/src/chan/partBundle.h

@@ -60,18 +60,9 @@ PUBLISHED:
 
   // This is the parameter to set_blend_type() and specifies the kind
   // of blending operation to be performed when multiple controls are
-  // in effect simultaneously (see set_control_effect()).
+  // in effect simultaneously (see set_control_effect()) or between
+  // sequential frames of the animation.
   enum BlendType {
-
-    // BT_single means no blending is performed.  Only one AnimControl
-    // is allowed to be set at a time in set_control_effect().  In
-    // this mode, and this mode only, activating a particular
-    // AnimControl (via play(), loop(), or pose()) will implicitly set
-    // the bundle's control_effect to 100% of that particular
-    // AnimControl, removing any AnimControls previously set.  In
-    // other modes, the control_effect must be set manually.
-    BT_single,
-
     // BT_linear does a componentwise average of all blended matrices,
     // which is a linear blend.  The result of this is that if a
     // particular vertex would have been at point P in one animation
@@ -100,9 +91,15 @@ PUBLISHED:
     BT_componentwise_quat,
   };
 
-  void set_blend_type(BlendType bt);
+  INLINE void set_blend_type(BlendType bt);
   INLINE BlendType get_blend_type() const;
 
+  void set_anim_blend_flag(bool anim_blend_flag);
+  INLINE bool get_anim_blend_flag() const;
+
+  INLINE void set_frame_blend_flag(bool frame_blend_flag);
+  INLINE bool get_frame_blend_flag() const;
+
   INLINE PartBundleNode *get_node() const;
 
   void clear_control_effects();
@@ -147,6 +144,8 @@ private:
     }
 
     BlendType _blend_type;
+    bool _anim_blend_flag;
+    bool _frame_blend_flag;
     AnimControl *_last_control_set;
     ChannelBlend _blend;
     float _net_blend;
@@ -193,6 +192,9 @@ inline ostream &operator <<(ostream &out, const PartBundle &bundle) {
   return out;
 }
 
+ostream &operator <<(ostream &out, PartBundle::BlendType blend_type);
+istream &operator >>(istream &in, PartBundle::BlendType &blend_type);
+
 #include "partBundle.I"
 
 #endif

+ 11 - 6
panda/src/char/character.cxx

@@ -54,6 +54,8 @@ Character(const Character &copy) :
   // update our _parts list.
 
   copy_joints(get_bundle(), copy.get_bundle());
+
+  _last_auto_update = -1.0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -154,15 +156,18 @@ cull_callback(CullTraverser *, CullTraverserData &) {
   // optimization later, to handle characters that might animate
   // themselves in front of the view frustum.
 
-  PStatTimer timer(_joints_pcollector);
+  double now = ClockObject::get_global_clock()->get_frame_time();
+  if (now != _last_auto_update) {
+    _last_auto_update = now;
 
-  if (char_cat.is_spam()) {
-    double now = ClockObject::get_global_clock()->get_frame_time();
-    char_cat.spam() << "Animating " << *this << " at time " << now << "\n";
+    PStatTimer timer(_joints_pcollector);
+    if (char_cat.is_spam()) {
+      char_cat.spam() << "Animating " << *this << " at time " << now << "\n";
+    }
+    
+    do_update();
   }
 
-  do_update();
-
   return true;
 }
 

+ 2 - 0
panda/src/char/character.h

@@ -119,6 +119,8 @@ private:
   typedef vector_PartGroupStar Parts;
   Parts _parts;
 
+  double _last_auto_update;
+
   // Statistics
   PStatCollector _joints_pcollector;
   PStatCollector _skinning_pcollector;

+ 28 - 5
panda/src/putil/animInterface.I

@@ -119,7 +119,7 @@ pingpong(bool restart, double from, double to) {
 ////////////////////////////////////////////////////////////////////
 INLINE void AnimInterface::
 stop() {
-  pose(get_frame());
+  pose(get_full_fframe());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -129,7 +129,7 @@ stop() {
 //               it there.
 ////////////////////////////////////////////////////////////////////
 INLINE void AnimInterface::
-pose(int frame) {
+pose(double frame) {
   {
     CDWriter cdata(_cycler);
     cdata->pose(frame);
@@ -192,7 +192,30 @@ get_frame() const {
   if (num_frames <= 0) {
     return 0;
   }
-  return cmod(get_full_frame(), num_frames);
+  CDReader cdata(_cycler);
+  return cmod(cdata->get_full_frame(0), num_frames);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: AnimInterface::get_next_frame
+//       Access: Published
+//  Description: Returns the current integer frame number + 1,
+//               constrained to the range 0 <= f < get_num_frames().
+//
+//               If the play mode is PM_play, this will clamp to the
+//               same value as get_frame() at the end of the
+//               animation.  If the play mode is any other value, this
+//               will wrap around to frame 0 at the end of the
+//               animation.
+////////////////////////////////////////////////////////////////////
+INLINE int AnimInterface::
+get_next_frame() const {
+  int num_frames = get_num_frames();
+  if (num_frames <= 0) {
+    return 0;
+  }
+  CDReader cdata(_cycler);
+  return cmod(cdata->get_full_frame(1), num_frames);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -229,7 +252,7 @@ get_frac() const {
 INLINE int AnimInterface::
 get_full_frame() const {
   CDReader cdata(_cycler);
-  return cdata->get_full_frame();
+  return cdata->get_full_frame(0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -308,7 +331,7 @@ set_num_frames(int num_frames) {
 ////////////////////////////////////////////////////////////////////
 INLINE double AnimInterface::CData::
 get_frac() const {
-  return get_full_fframe() - (double)get_full_frame();
+  return get_full_fframe() - (double)get_full_frame(0);
 }
 
 INLINE ostream &

+ 15 - 14
panda/src/putil/animInterface.cxx

@@ -229,7 +229,7 @@ fillin(DatagramIterator &scan, BamReader *) {
 void AnimInterface::CData::
 play(double from, double to) {
   if (from >= to) {
-    pose((int)from);
+    pose(from);
     return;
   }
 
@@ -259,7 +259,7 @@ play(double from, double to) {
 void AnimInterface::CData::
 loop(bool restart, double from, double to) {
   if (from >= to) {
-    pose((int)from);
+    pose(from);
     return;
   }
 
@@ -292,7 +292,7 @@ loop(bool restart, double from, double to) {
 void AnimInterface::CData::
 pingpong(bool restart, double from, double to) {
   if (from >= to) {
-    pose((int)from);
+    pose(from);
     return;
   }
 
@@ -322,20 +322,21 @@ pingpong(bool restart, double from, double to) {
 //               it there.
 ////////////////////////////////////////////////////////////////////
 void AnimInterface::CData::
-pose(int frame) {
+pose(double frame) {
   _play_mode = PM_pose;
   _start_time = ClockObject::get_global_clock()->get_frame_time();
-  _start_frame = (double)frame;
+  _start_frame = frame;
   _play_frames = 0.0;
-  _from_frame = frame;
-  _to_frame = frame;
+  _from_frame = (int)floor(frame);
+  _to_frame = (int)floor(frame);
   _paused_f = 0.0;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: AnimInterface::CData::get_full_frame
 //       Access: Public
-//  Description: Returns the current integer frame number.
+//  Description: Returns the current integer frame number, plus the
+//               indicated increment.
 //
 //               Unlike the value returned by get_frame(), this frame
 //               number may extend beyond the range of
@@ -347,8 +348,8 @@ pose(int frame) {
 //               to_frame in the play() method.
 ////////////////////////////////////////////////////////////////////
 int AnimInterface::CData::
-get_full_frame() const {
-  int frame = (int)floor(get_full_fframe());
+get_full_frame(int increment) const {
+  int frame = (int)floor(get_full_fframe()) + increment;
   if (_play_mode == PM_play) {
     // In play mode, we never let the return value exceed
     // (_from_frame, _to_frame).
@@ -435,19 +436,19 @@ void AnimInterface::CData::
 output(ostream &out) const {
   switch (_play_mode) {
   case PM_pose:
-    out << "pose, frame " << get_full_frame();
+    out << "pose, frame " << get_full_fframe();
     return;
 
   case PM_play:
-    out << "play, frame " << get_full_frame();
+    out << "play, frame " << get_full_fframe();
     return;
 
   case PM_loop:
-    out << "loop, frame " << get_full_frame();
+    out << "loop, frame " << get_full_fframe();
     return;
 
   case PM_pingpong:
-    out << "pingpong, frame " << get_full_frame();
+    out << "pingpong, frame " << get_full_fframe();
     return;
   }
 }

+ 4 - 3
panda/src/putil/animInterface.h

@@ -54,7 +54,7 @@ PUBLISHED:
   INLINE void pingpong(bool restart);
   INLINE void pingpong(bool restart, double from, double to);
   INLINE void stop();
-  INLINE void pose(int frame);
+  INLINE void pose(double frame);
 
   INLINE void set_play_rate(double play_rate);
   INLINE double get_play_rate() const;
@@ -62,6 +62,7 @@ PUBLISHED:
   virtual int get_num_frames() const;
 
   INLINE int get_frame() const;
+  INLINE int get_next_frame() const;
   INLINE double get_frac() const;
   INLINE int get_full_frame() const;
   INLINE double get_full_fframe() const;
@@ -102,10 +103,10 @@ private:
     void play(double from, double to);
     void loop(bool restart, double from, double to);
     void pingpong(bool restart, double from, double to);
-    void pose(int frame);
+    void pose(double frame);
 
     INLINE double get_frac() const;
-    int get_full_frame() const;
+    int get_full_frame(int increment) const;
     double get_full_fframe() const;
     bool is_playing() const;