Prechádzať zdrojové kódy

enable intra-frame animation interpolation

David Rose 19 rokov pred
rodič
commit
731ab7b576

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

@@ -1149,6 +1149,67 @@ class Actor(DirectObject, NodePath):
         for control in self.getAnimControls(animName, partName, lodName):
         for control in self.getAnimControls(animName, partName, lodName):
             control.pose(frame)
             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):
     def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None):
         """
         """
         Enables blending of multiple animations simultaneously.
         Enables blending of multiple animations simultaneously.
@@ -1161,25 +1222,19 @@ class Actor(DirectObject, NodePath):
         animation visible; you must also call setControlEffect() for
         animation visible; you must also call setControlEffect() for
         each animation you wish to use to indicate how much each
         each animation you wish to use to indicate how much each
         animation contributes to the final pose.
         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):
     def disableBlend(self, partName = None):
         """
         """
         Restores normal one-animation-at-a-time operation after a
         Restores normal one-animation-at-a-time operation after a
         previous call to enableBlend().
         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,
     def setControlEffect(self, animName, effect,
                          partName = None, lodName = None):
                          partName = None, lodName = None):
@@ -1187,7 +1242,7 @@ class Actor(DirectObject, NodePath):
         Sets the amount by which the named animation contributes to
         Sets the amount by which the named animation contributes to
         the overall pose.  This controls blending of multiple
         the overall pose.  This controls blending of multiple
         animations; it only makes sense to call this after a previous
         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):
         for control in self.getAnimControls(animName, partName, lodName):
             control.getPart().setControlEffect(control, effect)
             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 "
          "might want to do this would be to speed load time when you don't "
          "care about what the animation looks like."));
          "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) {
 ConfigureFn(config_chan) {
   AnimBundle::init_type();
   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 ConfigVariableBool compress_channels;
 EXPCL_PANDA extern ConfigVariableInt compress_chan_quality;
 EXPCL_PANDA extern ConfigVariableInt compress_chan_quality;
 EXPCL_PANDA extern ConfigVariableBool read_compressed_channels;
 EXPCL_PANDA extern ConfigVariableBool read_compressed_channels;
+EXPCL_PANDA extern ConfigVariableBool interpolate_frames;
 
 
 #endif
 #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.
     // No channel is bound; supply the default value.
     _value = _initial_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.
     // A single value, the normal case.
     AnimControl *control = (*cdata->_blend.begin()).first;
     AnimControl *control = (*cdata->_blend.begin()).first;
 
 
@@ -85,10 +85,11 @@ get_blend_value(const PartBundle *root) {
     }
     }
 
 
   } else {
   } 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) {
     switch (cdata->_blend_type) {
-    case PartBundle::BT_single:
     case PartBundle::BT_linear:
     case PartBundle::BT_linear:
       {
       {
         // An ordinary, linear blend.
         // An ordinary, linear blend.
@@ -107,8 +108,18 @@ get_blend_value(const PartBundle *root) {
           if (channel != (ChannelType *)NULL) {
           if (channel != (ChannelType *)NULL) {
             ValueType v;
             ValueType v;
             channel->get_value(control->get_frame(), 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;
             net += effect;
           }
           }
         }
         }
@@ -144,15 +155,35 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             ValueType v;
             ValueType v;
-            channel->get_value_no_scale_shear(control->get_frame(), v);
             LVecBase3f iscale, ishear;
             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;
             net += effect;
           }
           }
         }
         }
@@ -193,16 +224,41 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             LVecBase3f iscale, ihpr, ipos, ishear;
             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;
             net += effect;
           }
           }
         }
         }
