diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py index 1255e93b79..0f1ffa2ed3 100644 --- a/direct/src/actor/Actor.py +++ b/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) diff --git a/panda/src/chan/config_chan.cxx b/panda/src/chan/config_chan.cxx index 30ae4e0bd0..30537527e9 100644 --- a/panda/src/chan/config_chan.cxx +++ b/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(); diff --git a/panda/src/chan/config_chan.h b/panda/src/chan/config_chan.h index cb5d8402c6..63e96f416c 100644 --- a/panda/src/chan/config_chan.h +++ b/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 diff --git a/panda/src/chan/movingPartMatrix.cxx b/panda/src/chan/movingPartMatrix.cxx index f23139ed7a..6046a9b7d7 100644 --- a/panda/src/chan/movingPartMatrix.cxx +++ b/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; } } diff --git a/panda/src/chan/movingPartScalar.cxx b/panda/src/chan/movingPartScalar.cxx index ba625cc638..094d55c9a9 100644 --- a/panda/src/chan/movingPartScalar.cxx +++ b/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; } } diff --git a/panda/src/chan/partBundle.I b/panda/src/chan/partBundle.I index d06d343e84..75b59e921c 100644 --- a/panda/src/chan/partBundle.I +++ b/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:: diff --git a/panda/src/chan/partBundle.cxx b/panda/src/chan/partBundle.cxx index 25d3665893..4e24e8d1c3 100644 --- a/panda/src/chan/partBundle.cxx +++ b/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 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; +} diff --git a/panda/src/chan/partBundle.h b/panda/src/chan/partBundle.h index 6fdafdb857..353d46e81a 100644 --- a/panda/src/chan/partBundle.h +++ b/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 diff --git a/panda/src/char/character.cxx b/panda/src/char/character.cxx index 711dda1e2a..15278ae539 100644 --- a/panda/src/char/character.cxx +++ b/panda/src/char/character.cxx @@ -54,6 +54,8 @@ Character(const Character ©) : // 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; } diff --git a/panda/src/char/character.h b/panda/src/char/character.h index 6f9f5f6227..8a103c3196 100644 --- a/panda/src/char/character.h +++ b/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; diff --git a/panda/src/putil/animInterface.I b/panda/src/putil/animInterface.I index 6ba114f4a6..0375185fd0 100644 --- a/panda/src/putil/animInterface.I +++ b/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 & diff --git a/panda/src/putil/animInterface.cxx b/panda/src/putil/animInterface.cxx index 03e1072c37..6b53de4c14 100644 --- a/panda/src/putil/animInterface.cxx +++ b/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; } } diff --git a/panda/src/putil/animInterface.h b/panda/src/putil/animInterface.h index 88659dfe0a..bba78e225d 100644 --- a/panda/src/putil/animInterface.h +++ b/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;