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 LVector2f &v1, const LVector2f &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 LMatrix4f &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(const RenderState *state, 00363 PreparedGraphicsObjects *prepared_objects, 00364 Thread *current_thread) { 00365 LightReMutexHolder holder(_lock); 00366 StateDefs::iterator di; 00367 for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { 00368 NodePath &root = (*di)._root; 00369 if (!root.is_empty()) { 00370 PandaNode *child = root.node(); 00371 CPT(RenderState) child_state = state->compose(child->get_state()); 00372 child->r_prepare_scene(child_state, prepared_objects, current_thread); 00373 } 00374 } 00375 00376 PandaNode::r_prepare_scene(state, prepared_objects, current_thread); 00377 } 00378 00379 //////////////////////////////////////////////////////////////////// 00380 // Function: PGItem::xform 00381 // Access: Public, Virtual 00382 // Description: Transforms the contents of this node by the indicated 00383 // matrix, if it means anything to do so. For most 00384 // kinds of nodes, this does nothing. 00385 //////////////////////////////////////////////////////////////////// 00386 void PGItem:: 00387 xform(const LMatrix4f &mat) { 00388 LightReMutexHolder holder(_lock); 00389 // Transform the frame. 00390 LPoint3f ll(_frame[0], 0.0f, _frame[2]); 00391 LPoint3f ur(_frame[1], 0.0f, _frame[3]); 00392 ll = ll * mat; 00393 ur = ur * mat; 00394 _frame.set(ll[0], ur[0], ll[2], ur[2]); 00395 00396 // Transform the individual states and their frame styles. 00397 StateDefs::iterator di; 00398 for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { 00399 NodePath &root = (*di)._root; 00400 // Apply the matrix to the previous transform. 00401 root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat))); 00402 00403 // Now flatten the transform into the subgraph. 00404 SceneGraphReducer gr; 00405 gr.apply_attribs(root.node()); 00406 00407 // Transform the frame style too. 00408 if ((*di)._frame_style.xform(mat)) { 00409 (*di)._frame_stale = true; 00410 } 00411 } 00412 mark_internal_bounds_stale(); 00413 } 00414 00415 //////////////////////////////////////////////////////////////////// 00416 // Function: PGItem::activate_region 00417 // Access: Public 00418 // Description: Applies the indicated scene graph transform and order 00419 // as determined by the traversal from PGTop. 00420 // 00421 // The return value is true if the region is valid, or 00422 // false if it is empty or completely clipped. 00423 //////////////////////////////////////////////////////////////////// 00424 bool PGItem:: 00425 activate_region(const LMatrix4f &transform, int sort, 00426 const ClipPlaneAttrib *cpa, 00427 const ScissorAttrib *sa) { 00428 LightReMutexHolder holder(_lock); 00429 // Transform all four vertices, and get the new bounding box. This 00430 // way the region works (mostly) even if has been rotated. 00431 LPoint3f ll(_frame[0], 0.0f, _frame[2]); 00432 LPoint3f lr(_frame[1], 0.0f, _frame[2]); 00433 LPoint3f ul(_frame[0], 0.0f, _frame[3]); 00434 LPoint3f ur(_frame[1], 0.0f, _frame[3]); 00435 ll = ll * transform; 00436 lr = lr * transform; 00437 ul = ul * transform; 00438 ur = ur * transform; 00439 00440 LVecBase4f frame; 00441 if (cpa != (ClipPlaneAttrib *)NULL && cpa->get_num_on_planes() != 0) { 00442 // Apply the clip plane(s) and/or scissor region now that we are 00443 // here in world space. 00444 00445 pvector<LPoint2f> points; 00446 points.reserve(4); 00447 points.push_back(LPoint2f(ll[0], ll[2])); 00448 points.push_back(LPoint2f(lr[0], lr[2])); 00449 points.push_back(LPoint2f(ur[0], ur[2])); 00450 points.push_back(LPoint2f(ul[0], ul[2])); 00451 00452 int num_on_planes = cpa->get_num_on_planes(); 00453 for (int i = 0; i < num_on_planes; ++i) { 00454 NodePath plane_path = cpa->get_on_plane(i); 00455 Planef plane = DCAST(PlaneNode, plane_path.node())->get_plane(); 00456 plane.xform(plane_path.get_net_transform()->get_mat()); 00457 00458 // We ignore the y coordinate, assuming the frame is still in 00459 // the X-Z plane after being transformed. Not sure if we really 00460 // need to support general 3-D transforms on 2-D objects. 00461 clip_frame(points, plane); 00462 } 00463 00464 if (points.empty()) { 00465 // Turns out it's completely clipped after all. 00466 return false; 00467 } 00468 00469 pvector<LPoint2f>::iterator pi; 00470 pi = points.begin(); 00471 frame.set((*pi)[0], (*pi)[0], (*pi)[1], (*pi)[1]); 00472 ++pi; 00473 while (pi != points.end()) { 00474 frame[0] = min(frame[0], (*pi)[0]); 00475 frame[1] = max(frame[1], (*pi)[0]); 00476 frame[2] = min(frame[2], (*pi)[1]); 00477 frame[3] = max(frame[3], (*pi)[1]); 00478 ++pi; 00479 } 00480 } else { 00481 // Since there are no clip planes involved, just set the frame. 00482 frame.set(min(min(ll[0], lr[0]), min(ul[0], ur[0])), 00483 max(max(ll[0], lr[0]), max(ul[0], ur[0])), 00484 min(min(ll[2], lr[2]), min(ul[2], ur[2])), 00485 max(max(ll[2], lr[2]), max(ul[2], ur[2]))); 00486 } 00487 00488 if (sa != (ScissorAttrib *)NULL) { 00489 // Also restrict it to within the scissor region. 00490 const LVecBase4f &sf = sa->get_frame(); 00491 // Expand sf from 0..1 to -1..1. 00492 frame.set(max(frame[0], sf[0] * 2.0f - 1.0f), 00493 min(frame[1], sf[1] * 2.0f - 1.0f), 00494 max(frame[2], sf[2] * 2.0f - 1.0f), 00495 min(frame[3], sf[3] * 2.0f - 1.0f)); 00496 if (frame[1] <= frame[0] || frame[3] <= frame[2]) { 00497 // Completely outside the scissor region. 00498 return false; 00499 } 00500 } 00501 00502 _region->set_frame(frame); 00503 00504 _region->set_sort(sort); 00505 _region->set_active(true); 00506 00507 // calculate the inverse of this transform, which is needed to 00508 // go back to the frame space. 00509 _frame_inv_xform.invert_from(transform); 00510 00511 return true; 00512 } 00513 00514 //////////////////////////////////////////////////////////////////// 00515 // Function: PGItem::enter_region 00516 // Access: Public, Virtual 00517 // Description: This is a callback hook function, called whenever the 00518 // mouse enters the region. The mouse is only 00519 // considered to be "entered" in one region at a time; 00520 // in the case of nested regions, it exits the outer 00521 // region before entering the inner one. 00522 //////////////////////////////////////////////////////////////////// 00523 void PGItem:: 00524 enter_region(const MouseWatcherParameter ¶m) { 00525 LightReMutexHolder holder(_lock); 00526 if (pgui_cat.is_debug()) { 00527 pgui_cat.debug() 00528 << *this << "::enter_region(" << param << ")\n"; 00529 } 00530 00531 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00532 string event = get_enter_event(); 00533 play_sound(event); 00534 throw_event(event, EventParameter(ep)); 00535 00536 if (has_notify()) { 00537 get_notify()->item_enter(this, param); 00538 } 00539 } 00540 00541 //////////////////////////////////////////////////////////////////// 00542 // Function: PGItem::exit_region 00543 // Access: Public, Virtual 00544 // Description: This is a callback hook function, called whenever the 00545 // mouse exits the region. The mouse is only considered 00546 // to be "entered" in one region at a time; in the case 00547 // of nested regions, it exits the outer region before 00548 // entering the inner one. 00549 //////////////////////////////////////////////////////////////////// 00550 void PGItem:: 00551 exit_region(const MouseWatcherParameter ¶m) { 00552 LightReMutexHolder holder(_lock); 00553 if (pgui_cat.is_debug()) { 00554 pgui_cat.debug() 00555 << *this << "::exit_region(" << param << ")\n"; 00556 } 00557 00558 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00559 string event = get_exit_event(); 00560 play_sound(event); 00561 throw_event(event, EventParameter(ep)); 00562 00563 if (has_notify()) { 00564 get_notify()->item_exit(this, param); 00565 } 00566 00567 //pgui_cat.info() << get_name() << "::exit()" << endl; 00568 } 00569 00570 //////////////////////////////////////////////////////////////////// 00571 // Function: PGItem::within_region 00572 // Access: Public, Virtual 00573 // Description: This is a callback hook function, called whenever the 00574 // mouse moves within the boundaries of the region, even 00575 // if it is also within the boundaries of a nested 00576 // region. This is different from "enter", which is 00577 // only called whenever the mouse is within only that 00578 // region. 00579 //////////////////////////////////////////////////////////////////// 00580 void PGItem:: 00581 within_region(const MouseWatcherParameter ¶m) { 00582 LightReMutexHolder holder(_lock); 00583 if (pgui_cat.is_debug()) { 00584 pgui_cat.debug() 00585 << *this << "::within_region(" << param << ")\n"; 00586 } 00587 00588 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00589 string event = get_within_event(); 00590 play_sound(event); 00591 throw_event(event, EventParameter(ep)); 00592 00593 if (has_notify()) { 00594 get_notify()->item_within(this, param); 00595 } 00596 } 00597 00598 //////////////////////////////////////////////////////////////////// 00599 // Function: PGItem::without_region 00600 // Access: Public, Virtual 00601 // Description: This is a callback hook function, called whenever the 00602 // mouse moves completely outside the boundaries of the 00603 // region. See within(). 00604 //////////////////////////////////////////////////////////////////// 00605 void PGItem:: 00606 without_region(const MouseWatcherParameter ¶m) { 00607 LightReMutexHolder holder(_lock); 00608 if (pgui_cat.is_debug()) { 00609 pgui_cat.debug() 00610 << *this << "::without_region(" << param << ")\n"; 00611 } 00612 00613 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00614 string event = get_without_event(); 00615 play_sound(event); 00616 throw_event(event, EventParameter(ep)); 00617 00618 if (has_notify()) { 00619 get_notify()->item_without(this, param); 00620 } 00621 } 00622 00623 //////////////////////////////////////////////////////////////////// 00624 // Function: PGItem::focus_in 00625 // Access: Public, Virtual 00626 // Description: This is a callback hook function, called whenever the 00627 // widget gets the keyboard focus. 00628 //////////////////////////////////////////////////////////////////// 00629 void PGItem:: 00630 focus_in() { 00631 LightReMutexHolder holder(_lock); 00632 if (pgui_cat.is_debug()) { 00633 pgui_cat.debug() 00634 << *this << "::focus_in()\n"; 00635 } 00636 00637 string event = get_focus_in_event(); 00638 play_sound(event); 00639 throw_event(event); 00640 00641 if (has_notify()) { 00642 get_notify()->item_focus_in(this); 00643 } 00644 } 00645 00646 //////////////////////////////////////////////////////////////////// 00647 // Function: PGItem::focus_out 00648 // Access: Public, Virtual 00649 // Description: This is a callback hook function, called whenever the 00650 // widget loses the keyboard focus. 00651 //////////////////////////////////////////////////////////////////// 00652 void PGItem:: 00653 focus_out() { 00654 LightReMutexHolder holder(_lock); 00655 if (pgui_cat.is_debug()) { 00656 pgui_cat.debug() 00657 << *this << "::focus_out()\n"; 00658 } 00659 00660 string event = get_focus_out_event(); 00661 play_sound(event); 00662 throw_event(event); 00663 00664 if (has_notify()) { 00665 get_notify()->item_focus_out(this); 00666 } 00667 } 00668 00669 //////////////////////////////////////////////////////////////////// 00670 // Function: PGItem::press 00671 // Access: Public, Virtual 00672 // Description: This is a callback hook function, called whenever a 00673 // mouse or keyboard button is depressed while the mouse 00674 // is within the region. 00675 //////////////////////////////////////////////////////////////////// 00676 void PGItem:: 00677 press(const MouseWatcherParameter ¶m, bool background) { 00678 LightReMutexHolder holder(_lock); 00679 if (pgui_cat.is_debug()) { 00680 pgui_cat.debug() 00681 << *this << "::press(" << param << ", " << background << ")\n"; 00682 } 00683 00684 if (!background) { 00685 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00686 string event; 00687 if (param.is_keyrepeat()) { 00688 event = get_repeat_event(param.get_button()); 00689 } else { 00690 event = get_press_event(param.get_button()); 00691 } 00692 play_sound(event); 00693 throw_event(event, EventParameter(ep)); 00694 } 00695 00696 if (has_notify()) { 00697 get_notify()->item_press(this, param); 00698 } 00699 } 00700 00701 //////////////////////////////////////////////////////////////////// 00702 // Function: PGItem::release 00703 // Access: Public, Virtual 00704 // Description: This is a callback hook function, called whenever a 00705 // mouse or keyboard button previously depressed with 00706 // press() is released. 00707 //////////////////////////////////////////////////////////////////// 00708 void PGItem:: 00709 release(const MouseWatcherParameter ¶m, bool background) { 00710 LightReMutexHolder holder(_lock); 00711 if (pgui_cat.is_debug()) { 00712 pgui_cat.debug() 00713 << *this << "::release(" << param << ", " << background << ")\n"; 00714 } 00715 00716 if (!background) { 00717 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00718 string event = get_release_event(param.get_button()); 00719 play_sound(event); 00720 throw_event(event, EventParameter(ep)); 00721 } 00722 00723 if (has_notify()) { 00724 get_notify()->item_release(this, param); 00725 } 00726 } 00727 00728 //////////////////////////////////////////////////////////////////// 00729 // Function: PGItem::keystroke 00730 // Access: Public, Virtual 00731 // Description: This is a callback hook function, called whenever 00732 // the user presses a key. 00733 //////////////////////////////////////////////////////////////////// 00734 void PGItem:: 00735 keystroke(const MouseWatcherParameter ¶m, bool background) { 00736 LightReMutexHolder holder(_lock); 00737 if (pgui_cat.is_debug()) { 00738 pgui_cat.debug() 00739 << *this << "::keystroke(" << param << ", " << background << ")\n"; 00740 } 00741 00742 if (!background) { 00743 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); 00744 string event = get_keystroke_event(); 00745 play_sound(event); 00746 throw_event(event, EventParameter(ep)); 00747 00748 if (has_notify()) { 00749 get_notify()->item_keystroke(this, param); 00750 } 00751 } 00752 } 00753 00754 //////////////////////////////////////////////////////////////////// 00755 // Function: PGItem::candidate 00756 // Access: Public, Virtual 00757 // Description: This is a callback hook function, called whenever 00758 // the user highlights an option in the IME window. 00759 //////////////////////////////////////////////////////////////////// 00760 void PGItem:: 00761 candidate(const MouseWatcherParameter ¶m, bool background) { 00762 LightReMutexHolder holder(_lock); 00763 if (pgui_cat.is_debug()) { 00764 pgui_cat.debug() 00765 << *this << "::candidate(" << param << ", " << background << ")\n"; 00766 } 00767 00768 // We don't throw sound events for candidate selections for now. 00769 if (!background) { 00770 if (has_notify()) { 00771 get_notify()->item_candidate(this, param); 00772 } 00773 } 00774 } 00775 00776 //////////////////////////////////////////////////////////////////// 00777 // Function: PGItem::move 00778 // Access: Public, Virtual 00779 // Description: This is a callback hook function, called whenever a 00780 // mouse is moved while within the region. 00781 //////////////////////////////////////////////////////////////////// 00782 void PGItem:: 00783 move(const MouseWatcherParameter ¶m) { 00784 LightReMutexHolder holder(_lock); 00785 if (pgui_cat.is_debug()) { 00786 pgui_cat.debug() 00787 << *this << "::move(" << param << ")\n"; 00788 } 00789 00790 if (has_notify()) { 00791 get_notify()->item_move(this, param); 00792 } 00793 } 00794 00795 //////////////////////////////////////////////////////////////////// 00796 // Function: PGItem::background_press 00797 // Access: Public, Static 00798 // Description: Calls press() on all the PGItems with background 00799 // focus. 00800 //////////////////////////////////////////////////////////////////// 00801 void PGItem:: 00802 background_press(const MouseWatcherParameter ¶m) { 00803 BackgroundFocus::const_iterator fi; 00804 for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { 00805 PGItem *item = *fi; 00806 if (!item->get_focus()) { 00807 item->press(param, true); 00808 } 00809 } 00810 } 00811 00812 //////////////////////////////////////////////////////////////////// 00813 // Function: PGItem::background_release 00814 // Access: Public, Static 00815 // Description: Calls release() on all the PGItems with background 00816 // focus. 00817 //////////////////////////////////////////////////////////////////// 00818 void PGItem:: 00819 background_release(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->release(param, true); 00825 } 00826 } 00827 } 00828 00829 //////////////////////////////////////////////////////////////////// 00830 // Function: PGItem::background_keystroke 00831 // Access: Public, Static 00832 // Description: Calls keystroke() on all the PGItems with background 00833 // focus. 00834 //////////////////////////////////////////////////////////////////// 00835 void PGItem:: 00836 background_keystroke(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->keystroke(param, true); 00842 } 00843 } 00844 } 00845 00846 //////////////////////////////////////////////////////////////////// 00847 // Function: PGItem::background_candidate 00848 // Access: Public, Static 00849 // Description: Calls candidate() on all the PGItems with background 00850 // focus. 00851 //////////////////////////////////////////////////////////////////// 00852 void PGItem:: 00853 background_candidate(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->candidate(param, true); 00859 } 00860 } 00861 } 00862 00863 //////////////////////////////////////////////////////////////////// 00864 // Function: PGItem::set_active 00865 // Access: Published, Virtual 00866 // Description: Sets whether the PGItem is active for mouse watching. 00867 // This is not necessarily related to the 00868 // active/inactive appearance of the item, which is 00869 // controlled by set_state(), but it does affect whether 00870 // it responds to mouse events. 00871 //////////////////////////////////////////////////////////////////// 00872 void PGItem:: 00873 set_active(bool active) { 00874 LightReMutexHolder holder(_lock); 00875 if (active) { 00876 _flags |= F_active; 00877 } else { 00878 _flags &= ~F_active; 00879 // Deactivating the item automatically defocuses it too. 00880 if (get_focus()) { 00881 set_focus(false); 00882 } 00883 } 00884 } 00885 00886 //////////////////////////////////////////////////////////////////// 00887 // Function: PGItem::set_focus 00888 // Access: Published, Virtual 00889 // Description: Sets whether the PGItem currently has keyboard focus. 00890 // This simply means that the item may respond to 00891 // keyboard events as well as to mouse events; precisely 00892 // what this means is up to the individual item. 00893 // 00894 // Only one PGItem in the world is allowed to have focus 00895 // at any given time. Setting the focus on any other 00896 // item automatically disables the focus from the 00897 // previous item. 00898 //////////////////////////////////////////////////////////////////// 00899 void PGItem:: 00900 set_focus(bool focus) { 00901 LightReMutexHolder holder(_lock); 00902 if (focus) { 00903 if (!get_active()) { 00904 // Cannot set focus on an inactive item. 00905 return; 00906 } 00907 00908 // Set the keyboard focus to this item. 00909 if (_focus_item != this) { 00910 if (_focus_item != (PGItem *)NULL) { 00911 // Clear the focus from whatever item currently has it. 00912 _focus_item->set_focus(false); 00913 } 00914 _focus_item = this; 00915 } 00916 if (!get_focus()) { 00917 focus_in(); 00918 _flags |= F_focus; 00919 } 00920 00921 } else { 00922 if (_focus_item == this) { 00923 // Remove this item from the focus. 00924 _focus_item = (PGItem *)NULL; 00925 } 00926 00927 if (get_focus()) { 00928 focus_out(); 00929 _flags &= ~F_focus; 00930 } 00931 } 00932 _region->set_keyboard(focus); 00933 } 00934 00935 //////////////////////////////////////////////////////////////////// 00936 // Function: PGItem::set_background_focus 00937 // Access: Published 00938 // Description: Sets the background_focus flag for this item. When 00939 // background_focus is enabled, the item will receive 00940 // keypress events even if it is not in focus; in fact, 00941 // even if it is not onscreen. Unlike normal focus, 00942 // many items may have background_focus simultaneously. 00943 //////////////////////////////////////////////////////////////////// 00944 void PGItem:: 00945 set_background_focus(bool focus) { 00946 LightReMutexHolder holder(_lock); 00947 if (focus != get_background_focus()) { 00948 if (focus) { 00949 // Activate background focus. 00950 _flags |= F_background_focus; 00951 bool inserted = _background_focus.insert(this).second; 00952 nassertv(inserted); 00953 00954 } else { 00955 // Deactivate background focus. 00956 _flags &= ~F_background_focus; 00957 size_t num_erased = _background_focus.erase(this); 00958 nassertv(num_erased == 1); 00959 } 00960 } 00961 } 00962 00963 //////////////////////////////////////////////////////////////////// 00964 // Function: PGItem::get_num_state_defs 00965 // Access: Published 00966 // Description: Returns one more than the highest-numbered state def 00967 // that was ever assigned to the PGItem. The complete 00968 // set of state defs assigned may then be retrieved by 00969 // indexing from 0 to (get_num_state_defs() - 1). 00970 // 00971 // This is only an upper limit on the actual number of 00972 // state defs, since there may be holes in the list. 00973 //////////////////////////////////////////////////////////////////// 00974 int PGItem:: 00975 get_num_state_defs() const { 00976 LightReMutexHolder holder(_lock); 00977 return _state_defs.size(); 00978 } 00979 00980 //////////////////////////////////////////////////////////////////// 00981 // Function: PGItem::has_state_def 00982 // Access: Published 00983 // Description: Returns true if get_state_def() has ever been called 00984 // for the indicated state (thus defining a render 00985 // subgraph for this state index), false otherwise. 00986 //////////////////////////////////////////////////////////////////// 00987 bool PGItem:: 00988 has_state_def(int state) const { 00989 LightReMutexHolder holder(_lock); 00990 if (state < 0 || state >= (int)_state_defs.size()) { 00991 return false; 00992 } 00993 return (!_state_defs[state]._root.is_empty()); 00994 } 00995 00996 //////////////////////////////////////////////////////////////////// 00997 // Function: PGItem::clear_state_def 00998 // Access: Published 00999 // Description: Resets the NodePath assigned to the indicated state 01000 // to its initial default, with only a frame 01001 // representation if appropriate. 01002 //////////////////////////////////////////////////////////////////// 01003 void PGItem:: 01004 clear_state_def(int state) { 01005 LightReMutexHolder holder(_lock); 01006 if (state < 0 || state >= (int)_state_defs.size()) { 01007 return; 01008 } 01009 01010 _state_defs[state]._root = NodePath(); 01011 _state_defs[state]._frame = NodePath(); 01012 _state_defs[state]._frame_stale = true; 01013 01014 mark_internal_bounds_stale(); 01015 } 01016 01017 //////////////////////////////////////////////////////////////////// 01018 // Function: PGItem::get_state_def 01019 // Access: Published 01020 // Description: Returns the Node that is the root of the subgraph 01021 // that will be drawn when the PGItem is in the 01022 // indicated state. The first time this is called for a 01023 // particular state index, it may create the Node. 01024 //////////////////////////////////////////////////////////////////// 01025 NodePath &PGItem:: 01026 get_state_def(int state) { 01027 LightReMutexHolder holder(_lock); 01028 nassertr(state >= 0 && state < 1000, get_state_def(0)); // Sanity check. 01029 slot_state_def(state); 01030 01031 if (_state_defs[state]._root.is_empty()) { 01032 // Create a new node. 01033 _state_defs[state]._root = NodePath("state_" + format_string(state)); 01034 _state_defs[state]._frame_stale = true; 01035 } 01036 01037 if (_state_defs[state]._frame_stale) { 01038 update_frame(state); 01039 } 01040 01041 return _state_defs[state]._root; 01042 } 01043 01044 //////////////////////////////////////////////////////////////////// 01045 // Function: PGItem::instance_to_state_def 01046 // Access: Published 01047 // Description: Parents an instance of the bottom node of the 01048 // indicated NodePath to the indicated state index. 01049 //////////////////////////////////////////////////////////////////// 01050 NodePath PGItem:: 01051 instance_to_state_def(int state, const NodePath &path) { 01052 LightReMutexHolder holder(_lock); 01053 if (path.is_empty()) { 01054 // If the source is empty, quietly do nothing. 01055 return NodePath(); 01056 } 01057 01058 mark_internal_bounds_stale(); 01059 01060 return path.instance_to(get_state_def(state)); 01061 } 01062 01063 //////////////////////////////////////////////////////////////////// 01064 // Function: PGItem::get_frame_style 01065 // Access: Published 01066 // Description: Returns the kind of frame that will be drawn behind 01067 // the item when it is in the indicated state. 01068 //////////////////////////////////////////////////////////////////// 01069 PGFrameStyle PGItem:: 01070 get_frame_style(int state) { 01071 LightReMutexHolder holder(_lock); 01072 if (state < 0 || state >= (int)_state_defs.size()) { 01073 return PGFrameStyle(); 01074 } 01075 return _state_defs[state]._frame_style; 01076 } 01077 01078 //////////////////////////////////////////////////////////////////// 01079 // Function: PGItem::set_frame_style 01080 // Access: Published 01081 // Description: Changes the kind of frame that will be drawn behind 01082 // the item when it is in the indicated state. 01083 //////////////////////////////////////////////////////////////////// 01084 void PGItem:: 01085 set_frame_style(int state, const PGFrameStyle &style) { 01086 LightReMutexHolder holder(_lock); 01087 // Get the state def node, mainly to ensure that this state is 01088 // slotted and listed as having been defined. 01089 NodePath &root = get_state_def(state); 01090 nassertv(!root.is_empty()); 01091 01092 _state_defs[state]._frame_style = style; 01093 _state_defs[state]._frame_stale = true; 01094 01095 mark_internal_bounds_stale(); 01096 } 01097 01098 #ifdef HAVE_AUDIO 01099 //////////////////////////////////////////////////////////////////// 01100 // Function: PGItem::set_sound 01101 // Access: Published 01102 // Description: Sets the sound that will be played whenever the 01103 // indicated event occurs. 01104 //////////////////////////////////////////////////////////////////// 01105 void PGItem:: 01106 set_sound(const string &event, AudioSound *sound) { 01107 LightReMutexHolder holder(_lock); 01108 _sounds[event] = sound; 01109 } 01110 01111 //////////////////////////////////////////////////////////////////// 01112 // Function: PGItem::clear_sound 01113 // Access: Published 01114 // Description: Removes the sound associated with the indicated 01115 // event. 01116 //////////////////////////////////////////////////////////////////// 01117 void PGItem:: 01118 clear_sound(const string &event) { 01119 LightReMutexHolder holder(_lock); 01120 _sounds.erase(event); 01121 } 01122 01123 //////////////////////////////////////////////////////////////////// 01124 // Function: PGItem::get_sound 01125 // Access: Published 01126 // Description: Returns the sound associated with the indicated 01127 // event, or NULL if there is no associated sound. 01128 //////////////////////////////////////////////////////////////////// 01129 AudioSound *PGItem:: 01130 get_sound(const string &event) const { 01131 LightReMutexHolder holder(_lock); 01132 Sounds::const_iterator si = _sounds.find(event); 01133 if (si != _sounds.end()) { 01134 return (*si).second; 01135 } 01136 return (AudioSound *)NULL; 01137 } 01138 01139 //////////////////////////////////////////////////////////////////// 01140 // Function: PGItem::has_sound 01141 // Access: Published 01142 // Description: Returns true if there is a sound associated with the 01143 // indicated event, or false otherwise. 01144 //////////////////////////////////////////////////////////////////// 01145 bool PGItem:: 01146 has_sound(const string &event) const { 01147 LightReMutexHolder holder(_lock); 01148 return (_sounds.count(event) != 0); 01149 } 01150 #endif // HAVE_AUDIO 01151 01152 //////////////////////////////////////////////////////////////////// 01153 // Function: PGItem::get_text_node 01154 // Access: Published, Static 01155 // Description: Returns the TextNode object that will be used by all 01156 // PGItems to generate default labels given a string. 01157 // This can be loaded with the default font, etc. 01158 //////////////////////////////////////////////////////////////////// 01159 TextNode *PGItem:: 01160 get_text_node() { 01161 if (_text_node == (TextNode *)NULL) { 01162 _text_node = new TextNode("pguiText"); 01163 _text_node->set_text_color(0.0f, 0.0f, 0.0f, 1.0f); 01164 01165 // The default TextNode is aligned to the left, for the 01166 // convenience of PGEntry. 01167 _text_node->set_align(TextNode::A_left); 01168 } 01169 return _text_node; 01170 } 01171 01172 //////////////////////////////////////////////////////////////////// 01173 // Function: PGItem::play_sound 01174 // Access: Protected 01175 // Description: Plays the sound associated with the indicated event, 01176 // if there is one. 01177 //////////////////////////////////////////////////////////////////// 01178 void PGItem:: 01179 play_sound(const string &event) { 01180 #ifdef HAVE_AUDIO 01181 LightReMutexHolder holder(_lock); 01182 Sounds::const_iterator si = _sounds.find(event); 01183 if (si != _sounds.end()) { 01184 AudioSound *sound = (*si).second; 01185 sound->play(); 01186 } 01187 #endif // HAVE_AUDIO 01188 } 01189 01190 //////////////////////////////////////////////////////////////////// 01191 // Function: PGItem::reduce_region 01192 // Access: Protected 01193 // Description: The frame parameter is an in/out parameter. This 01194 // function adjusts frame so that it represents the 01195 // largest part of the rectangular region passed in, 01196 // that does not overlap with the rectangular region of 01197 // the indicated obscurer. If the obscurer is NULL, or 01198 // is a hidden node, it is not considered and the frame 01199 // is left unchanged. 01200 // 01201 // This is used by slider bars and scroll frames, which 01202 // have to automatically figure out how much space they 01203 // have to work with after allowing space for scroll 01204 // bars and buttons. 01205 //////////////////////////////////////////////////////////////////// 01206 void PGItem:: 01207 reduce_region(LVecBase4f &frame, PGItem *obscurer) const { 01208 if (obscurer != (PGItem *)NULL && !obscurer->is_overall_hidden()) { 01209 LVecBase4f oframe = get_relative_frame(obscurer); 01210 01211 // Determine the four rectangular regions on the four sides of the 01212 // obscuring region. 01213 LVecBase4f right(max(frame[0], oframe[1]), frame[1], frame[2], frame[3]); 01214 LVecBase4f left(frame[0], min(frame[1], oframe[0]), frame[2], frame[3]); 01215 LVecBase4f above(frame[0], frame[1], max(frame[2], oframe[3]), frame[3]); 01216 LVecBase4f below(frame[0], frame[1], frame[2], min(frame[3], oframe[2])); 01217 01218 // Now choose the largest of those four. 01219 const LVecBase4f *largest = &right; 01220 float largest_area = compute_area(*largest); 01221 compare_largest(largest, largest_area, &left); 01222 compare_largest(largest, largest_area, &above); 01223 compare_largest(largest, largest_area, &below); 01224 01225 frame = *largest; 01226 } 01227 } 01228 01229 //////////////////////////////////////////////////////////////////// 01230 // Function: PGItem::get_relative_frame 01231 // Access: Protected 01232 // Description: Returns the LVecBase4f frame of the indicated item, 01233 // converted into this item's coordinate space. 01234 // Presumably, item is a child of this node. 01235 //////////////////////////////////////////////////////////////////// 01236 LVecBase4f PGItem:: 01237 get_relative_frame(PGItem *item) const { 01238 NodePath this_np = NodePath::any_path((PGItem *)this); 01239 NodePath item_np = this_np.find_path_to(item); 01240 if (item_np.is_empty()) { 01241 item_np = NodePath::any_path(item); 01242 } 01243 const LVecBase4f &orig_frame = item->get_frame(); 01244 LMatrix4f transform = item_np.get_mat(this_np); 01245 01246 // Transform the item's frame into the PGScrollFrame's 01247 // coordinate space. Transform all four vertices, and get the 01248 // new bounding box. This way the region works (mostly) even if 01249 // has been rotated. 01250 LPoint3f ll(orig_frame[0], 0.0f, orig_frame[2]); 01251 LPoint3f lr(orig_frame[1], 0.0f, orig_frame[2]); 01252 LPoint3f ul(orig_frame[0], 0.0f, orig_frame[3]); 01253 LPoint3f ur(orig_frame[1], 0.0f, orig_frame[3]); 01254 ll = ll * transform; 01255 lr = lr * transform; 01256 ul = ul * transform; 01257 ur = ur * transform; 01258 01259 return LVecBase4f(min(min(ll[0], lr[0]), min(ul[0], ur[0])), 01260 max(max(ll[0], lr[0]), max(ul[0], ur[0])), 01261 min(min(ll[2], lr[2]), min(ul[2], ur[2])), 01262 max(max(ll[2], lr[2]), max(ul[2], ur[2]))); 01263 } 01264 01265 //////////////////////////////////////////////////////////////////// 01266 // Function: PGItem::mouse_to_local 01267 // Access: Protected 01268 // Description: Converts from the 2-d mouse coordinates into the 01269 // coordinate space of the item. 01270 //////////////////////////////////////////////////////////////////// 01271 LPoint3f PGItem:: 01272 mouse_to_local(const LPoint2f &mouse_point) const { 01273 // This is ambiguous if the PGItem has multiple instances. Why 01274 // would you do that, anyway? 01275 NodePath this_np((PGItem *)this); 01276 CPT(TransformState) inv_transform = NodePath().get_transform(this_np); 01277 return inv_transform->get_mat().xform_point(LVector3f::rfu(mouse_point[0], 0, mouse_point[1])); 01278 } 01279 01280 //////////////////////////////////////////////////////////////////// 01281 // Function: PGItem::frame_changed 01282 // Access: Protected, Virtual 01283 // Description: Called when the user changes the frame size. 01284 //////////////////////////////////////////////////////////////////// 01285 void PGItem:: 01286 frame_changed() { 01287 mark_frames_stale(); 01288 if (has_notify()) { 01289 get_notify()->item_frame_changed(this); 01290 } 01291 } 01292 01293 //////////////////////////////////////////////////////////////////// 01294 // Function: PGItem::slot_state_def 01295 // Access: Private 01296 // Description: Ensures there is a slot in the array for the given 01297 // state definition. 01298 //////////////////////////////////////////////////////////////////// 01299 void PGItem:: 01300 slot_state_def(int state) { 01301 while (state >= (int)_state_defs.size()) { 01302 _state_defs.push_back(StateDef()); 01303 } 01304 } 01305 01306 //////////////////////////////////////////////////////////////////// 01307 // Function: PGItem::update_frame 01308 // Access: Private 01309 // Description: Generates a new instance of the frame geometry for 01310 // the indicated state. 01311 //////////////////////////////////////////////////////////////////// 01312 void PGItem:: 01313 update_frame(int state) { 01314 // First, remove the old frame geometry, if any. 01315 if (state >= 0 && state < (int)_state_defs.size()) { 01316 _state_defs[state]._frame.remove_node(); 01317 } 01318 01319 // We must turn off the stale flag first, before we call 01320 // get_state_def(), to prevent get_state_def() from being a 01321 // recursive call. 01322 _state_defs[state]._frame_stale = false; 01323 01324 // Now create new frame geometry. 01325 if (has_frame()) { 01326 NodePath &root = get_state_def(state); 01327 _state_defs[state]._frame = 01328 _state_defs[state]._frame_style.generate_into(root, _frame); 01329 } 01330 } 01331 01332 //////////////////////////////////////////////////////////////////// 01333 // Function: PGItem::mark_frames_stale 01334 // Access: Private 01335 // Description: Marks all the frames in all states stale, so that 01336 // they will be regenerated the next time each state is 01337 // requested. 01338 //////////////////////////////////////////////////////////////////// 01339 void PGItem:: 01340 mark_frames_stale() { 01341 StateDefs::iterator di; 01342 for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { 01343 // Remove the old frame, if any. 01344 (*di)._frame.remove_node(); 01345 (*di)._frame_stale = true; 01346 } 01347 mark_internal_bounds_stale(); 01348 } 01349 01350 //////////////////////////////////////////////////////////////////// 01351 // Function: PGItem::clip_frame 01352 // Access: Private 01353 // Description: Clips the four corners of the item's frame by the 01354 // indicated clipping plane, and modifies the points to 01355 // reflect the new set of clipped points. 01356 // 01357 // The return value is true if the set of points is 01358 // unmodified (all points are behind the clip plane), or 01359 // false otherwise. 01360 //////////////////////////////////////////////////////////////////// 01361 bool PGItem:: 01362 clip_frame(pvector<LPoint2f> &source_points, const Planef &plane) const { 01363 if (source_points.empty()) { 01364 return true; 01365 } 01366 01367 LPoint3f from3d; 01368 LVector3f delta3d; 01369 if (!plane.intersects_plane(from3d, delta3d, Planef(LVector3f(0, 1, 0), LPoint3f::zero()))) { 01370 // The clipping plane is parallel to the polygon. The polygon is 01371 // either all in or all out. 01372 if (plane.dist_to_plane(LPoint3f::zero()) < 0.0) { 01373 // A point within the polygon is behind the clipping plane: the 01374 // polygon is all in. 01375 return true; 01376 } 01377 return false; 01378 } 01379 01380 // Project the line of intersection into the X-Z plane. Now we have 01381 // a 2-d clipping line. 01382 LPoint2f from2d(from3d[0], from3d[2]); 01383 LVector2f delta2d(delta3d[0], delta3d[2]); 01384 01385 float a = -delta2d[1]; 01386 float b = delta2d[0]; 01387 float c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0]; 01388 01389 // Now walk through the points. Any point on the left of our line 01390 // gets removed, and the line segment clipped at the point of 01391 // intersection. 01392 01393 // We might increase the number of vertices by as many as 1, if the 01394 // plane clips off exactly one corner. (We might also decrease the 01395 // number of vertices, or keep them the same number.) 01396 pvector<LPoint2f> new_points; 01397 new_points.reserve(source_points.size() + 1); 01398 01399 LPoint2f last_point = source_points.back(); 01400 bool last_is_in = is_right(last_point - from2d, delta2d); 01401 bool all_in = last_is_in; 01402 pvector<LPoint2f>::const_iterator pi; 01403 for (pi = source_points.begin(); pi != source_points.end(); ++pi) { 01404 const LPoint2f &this_point = (*pi); 01405 bool this_is_in = is_right(this_point - from2d, delta2d); 01406 01407 // There appears to be a compiler bug in gcc 4.0: we need to 01408 // extract this comparison outside of the if statement. 01409 bool crossed_over = (this_is_in != last_is_in); 01410 if (crossed_over) { 01411 // We have just crossed over the clipping line. Find the point 01412 // of intersection. 01413 LVector2f d = this_point - last_point; 01414 float denom = (a * d[0] + b * d[1]); 01415 if (denom != 0.0) { 01416 float t = -(a * last_point[0] + b * last_point[1] + c) / denom; 01417 LPoint2f p = last_point + t * d; 01418 01419 new_points.push_back(p); 01420 last_is_in = this_is_in; 01421 } 01422 } 01423 01424 if (this_is_in) { 01425 // We are behind the clipping line. Keep the point. 01426 new_points.push_back(this_point); 01427 } else { 01428 all_in = false; 01429 } 01430 01431 last_point = this_point; 01432 } 01433 01434 source_points.swap(new_points); 01435 return all_in; 01436 }