Panda3D
 All Classes Functions Variables Enumerations
pgItem.cxx
00001 // Filename: pgItem.cxx
00002 // Created by:  drose (13Mar02)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pgItem.h"
00016 #include "pgMouseWatcherParameter.h"
00017 #include "pgCullTraverser.h"
00018 #include "config_pgui.h"
00019 #include "boundingVolume.h"
00020 #include "pandaNode.h"
00021 #include "sceneGraphReducer.h"
00022 #include "throw_event.h"
00023 #include "string_utils.h"
00024 #include "nodePath.h"
00025 #include "cullTraverser.h"
00026 #include "cullTraverserData.h"
00027 #include "cullBinManager.h"
00028 #include "clipPlaneAttrib.h"
00029 #include "scissorAttrib.h"
00030 #include "dcast.h"
00031 #include "boundingSphere.h"
00032 #include "boundingBox.h"
00033 #include "config_mathutil.h"
00034 
00035 #ifdef HAVE_AUDIO
00036 #include "audioSound.h"
00037 #endif
00038 
00039 TypeHandle PGItem::_type_handle;
00040 PT(TextNode) PGItem::_text_node;
00041 PGItem *PGItem::_focus_item = (PGItem *)NULL;
00042 PGItem::BackgroundFocus PGItem::_background_focus;
00043 
00044 
00045 ////////////////////////////////////////////////////////////////////
00046 //     Function: is_right
00047 //  Description: Returns true if the 2-d v1 is to the right of v2.
00048 ////////////////////////////////////////////////////////////////////
00049 INLINE bool
00050 is_right(const LVector2 &v1, const LVector2 &v2) {
00051   return (v1[0] * v2[1] - v1[1] * v2[0]) > 0;
00052 }
00053 
00054 ////////////////////////////////////////////////////////////////////
00055 //     Function: PGItem::Constructor
00056 //       Access: Published
00057 //  Description: 
00058 ////////////////////////////////////////////////////////////////////
00059 PGItem::
00060 PGItem(const string &name) : 
00061   PandaNode(name),
00062   _lock(name)
00063 {
00064   set_cull_callback();
00065 
00066   _notify = NULL;
00067   _has_frame = false;
00068   _frame.set(0, 0, 0, 0);
00069   _region = new PGMouseWatcherRegion(this);
00070   _state = 0;
00071   _flags = 0;
00072 }
00073 
00074 ////////////////////////////////////////////////////////////////////
00075 //     Function: PGItem::Destructor
00076 //       Access: Public, Virtual
00077 //  Description: 
00078 ////////////////////////////////////////////////////////////////////
00079 PGItem::
00080 ~PGItem() {
00081   if (_notify != (PGItemNotify *)NULL) {
00082     _notify->remove_item(this);
00083     _notify = NULL;
00084   }
00085 
00086   nassertv(_region->_item == this);
00087   _region->_item = (PGItem *)NULL;
00088 
00089   set_background_focus(false);
00090   if (_focus_item == this) {
00091     _focus_item = (PGItem *)NULL;
00092   }
00093 }
00094 
00095 ////////////////////////////////////////////////////////////////////
00096 //     Function: PGItem::Copy Constructor
00097 //       Access: Public
00098 //  Description: 
00099 ////////////////////////////////////////////////////////////////////
00100 PGItem::
00101 PGItem(const PGItem &copy) :
00102   PandaNode(copy),
00103   _has_frame(copy._has_frame),
00104   _frame(copy._frame),
00105   _state(copy._state),
00106   _flags(copy._flags)
00107 #ifdef HAVE_AUDIO
00108   , _sounds(copy._sounds)
00109 #endif
00110 {
00111   _notify = NULL;
00112   _region = new PGMouseWatcherRegion(this);
00113   
00114   // We give our region the same name as the region for the PGItem
00115   // we're copying--so that this PGItem will generate the same event
00116   // names when the user interacts with it.
00117   _region->set_name(copy._region->get_name());
00118 
00119   // Make a deep copy of all of the original PGItem's StateDefs.
00120   size_t num_state_defs = copy._state_defs.size();
00121   _state_defs.reserve(num_state_defs);
00122   for (size_t i = 0; i < num_state_defs; ++i) {
00123     // We cheat and cast away the const, because the frame is just a
00124     // cache.  But we have to get the frame out of the source before we
00125     // can safely copy it.
00126     StateDef &old_sd = (StateDef &)(copy._state_defs[i]);
00127     old_sd._frame.remove_node();
00128     old_sd._frame_stale = true;
00129 
00130     StateDef new_sd;
00131     new_sd._root = old_sd._root.copy_to(NodePath());
00132     new_sd._frame_style = old_sd._frame_style;
00133 
00134     _state_defs.push_back(new_sd);
00135   }
00136 }
00137 
00138 ////////////////////////////////////////////////////////////////////
00139 //     Function: PGItem::make_copy
00140 //       Access: Protected, Virtual
00141 //  Description: Returns a newly-allocated Node that is a shallow copy
00142 //               of this one.  It will be a different Node pointer,
00143 //               but its internal data may or may not be shared with
00144 //               that of the original Node.
00145 ////////////////////////////////////////////////////////////////////
00146 PandaNode *PGItem::
00147 make_copy() const {
00148   LightReMutexHolder holder(_lock);
00149   return new PGItem(*this);
00150 }
00151 
00152 ////////////////////////////////////////////////////////////////////
00153 //     Function: PGItem::transform_changed
00154 //       Access: Protected, Virtual
00155 //  Description: Called after the node's transform has been changed
00156 //               for any reason, this just provides a hook so derived
00157 //               classes can do something special in this case.
00158 ////////////////////////////////////////////////////////////////////
00159 void PGItem::
00160 transform_changed() {
00161   LightReMutexHolder holder(_lock);
00162   PandaNode::transform_changed();
00163   if (has_notify()) {
00164     get_notify()->item_transform_changed(this);
00165   }
00166 }
00167 
00168 ////////////////////////////////////////////////////////////////////
00169 //     Function: PGItem::draw_mask_changed
00170 //       Access: Protected, Virtual
00171 //  Description: Called after the node's draw_mask has been changed
00172 //               for any reason, this just provides a hook so derived
00173 //               classes can do something special in this case.
00174 ////////////////////////////////////////////////////////////////////
00175 void PGItem::
00176 draw_mask_changed() {
00177   LightReMutexHolder holder(_lock);
00178   PandaNode::draw_mask_changed();
00179   if (has_notify()) {
00180     get_notify()->item_draw_mask_changed(this);
00181   }
00182 }
00183 
00184 ////////////////////////////////////////////////////////////////////
00185 //     Function: PGItem::cull_callback
00186 //       Access: Protected, Virtual
00187 //  Description: This function will be called during the cull
00188 //               traversal to perform any additional operations that
00189 //               should be performed at cull time.  This may include
00190 //               additional manipulation of render state or additional
00191 //               visible/invisible decisions, or any other arbitrary
00192 //               operation.
00193 //
00194 //               Note that this function will *not* be called unless
00195 //               set_cull_callback() is called in the constructor of
00196 //               the derived class.  It is necessary to call
00197 //               set_cull_callback() to indicated that we require
00198 //               cull_callback() to be called.
00199 //
00200 //               By the time this function is called, the node has
00201 //               already passed the bounding-volume test for the
00202 //               viewing frustum, and the node's transform and state
00203 //               have already been applied to the indicated
00204 //               CullTraverserData object.
00205 //
00206 //               The return value is true if this node should be
00207 //               visible, or false if it should be culled.
00208 ////////////////////////////////////////////////////////////////////
00209 bool PGItem::
00210 cull_callback(CullTraverser *trav, CullTraverserData &data) {
00211   LightReMutexHolder holder(_lock);
00212   bool this_node_hidden = data.is_this_node_hidden(trav);
00213   if (!this_node_hidden && has_frame() && get_active()) {
00214     // The item has a frame, so we want to generate a region for it
00215     // and update the MouseWatcher.
00216 
00217     // We can only do this if our traverser is a PGCullTraverser
00218     // (which will be the case if this node was parented somewhere
00219     // under a PGTop node).
00220     if (trav->is_exact_type(PGCullTraverser::get_class_type())) {
00221       PGCullTraverser *pg_trav;
00222       DCAST_INTO_R(pg_trav, trav, true);
00223 
00224       CPT(TransformState) net_transform = data.get_net_transform(trav);
00225       const LMatrix4 &transform = net_transform->get_mat();
00226 
00227       // Consider the cull bin this object is in.  Since the binning
00228       // affects the render order, we want bins that render later to
00229       // get higher sort values.
00230       int bin_index = data._state->get_bin_index();
00231       int sort;
00232 
00233       CullBinManager *bin_manager = CullBinManager::get_global_ptr();
00234       CullBinManager::BinType bin_type = bin_manager->get_bin_type(bin_index);
00235       if (bin_type == CullBinManager::BT_fixed) {
00236         // If the bin is a "fixed" type bin, our local sort is based
00237         // on the fixed order.
00238         sort = data._state->get_draw_order();
00239 
00240       } else if (bin_type == CullBinManager::BT_unsorted) {
00241         // If the bin is an "unsorted" type bin, we base the local
00242         // sort on the scene graph order.
00243         sort = pg_trav->_sort_index;
00244         pg_trav->_sort_index++;
00245 
00246       } else {
00247         // Otherwise, the local sort is irrelevant.
00248         sort = 0;
00249       }
00250 
00251       // Now what order does this bin sort relative to the other bins?
00252       // This becomes the high-order part of the final sort count.
00253       int bin_sort = bin_manager->get_bin_sort(data._state->get_bin_index());
00254 
00255       // Combine the two sorts into a single int.  This assumes we
00256       // only need 16 bits for each sort number, possibly an erroneous
00257       // assumption.  We should really provide two separate sort
00258       // values, both ints, in the MouseWatcherRegion; but in the
00259       // interest of expediency we work within the existing interface
00260       // which only provides one.
00261       sort = (bin_sort << 16) | ((sort + 0x8000) & 0xffff);
00262 
00263       if (activate_region(transform, sort, 
00264                           DCAST(ClipPlaneAttrib, data._state->get_attrib(ClipPlaneAttrib::get_class_slot())),
00265                           DCAST(ScissorAttrib, data._state->get_attrib(ScissorAttrib::get_class_slot())))) {
00266         pg_trav->_top->add_region(get_region());
00267       }
00268     }
00269   }
00270 
00271   if (has_state_def(get_state())) {
00272     // This item has a current state definition that we should use
00273     // to render the item.
00274     NodePath &root = get_state_def(get_state());
00275     CullTraverserData next_data(data, root.node());
00276     trav->traverse(next_data);
00277   }
00278 
00279   // Now continue to render everything else below this node.
00280   return true;
00281 }
00282 
00283 ////////////////////////////////////////////////////////////////////
00284 //     Function: PGItem::is_renderable
00285 //       Access: Public, Virtual
00286 //  Description: Returns true if there is some value to visiting this
00287 //               particular node during the cull traversal for any
00288 //               camera, false otherwise.  This will be used to
00289 //               optimize the result of get_net_draw_show_mask(), so
00290 //               that any subtrees that contain only nodes for which
00291 //               is_renderable() is false need not be visited.
00292 ////////////////////////////////////////////////////////////////////
00293 bool PGItem::
00294 is_renderable() const {
00295   return true;
00296 }
00297 
00298 ////////////////////////////////////////////////////////////////////
00299 //     Function: PGItem::compute_internal_bounds
00300 //       Access: Protected, Virtual
00301 //  Description: Called when needed to recompute the node's
00302 //               _internal_bound object.  Nodes that contain anything
00303 //               of substance should redefine this to do the right
00304 //               thing.
00305 ////////////////////////////////////////////////////////////////////
00306 void PGItem::
00307 compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
00308                         int &internal_vertices,
00309                         int pipeline_stage,
00310                         Thread *current_thread) const {
00311   LightReMutexHolder holder(_lock, current_thread);
00312   int num_vertices = 0;
00313 
00314   // First, get ourselves a fresh, empty bounding volume.
00315   PT(BoundingVolume) bound;
00316 
00317   BoundingVolume::BoundsType btype = get_bounds_type();
00318   if (btype == BoundingVolume::BT_default) {
00319     btype = bounds_type;
00320   }
00321 
00322   if (btype == BoundingVolume::BT_sphere) {
00323     bound = new BoundingSphere;
00324   } else {
00325     bound = new BoundingBox;
00326   }
00327 
00328   // Now actually compute the bounding volume by putting it around all
00329   // of our states' bounding volumes.
00330   pvector<const BoundingVolume *> child_volumes;
00331 
00332   // We walk through the list of state defs indirectly, calling
00333   // get_state_def() on each one, to ensure that the frames are
00334   // updated correctly before we measure their bounding volumes.
00335   for (int i = 0; i < (int)_state_defs.size(); i++) {
00336     NodePath &root = ((PGItem *)this)->get_state_def(i);
00337     if (!root.is_empty()) {
00338       PandaNode *node = root.node();
00339       child_volumes.push_back(node->get_bounds(current_thread));
00340       num_vertices += node->get_nested_vertices(current_thread);
00341     }
00342   }
00343 
00344   const BoundingVolume **child_begin = &child_volumes[0];
00345   const BoundingVolume **child_end = child_begin + child_volumes.size();
00346 
00347   bound->around(child_begin, child_end);
00348 
00349   internal_bounds = bound;
00350   internal_vertices = num_vertices;
00351 }
00352 
00353 ////////////////////////////////////////////////////////////////////
00354 //     Function: PGItem::r_prepare_scene
00355 //       Access: Protected, Virtual
00356 //  Description: The recursive implementation of prepare_scene().
00357 //               Don't call this directly; call
00358 //               PandaNode::prepare_scene() or
00359 //               NodePath::prepare_scene() instead.
00360 ////////////////////////////////////////////////////////////////////
00361 void PGItem::
00362 r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
00363                 GeomTransformer &transformer, Thread *current_thread) {
00364   LightReMutexHolder holder(_lock);
00365   StateDefs::iterator di;
00366   for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
00367     NodePath &root = (*di)._root;
00368     if (!root.is_empty()) {
00369       PandaNode *child = root.node();
00370       CPT(RenderState) child_state = node_state->compose(child->get_state());
00371       child->r_prepare_scene(gsg, child_state, transformer, current_thread);
00372     }
00373   }
00374   
00375   PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread);
00376 }
00377 
00378 ////////////////////////////////////////////////////////////////////
00379 //     Function: PGItem::xform
00380 //       Access: Public, Virtual
00381 //  Description: Transforms the contents of this node by the indicated
00382 //               matrix, if it means anything to do so.  For most
00383 //               kinds of nodes, this does nothing.
00384 ////////////////////////////////////////////////////////////////////
00385 void PGItem::
00386 xform(const LMatrix4 &mat) {
00387   LightReMutexHolder holder(_lock);
00388   // Transform the frame.
00389   LPoint3 ll(_frame[0], 0.0f, _frame[2]);
00390   LPoint3 ur(_frame[1], 0.0f, _frame[3]);
00391   ll = ll * mat;
00392   ur = ur * mat;
00393   _frame.set(ll[0], ur[0], ll[2], ur[2]);
00394 
00395   // Transform the individual states and their frame styles.
00396   StateDefs::iterator di;
00397   for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
00398     NodePath &root = (*di)._root;
00399     // Apply the matrix to the previous transform.
00400     root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat)));
00401 
00402     // Now flatten the transform into the subgraph.
00403     SceneGraphReducer gr;
00404     gr.apply_attribs(root.node());
00405 
00406     // Transform the frame style too.
00407     if ((*di)._frame_style.xform(mat)) {
00408       (*di)._frame_stale = true;
00409     }
00410   }
00411   mark_internal_bounds_stale();
00412 }
00413 
00414 ////////////////////////////////////////////////////////////////////
00415 //     Function: PGItem::activate_region
00416 //       Access: Public
00417 //  Description: Applies the indicated scene graph transform and order
00418 //               as determined by the traversal from PGTop.
00419 //
00420 //               The return value is true if the region is valid, or
00421 //               false if it is empty or completely clipped.
00422 ////////////////////////////////////////////////////////////////////
00423 bool PGItem::
00424 activate_region(const LMatrix4 &transform, int sort,
00425                 const ClipPlaneAttrib *cpa,
00426                 const ScissorAttrib *sa) {
00427   LightReMutexHolder holder(_lock);
00428   // Transform all four vertices, and get the new bounding box.  This
00429   // way the region works (mostly) even if has been rotated.
00430   LPoint3 ll = LPoint3::rfu(_frame[0], 0.0f, _frame[2]) * transform;
00431   LPoint3 lr = LPoint3::rfu(_frame[1], 0.0f, _frame[2]) * transform;
00432   LPoint3 ul = LPoint3::rfu(_frame[0], 0.0f, _frame[3]) * transform;
00433   LPoint3 ur = LPoint3::rfu(_frame[1], 0.0f, _frame[3]) * transform;
00434   LVector3 up = LVector3::up();
00435   int up_axis;
00436   if (up[1]) {
00437     up_axis = 1;
00438   }
00439   else if (up[2]) {
00440     up_axis = 2;
00441   }
00442   else {
00443     up_axis = 0;
00444   }
00445   LVector3 right = LVector3::right();
00446   int right_axis;
00447   if (right[0]) {
00448     right_axis = 0;
00449   }
00450   else if (right[2]) {
00451     right_axis = 2;
00452   }
00453   else {
00454     right_axis = 1;
00455   }
00456 
00457   LVecBase4 frame;
00458   if (cpa != (ClipPlaneAttrib *)NULL && cpa->get_num_on_planes() != 0) {
00459     // Apply the clip plane(s) and/or scissor region now that we are
00460     // here in world space.
00461     
00462     ClipPoints points;
00463     points.reserve(4);
00464     points.push_back(LPoint2(ll[right_axis], ll[up_axis]));
00465     points.push_back(LPoint2(lr[right_axis], lr[up_axis]));
00466     points.push_back(LPoint2(ur[right_axis], ur[up_axis]));
00467     points.push_back(LPoint2(ul[right_axis], ul[up_axis]));
00468 
00469     int num_on_planes = cpa->get_num_on_planes();
00470     for (int i = 0; i < num_on_planes; ++i) {
00471       NodePath plane_path = cpa->get_on_plane(i);
00472       LPlane plane = DCAST(PlaneNode, plane_path.node())->get_plane();
00473       plane.xform(plane_path.get_net_transform()->get_mat());
00474       
00475       // We ignore the forward axis, assuming the frame is still in
00476       // the right-up plane after being transformed.  Not sure if we really
00477       // need to support general 3-D transforms on 2-D objects.
00478       clip_frame(points, plane);
00479     }
00480     
00481     if (points.empty()) {
00482       // Turns out it's completely clipped after all.
00483       return false;
00484     }
00485 
00486     ClipPoints::iterator pi;
00487     pi = points.begin();
00488     frame.set((*pi)[0], (*pi)[0], (*pi)[1], (*pi)[1]);
00489     ++pi;
00490     while (pi != points.end()) {
00491       frame[0] = min(frame[0], (*pi)[0]);
00492       frame[1] = max(frame[1], (*pi)[0]);
00493       frame[2] = min(frame[2], (*pi)[1]);
00494       frame[3] = max(frame[3], (*pi)[1]);
00495       ++pi;
00496     }
00497   } else {
00498     // Since there are no clip planes involved, just set the frame.
00499     frame.set(min(min(ll[right_axis], lr[right_axis]), min(ul[right_axis], ur[right_axis])),
00500               max(max(ll[right_axis], lr[right_axis]), max(ul[right_axis], ur[right_axis])),
00501               min(min(ll[up_axis], lr[up_axis]), min(ul[up_axis], ur[up_axis])),
00502               max(max(ll[up_axis], lr[up_axis]), max(ul[up_axis], ur[up_axis])));
00503   }
00504 
00505   if (sa != (ScissorAttrib *)NULL) {
00506     // Also restrict it to within the scissor region.
00507     const LVecBase4 &sf = sa->get_frame();
00508     // Expand sf from 0..1 to -1..1.
00509     frame.set(max(frame[0], sf[0] * 2.0f - 1.0f),
00510               min(frame[1], sf[1] * 2.0f - 1.0f),
00511               max(frame[2], sf[2] * 2.0f - 1.0f),
00512               min(frame[3], sf[3] * 2.0f - 1.0f));
00513     if (frame[1] <= frame[0] || frame[3] <= frame[2]) {
00514       // Completely outside the scissor region.
00515       return false;
00516     }
00517   }
00518 
00519   _region->set_frame(frame);
00520                      
00521   _region->set_sort(sort);
00522   _region->set_active(true);
00523 
00524   // calculate the inverse of this transform, which is needed to 
00525   // go back to the frame space.
00526   _frame_inv_xform.invert_from(transform);
00527 
00528   return true;
00529 }
00530 
00531 ////////////////////////////////////////////////////////////////////
00532 //     Function: PGItem::enter_region
00533 //       Access: Public, Virtual
00534 //  Description: This is a callback hook function, called whenever the
00535 //               mouse enters the region.  The mouse is only
00536 //               considered to be "entered" in one region at a time;
00537 //               in the case of nested regions, it exits the outer
00538 //               region before entering the inner one.
00539 ////////////////////////////////////////////////////////////////////
00540 void PGItem::
00541 enter_region(const MouseWatcherParameter &param) {
00542   LightReMutexHolder holder(_lock);
00543   if (pgui_cat.is_debug()) {
00544     pgui_cat.debug()
00545       << *this << "::enter_region(" << param << ")\n";
00546   }
00547 
00548   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00549   string event = get_enter_event();
00550   play_sound(event);
00551   throw_event(event, EventParameter(ep));
00552 
00553   if (has_notify()) {
00554     get_notify()->item_enter(this, param);
00555   }
00556 }
00557 
00558 ////////////////////////////////////////////////////////////////////
00559 //     Function: PGItem::exit_region
00560 //       Access: Public, Virtual
00561 //  Description: This is a callback hook function, called whenever the
00562 //               mouse exits the region.  The mouse is only considered
00563 //               to be "entered" in one region at a time; in the case
00564 //               of nested regions, it exits the outer region before
00565 //               entering the inner one.
00566 ////////////////////////////////////////////////////////////////////
00567 void PGItem::
00568 exit_region(const MouseWatcherParameter &param) {
00569   LightReMutexHolder holder(_lock);
00570   if (pgui_cat.is_debug()) {
00571     pgui_cat.debug()
00572       << *this << "::exit_region(" << param << ")\n";
00573   }
00574 
00575   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00576   string event = get_exit_event();
00577   play_sound(event);
00578   throw_event(event, EventParameter(ep));
00579 
00580   if (has_notify()) {
00581     get_notify()->item_exit(this, param);
00582   }
00583 
00584   //pgui_cat.info() << get_name() << "::exit()" << endl;
00585 }
00586 
00587 ////////////////////////////////////////////////////////////////////
00588 //     Function: PGItem::within_region
00589 //       Access: Public, Virtual
00590 //  Description: This is a callback hook function, called whenever the
00591 //               mouse moves within the boundaries of the region, even
00592 //               if it is also within the boundaries of a nested
00593 //               region.  This is different from "enter", which is
00594 //               only called whenever the mouse is within only that
00595 //               region.
00596 ////////////////////////////////////////////////////////////////////
00597 void PGItem::
00598 within_region(const MouseWatcherParameter &param) {
00599   LightReMutexHolder holder(_lock);
00600   if (pgui_cat.is_debug()) {
00601     pgui_cat.debug()
00602       << *this << "::within_region(" << param << ")\n";
00603   }
00604 
00605   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00606   string event = get_within_event();
00607   play_sound(event);
00608   throw_event(event, EventParameter(ep));
00609 
00610   if (has_notify()) {
00611     get_notify()->item_within(this, param);
00612   }
00613 }
00614 
00615 ////////////////////////////////////////////////////////////////////
00616 //     Function: PGItem::without_region
00617 //       Access: Public, Virtual
00618 //  Description: This is a callback hook function, called whenever the
00619 //               mouse moves completely outside the boundaries of the
00620 //               region.  See within().
00621 ////////////////////////////////////////////////////////////////////
00622 void PGItem::
00623 without_region(const MouseWatcherParameter &param) {
00624   LightReMutexHolder holder(_lock);
00625   if (pgui_cat.is_debug()) {
00626     pgui_cat.debug()
00627       << *this << "::without_region(" << param << ")\n";
00628   }
00629 
00630   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00631   string event = get_without_event();
00632   play_sound(event);
00633   throw_event(event, EventParameter(ep));
00634 
00635   if (has_notify()) {
00636     get_notify()->item_without(this, param);
00637   }
00638 }
00639 
00640 ////////////////////////////////////////////////////////////////////
00641 //     Function: PGItem::focus_in
00642 //       Access: Public, Virtual
00643 //  Description: This is a callback hook function, called whenever the
00644 //               widget gets the keyboard focus.
00645 ////////////////////////////////////////////////////////////////////
00646 void PGItem::
00647 focus_in() {
00648   LightReMutexHolder holder(_lock);
00649   if (pgui_cat.is_debug()) {
00650     pgui_cat.debug()
00651       << *this << "::focus_in()\n";
00652   }
00653 
00654   string event = get_focus_in_event();
00655   play_sound(event);
00656   throw_event(event);
00657 
00658   if (has_notify()) {
00659     get_notify()->item_focus_in(this);
00660   }
00661 }
00662 
00663 ////////////////////////////////////////////////////////////////////
00664 //     Function: PGItem::focus_out
00665 //       Access: Public, Virtual
00666 //  Description: This is a callback hook function, called whenever the
00667 //               widget loses the keyboard focus.
00668 ////////////////////////////////////////////////////////////////////
00669 void PGItem::
00670 focus_out() {
00671   LightReMutexHolder holder(_lock);
00672   if (pgui_cat.is_debug()) {
00673     pgui_cat.debug()
00674       << *this << "::focus_out()\n";
00675   }
00676 
00677   string event = get_focus_out_event();
00678   play_sound(event);
00679   throw_event(event);
00680 
00681   if (has_notify()) {
00682     get_notify()->item_focus_out(this);
00683   }
00684 }
00685 
00686 ////////////////////////////////////////////////////////////////////
00687 //     Function: PGItem::press
00688 //       Access: Public, Virtual
00689 //  Description: This is a callback hook function, called whenever a
00690 //               mouse or keyboard button is depressed while the mouse
00691 //               is within the region.
00692 ////////////////////////////////////////////////////////////////////
00693 void PGItem::
00694 press(const MouseWatcherParameter &param, bool background) {
00695   LightReMutexHolder holder(_lock);
00696   if (pgui_cat.is_debug()) {
00697     pgui_cat.debug()
00698       << *this << "::press(" << param << ", " << background << ")\n";
00699   }
00700 
00701   if (!background) {
00702     PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00703     string event;
00704     if (param.is_keyrepeat()) {
00705       event = get_repeat_event(param.get_button());
00706     } else {
00707       event = get_press_event(param.get_button());
00708     }
00709     play_sound(event);
00710     throw_event(event, EventParameter(ep));
00711   }
00712 
00713   if (has_notify()) {
00714     get_notify()->item_press(this, param);
00715   }
00716 }
00717 
00718 ////////////////////////////////////////////////////////////////////
00719 //     Function: PGItem::release
00720 //       Access: Public, Virtual
00721 //  Description: This is a callback hook function, called whenever a
00722 //               mouse or keyboard button previously depressed with
00723 //               press() is released.
00724 ////////////////////////////////////////////////////////////////////
00725 void PGItem::
00726 release(const MouseWatcherParameter &param, bool background) {
00727   LightReMutexHolder holder(_lock);
00728   if (pgui_cat.is_debug()) {
00729     pgui_cat.debug()
00730       << *this << "::release(" << param << ", " << background << ")\n";
00731   }
00732 
00733   if (!background) {
00734     PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00735     string event = get_release_event(param.get_button());
00736     play_sound(event);
00737     throw_event(event, EventParameter(ep));
00738   }
00739 
00740   if (has_notify()) {
00741     get_notify()->item_release(this, param);
00742   }
00743 }
00744 
00745 ////////////////////////////////////////////////////////////////////
00746 //     Function: PGItem::keystroke
00747 //       Access: Public, Virtual
00748 //  Description: This is a callback hook function, called whenever
00749 //               the user presses a key.
00750 ////////////////////////////////////////////////////////////////////
00751 void PGItem::
00752 keystroke(const MouseWatcherParameter &param, bool background) {
00753   LightReMutexHolder holder(_lock);
00754   if (pgui_cat.is_debug()) {
00755     pgui_cat.debug()
00756       << *this << "::keystroke(" << param << ", " << background << ")\n";
00757   }
00758 
00759   if (!background) {
00760     PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00761     string event = get_keystroke_event();
00762     play_sound(event);
00763     throw_event(event, EventParameter(ep));
00764 
00765     if (has_notify()) {
00766       get_notify()->item_keystroke(this, param);
00767     }
00768   }
00769 }
00770 
00771 ////////////////////////////////////////////////////////////////////
00772 //     Function: PGItem::candidate
00773 //       Access: Public, Virtual
00774 //  Description: This is a callback hook function, called whenever
00775 //               the user highlights an option in the IME window.
00776 ////////////////////////////////////////////////////////////////////
00777 void PGItem::
00778 candidate(const MouseWatcherParameter &param, bool background) {
00779   LightReMutexHolder holder(_lock);
00780   if (pgui_cat.is_debug()) {
00781     pgui_cat.debug()
00782       << *this << "::candidate(" << param << ", " << background << ")\n";
00783   }
00784 
00785   // We don't throw sound events for candidate selections for now.
00786   if (!background) {
00787     if (has_notify()) {
00788       get_notify()->item_candidate(this, param);
00789     }
00790   }
00791 }
00792 
00793 ////////////////////////////////////////////////////////////////////
00794 //     Function: PGItem::move
00795 //       Access: Public, Virtual
00796 //  Description: This is a callback hook function, called whenever a
00797 //               mouse is moved while within the region.
00798 ////////////////////////////////////////////////////////////////////
00799 void PGItem::
00800 move(const MouseWatcherParameter &param) {
00801   LightReMutexHolder holder(_lock);
00802   if (pgui_cat.is_debug()) {
00803     pgui_cat.debug()
00804       << *this << "::move(" << param << ")\n";
00805   }
00806 
00807   if (has_notify()) {
00808     get_notify()->item_move(this, param);
00809   }
00810 }
00811 
00812 ////////////////////////////////////////////////////////////////////
00813 //     Function: PGItem::background_press
00814 //       Access: Public, Static
00815 //  Description: Calls press() on all the PGItems with background
00816 //               focus.
00817 ////////////////////////////////////////////////////////////////////
00818 void PGItem::
00819 background_press(const MouseWatcherParameter &param) {
00820   BackgroundFocus::const_iterator fi;
00821   for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
00822     PGItem *item = *fi;
00823     if (!item->get_focus()) {
00824       item->press(param, true);
00825     }
00826   }
00827 }
00828 
00829 ////////////////////////////////////////////////////////////////////
00830 //     Function: PGItem::background_release
00831 //       Access: Public, Static
00832 //  Description: Calls release() on all the PGItems with background
00833 //               focus.
00834 ////////////////////////////////////////////////////////////////////
00835 void PGItem::
00836 background_release(const MouseWatcherParameter &param) {
00837   BackgroundFocus::const_iterator fi;
00838   for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
00839     PGItem *item = *fi;
00840     if (!item->get_focus()) {
00841       item->release(param, true);
00842     }
00843   }
00844 }
00845 
00846 ////////////////////////////////////////////////////////////////////
00847 //     Function: PGItem::background_keystroke
00848 //       Access: Public, Static
00849 //  Description: Calls keystroke() on all the PGItems with background
00850 //               focus.
00851 ////////////////////////////////////////////////////////////////////
00852 void PGItem::
00853 background_keystroke(const MouseWatcherParameter &param) {
00854   BackgroundFocus::const_iterator fi;
00855   for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
00856     PGItem *item = *fi;
00857     if (!item->get_focus()) {
00858       item->keystroke(param, true);
00859     }
00860   }
00861 }
00862 
00863 ////////////////////////////////////////////////////////////////////
00864 //     Function: PGItem::background_candidate
00865 //       Access: Public, Static
00866 //  Description: Calls candidate() on all the PGItems with background
00867 //               focus.
00868 ////////////////////////////////////////////////////////////////////
00869 void PGItem::
00870 background_candidate(const MouseWatcherParameter &param) {
00871   BackgroundFocus::const_iterator fi;
00872   for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
00873     PGItem *item = *fi;
00874     if (!item->get_focus()) {
00875       item->candidate(param, true);
00876     }
00877   }
00878 }
00879 
00880 ////////////////////////////////////////////////////////////////////
00881 //     Function: PGItem::set_active
00882 //       Access: Published, Virtual
00883 //  Description: Sets whether the PGItem is active for mouse watching.
00884 //               This is not necessarily related to the
00885 //               active/inactive appearance of the item, which is
00886 //               controlled by set_state(), but it does affect whether
00887 //               it responds to mouse events.
00888 ////////////////////////////////////////////////////////////////////
00889 void PGItem::
00890 set_active(bool active) {
00891   LightReMutexHolder holder(_lock);
00892   if (active) {
00893     _flags |= F_active;
00894   } else {
00895     _flags &= ~F_active;
00896     // Deactivating the item automatically defocuses it too.
00897     if (get_focus()) {
00898       set_focus(false);
00899     }
00900   }
00901 }
00902 
00903 ////////////////////////////////////////////////////////////////////
00904 //     Function: PGItem::set_focus
00905 //       Access: Published, Virtual
00906 //  Description: Sets whether the PGItem currently has keyboard focus.
00907 //               This simply means that the item may respond to
00908 //               keyboard events as well as to mouse events; precisely
00909 //               what this means is up to the individual item.  
00910 //
00911 //               Only one PGItem in the world is allowed to have focus
00912 //               at any given time.  Setting the focus on any other
00913 //               item automatically disables the focus from the
00914 //               previous item.
00915 ////////////////////////////////////////////////////////////////////
00916 void PGItem::
00917 set_focus(bool focus) {
00918   LightReMutexHolder holder(_lock);
00919   if (focus) {
00920     if (!get_active()) {
00921       // Cannot set focus on an inactive item.
00922       return;
00923     }
00924 
00925     // Set the keyboard focus to this item.
00926     if (_focus_item != this) {
00927       if (_focus_item != (PGItem *)NULL) {
00928         // Clear the focus from whatever item currently has it.
00929         _focus_item->set_focus(false);
00930       }
00931       _focus_item = this;
00932     }
00933     if (!get_focus()) {
00934       focus_in();
00935       _flags |= F_focus;
00936     }
00937 
00938   } else {
00939     if (_focus_item == this) {
00940       // Remove this item from the focus.
00941       _focus_item = (PGItem *)NULL;
00942     }
00943 
00944     if (get_focus()) {
00945       focus_out();
00946       _flags &= ~F_focus;
00947     }
00948   }
00949   _region->set_keyboard(focus);
00950 }
00951 
00952 ////////////////////////////////////////////////////////////////////
00953 //     Function: PGItem::set_background_focus
00954 //       Access: Published
00955 //  Description: Sets the background_focus flag for this item.  When
00956 //               background_focus is enabled, the item will receive
00957 //               keypress events even if it is not in focus; in fact,
00958 //               even if it is not onscreen.  Unlike normal focus,
00959 //               many items may have background_focus simultaneously.
00960 ////////////////////////////////////////////////////////////////////
00961 void PGItem::
00962 set_background_focus(bool focus) {
00963   LightReMutexHolder holder(_lock);
00964   if (focus != get_background_focus()) {
00965     if (focus) {
00966       // Activate background focus.
00967       _flags |= F_background_focus;
00968       bool inserted = _background_focus.insert(this).second;
00969       nassertv(inserted);
00970 
00971     } else {
00972       // Deactivate background focus.
00973       _flags &= ~F_background_focus;
00974       size_t num_erased = _background_focus.erase(this);
00975       nassertv(num_erased == 1);
00976     }
00977   }
00978 }
00979 
00980 ////////////////////////////////////////////////////////////////////
00981 //     Function: PGItem::get_num_state_defs
00982 //       Access: Published
00983 //  Description: Returns one more than the highest-numbered state def
00984 //               that was ever assigned to the PGItem.  The complete
00985 //               set of state defs assigned may then be retrieved by
00986 //               indexing from 0 to (get_num_state_defs() - 1).
00987 //
00988 //               This is only an upper limit on the actual number of
00989 //               state defs, since there may be holes in the list.
00990 ////////////////////////////////////////////////////////////////////
00991 int PGItem::
00992 get_num_state_defs() const {
00993   LightReMutexHolder holder(_lock);
00994   return _state_defs.size();
00995 }
00996 
00997 ////////////////////////////////////////////////////////////////////
00998 //     Function: PGItem::has_state_def
00999 //       Access: Published
01000 //  Description: Returns true if get_state_def() has ever been called
01001 //               for the indicated state (thus defining a render
01002 //               subgraph for this state index), false otherwise.
01003 ////////////////////////////////////////////////////////////////////
01004 bool PGItem::
01005 has_state_def(int state) const {
01006   LightReMutexHolder holder(_lock);
01007   if (state < 0 || state >= (int)_state_defs.size()) {
01008     return false;
01009   }
01010   return (!_state_defs[state]._root.is_empty());
01011 }
01012 
01013 ////////////////////////////////////////////////////////////////////
01014 //     Function: PGItem::clear_state_def
01015 //       Access: Published
01016 //  Description: Resets the NodePath assigned to the indicated state
01017 //               to its initial default, with only a frame
01018 //               representation if appropriate.
01019 ////////////////////////////////////////////////////////////////////
01020 void PGItem::
01021 clear_state_def(int state) {
01022   LightReMutexHolder holder(_lock);
01023   if (state < 0 || state >= (int)_state_defs.size()) {
01024     return;
01025   }
01026 
01027   _state_defs[state]._root = NodePath();
01028   _state_defs[state]._frame = NodePath();
01029   _state_defs[state]._frame_stale = true;
01030 
01031   mark_internal_bounds_stale();
01032 }
01033 
01034 ////////////////////////////////////////////////////////////////////
01035 //     Function: PGItem::get_state_def
01036 //       Access: Published
01037 //  Description: Returns the Node that is the root of the subgraph
01038 //               that will be drawn when the PGItem is in the
01039 //               indicated state.  The first time this is called for a
01040 //               particular state index, it may create the Node.
01041 ////////////////////////////////////////////////////////////////////
01042 NodePath &PGItem::
01043 get_state_def(int state) {
01044   LightReMutexHolder holder(_lock);
01045   nassertr(state >= 0 && state < 1000, get_state_def(0));  // Sanity check.
01046   slot_state_def(state);
01047 
01048   if (_state_defs[state]._root.is_empty()) {
01049     // Create a new node.
01050     _state_defs[state]._root = NodePath("state_" + format_string(state));
01051     _state_defs[state]._frame_stale = true;
01052   }
01053 
01054   if (_state_defs[state]._frame_stale) {
01055     update_frame(state);
01056   }
01057 
01058   return _state_defs[state]._root;
01059 }
01060 
01061 ////////////////////////////////////////////////////////////////////
01062 //     Function: PGItem::instance_to_state_def
01063 //       Access: Published
01064 //  Description: Parents an instance of the bottom node of the
01065 //               indicated NodePath to the indicated state index.
01066 ////////////////////////////////////////////////////////////////////
01067 NodePath PGItem::
01068 instance_to_state_def(int state, const NodePath &path) {
01069   LightReMutexHolder holder(_lock);
01070   if (path.is_empty()) {
01071     // If the source is empty, quietly do nothing.
01072     return NodePath();
01073   }
01074 
01075   mark_internal_bounds_stale();
01076 
01077   return path.instance_to(get_state_def(state));
01078 }
01079 
01080 ////////////////////////////////////////////////////////////////////
01081 //     Function: PGItem::get_frame_style
01082 //       Access: Published
01083 //  Description: Returns the kind of frame that will be drawn behind
01084 //               the item when it is in the indicated state.
01085 ////////////////////////////////////////////////////////////////////
01086 PGFrameStyle PGItem::
01087 get_frame_style(int state) {
01088   LightReMutexHolder holder(_lock);
01089   if (state < 0 || state >= (int)_state_defs.size()) {
01090     return PGFrameStyle();
01091   }
01092   return _state_defs[state]._frame_style;
01093 }
01094 
01095 ////////////////////////////////////////////////////////////////////
01096 //     Function: PGItem::set_frame_style
01097 //       Access: Published
01098 //  Description: Changes the kind of frame that will be drawn behind
01099 //               the item when it is in the indicated state.
01100 ////////////////////////////////////////////////////////////////////
01101 void PGItem::
01102 set_frame_style(int state, const PGFrameStyle &style) {
01103   LightReMutexHolder holder(_lock);
01104   // Get the state def node, mainly to ensure that this state is
01105   // slotted and listed as having been defined.
01106   NodePath &root = get_state_def(state);
01107   nassertv(!root.is_empty());
01108   
01109   _state_defs[state]._frame_style = style;
01110   _state_defs[state]._frame_stale = true;
01111     
01112   mark_internal_bounds_stale();
01113 }
01114 
01115 #ifdef HAVE_AUDIO
01116 ////////////////////////////////////////////////////////////////////
01117 //     Function: PGItem::set_sound
01118 //       Access: Published
01119 //  Description: Sets the sound that will be played whenever the
01120 //               indicated event occurs.
01121 ////////////////////////////////////////////////////////////////////
01122 void PGItem::
01123 set_sound(const string &event, AudioSound *sound) {
01124   LightReMutexHolder holder(_lock);
01125   _sounds[event] = sound;
01126 }
01127 
01128 ////////////////////////////////////////////////////////////////////
01129 //     Function: PGItem::clear_sound
01130 //       Access: Published
01131 //  Description: Removes the sound associated with the indicated
01132 //               event.
01133 ////////////////////////////////////////////////////////////////////
01134 void PGItem::
01135 clear_sound(const string &event) {
01136   LightReMutexHolder holder(_lock);
01137   _sounds.erase(event);
01138 }
01139 
01140 ////////////////////////////////////////////////////////////////////
01141 //     Function: PGItem::get_sound
01142 //       Access: Published
01143 //  Description: Returns the sound associated with the indicated
01144 //               event, or NULL if there is no associated sound.
01145 ////////////////////////////////////////////////////////////////////
01146 AudioSound *PGItem::
01147 get_sound(const string &event) const {
01148   LightReMutexHolder holder(_lock);
01149   Sounds::const_iterator si = _sounds.find(event);
01150   if (si != _sounds.end()) {
01151     return (*si).second;
01152   }
01153   return (AudioSound *)NULL;
01154 }
01155 
01156 ////////////////////////////////////////////////////////////////////
01157 //     Function: PGItem::has_sound
01158 //       Access: Published
01159 //  Description: Returns true if there is a sound associated with the
01160 //               indicated event, or false otherwise.
01161 ////////////////////////////////////////////////////////////////////
01162 bool PGItem::
01163 has_sound(const string &event) const {
01164   LightReMutexHolder holder(_lock);
01165   return (_sounds.count(event) != 0);
01166 }
01167 #endif  // HAVE_AUDIO
01168 
01169 ////////////////////////////////////////////////////////////////////
01170 //     Function: PGItem::get_text_node
01171 //       Access: Published, Static
01172 //  Description: Returns the TextNode object that will be used by all
01173 //               PGItems to generate default labels given a string.
01174 //               This can be loaded with the default font, etc.
01175 ////////////////////////////////////////////////////////////////////
01176 TextNode *PGItem::
01177 get_text_node() {
01178   if (_text_node == (TextNode *)NULL) {
01179     _text_node = new TextNode("pguiText");
01180     _text_node->set_text_color(0.0f, 0.0f, 0.0f, 1.0f);
01181 
01182     // The default TextNode is aligned to the left, for the
01183     // convenience of PGEntry.
01184     _text_node->set_align(TextNode::A_left);
01185   }
01186   return _text_node;
01187 }
01188 
01189 ////////////////////////////////////////////////////////////////////
01190 //     Function: PGItem::play_sound
01191 //       Access: Protected
01192 //  Description: Plays the sound associated with the indicated event,
01193 //               if there is one.
01194 ////////////////////////////////////////////////////////////////////
01195 void PGItem::
01196 play_sound(const string &event) {
01197 #ifdef HAVE_AUDIO
01198   LightReMutexHolder holder(_lock);
01199   Sounds::const_iterator si = _sounds.find(event);
01200   if (si != _sounds.end()) {
01201     AudioSound *sound = (*si).second;
01202     sound->play();
01203   }
01204 #endif  // HAVE_AUDIO
01205 }
01206 
01207 ////////////////////////////////////////////////////////////////////
01208 //     Function: PGItem::reduce_region
01209 //       Access: Protected
01210 //  Description: The frame parameter is an in/out parameter.  This
01211 //               function adjusts frame so that it represents the
01212 //               largest part of the rectangular region passed in,
01213 //               that does not overlap with the rectangular region of
01214 //               the indicated obscurer.  If the obscurer is NULL, or
01215 //               is a hidden node, it is not considered and the frame
01216 //               is left unchanged.
01217 //
01218 //               This is used by slider bars and scroll frames, which
01219 //               have to automatically figure out how much space they
01220 //               have to work with after allowing space for scroll
01221 //               bars and buttons.
01222 ////////////////////////////////////////////////////////////////////
01223 void PGItem::
01224 reduce_region(LVecBase4 &frame, PGItem *obscurer) const {
01225   if (obscurer != (PGItem *)NULL && !obscurer->is_overall_hidden()) {
01226     LVecBase4 oframe = get_relative_frame(obscurer);
01227 
01228     // Determine the four rectangular regions on the four sides of the
01229     // obscuring region.
01230     LVecBase4 right(max(frame[0], oframe[1]), frame[1], frame[2], frame[3]);
01231     LVecBase4 left(frame[0], min(frame[1], oframe[0]), frame[2], frame[3]);
01232     LVecBase4 above(frame[0], frame[1], max(frame[2], oframe[3]), frame[3]);
01233     LVecBase4 below(frame[0], frame[1], frame[2], min(frame[3], oframe[2]));
01234 
01235     // Now choose the largest of those four.
01236     const LVecBase4 *largest = &right;
01237     PN_stdfloat largest_area = compute_area(*largest);
01238     compare_largest(largest, largest_area, &left);
01239     compare_largest(largest, largest_area, &above);
01240     compare_largest(largest, largest_area, &below);
01241 
01242     frame = *largest;
01243   }
01244 }
01245 
01246 ////////////////////////////////////////////////////////////////////
01247 //     Function: PGItem::get_relative_frame
01248 //       Access: Protected
01249 //  Description: Returns the LVecBase4 frame of the indicated item,
01250 //               converted into this item's coordinate space.
01251 //               Presumably, item is a child of this node.
01252 ////////////////////////////////////////////////////////////////////
01253 LVecBase4 PGItem::
01254 get_relative_frame(PGItem *item) const {
01255   NodePath this_np = NodePath::any_path((PGItem *)this);
01256   NodePath item_np = this_np.find_path_to(item);
01257   if (item_np.is_empty()) { 
01258     item_np = NodePath::any_path(item);
01259   }
01260   const LVecBase4 &orig_frame = item->get_frame();
01261   LMatrix4 transform = item_np.get_mat(this_np);
01262   
01263   // Transform the item's frame into the PGScrollFrame's
01264   // coordinate space.  Transform all four vertices, and get the
01265   // new bounding box.  This way the region works (mostly) even if
01266   // has been rotated.
01267   LPoint3 ll(orig_frame[0], 0.0f, orig_frame[2]);
01268   LPoint3 lr(orig_frame[1], 0.0f, orig_frame[2]);
01269   LPoint3 ul(orig_frame[0], 0.0f, orig_frame[3]);
01270   LPoint3 ur(orig_frame[1], 0.0f, orig_frame[3]);
01271   ll = ll * transform;
01272   lr = lr * transform;
01273   ul = ul * transform;
01274   ur = ur * transform;
01275   
01276   return LVecBase4(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
01277                     max(max(ll[0], lr[0]), max(ul[0], ur[0])),
01278                     min(min(ll[2], lr[2]), min(ul[2], ur[2])),
01279                     max(max(ll[2], lr[2]), max(ul[2], ur[2])));
01280 }
01281 
01282 ////////////////////////////////////////////////////////////////////
01283 //     Function: PGItem::mouse_to_local
01284 //       Access: Protected
01285 //  Description: Converts from the 2-d mouse coordinates into the
01286 //               coordinate space of the item.
01287 ////////////////////////////////////////////////////////////////////
01288 LPoint3 PGItem::
01289 mouse_to_local(const LPoint2 &mouse_point) const {
01290   // This is ambiguous if the PGItem has multiple instances.  Why
01291   // would you do that, anyway?
01292   NodePath this_np((PGItem *)this);
01293   CPT(TransformState) inv_transform = NodePath().get_transform(this_np);
01294   return inv_transform->get_mat().xform_point(LVector3::rfu(mouse_point[0], 0, mouse_point[1]));
01295 }
01296 
01297 ////////////////////////////////////////////////////////////////////
01298 //     Function: PGItem::frame_changed
01299 //       Access: Protected, Virtual
01300 //  Description: Called when the user changes the frame size.
01301 ////////////////////////////////////////////////////////////////////
01302 void PGItem::
01303 frame_changed() {
01304   mark_frames_stale();
01305   if (has_notify()) {
01306     get_notify()->item_frame_changed(this);
01307   }
01308 }
01309 
01310 ////////////////////////////////////////////////////////////////////
01311 //     Function: PGItem::slot_state_def
01312 //       Access: Private
01313 //  Description: Ensures there is a slot in the array for the given
01314 //               state definition.
01315 ////////////////////////////////////////////////////////////////////
01316 void PGItem::
01317 slot_state_def(int state) {
01318   while (state >= (int)_state_defs.size()) {
01319     _state_defs.push_back(StateDef());
01320   }
01321 }
01322 
01323 ////////////////////////////////////////////////////////////////////
01324 //     Function: PGItem::update_frame
01325 //       Access: Private
01326 //  Description: Generates a new instance of the frame geometry for
01327 //               the indicated state.
01328 ////////////////////////////////////////////////////////////////////
01329 void PGItem::
01330 update_frame(int state) {
01331   // First, remove the old frame geometry, if any.
01332   if (state >= 0 && state < (int)_state_defs.size()) {
01333     _state_defs[state]._frame.remove_node();
01334   }
01335 
01336   // We must turn off the stale flag first, before we call
01337   // get_state_def(), to prevent get_state_def() from being a
01338   // recursive call.
01339   _state_defs[state]._frame_stale = false;
01340 
01341   // Now create new frame geometry.
01342   if (has_frame()) {
01343     NodePath &root = get_state_def(state);
01344     _state_defs[state]._frame = 
01345       _state_defs[state]._frame_style.generate_into(root, _frame);
01346   }
01347 }
01348 
01349 ////////////////////////////////////////////////////////////////////
01350 //     Function: PGItem::mark_frames_stale
01351 //       Access: Private
01352 //  Description: Marks all the frames in all states stale, so that
01353 //               they will be regenerated the next time each state is
01354 //               requested.
01355 ////////////////////////////////////////////////////////////////////
01356 void PGItem::
01357 mark_frames_stale() {
01358   StateDefs::iterator di;
01359   for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
01360     // Remove the old frame, if any.
01361     (*di)._frame.remove_node();
01362     (*di)._frame_stale = true;
01363   }
01364   mark_internal_bounds_stale();
01365 }
01366 
01367 ////////////////////////////////////////////////////////////////////
01368 //     Function: PGItem::clip_frame
01369 //       Access: Private
01370 //  Description: Clips the four corners of the item's frame by the
01371 //               indicated clipping plane, and modifies the points to
01372 //               reflect the new set of clipped points.
01373 //
01374 //               The return value is true if the set of points is
01375 //               unmodified (all points are behind the clip plane), or
01376 //               false otherwise.
01377 ////////////////////////////////////////////////////////////////////
01378 bool PGItem::
01379 clip_frame(ClipPoints &source_points, const LPlane &plane) const {
01380   if (source_points.empty()) {
01381     return true;
01382   }
01383 
01384   LPoint3 from3d;
01385   LVector3 delta3d;
01386   if (!plane.intersects_plane(from3d, delta3d, LPlane(LVector3(0, 1, 0), LPoint3::zero()))) {
01387     // The clipping plane is parallel to the polygon.  The polygon is
01388     // either all in or all out.
01389     if (plane.dist_to_plane(LPoint3::zero()) < 0.0) {
01390       // A point within the polygon is behind the clipping plane: the
01391       // polygon is all in.
01392       return true;
01393     }
01394     return false;
01395   }
01396 
01397   // Project the line of intersection into the X-Z plane.  Now we have
01398   // a 2-d clipping line.
01399   LPoint2 from2d(from3d[0], from3d[2]);
01400   LVector2 delta2d(delta3d[0], delta3d[2]);
01401 
01402   PN_stdfloat a = -delta2d[1];
01403   PN_stdfloat b = delta2d[0];
01404   PN_stdfloat c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0];
01405 
01406   // Now walk through the points.  Any point on the left of our line
01407   // gets removed, and the line segment clipped at the point of
01408   // intersection.
01409 
01410   // We might increase the number of vertices by as many as 1, if the
01411   // plane clips off exactly one corner.  (We might also decrease the
01412   // number of vertices, or keep them the same number.)
01413   ClipPoints new_points;
01414   new_points.reserve(source_points.size() + 1);
01415 
01416   LPoint2 last_point(source_points.back());
01417   bool last_is_in = is_right(last_point - from2d, delta2d);
01418   bool all_in = last_is_in;
01419   ClipPoints::const_iterator pi;
01420   for (pi = source_points.begin(); pi != source_points.end(); ++pi) {
01421     LPoint2 this_point(*pi);
01422     bool this_is_in = is_right(this_point - from2d, delta2d);
01423 
01424     // There appears to be a compiler bug in gcc 4.0: we need to
01425     // extract this comparison outside of the if statement.
01426     bool crossed_over = (this_is_in != last_is_in);
01427     if (crossed_over) {
01428       // We have just crossed over the clipping line.  Find the point
01429       // of intersection.
01430       LVector2 d = this_point - last_point;
01431       PN_stdfloat denom = (a * d[0] + b * d[1]);
01432       if (denom != 0.0) {
01433         PN_stdfloat t = -(a * last_point[0] + b * last_point[1] + c) / denom;
01434         LPoint2 p = last_point + t * d;
01435         
01436         new_points.push_back(p);
01437         last_is_in = this_is_in;
01438       }
01439     } 
01440 
01441     if (this_is_in) {
01442       // We are behind the clipping line.  Keep the point.
01443       new_points.push_back(this_point);
01444     } else {
01445       all_in = false;
01446     }
01447 
01448     last_point = this_point;
01449   }
01450 
01451   source_points.swap(new_points);
01452   return all_in;
01453 }
 All Classes Functions Variables Enumerations