diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py index 1bbbe791cb..1136bdefaa 100644 --- a/direct/src/actor/Actor.py +++ b/direct/src/actor/Actor.py @@ -196,6 +196,8 @@ class Actor(DirectObject, NodePath): self.__subpartsComplete = False self.__LODNode = None + self.__LODAnimation = None + self.__LODCenter = Point3(0, 0, 0) self.switches = None if (other == None): @@ -723,8 +725,60 @@ class Actor(DirectObject, NodePath): return self.__hasLOD def setCenter(self, center): - if center != None: - self.__LODNode.node().setCenter(center) + if center == None: + center = Point3(0, 0, 0) + self.__LODCenter = center + if self.__LODNode: + self.__LODNode.node().setCenter(self.__LODCenter) + if self.__LODAnimation: + self.setLODAnimation(*self.__LODAnimation) + + def setLODAnimation(self, farDistance, nearDistance, delayFactor): + """ Activates a special mode in which the Actor animates less + frequently as it gets further from the camera. This is + intended as a simple optimization to minimize the effort of + computing animation for lots of characters that may not + necessarily be very important to animate every frame. + + If the character is closer to the camera than near_distance, + then it is animated its normal rate, every frame. If the + character is exactly far_distance away, it is animated only + every delay_factor seconds (which should be a number greater + than 0). If the character is between near_distance and + far_distance, its animation rate is linearly interpolated + according to its distance between the two. The interpolation + function continues beyond far_distance, so that the character + is animated increasingly less frequently as it gets farther + away. """ + + self.__LODAnimation = (farDistance, nearDistance, delayFactor) + + # Temporary hasattr for old Panda. + if not hasattr(Character, 'setLodAnimation'): + return + + for lodData in self.__partBundleDict.values(): + for partData in lodData.values(): + char = partData.partBundleNP + char.node().setLodAnimation(self.__LODCenter, farDistance, nearDistance, delayFactor) + + def clearLODAnimation(self): + """ Description: Undoes the effect of a recent call to + set_lod_animation(). Henceforth, the character will animate + every frame, regardless of its distance from the camera. + """ + + self.__LODAnimation = None + + # Temporary hasattr for old Panda. + if not hasattr(Character, 'setLodAnimation'): + return + + for lodData in self.__partBundleDict.values(): + for partData in lodData.values(): + char = partData.partBundleNP + char.node().clearLodAnimation() + def update(self, lod=0, partName=None, lodName=None, force=False): """ Updates all of the Actor's joints in the indicated LOD. diff --git a/panda/src/chan/partBundle.I b/panda/src/chan/partBundle.I index 57f5e9fe3e..090f441f9c 100644 --- a/panda/src/chan/partBundle.I +++ b/panda/src/chan/partBundle.I @@ -248,3 +248,17 @@ get_control_effect(AnimControl *control) const { CDReader cdata(_cycler); return do_get_control_effect(control, cdata); } + +//////////////////////////////////////////////////////////////////// +// Function: PartBundle::set_update_delay +// Access: Public +// Description: Specifies the minimum amount of time, in seconds, +// that should elapse between any two consecutive +// updates. This is normally used by +// Character::set_lod_animation(), and should not be +// called directly. +//////////////////////////////////////////////////////////////////// +INLINE void PartBundle:: +set_update_delay(double delay) { + _update_delay = delay; +} diff --git a/panda/src/chan/partBundle.cxx b/panda/src/chan/partBundle.cxx index 59a85c84c2..41b9ee25d3 100644 --- a/panda/src/chan/partBundle.cxx +++ b/panda/src/chan/partBundle.cxx @@ -54,6 +54,7 @@ PartBundle(const PartBundle ©) : PartGroup(copy) { _anim_preload = copy._anim_preload; + _update_delay = 0.0; CDWriter cdata(_cycler, true); CDReader cdata_from(copy._cycler); @@ -74,6 +75,7 @@ PartBundle:: PartBundle(const string &name) : PartGroup(name) { + _update_delay = 0.0; } //////////////////////////////////////////////////////////////////// @@ -483,24 +485,27 @@ release_joint(const string &joint_name) { bool PartBundle:: update() { Thread *current_thread = Thread::get_current_thread(); - bool anim_changed; - bool frame_blend_flag; CDWriter cdata(_cycler, false, current_thread); - anim_changed = cdata->_anim_changed; - frame_blend_flag = cdata->_frame_blend_flag; + bool any_changed = false; - bool any_changed = do_update(this, cdata, NULL, false, anim_changed, - current_thread); + double now = ClockObject::get_global_clock()->get_frame_time(current_thread); + if (now > cdata->_last_update + _update_delay || cdata->_anim_changed) { + bool anim_changed = cdata->_anim_changed; + bool frame_blend_flag = cdata->_frame_blend_flag; - // Now update all the controls for next time. - // CDWriter cdata(_cycler, false, current_thread); - ChannelBlend::const_iterator cbi; - for (cbi = cdata->_blend.begin(); cbi != cdata->_blend.end(); ++cbi) { - AnimControl *control = (*cbi).first; - control->mark_channels(frame_blend_flag); + any_changed = do_update(this, cdata, NULL, false, anim_changed, + current_thread); + + // Now update all the controls for next time. + ChannelBlend::const_iterator cbi; + for (cbi = cdata->_blend.begin(); cbi != cdata->_blend.end(); ++cbi) { + AnimControl *control = (*cbi).first; + control->mark_channels(frame_blend_flag); + } + + cdata->_anim_changed = false; + cdata->_last_update = now; } - - cdata->_anim_changed = false; return any_changed; } @@ -860,6 +865,7 @@ CData() { _last_control_set = NULL; _net_blend = 0.0f; _anim_changed = false; + _last_update = 0.0; } //////////////////////////////////////////////////////////////////// @@ -876,7 +882,8 @@ CData(const PartBundle::CData ©) : _last_control_set(copy._last_control_set), _blend(copy._blend), _net_blend(copy._net_blend), - _anim_changed(copy._anim_changed) + _anim_changed(copy._anim_changed), + _last_update(copy._last_update) { // Note that this copy constructor is not used by the PartBundle // copy constructor! Any elements that must be copied between diff --git a/panda/src/chan/partBundle.h b/panda/src/chan/partBundle.h index 4178505d1b..dfd744e38a 100644 --- a/panda/src/chan/partBundle.h +++ b/panda/src/chan/partBundle.h @@ -147,6 +147,7 @@ public: // interface; they're just public so we don't have to declare a // bunch of friends. virtual void control_activated(AnimControl *control); + INLINE void set_update_delay(double delay); bool do_bind_anim(AnimControl *control, AnimBundle *anim, int hierarchy_match_flags, const PartSubset &subset); @@ -171,6 +172,8 @@ private: typedef pmap AppliedTransforms; AppliedTransforms _applied_transforms; + double _update_delay; + // This is the data that must be cycled between pipeline stages. class CData : public CycleData { public: @@ -192,6 +195,7 @@ private: ChannelBlend _blend; float _net_blend; bool _anim_changed; + double _last_update; }; PipelineCycler _cycler; diff --git a/panda/src/char/character.cxx b/panda/src/char/character.cxx index 41728d6487..6e05644d9c 100644 --- a/panda/src/char/character.cxx +++ b/panda/src/char/character.cxx @@ -25,6 +25,9 @@ #include "animControl.h" #include "clockObject.h" #include "pStatTimer.h" +#include "camera.h" +#include "cullTraverser.h" +#include "cullTraverserData.h" TypeHandle Character::_type_handle; @@ -38,6 +41,11 @@ PStatCollector Character::_animation_pcollector("*:Animation"); Character:: Character(const Character ©, bool copy_bundles) : PartBundleNode(copy), + _lod_center(copy._lod_center), + _lod_far_distance(copy._lod_far_distance), + _lod_near_distance(copy._lod_near_distance), + _lod_delay_factor(copy._lod_delay_factor), + _do_lod_animation(copy._do_lod_animation), _joints_pcollector(copy._joints_pcollector), _skinning_pcollector(copy._skinning_pcollector) { @@ -60,6 +68,8 @@ Character(const Character ©, bool copy_bundles) : } } _last_auto_update = -1.0; + _view_frame = -1; + _view_distance2 = 0.0f; } //////////////////////////////////////////////////////////////////// @@ -74,6 +84,10 @@ Character(const string &name) : _skinning_pcollector(PStatCollector(_animation_pcollector, name), "Vertices") { set_cull_callback(); + clear_lod_animation(); + _last_auto_update = -1.0; + _view_frame = -1; + _view_distance2 = 0.0f; } //////////////////////////////////////////////////////////////////// @@ -174,12 +188,42 @@ combine_with(PandaNode *other) { // visible, or false if it should be culled. //////////////////////////////////////////////////////////////////// bool Character:: -cull_callback(CullTraverser *, CullTraverserData &) { +cull_callback(CullTraverser *trav, CullTraverserData &data) { // For now, we update the character during the cull traversal; this // prevents us from needlessly updating characters that aren't in // the view frustum. We may need a better way to do this // optimization later, to handle characters that might animate // themselves in front of the view frustum. + + if (_do_lod_animation) { + int this_frame = ClockObject::get_global_clock()->get_frame_count(); + + CPT(TransformState) rel_transform = get_rel_transform(trav, data); + LPoint3f center = _lod_center * rel_transform->get_mat(); + float dist2 = center.dot(center); + + if (this_frame != _view_frame || dist2 < _view_distance2) { + _view_frame = this_frame; + _view_distance2 = dist2; + + // Now compute the lod delay. + float dist = sqrt(dist2); + double delay = 0.0; + if (dist > _lod_near_distance) { + delay = _lod_delay_factor * (dist - _lod_near_distance) / (_lod_far_distance - _lod_near_distance); + nassertr(delay > 0.0, false); + } + set_lod_current_delay(delay); + + if (char_cat.is_spam()) { + char_cat.spam() + << "Distance to " << NodePath::any_path(this) << " in frame " + << this_frame << " is " << dist << ", computed delay is " << delay + << "\n"; + } + } + } + update(); return true; } @@ -288,6 +332,71 @@ merge_bundles(PartBundleHandle *old_bundle_handle, update_bundle(old_bundle_handle, new_bundle); } +//////////////////////////////////////////////////////////////////// +// Function: Character::set_lod_animation +// Access: Published +// Description: Activates a special mode in which the character +// animates less frequently as it gets further from the +// camera. This is intended as a simple optimization to +// minimize the effort of computing animation for lots +// of characters that may not necessarily be very +// important to animate every frame. +// +// If the character is closer to the camera than +// near_distance, then it is animated its normal rate, +// every frame. If the character is exactly +// far_distance away, it is animated only every +// delay_factor seconds (which should be a number +// greater than 0). If the character is between +// near_distance and far_distance, its animation rate is +// linearly interpolated according to its distance +// between the two. The interpolation function +// continues beyond far_distance, so that the character +// is animated increasingly less frequently as it gets +// farther away. +// +// The distance calculations are made from center, which +// is a fixed point relative to the character node, to +// the camera's lod center or cull center node (or to +// the camera node itself). +// +// If multiple cameras are viewing the character in any +// given frame, the closest one counts. +//////////////////////////////////////////////////////////////////// +void Character:: +set_lod_animation(const LPoint3f ¢er, + float far_distance, float near_distance, + float delay_factor) { + nassertv(far_distance >= near_distance); + nassertv(delay_factor >= 0.0f); + _lod_center = center; + _lod_far_distance = far_distance; + _lod_near_distance = near_distance; + _lod_delay_factor = delay_factor; + _do_lod_animation = (_lod_far_distance > _lod_near_distance && _lod_delay_factor > 0.0); + if (!_do_lod_animation) { + set_lod_current_delay(0.0); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: Character::clear_lod_animation +// Access: Published +// Description: Undoes the effect of a recent call to +// set_lod_animation(). Henceforth, the character will +// animate every frame, regardless of its distance from +// the camera. +//////////////////////////////////////////////////////////////////// +void Character:: +clear_lod_animation() { + _lod_center = LPoint3f::zero(); + _lod_far_distance = 0.0f; + _lod_near_distance = 0.0f; + _lod_delay_factor = 0.0f; + _do_lod_animation = false; + set_lod_current_delay(0.0); +} + //////////////////////////////////////////////////////////////////// // Function: Character::find_joint // Access: Published @@ -390,13 +499,13 @@ update() { if (now != _last_auto_update) { _last_auto_update = now; - PStatTimer timer(_joints_pcollector); if (char_cat.is_spam()) { char_cat.spam() << "Animating " << NodePath::any_path(this) << " at time " << now << "\n"; } + PStatTimer timer(_joints_pcollector); do_update(); } } @@ -495,6 +604,37 @@ update_bundle(PartBundleHandle *old_bundle_handle, PartBundle *new_bundle) { r_update_geom(this, joint_map, gvmap, gjmap, gsmap); } +//////////////////////////////////////////////////////////////////// +// Function: Character::get_rel_transform +// Access: Protected +// Description: Returns the relative transform to convert from the +// LODNode space to the camera space. +//////////////////////////////////////////////////////////////////// +CPT(TransformState) Character:: +get_rel_transform(CullTraverser *trav, CullTraverserData &data) { + // Get a pointer to the camera node. + Camera *camera = trav->get_scene()->get_camera_node(); + + // Get the camera space transform. + CPT(TransformState) rel_transform; + + NodePath lod_center = camera->get_lod_center(); + if (!lod_center.is_empty()) { + rel_transform = + lod_center.get_net_transform()->invert_compose(data.get_net_transform(trav)); + } else { + NodePath cull_center = camera->get_cull_center(); + if (!cull_center.is_empty()) { + rel_transform = + cull_center.get_net_transform()->invert_compose(data.get_net_transform(trav)); + } else { + rel_transform = data.get_modelview_transform(trav); + } + } + + return rel_transform; +} + //////////////////////////////////////////////////////////////////// // Function: Character::do_update // Access: Private @@ -517,6 +657,20 @@ do_update() { } } +//////////////////////////////////////////////////////////////////// +// Function: Character::set_lod_current_delay +// Access: Private +// Description: Changes the amount of delay we should impose due to +// the LOD animation setting. +//////////////////////////////////////////////////////////////////// +void Character:: +set_lod_current_delay(double delay) { + int num_bundles = get_num_bundles(); + for (int i = 0; i < num_bundles; ++i) { + get_bundle(i)->set_update_delay(delay); + } +} + //////////////////////////////////////////////////////////////////// // Function: Character::fill_joint_map // Access: Private diff --git a/panda/src/char/character.h b/panda/src/char/character.h index 9e66b63927..675e487dba 100644 --- a/panda/src/char/character.h +++ b/panda/src/char/character.h @@ -66,6 +66,11 @@ PUBLISHED: void merge_bundles(PartBundleHandle *old_bundle_handle, PartBundleHandle *other_bundle_handle); + void set_lod_animation(const LPoint3f ¢er, + float far_distance, float near_distance, + float delay_factor); + void clear_lod_animation(); + CharacterJoint *find_joint(const string &name) const; CharacterSlider *find_slider(const string &name) const; @@ -81,9 +86,11 @@ protected: Thread *current_thread); virtual void update_bundle(PartBundleHandle *old_bundle_handle, PartBundle *new_bundle); + CPT(TransformState) get_rel_transform(CullTraverser *trav, CullTraverserData &data); private: void do_update(); + void set_lod_current_delay(double delay); typedef pmap NodeMap; typedef pmap JointMap; @@ -129,6 +136,15 @@ private: double _last_auto_update; + int _view_frame; + double _view_distance2; + + LPoint3f _lod_center; + float _lod_far_distance; + float _lod_near_distance; + float _lod_delay_factor; + bool _do_lod_animation; + // Statistics PStatCollector _joints_pcollector; PStatCollector _skinning_pcollector;