// Filename: pandaFramework.cxx // Created by: drose (02Apr02) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "pandaFramework.h" #include "clockObject.h" #include "pStatClient.h" #include "eventQueue.h" #include "dataGraphTraverser.h" #include "depthOffsetAttrib.h" #include "collisionNode.h" #include "config_framework.h" #include "graphicsPipeSelection.h" #include "nodePathCollection.h" #include "textNode.h" #include "mouseAndKeyboard.h" #include "mouseRecorder.h" #include "throw_event.h" #include "executionEnvironment.h" LoaderOptions PandaFramework::_loader_options; //////////////////////////////////////////////////////////////////// // Function: PandaFramework::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PandaFramework:: PandaFramework() : _event_handler(*EventHandler::get_global_event_handler()) { _is_open = false; _made_default_pipe = false; _window_title = string(); _engine = (GraphicsEngine *)NULL; _start_time = 0.0; _frame_count = 0; _wireframe_enabled = false; _texture_enabled = true; _two_sided_enabled = false; _lighting_enabled = false; _perpixel_enabled = false; _background_type = WindowFramework::BT_default; _default_keys_enabled = false; _exit_flag = false; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::Destructor // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// PandaFramework:: ~PandaFramework() { if (_is_open) { close_framework(); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::open_framework // Access: Public // Description: Should be called once at the beginning of the // application to initialize Panda (and the framework) // for use. The command-line arguments should be passed // in so Panda can remove any arguments that it // recognizes as special control parameters. //////////////////////////////////////////////////////////////////// void PandaFramework:: open_framework(int &argc, char **&argv) { if (_is_open) { return; } _is_open = true; reset_frame_rate(); _data_root = NodePath("data"); _highlight_wireframe = NodePath("wireframe"); _highlight_wireframe.set_render_mode_wireframe(1); _highlight_wireframe.set_texture_off(1); _highlight_wireframe.set_color(1.0f, 0.0f, 0.0f, 1.0f, 1); _highlight_wireframe.set_attrib(DepthOffsetAttrib::make()); if (!playback_session.empty()) { // If the config file so indicates, create a recorder and start it // playing. _recorder = new RecorderController; _recorder->begin_playback(Filename::from_os_specific(playback_session)); } else if (!record_session.empty()) { // If the config file so indicates, create a recorder and start it // recording. _recorder = new RecorderController; _recorder->begin_record(Filename::from_os_specific(record_session)); } _event_handler.add_hook("window-event", event_window_event, this); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::close_framework // Access: Public // Description: Should be called at the end of an application to // close Panda. This is optional, as the destructor // will do the same thing. //////////////////////////////////////////////////////////////////// void PandaFramework:: close_framework() { if (!_is_open) { return; } close_all_windows(); // Also close down any other windows that might have been opened. if (_engine != (GraphicsEngine *)NULL) { delete _engine; _engine = NULL; } _event_handler.remove_all_hooks(); _is_open = false; _made_default_pipe = false; _default_pipe.clear(); _start_time = 0.0; _frame_count = 0; _wireframe_enabled = false; _two_sided_enabled = false; _lighting_enabled = false; _default_keys_enabled = false; _exit_flag = false; _recorder = NULL; Thread::prepare_for_exit(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::get_default_pipe // Access: Public // Description: Returns the default pipe. This is the GraphicsPipe // that all windows in the framework will be created on, // unless otherwise specified in open_window(). It is // usually the primary graphics interface on the local // machine. // // If the default pipe has not yet been created, this // creates it. // // The return value is the default pipe, or NULL if no // default pipe could be created. //////////////////////////////////////////////////////////////////// GraphicsPipe *PandaFramework:: get_default_pipe() { nassertr(_is_open, NULL); if (!_made_default_pipe) { make_default_pipe(); _made_default_pipe = true; } return _default_pipe; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::get_mouse // Access: Public // Description: Returns a NodePath to the MouseAndKeyboard associated // with the indicated GraphicsWindow object. If there's // not yet a mouse associated with the window, creates // one. // // This allows multiple WindowFramework objects that // represent different display regions of the same // GraphicsWindow to share the same mouse. //////////////////////////////////////////////////////////////////// NodePath PandaFramework:: get_mouse(GraphicsWindow *window) { Mouses::iterator mi = _mouses.find(window); if (mi != _mouses.end()) { return (*mi).second; } NodePath mouse; NodePath data_root = get_data_root(); MouseAndKeyboard *mouse_node = new MouseAndKeyboard(window, 0, "mouse"); mouse = data_root.attach_new_node(mouse_node); RecorderController *recorder = get_recorder(); if (recorder != (RecorderController *)NULL) { // If we're in recording or playback mode, associate a recorder. MouseRecorder *mouse_recorder = new MouseRecorder("mouse"); mouse = mouse.attach_new_node(mouse_recorder); recorder->add_recorder("mouse", mouse_recorder); } _mouses[window] = mouse; return mouse; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::remove_mouse // Access: Public // Description: Removes the mouse that may have been created by an // earlier call to get_mouse(). //////////////////////////////////////////////////////////////////// void PandaFramework:: remove_mouse(const GraphicsWindow *window) { Mouses::iterator mi = _mouses.find(window); if (mi != _mouses.end()) { (*mi).second.remove_node(); _mouses.erase(mi); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::define_key // Access: Public // Description: Sets up a handler for the indicated key. When the // key is pressed in a window, the given callback will // be called. The description is a one-line description // of the function of the key, for display to the user. //////////////////////////////////////////////////////////////////// void PandaFramework:: define_key(const string &event_name, const string &description, EventHandler::EventCallbackFunction *function, void *data) { if (_event_handler.has_hook(event_name)) { // If there is already a hook for the indicated keyname, we're // most likely replacing a previous definition of a key. Search // for the old definition and remove it. KeyDefinitions::iterator di; di = _key_definitions.begin(); while (di != _key_definitions.end() && (*di)._event_name != event_name) { ++di; } if (di != _key_definitions.end()) { _key_definitions.erase(di); } } // Now add a new hook for the keyname, and also add the new // description. _event_handler.add_hook(event_name, function, data); if (!description.empty()) { KeyDefinition keydef; keydef._event_name = event_name; keydef._description = description; _key_definitions.push_back(keydef); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::get_default_window_props // Access: Public, Virtual // Description: Fills in the indicated window properties structure // according to the normal window properties for this // application. //////////////////////////////////////////////////////////////////// void PandaFramework:: get_default_window_props(WindowProperties &props) { // This function is largely vestigial and will be removed soon. We // have moved the default window properties into // WindowProperties::get_default(). props.add_properties(WindowProperties::get_default()); if (!_window_title.empty()) { props.set_title(_window_title); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::open_window // Access: Public // Description: Opens a window on the default graphics pipe. If the // default graphics pipe can't open a window for some // reason, automatically fails over to the next // available graphics pipe, and updates _default_pipe // accordingly. Returns NULL only if all graphics pipes // fail. //////////////////////////////////////////////////////////////////// WindowFramework *PandaFramework:: open_window() { GraphicsPipe *pipe = get_default_pipe(); if (pipe == (GraphicsPipe *)NULL) { // Can't get a pipe. return NULL; } WindowFramework *wf = open_window(pipe, NULL); if (wf == (WindowFramework *)NULL) { // Ok, the default graphics pipe failed; try a little harder. GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); selection->load_aux_modules(); int num_pipe_types = selection->get_num_pipe_types(); for (int i = 0; i < num_pipe_types; i++) { TypeHandle pipe_type = selection->get_pipe_type(i); if (pipe_type != _default_pipe->get_type()) { PT(GraphicsPipe) new_pipe = selection->make_pipe(pipe_type); if (new_pipe != (GraphicsPipe *)NULL) { wf = open_window(new_pipe, NULL); if (wf != (WindowFramework *)NULL) { // Here's the winner! _default_pipe = new_pipe; return wf; } } } } // Too bad; none of the pipes could open a window. Fall through // and return NULL. } return wf; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::open_window // Access: Public // Description: Opens a new window on the indicated pipe, using the // default parameters. Returns the new WindowFramework // if successful, or NULL if not. //////////////////////////////////////////////////////////////////// WindowFramework *PandaFramework:: open_window(GraphicsPipe *pipe, GraphicsStateGuardian *gsg) { nassertr(_is_open, NULL); WindowProperties props; get_default_window_props(props); return open_window(props, pipe, gsg); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::open_window // Access: Public // Description: Opens a new window using the indicated properties. // (You may initialize the properties to their default // values by calling get_default_window_props() first.) // // Returns the new WindowFramework if successful, or // NULL if not. //////////////////////////////////////////////////////////////////// WindowFramework *PandaFramework:: open_window(const WindowProperties &props, GraphicsPipe *pipe, GraphicsStateGuardian *gsg) { if (pipe == (GraphicsPipe *)NULL) { pipe = get_default_pipe(); if (pipe == (GraphicsPipe *)NULL) { // Can't get a pipe. return NULL; } } nassertr(_is_open, NULL); PT(WindowFramework) wf = make_window_framework(); wf->set_wireframe(get_wireframe()); wf->set_texture(get_texture()); wf->set_two_sided(get_two_sided()); wf->set_lighting(get_lighting()); wf->set_perpixel(get_perpixel()); wf->set_background_type(get_background_type()); GraphicsWindow *win = wf->open_window(props, get_graphics_engine(), pipe, gsg); _engine->open_windows(); if (win != (GraphicsWindow *)NULL && !win->is_valid()) { // The window won't open. _engine->remove_window(win); wf->close_window(); win = NULL; } if (win == (GraphicsWindow *)NULL) { // Oops, couldn't make an actual window. framework_cat.error() << "Unable to create window.\n"; return NULL; } _windows.push_back(wf); return wf; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::find_window // Access: Public // Description: Returns the index of the first WindowFramework object // found that references the indicated GraphicsWindow // pointer, or -1 if none do. //////////////////////////////////////////////////////////////////// int PandaFramework:: find_window(const GraphicsWindow *win) const { int n; for (n = 0; n < (int)_windows.size(); n++) { if (_windows[n]->get_graphics_window() == win) { return n; } } return -1; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::find_window // Access: Public // Description: Returns the index of the given WindowFramework // object, or -1 if the object does not represent a // window opened with this PandaFramework. //////////////////////////////////////////////////////////////////// int PandaFramework:: find_window(const WindowFramework *wf) const { int n; for (n = 0; n < (int)_windows.size(); n++) { if (_windows[n] == wf) { return n; } } return -1; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::close_window // Access: Public // Description: Closes the nth window and removes it from the list. //////////////////////////////////////////////////////////////////// void PandaFramework:: close_window(int n) { nassertv(n >= 0 && n < (int)_windows.size()); WindowFramework *wf = _windows[n]; GraphicsWindow *win = wf->get_graphics_window(); if (win != (GraphicsWindow *)NULL) { _engine->remove_window(win); } wf->close_window(); _windows.erase(_windows.begin() + n); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::close_all_windows // Access: Public // Description: Closes all currently open windows and empties the // list of windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: close_all_windows() { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); GraphicsWindow *win = wf->get_graphics_window(); if (win != (GraphicsWindow *)NULL) { _engine->remove_window(win); } wf->close_window(); } Mouses::iterator mi; for (mi = _mouses.begin(); mi != _mouses.end(); ++mi) { (*mi).second.remove_node(); } _windows.clear(); _mouses.clear(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::all_windows_closed // Access: Public // Description: Returns true if all of the opened windows have been // closed by the user, false otherwise. //////////////////////////////////////////////////////////////////// bool PandaFramework:: all_windows_closed() const { Windows::const_iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); if (wf->get_graphics_window()->get_properties().get_open()) { return false; } } return true; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::get_models // Access: Public // Description: Returns the root of the scene graph normally reserved // for parenting models and such. This scene graph may // be instanced to each window's render tree as the // window is created. //////////////////////////////////////////////////////////////////// NodePath &PandaFramework:: get_models() { if (_models.is_empty()) { _models = NodePath("models"); } return _models; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::report_frame_rate // Access: Public // Description: Reports the currently measured average frame rate to // the indicated ostream. //////////////////////////////////////////////////////////////////// void PandaFramework:: report_frame_rate(ostream &out) const { double now = ClockObject::get_global_clock()->get_frame_time(); double delta = now - _start_time; int frame_count = ClockObject::get_global_clock()->get_frame_count(); int num_frames = frame_count - _frame_count; if (num_frames > 0) { out << num_frames << " frames in " << delta << " seconds.\n"; double fps = ((double)num_frames) / delta; out << fps << " fps average (" << 1000.0 / fps << "ms)\n"; } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::reset_frame_rate // Access: Public // Description: Resets the frame rate computation. //////////////////////////////////////////////////////////////////// void PandaFramework:: reset_frame_rate() { _start_time = ClockObject::get_global_clock()->get_frame_time(); _frame_count = ClockObject::get_global_clock()->get_frame_count(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_wireframe // Access: Public // Description: Sets the wireframe state on all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_wireframe(bool enable) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_wireframe(enable); } _wireframe_enabled = enable; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_texture // Access: Public // Description: Sets the texture state on all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_texture(bool enable) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_texture(enable); } _texture_enabled = enable; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_two_sided // Access: Public // Description: Sets the two_sided state on all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_two_sided(bool enable) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_two_sided(enable); } _two_sided_enabled = enable; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_lighting // Access: Public // Description: Sets the lighting state on all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_lighting(bool enable) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_lighting(enable); } _lighting_enabled = enable; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_perpixel // Access: Public // Description: Sets the perpixel state on all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_perpixel(bool enable) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_perpixel(enable); } _perpixel_enabled = enable; } //////////////////////////////////////////////////////////////////// // Function: BackgroundFramework::set_background_type // Access: Public // Description: Sets the background type of all windows. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_background_type(WindowFramework::BackgroundType type) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { WindowFramework *wf = (*wi); wf->set_background_type(type); } _background_type = type; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::hide_collision_solids // Access: Public // Description: Hides any collision solids which are visible in the // indicated scene graph. Returns the number of // collision solids hidden. //////////////////////////////////////////////////////////////////// int PandaFramework:: hide_collision_solids(NodePath node) { int num_changed = 0; if (node.node()->is_of_type(CollisionNode::get_class_type())) { if (!node.is_hidden()) { node.hide(); num_changed++; } } int num_children = node.get_num_children(); for (int i = 0; i < num_children; i++) { num_changed += hide_collision_solids(node.get_child(i)); } return num_changed; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::show_collision_solids // Access: Public // Description: Shows any collision solids which are directly hidden // in the indicated scene graph. Returns the number of // collision solids shown. //////////////////////////////////////////////////////////////////// int PandaFramework:: show_collision_solids(NodePath node) { int num_changed = 0; if (node.node()->is_of_type(CollisionNode::get_class_type())) { if (node.get_hidden_ancestor() == node) { node.show(); num_changed++; } } int num_children = node.get_num_children(); for (int i = 0; i < num_children; i++) { num_changed += show_collision_solids(node.get_child(i)); } return num_changed; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::set_highlight // Access: Public // Description: Sets the indicated node (normally a node within the // get_models() tree) up as the highlighted node. // Certain operations affect the highlighted node only. //////////////////////////////////////////////////////////////////// void PandaFramework:: set_highlight(const NodePath &node) { clear_highlight(); _highlight = node; if (!_highlight.is_empty()) { framework_cat.info(false) << _highlight << "\n"; _highlight.show_bounds(); // Also create a new instance of the highlighted geometry, as a // sibling of itself, under the special highlight property. if (_highlight.has_parent()) { _highlight_wireframe.reparent_to(_highlight.get_parent()); _highlight.instance_to(_highlight_wireframe); } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::clear_highlight // Access: Public // Description: Unhighlights the currently highlighted node, if any. //////////////////////////////////////////////////////////////////// void PandaFramework:: clear_highlight() { if (!_highlight.is_empty()) { _highlight.hide_bounds(); _highlight = NodePath(); // Clean up the special highlight instance. _highlight_wireframe.detach_node(); _highlight_wireframe.get_children().detach(); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::enable_default_keys // Access: Public // Description: Sets callbacks on the event handler to handle all of // the normal viewer keys, like t to toggle texture, ESC // or q to quit, etc. //////////////////////////////////////////////////////////////////// void PandaFramework:: enable_default_keys() { if (!_default_keys_enabled) { do_enable_default_keys(); _default_keys_enabled = true; } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::do_frame // Access: Public, Virtual // Description: Renders one frame and performs all associated // processing. Returns true if we should continue // rendering, false if we should exit. This is normally // called only from main_loop(). //////////////////////////////////////////////////////////////////// bool PandaFramework:: do_frame(Thread *current_thread) { nassertr(_is_open, false); DataGraphTraverser dg_trav(current_thread); dg_trav.traverse(_data_root.node()); throw_event("NewFrame"); _event_handler.process_events(); if (!_screenshot_text.is_empty()) { double now = ClockObject::get_global_clock()->get_frame_time(); if (now >= _screenshot_clear_time) { _screenshot_text.remove_node(); } } if (_recorder != (RecorderController *)NULL) { _recorder->record_frame(); } if (_engine != (GraphicsEngine *)NULL) { _engine->render_frame(); } if (_recorder != (RecorderController *)NULL) { _recorder->play_frame(); } return !_exit_flag; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::main_loop // Access: Public // Description: Called to yield control to the panda framework. This // function does not return until set_exit_flag() has // been called. //////////////////////////////////////////////////////////////////// void PandaFramework:: main_loop() { Thread *current_thread = Thread::get_current_thread(); while (do_frame(current_thread)) { } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::make_window_framework // Access: Protected, Virtual // Description: Creates a new WindowFramework object. This is // provided as a hook so derived PandaFramework classes // can create custom WindowFramework objects. //////////////////////////////////////////////////////////////////// PT(WindowFramework) PandaFramework:: make_window_framework() { return new WindowFramework(this); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::make_default_pipe // Access: Protected, Virtual // Description: Creates the default GraphicsPipe that will contain // all windows that are not opened on a specific pipe. //////////////////////////////////////////////////////////////////// void PandaFramework:: make_default_pipe() { // This depends on the shared library or libraries (DLL's to you // Windows folks) that have been loaded in at runtime from the // load-display and/or aux-display Configrc variables. GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); selection->print_pipe_types(); _default_pipe = selection->make_default_pipe(); if (_default_pipe == (GraphicsPipe*)NULL) { nout << "No graphics pipe is available!\n" << "Your Config.prc file must name at least one valid panda display\n" << "library via load-display or aux-display.\n"; } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::do_enable_default_keys // Access: Protected, Virtual // Description: The implementation of enable_default_keys(). //////////////////////////////////////////////////////////////////// void PandaFramework:: do_enable_default_keys() { define_key("escape", "close window", event_esc, this); define_key("q", "close window", event_esc, this); define_key("f", "report frame rate", event_f, this); define_key("w", "toggle wireframe mode", event_w, this); define_key("t", "toggle texturing", event_t, this); define_key("b", "toggle backface (double-sided) rendering", event_b, this); define_key("i", "invert (reverse) single-sided faces", event_i, this); define_key("l", "toggle lighting", event_l, this); define_key("p", "toggle per-pixel lighting", event_p, 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); define_key("shift-a", "analyze hierarchy", event_A, this); define_key("h", "highlight node", event_h, this); define_key("arrow_up", "move highlight to parent", event_arrow_up, this); define_key("arrow_down", "move highlight to child", event_arrow_down, this); define_key("arrow_left", "move highlight to sibling", event_arrow_left, this); define_key("arrow_right", "move highlight to sibling", event_arrow_right, this); define_key("shift-s", "activate PStats", event_S, this); define_key("f9", "Take screenshot", event_f9, this); define_key(",", "change background color", event_comma, this); define_key("?", "", event_question, this); define_key("shift-/", "", event_question, this); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::clear_text // Access: Protected // Description: Removes any onscreen text (like help text or // screenshot filename). Returns true if there was any // text in the first place, false otherwise. //////////////////////////////////////////////////////////////////// bool PandaFramework:: clear_text() { bool any_text = false; if (!_screenshot_text.is_empty()) { _screenshot_text.remove_node(); any_text = true; } if (!_help_text.is_empty()) { _help_text.remove_node(); any_text = true; } return any_text; } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_esc // Access: Public, Static // Description: Default handler for ESC or q key: close the current // window (and exit the application if that was the last // window). //////////////////////////////////////////////////////////////////// void PandaFramework:: event_esc(const 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()); PT(GraphicsWindow) win = wf->get_graphics_window(); PandaFramework *self = (PandaFramework *)data; self->close_window(wf); // Also close any other WindowFrameworks on the same window. int window_index = self->find_window(win); while (window_index != -1) { self->close_window(window_index); window_index = self->find_window(win); } // Free up the mouse for that window. self->remove_mouse(win); // Make sure the close request propagates through the system. self->_engine->open_windows(); // If we closed the last window, shut down. if (self->all_windows_closed()) { self->_exit_flag = true; } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_f // Access: Public, Static // Description: Default handler for f key: report and reset frame // rate. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_f(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; self->report_frame_rate(nout); self->reset_frame_rate(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_w // Access: Public, Static // Description: Default handler for w key: toggle wireframe. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_w(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_wireframe(!wf->get_wireframe()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_t // Access: Public, Static // Description: Default handler for t key: toggle texture. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_t(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_texture(!wf->get_texture()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_b // Access: Public, Static // Description: Default handler for b key: toggle backface (two-sided // rendering). //////////////////////////////////////////////////////////////////// void PandaFramework:: event_b(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_two_sided(!wf->get_two_sided()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_i // Access: Public, Static // Description: Default handler for i key: invert one-sided faces. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_i(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_one_sided_reverse(!wf->get_one_sided_reverse()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_l // Access: Public, Static // Description: Default handler for l key: toggle lighting. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_l(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_lighting(!wf->get_lighting()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_p // Access: Public, Static // Description: Default handler for p key: toggle per-pixel lighting. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_p(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); wf->set_perpixel(!wf->get_perpixel()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_c // Access: Public, Static // Description: Default handler for c key: center the trackball over // the scene, or over the highlighted part of the scene. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_c(const 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()); PandaFramework *self = (PandaFramework *)data; NodePath node = self->get_highlight(); if (node.is_empty()) { node = self->get_models(); } wf->center_trackball(node); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_a // Access: Public, Static // Description: Default handler for a key: toggle the animation // controls. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_a(const 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 // Description: Default handler for shift-C key: toggle the showing // of collision solids. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_C(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; NodePath node = self->get_highlight(); if (node.is_empty()) { node = self->get_models(); } if (self->hide_collision_solids(node) == 0) { self->show_collision_solids(node); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_B // Access: Public, Static // Description: Default handler for shift-B key: describe the // bounding volume of the currently selected object, or // the entire scene. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_B(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; NodePath node = self->get_highlight(); if (node.is_empty()) { node = self->get_models(); } node.get_bounds()->write(nout); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_L // Access: Public, Static // Description: Default handler for shift-L key: list the contents of // the scene graph, or the highlighted node. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_L(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; NodePath node = self->get_highlight(); if (node.is_empty()) { node = self->get_models(); } node.ls(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_A // Access: Public, Static // Description: Default handler for shift-A key: analyze the contents // of the scene graph, or the highlighted node. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_A(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; NodePath node = self->get_highlight(); if (node.is_empty()) { node = self->get_models(); } node.analyze(); } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_h // Access: Public, Static // Description: Default handler for h key: toggle highlight mode. In // this mode, you can walk the scene graph with the // arrow keys to highlight different nodes. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_h(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; if (self->has_highlight()) { self->clear_highlight(); } else { self->set_highlight(self->get_models()); } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_arrow_up // Access: Public, Static // Description: Default handler for up arrow key: in highlight mode, // move the highlight to the node's parent. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_arrow_up(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; if (self->has_highlight()) { NodePath node = self->get_highlight(); if (node.has_parent() && node != self->get_models()) { self->set_highlight(node.get_parent()); } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_arrow_down // Access: Public, Static // Description: Default handler for up arrow key: in highlight mode, // move the highlight to the node's first child. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_arrow_down(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; if (self->has_highlight()) { NodePath node = self->get_highlight(); if (node.get_num_children() > 0) { self->set_highlight(node.get_child(0)); } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_arrow_left // Access: Public, Static // Description: Default handler for up arrow key: in highlight mode, // move the highlight to the node's nearest sibling on // the left. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_arrow_left(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; if (self->has_highlight()) { NodePath node = self->get_highlight(); NodePath parent = node.get_parent(); if (node.has_parent() && node != self->get_models()) { int index = parent.node()->find_child(node.node()); nassertv(index >= 0); int sibling = index - 1; if (sibling >= 0 && parent.node()->get_child(sibling) == self->_highlight_wireframe.node()) { // Skip over the special highlight node. sibling--; } if (sibling >= 0) { self->set_highlight(NodePath(parent, parent.node()->get_child(sibling))); } } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_arrow_right // Access: Public, Static // Description: Default handler for up arrow key: in highlight mode, // move the highlight to the node's nearest sibling on // the right. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_arrow_right(const Event *, void *data) { PandaFramework *self = (PandaFramework *)data; if (self->has_highlight()) { NodePath node = self->get_highlight(); NodePath parent = node.get_parent(); if (node.has_parent() && node != self->get_models()) { int index = parent.node()->find_child(node.node()); nassertv(index >= 0); int num_children = parent.node()->get_num_children(); int sibling = index + 1; if (sibling < num_children && parent.node()->get_child(sibling) == self->_highlight_wireframe.node()) { // Skip over the special highlight node. sibling++; } if (sibling < num_children) { self->set_highlight(NodePath(parent, parent.node()->get_child(sibling))); } } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_S // Access: Public, Static // Description: Default handler for shift-S key: activate stats. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_S(const Event *, void *) { #ifdef DO_PSTATS nout << "Connecting to stats host" << endl; PStatClient::connect(); #else nout << "Stats host not supported." << endl; #endif } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_f9 // Access: Public, Static // Description: Default handler for f9 key: take screenshot. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_f9(const Event *event, void *data) { PandaFramework *self = (PandaFramework *)data; if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); if (self->clear_text()) { // Render one more frame to remove the text. self->_engine->render_frame(); } Filename filename = wf->get_graphics_window()->save_screenshot_default(); string text; if (filename.empty()) { text = "Screenshot failed"; } else { text = filename; } // Adds the full path to the output string string output_text = (string)ExecutionEnvironment::get_cwd() + "/" + (string)text; TextNode *text_node = new TextNode("screenshot"); self->_screenshot_text = NodePath(text_node); text_node->set_align(TextNode::A_center); text_node->set_shadow_color(0.0f, 0.0f, 0.0f, 1.0f); text_node->set_shadow(0.04f, 0.04f); text_node->set_text(output_text); self->_screenshot_text.set_scale(0.06); self->_screenshot_text.set_pos(0.0, 0.0, -0.7); self->_screenshot_text.reparent_to(wf->get_aspect_2d()); cout << "Screenshot saved: " + output_text + "\n"; double now = ClockObject::get_global_clock()->get_frame_time(); self->_screenshot_clear_time = now + 3.0; } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_comma // Access: Public, Static // Description: Default handler for comma key: rotate background color. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_comma(const Event *event, void *) { if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); switch (wf->get_background_type()) { case WindowFramework::BT_other: case WindowFramework::BT_none: break; case WindowFramework::BT_white: wf->set_background_type(WindowFramework::BT_default); break; default: wf->set_background_type((WindowFramework::BackgroundType)(wf->get_background_type() + 1)); } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_question // Access: Public, Static // Description: Default handler for ? key: show the available keys. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_question(const Event *event, void *data) { PandaFramework *self = (PandaFramework *)data; if (event->get_num_parameters() == 1) { EventParameter param = event->get_parameter(0); WindowFramework *wf; DCAST_INTO_V(wf, param.get_ptr()); self->_screenshot_text.remove_node(); if (!self->_help_text.is_empty()) { self->_help_text.remove_node(); // This key is a toggle; remove the help text and do nothing else. } else { // Build up a string to display. ostringstream help; KeyDefinitions::const_iterator ki; for (ki = self->_key_definitions.begin(); ki != self->_key_definitions.end(); ++ki) { const KeyDefinition &keydef = (*ki); help << keydef._event_name << "\t" << keydef._description << "\n"; } string help_text = help.str(); TextNode *text_node = new TextNode("help"); self->_help_text = NodePath(text_node); text_node->set_align(TextNode::A_left); text_node->set_shadow_color(0.0f, 0.0f, 0.0f, 1.0f); text_node->set_shadow(0.04f, 0.04f); text_node->set_text(help_text); LVecBase4f frame = text_node->get_frame_actual(); float height = frame[3] - frame[2]; float scale = min(0.06, 1.8 / height); self->_help_text.set_scale(scale); float pos_scale = scale / -2.0; self->_help_text.set_pos((frame[0] + frame[1]) * pos_scale, 0.0, (frame[2] + frame[3]) * pos_scale); self->_help_text.reparent_to(wf->get_aspect_2d()); } } } //////////////////////////////////////////////////////////////////// // Function: PandaFramework::event_window_event // Access: Public, Static // Description: Default handler for window events: window resized or // closed, etc. //////////////////////////////////////////////////////////////////// void PandaFramework:: event_window_event(const Event *event, void *data) { PandaFramework *self = (PandaFramework *)data; if (event->get_num_parameters() == 1) { // The parameter of the window event is the window itself, rather // than the window framework object (which is the parameter of all // of the keyboard events). EventParameter param = event->get_parameter(0); const GraphicsWindow *win; DCAST_INTO_V(win, param.get_ptr()); // Is this a window we've heard about? int window_index = self->find_window(win); if (window_index == -1) { framework_cat.debug() << "Ignoring message from unknown window.\n"; } else { if (!win->get_properties().get_open()) { int window_index = self->find_window(win); while (window_index != -1) { self->close_window(window_index); window_index = self->find_window(win); } // Free up the mouse for that window. self->remove_mouse(win); // If the last window was closed, exit the application. if (self->all_windows_closed() && !self->_exit_flag) { framework_cat.info() << "Last window was closed by user.\n"; self->_exit_flag = true; } } } } }