diff --git a/panda/src/chan/animControlCollection.I b/panda/src/chan/animControlCollection.I index 213d0cf94b..c6c65b1f08 100644 --- a/panda/src/chan/animControlCollection.I +++ b/panda/src/chan/animControlCollection.I @@ -186,6 +186,20 @@ get_num_frames(const string &anim_name) const { return control->get_num_frames(); } +//////////////////////////////////////////////////////////////////// +// Function: AnimControlCollection::get_num_frames +// Access: Public +// Description: Returns the total number of frames in the +// last-started animation. +//////////////////////////////////////////////////////////////////// +INLINE int AnimControlCollection:: +get_num_frames() const { + if (_last_started_control == (AnimControl *)NULL) { + return 0; + } + return _last_started_control->get_num_frames(); +} + INLINE ostream & operator << (ostream &out, const AnimControlCollection &collection) { collection.output(out); diff --git a/panda/src/chan/animControlCollection.cxx b/panda/src/chan/animControlCollection.cxx index 0fad4dd6dc..777e6a6624 100644 --- a/panda/src/chan/animControlCollection.cxx +++ b/panda/src/chan/animControlCollection.cxx @@ -185,6 +185,7 @@ play_all() { Controls::const_iterator ci; for (ci = _controls.begin(); ci != _controls.end(); ++ci) { (*ci)._control->play(); + _last_started_control = (*ci)._control; } } @@ -198,6 +199,7 @@ play_all(int from, int to) { Controls::const_iterator ci; for (ci = _controls.begin(); ci != _controls.end(); ++ci) { (*ci)._control->play(from, to); + _last_started_control = (*ci)._control; } } @@ -211,6 +213,7 @@ loop_all(bool restart) { Controls::const_iterator ci; for (ci = _controls.begin(); ci != _controls.end(); ++ci) { (*ci)._control->loop(restart); + _last_started_control = (*ci)._control; } } @@ -224,6 +227,7 @@ loop_all(bool restart, int from, int to) { Controls::const_iterator ci; for (ci = _controls.begin(); ci != _controls.end(); ++ci) { (*ci)._control->loop(restart, from, to); + _last_started_control = (*ci)._control; } } @@ -258,6 +262,7 @@ pose_all(int frame) { Controls::const_iterator ci; for (ci = _controls.begin(); ci != _controls.end(); ++ci) { (*ci)._control->pose(frame); + _last_started_control = (*ci)._control; } } diff --git a/panda/src/chan/animControlCollection.h b/panda/src/chan/animControlCollection.h index 920514be55..48bd23b9c7 100644 --- a/panda/src/chan/animControlCollection.h +++ b/panda/src/chan/animControlCollection.h @@ -74,6 +74,7 @@ PUBLISHED: INLINE int get_frame() const; INLINE int get_num_frames(const string &anim_name) const; + INLINE int get_num_frames() const; INLINE bool is_playing(const string &anim_name) const; INLINE bool is_playing() const; diff --git a/panda/src/framework/pandaFramework.cxx b/panda/src/framework/pandaFramework.cxx index 4b3dee2f25..a4cc060d0b 100644 --- a/panda/src/framework/pandaFramework.cxx +++ b/panda/src/framework/pandaFramework.cxx @@ -28,6 +28,7 @@ #include "textNode.h" #include "mouseAndKeyboard.h" #include "mouseRecorder.h" +#include "throw_event.h" LoaderOptions PandaFramework::_loader_options; @@ -747,6 +748,8 @@ do_frame() { nassertr(_is_open, false); DataGraphTraverser dg_trav; dg_trav.traverse(_data_root.node()); + + throw_event("NewFrame"); _event_handler.process_events(); if (!_screenshot_text.is_empty()) { @@ -834,6 +837,7 @@ do_enable_default_keys() { define_key("i", "invert (reverse) single-sided faces", event_i, this); define_key("l", "toggle lighting", event_l, this); define_key("c", "recenter view on object", event_c, this); + define_key("a", "toggle animation controls", event_a, this); define_key("shift-c", "toggle collision surfaces", event_C, this); define_key("shift-b", "report bounding volume", event_B, this); define_key("shift-l", "list hierarchy", event_L, this); @@ -1025,6 +1029,23 @@ event_c(CPT_Event event, void *data) { } } +//////////////////////////////////////////////////////////////////// +// Function: PandaFramework::event_a +// Access: Public, Static +// Description: Default handler for a key: toggle the animation +// controls. +//////////////////////////////////////////////////////////////////// +void PandaFramework:: +event_a(CPT_Event event, void *data) { + if (event->get_num_parameters() == 1) { + EventParameter param = event->get_parameter(0); + WindowFramework *wf; + DCAST_INTO_V(wf, param.get_ptr()); + + wf->next_anim_control(); + } +} + //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_C // Access: Public, Static diff --git a/panda/src/framework/pandaFramework.h b/panda/src/framework/pandaFramework.h index a403b8dda8..82492c90f6 100644 --- a/panda/src/framework/pandaFramework.h +++ b/panda/src/framework/pandaFramework.h @@ -132,6 +132,7 @@ public: static void event_i(CPT_Event, void *data); static void event_l(CPT_Event, void *data); static void event_c(CPT_Event, void *data); + static void event_a(CPT_Event, void *data); static void event_C(CPT_Event, void *data); static void event_B(CPT_Event, void *data); static void event_L(CPT_Event, void *data); diff --git a/panda/src/framework/windowFramework.I b/panda/src/framework/windowFramework.I index 7c62221c07..4e929c0e5a 100644 --- a/panda/src/framework/windowFramework.I +++ b/panda/src/framework/windowFramework.I @@ -63,6 +63,16 @@ get_camera(int n) const { return _cameras[n]; } +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::get_anim_controls +// Access: Public +// Description: Returns the current state of the anim_controls flag. +//////////////////////////////////////////////////////////////////// +INLINE bool WindowFramework:: +get_anim_controls() const { + return _anim_controls_enabled; +} + //////////////////////////////////////////////////////////////////// // Function: WindowFramework::get_wireframe // Access: Public diff --git a/panda/src/framework/windowFramework.cxx b/panda/src/framework/windowFramework.cxx index ff1e8561cb..0ed406a193 100644 --- a/panda/src/framework/windowFramework.cxx +++ b/panda/src/framework/windowFramework.cxx @@ -54,6 +54,7 @@ #include "loaderFileTypeRegistry.h" #include "pnmImage.h" #include "virtualFileSystem.h" +#include "string_utils.h" // This is generated data for the standard texture we apply to the // blue triangle. @@ -79,6 +80,8 @@ WindowFramework(PandaFramework *panda_framework) : _got_keyboard = false; _got_trackball = false; _got_lights = false; + _anim_controls_enabled = false; + _anim_index = 0; _wireframe_enabled = false; _texture_enabled = true; _two_sided_enabled = false; @@ -103,6 +106,8 @@ WindowFramework(const WindowFramework ©, DisplayRegion *display_region) : _got_keyboard = false; _got_trackball = false; _got_lights = false; + _anim_controls_enabled = false; + _anim_index = 0; _wireframe_enabled = false; _texture_enabled = true; _two_sided_enabled = false; @@ -345,8 +350,8 @@ get_mouse() { // from get_mouse() is actually a MouseWatcher, but since it // presents the same interface as a Mouse, no one should mind. - // Another advantage to using a MouseWatcher is that it the PGTop - // of aspect2d likes it better. + // Another advantage to using a MouseWatcher is that the PGTop of + // aspect2d likes it better. PT(MouseWatcher) mw = new MouseWatcher("watcher"); mw->set_display_region(_display_region_3d); _mouse = mouse.attach_new_node(mw); @@ -674,6 +679,50 @@ loop_animations(int hierarchy_match_flags) { _anim_controls.loop_all(true); } +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::next_anim_control +// Access: Public +// Description: Rotates the animation controls through all of the +// available animations. If the animation controls are +// not already enabled, enables them at sets to the +// first animation; if they are already enabled, steps +// to the next animation; if that is the last animation, +// disables the animation controls. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +next_anim_control() { + if (_anim_controls_enabled) { + destroy_anim_controls(); + + ++_anim_index; + if (_anim_index >= _anim_controls.get_num_anims()) { + set_anim_controls(false); + } else { + create_anim_controls(); + } + } else { + _anim_index = 0; + set_anim_controls(true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::set_anim_controls +// Access: Public +// Description: Creates an onscreen animation slider for +// frame-stepping through the animations. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +set_anim_controls(bool enable) { + _anim_controls_enabled = enable; + if (_anim_controls_enabled) { + create_anim_controls(); + + } else { + destroy_anim_controls(); + } +} + //////////////////////////////////////////////////////////////////// // Function: WindowFramework::split_window // Access: Public @@ -1060,3 +1109,121 @@ load_image_as_model(const Filename &filename) { return card_node.p(); } + +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::create_anim_controls +// Access: Private +// Description: Creates an onscreen animation slider for +// frame-stepping through the animations. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +create_anim_controls() { + destroy_anim_controls(); + + AnimControl *control = _anim_controls.get_anim(_anim_index); + nassertv(control != (AnimControl *)NULL); + + PT(PGItem) group = new PGItem("anim_controls_group"); + PGFrameStyle style; + style.set_type(PGFrameStyle::T_flat); + style.set_color(0.0f, 0.0f, 0.0f, 0.3f); + group->set_frame(-1.0f, 1.0f, 0.0f, 0.2f); + group->set_frame_style(0, style); + group->set_suppress_flags(MouseWatcherRegion::SF_mouse_button); + group->set_active(true); + + _anim_controls_group = get_aspect_2d().attach_new_node(group); + _anim_controls_group.set_pos(0.0f, 0.0f, -0.9f); + + if (_anim_index >= _anim_controls.get_num_anims()) { + PT(TextNode) label = new TextNode("label"); + label->set_align(TextNode::A_center); + label->set_text("No animation."); + NodePath tnp = _anim_controls_group.attach_new_node(label); + tnp.set_pos(0.0f, 0.0f, 0.07f); + tnp.set_scale(0.1f); + + return; + } + + PT(TextNode) label = new TextNode("anim_name"); + label->set_align(TextNode::A_left); + label->set_text(_anim_controls.get_anim_name(_anim_index)); + NodePath tnp = _anim_controls_group.attach_new_node(label); + tnp.set_pos(-1.0f, 0.0f, 0.15f); + tnp.set_scale(0.05f); + + _anim_slider = new PGSliderBar("anim_slider"); + _anim_slider->setup_slider(false, 1.8f, 0.1f, 0.01f); + _anim_slider->set_suppress_flags(MouseWatcherRegion::SF_mouse_button); + _anim_slider->get_thumb_button()->set_suppress_flags(MouseWatcherRegion::SF_mouse_button); + + _anim_slider->set_range(0.0f, (float)(control->get_num_frames() - 1)); + _anim_slider->set_scroll_size(0.0f); + _anim_slider->set_page_size(1.0f); + NodePath snp = _anim_controls_group.attach_new_node(_anim_slider); + snp.set_pos(0.0f, 0.0f, 0.06f); + + _frame_number = new TextNode("frame_number"); + _frame_number->set_text_color(0.0f, 0.0f, 0.0f, 1.0f); + _frame_number->set_align(TextNode::A_center); + _frame_number->set_text(format_string(control->get_frame())); + NodePath fnp = NodePath(_anim_slider->get_thumb_button()).attach_new_node(_frame_number); + fnp.set_scale(0.05f); + fnp.set_pos(0.0f, 0.0f, -0.01f); + + _panda_framework->get_event_handler().add_hook("NewFrame", st_update_anim_controls, (void *)this); +} + +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::destroy_anim_controls +// Access: Private +// Description: Removes the previously-created anim controls, if any. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +destroy_anim_controls() { + if (!_anim_controls_group.is_empty()) { + _anim_controls_group.remove_node(); + + _panda_framework->get_event_handler().remove_hook("NewFrame", st_update_anim_controls, (void *)this); + + if (_anim_index < _anim_controls.get_num_anims()) { + AnimControl *control = _anim_controls.get_anim(_anim_index); + nassertv(control != (AnimControl *)NULL); + control->loop(false); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::update_anim_controls +// Access: Private +// Description: A per-frame callback to update the anim slider for +// the current frame. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +update_anim_controls() { + AnimControl *control = _anim_controls.get_anim(_anim_index); + nassertv(control != (AnimControl *)NULL); + + if (_anim_slider->is_button_down()) { + control->pose((int)(_anim_slider->get_value() + 0.5)); + } else { + _anim_slider->set_value((float)control->get_frame()); + } + + _frame_number->set_text(format_string(control->get_frame())); +} + + +//////////////////////////////////////////////////////////////////// +// Function: WindowFramework::st_update_anim_controls +// Access: Private, Static +// Description: The static event handler function. +//////////////////////////////////////////////////////////////////// +void WindowFramework:: +st_update_anim_controls(CPT_Event, void *data) { + WindowFramework *self = (WindowFramework *)data; + self->update_anim_controls(); +} + diff --git a/panda/src/framework/windowFramework.h b/panda/src/framework/windowFramework.h index 9d50be8681..abbc40dc26 100644 --- a/panda/src/framework/windowFramework.h +++ b/panda/src/framework/windowFramework.h @@ -33,6 +33,8 @@ #include "typedWritableReferenceCount.h" #include "graphicsWindow.h" #include "loaderOptions.h" +#include "pgSliderBar.h" +#include "textNode.h" class PandaFramework; class AmbientLight; @@ -92,6 +94,9 @@ public: void loop_animations(int hierarchy_match_flags = PartGroup::HMF_ok_part_extra | PartGroup::HMF_ok_anim_extra); + void next_anim_control(); + void set_anim_controls(bool enable); + INLINE bool get_anim_controls() const; enum BackgroundType { BT_other = 0, @@ -129,6 +134,10 @@ protected: private: PT(PandaNode) load_image_as_model(const Filename &filename); + void create_anim_controls(); + void destroy_anim_controls(); + void update_anim_controls(); + static void st_update_anim_controls(CPT_Event, void *data); private: PandaFramework *_panda_framework; @@ -143,7 +152,13 @@ private: NodePath _render; NodePath _render_2d; NodePath _aspect_2d; + AnimControlCollection _anim_controls; + bool _anim_controls_enabled; + int _anim_index; + NodePath _anim_controls_group; + PT(PGSliderBar) _anim_slider; + PT(TextNode) _frame_number; NodePath _mouse; PT(Trackball) _trackball; diff --git a/panda/src/pgui/pgButton.I b/panda/src/pgui/pgButton.I index ea389ef8ad..a4cefd9fc8 100644 --- a/panda/src/pgui/pgButton.I +++ b/panda/src/pgui/pgButton.I @@ -103,7 +103,8 @@ get_click_event(const ButtonHandle &button) const { //////////////////////////////////////////////////////////////////// // Function: PGButton::is_button_down // Access: Published -// Description: Returns if the value of button_down +// Description: Returns true if the user is currently holding the +// mouse button down on the button, false otherwise. //////////////////////////////////////////////////////////////////// INLINE bool PGButton:: is_button_down() { diff --git a/panda/src/pgui/pgSliderBar.I b/panda/src/pgui/pgSliderBar.I index 94d38abf43..b9bbe41023 100755 --- a/panda/src/pgui/pgSliderBar.I +++ b/panda/src/pgui/pgSliderBar.I @@ -196,9 +196,9 @@ get_value() const { //////////////////////////////////////////////////////////////////// INLINE void PGSliderBar:: set_ratio(float ratio) { - _ratio = max(min(ratio, 1.0f), 0.0f); - _needs_reposition = true; - adjust(); + if (!is_button_down()) { + internal_set_ratio(ratio); + } } //////////////////////////////////////////////////////////////////// @@ -212,6 +212,20 @@ get_ratio() const { return _ratio; } +//////////////////////////////////////////////////////////////////// +// Function: PGSliderBar::is_button_down +// Access: Published +// Description: Returns true if the user is currently holding down +// the mouse button to manipulate the slider. When +// true, calls to set_ratio() or set_value() will have +// no effect. +//////////////////////////////////////////////////////////////////// +INLINE bool PGSliderBar:: +is_button_down() const { + return _dragging || _mouse_button_page || + (_scroll_button_held != (PGItem *)NULL); +} + //////////////////////////////////////////////////////////////////// // Function: PGSliderBar::set_resize_thumb // Access: Published @@ -433,3 +447,17 @@ INLINE string PGSliderBar:: get_adjust_event() const { return get_adjust_prefix() + get_id(); } + +//////////////////////////////////////////////////////////////////// +// Function: PGSliderBar::internal_set_ratio +// Access: Private +// Description: Sets the current value of the slider, expressed in +// the range 0 .. 1, without checking whether the user +// is currently manipulating the slider. +//////////////////////////////////////////////////////////////////// +INLINE void PGSliderBar:: +internal_set_ratio(float ratio) { + _ratio = max(min(ratio, 1.0f), 0.0f); + _needs_reposition = true; + adjust(); +} diff --git a/panda/src/pgui/pgSliderBar.cxx b/panda/src/pgui/pgSliderBar.cxx index 9471aeafa2..f72a01f279 100755 --- a/panda/src/pgui/pgSliderBar.cxx +++ b/panda/src/pgui/pgSliderBar.cxx @@ -713,10 +713,10 @@ reposition() { void PGSliderBar:: advance_scroll() { if (_scroll_button_held == _left_button) { - set_ratio(max(_ratio - _scroll_ratio, 0.0f)); + internal_set_ratio(max(_ratio - _scroll_ratio, 0.0f)); } else if (_scroll_button_held == _right_button) { - set_ratio(min(_ratio + _scroll_ratio, 1.0f)); + internal_set_ratio(min(_ratio + _scroll_ratio, 1.0f)); } _next_advance_time = @@ -744,7 +744,7 @@ advance_page() { } else { t = min(_ratio + _page_ratio - _scroll_ratio, target_ratio); } - set_ratio(t); + internal_set_ratio(t); if (t == target_ratio) { // We made it; begin dragging from now on until the user releases // the mouse. @@ -786,7 +786,7 @@ continue_drag() { } if (_range_x != 0.0f) { float current_x = mouse_to_local(_mouse_pos).dot(_axis); - set_ratio((current_x - _drag_start_x) / _range_x); + internal_set_ratio((current_x - _drag_start_x) / _range_x); } } diff --git a/panda/src/pgui/pgSliderBar.h b/panda/src/pgui/pgSliderBar.h index c471395c6c..cce92b9917 100755 --- a/panda/src/pgui/pgSliderBar.h +++ b/panda/src/pgui/pgSliderBar.h @@ -81,6 +81,8 @@ PUBLISHED: INLINE void set_ratio(float ratio); INLINE float get_ratio() const; + INLINE bool is_button_down() const; + INLINE void set_resize_thumb(bool resize_thumb); INLINE bool get_resize_thumb() const; @@ -118,6 +120,8 @@ protected: virtual void item_move(PGItem *item, const MouseWatcherParameter ¶m); private: + INLINE void internal_set_ratio(float ratio); + void reposition(); void advance_scroll(); void advance_page(); diff --git a/panda/src/testbed/pview.cxx b/panda/src/testbed/pview.cxx index c0565d6fe3..10a18d579b 100644 --- a/panda/src/testbed/pview.cxx +++ b/panda/src/testbed/pview.cxx @@ -213,7 +213,8 @@ help() { " -a\n" " Convert and play animations, if loading an external file type\n" - " (like .mb) directly and if the converter supports animations.\n\n" + " (like .mb) directly and if the converter supports animations.\n" + " Also implicitly enables the animation controls.\n\n" " -c\n" " Automatically center models within the viewing window on startup.\n" @@ -252,6 +253,7 @@ main(int argc, char *argv[]) { framework.open_framework(argc, argv); framework.set_window_title("Panda Viewer"); + bool anim_controls = false; bool auto_center = false; bool show_loading = false; bool auto_screenshot = false; @@ -267,6 +269,7 @@ main(int argc, char *argv[]) { while (flag != EOF) { switch (flag) { case 'a': + anim_controls = true; PandaFramework::_loader_options.set_flags(PandaFramework::_loader_options.get_flags() | LoaderOptions::LF_convert_anim); break; @@ -353,6 +356,10 @@ main(int argc, char *argv[]) { return(output_screenshot(screenshotfn) ? 0:1); } + if (anim_controls) { + window->set_anim_controls(true); + } + framework.enable_default_keys(); framework.define_key("shift-w", "open a new window", event_W, NULL); framework.define_key("shift-f", "flatten hierarchy", event_F, NULL);