@@ -241,17 +297,43 @@ get_blend_value(const PartBundle *root) {
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           nassertv(channel_index >= 0 && channel_index < (int)_channels.size());
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           ChannelType *channel = DCAST(ChannelType, _channels[channel_index]);
           if (channel != (ChannelType *)NULL) {
           if (channel != (ChannelType *)NULL) {
+            int frame = control->get_frame();
             LVecBase3f iscale, ipos, ishear;
             LVecBase3f iscale, ipos, ishear;
             LQuaternionf iquat;
             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;
             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.
     // No channel is bound; supply the default value.
     _value = _initial_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.
     // A single value, the normal case.
     AnimControl *control = (*cdata->_blend.begin()).first;
     AnimControl *control = (*cdata->_blend.begin()).first;
 
 
@@ -87,7 +87,17 @@ get_blend_value(const PartBundle *root) {
         ValueType v;
         ValueType v;
         channel->get_value(control->get_frame(), 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;
         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
 //     Function: PartBundle::get_blend_type
 //       Access: Published
 //       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::
 INLINE PartBundle::BlendType PartBundle::
 get_blend_type() const {
 get_blend_type() const {
@@ -29,6 +49,62 @@ get_blend_type() const {
   return cdata->_blend_type;
   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
 //     Function: PartBundle::get_node
 //       Access: Published
 //       Access: Published
@@ -50,11 +126,11 @@ get_node() const {
 //               Zero indicates the animation does not affect the
 //               Zero indicates the animation does not affect the
 //               character, and one means it does.
 //               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.
 //               animation.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PartBundle::
 void PartBundle::

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

@@ -22,16 +22,24 @@
 #include "animControl.h"
 #include "animControl.h"
 #include "config_chan.h"
 #include "config_chan.h"
 #include "bitArray.h"
 #include "bitArray.h"
-
+#include "string_utils.h"
 #include "indent.h"
 #include "indent.h"
 #include "datagram.h"
 #include "datagram.h"
 #include "datagramIterator.h"
 #include "datagramIterator.h"
 #include "bamReader.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "bamWriter.h"
+#include "configVariableEnum.h"
 
 
 TypeHandle PartBundle::_type_handle;
 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
 //     Function: PartBundle::Copy Constructor
@@ -71,37 +79,33 @@ make_copy() const {
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: PartBundle::set_blend_type
+//     Function: PartBundle::set_anim_blend_flag
 //       Access: Published
 //       Access: Published
 //  Description: Defines the way the character responds to multiple
 //  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::
 void PartBundle::
-set_blend_type(BlendType bt) {
+set_anim_blend_flag(bool anim_blend_flag) {
   nassertv(Thread::get_current_pipeline_stage() == 0);
   nassertv(Thread::get_current_pipeline_stage() == 0);
 
 
   CDLockedReader cdata(_cycler);
   CDLockedReader cdata(_cycler);
-  if (cdata->_blend_type != bt) {
+  if (cdata->_anim_blend_flag != anim_blend_flag) {
     CDWriter cdataw(_cycler, cdata);
     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.
       // most-recently-added one.
 
 
       nassertv(cdataw->_last_control_set != NULL);
       nassertv(cdataw->_last_control_set != NULL);
@@ -245,6 +249,12 @@ update() {
   {
   {
     CDReader cdata(_cycler, current_thread);
     CDReader cdata(_cycler, current_thread);
     anim_changed = cdata->_anim_changed;
     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);
   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);
   nassertv(control->get_part() == this);
 
 
   CDLockedReader cdata(_cycler);
   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);
     CDWriter cdataw(_cycler, cdata);
     do_set_control_effect(control, 1.0f, cdataw);
     do_set_control_effect(control, 1.0f, cdataw);
   }
   }
@@ -330,9 +341,9 @@ do_set_control_effect(AnimControl *control, float effect, CData *cdata) {
   } else {
   } else {
     // Otherwise we define it.
     // 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);
       clear_and_stop_intersecting(control, cdata);
     }
     }
 
 
@@ -388,9 +399,9 @@ recompute_net_blend(CData *cdata) {
 //  Description: Removes and stops all the currently activated
 //  Description: Removes and stops all the currently activated
 //               AnimControls that animate some joints also animated
 //               AnimControls that animate some joints also animated
 //               by the indicated AnimControl.  This is a special
 //               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::
 void PartBundle::
 clear_and_stop_intersecting(AnimControl *control, CData *cdata) {
 clear_and_stop_intersecting(AnimControl *control, CData *cdata) {
@@ -470,7 +481,9 @@ register_with_read_factory()
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PartBundle::CData::
 PartBundle::CData::
 CData() {
 CData() {
-  _blend_type = BT_single;
+  _blend_type = anim_blend_type;
+  _anim_blend_flag = false;
+  _frame_blend_flag = interpolate_frames;
   _last_control_set = NULL;
   _last_control_set = NULL;
   _net_blend = 0.0f;
   _net_blend = 0.0f;
   _anim_changed = false;
   _anim_changed = false;
@@ -503,3 +516,58 @@ CycleData *PartBundle::CData::
 make_copy() const {
 make_copy() const {
   return new CData(*this);
   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
   // This is the parameter to set_blend_type() and specifies the kind
   // of blending operation to be performed when multiple controls are
   // 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 {
   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,
     // BT_linear does a componentwise average of all blended matrices,
     // which is a linear blend.  The result of this is that if a
     // which is a linear blend.  The result of this is that if a
     // particular vertex would have been at point P in one animation
     // particular vertex would have been at point P in one animation
@@ -100,9 +91,15 @@ PUBLISHED:
     BT_componentwise_quat,
     BT_componentwise_quat,
   };
   };
 
 
-  void set_blend_type(BlendType bt);
+  INLINE void set_blend_type(BlendType bt);
   INLINE BlendType get_blend_type() const;
   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;
   INLINE PartBundleNode *get_node() const;
 
 
   void clear_control_effects();
   void clear_control_effects();
@@ -147,6 +144,8 @@ private:
     }
     }
 
 
     BlendType _blend_type;
     BlendType _blend_type;
+    bool _anim_blend_flag;
+    bool _frame_blend_flag;
     AnimControl *_last_control_set;
     AnimControl *_last_control_set;
     ChannelBlend _blend;
     ChannelBlend _blend;
     float _net_blend;
     float _net_blend;
@@ -193,6 +192,9 @@ inline ostream &operator <<(ostream &out, const PartBundle &bundle) {
   return out;
   return out;
 }
 }
 
 
+ostream &operator <<(ostream &out, PartBundle::BlendType blend_type);
+istream &operator >>(istream &in, PartBundle::BlendType &blend_type);
+
 #include "partBundle.I"
 #include "partBundle.I"
 
 
 #endif
 #endif

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

@@ -54,6 +54,8 @@ Character(const Character &copy) :
   // update our _parts list.
   // update our _parts list.
 
 
   copy_joints(get_bundle(), copy.get_bundle());
   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
   // optimization later, to handle characters that might animate
   // themselves in front of the view frustum.
   // 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;
   return true;
 }
 }
 
 

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

@@ -119,6 +119,8 @@ private:
   typedef vector_PartGroupStar Parts;
   typedef vector_PartGroupStar Parts;
   Parts _parts;
   Parts _parts;
 
 
+  double _last_auto_update;
+
   // Statistics
   // Statistics
   PStatCollector _joints_pcollector;
   PStatCollector _joints_pcollector;
   PStatCollector _skinning_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::
 INLINE void AnimInterface::
 stop() {
 stop() {
-  pose(get_frame());
+  pose(get_full_fframe());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -129,7 +129,7 @@ stop() {
 //               it there.
 //               it there.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void AnimInterface::
 INLINE void AnimInterface::
-pose(int frame) {
+pose(double frame) {
   {
   {
     CDWriter cdata(_cycler);
     CDWriter cdata(_cycler);
     cdata->pose(frame);
     cdata->pose(frame);
@@ -192,7 +192,30 @@ get_frame() const {
   if (num_frames <= 0) {
   if (num_frames <= 0) {
     return 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::
 INLINE int AnimInterface::
 get_full_frame() const {
 get_full_frame() const {
   CDReader cdata(_cycler);
   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::
 INLINE double AnimInterface::CData::
 get_frac() const {
 get_frac() const {
-  return get_full_fframe() - (double)get_full_frame();
+  return get_full_fframe() - (double)get_full_frame(0);
 }
 }
 
 
 INLINE ostream &
 INLINE ostream &

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

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

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

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