Panda3D
|
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 ©) : 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m, 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 ¶m, 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 ¶m, 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 ¶m, 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 }