// Filename: pandaNode.cxx // Created by: drose (20Feb02) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://www.panda3d.org/license.txt . // // To contact the maintainers of this program write to // panda3d@yahoogroups.com . // //////////////////////////////////////////////////////////////////// #include "pandaNode.h" #include "config_pgraph.h" #include "nodePathComponent.h" #include "bamReader.h" #include "bamWriter.h" #include "indent.h" #include "geometricBoundingVolume.h" #include "sceneGraphReducer.h" #include "accumulatedAttribs.h" TypeHandle PandaNode::_type_handle; // // There are two different interfaces here for making and breaking // parent-child connections: the fundamental PandaNode interface, via // add_child() and remove_child() (and related functions), and the // NodePath support interface, via attach(), detach(), and reparent(). // They both do essentially the same thing, but with slightly // different inputs. The PandaNode interfaces try to guess which // NodePaths should be updated as a result of the scene graph change, // while the NodePath interfaces already know. // // The NodePath support interface functions are strictly called from // within the NodePath class, and are used to implement // NodePath::reparent_to() and NodePath::remove_node(), etc. The // fundamental interface, on the other hand, is intended to be called // directly by the user. // // The fundamental interface has a slightly lower overhead because it // does not need to create a NodePathComponent chain where one does // not already exist; however, the NodePath support interface is more // useful when the NodePath already does exist, because it ensures // that the particular NodePath calling it is kept appropriately // up-to-date. // //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::make_copy // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// CycleData *PandaNode::CData:: make_copy() const { return new CData(*this); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::write_datagram // Access: Public, Virtual // Description: Writes the contents of this object to the datagram // for shipping out to a Bam file. //////////////////////////////////////////////////////////////////// void PandaNode::CData:: write_datagram(BamWriter *manager, Datagram &dg) const { manager->write_pointer(dg, _state); manager->write_pointer(dg, _effects); manager->write_pointer(dg, _transform); dg.add_uint32(_draw_mask.get_word()); write_up_list(_up, manager, dg); write_down_list(_down, manager, dg); write_down_list(_stashed, manager, dg); dg.add_uint32(_tag_data.size()); TagData::const_iterator ti; for (ti = _tag_data.begin(); ti != _tag_data.end(); ++ti) { dg.add_string((*ti).first); dg.add_string((*ti).second); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::complete_pointers // Access: Public, Virtual // Description: Receives an array of pointers, one for each time // manager->read_pointer() was called in fillin(). // Returns the number of pointers processed. //////////////////////////////////////////////////////////////////// int PandaNode::CData:: complete_pointers(TypedWritable **p_list, BamReader *manager) { int pi = CycleData::complete_pointers(p_list, manager); // Get the state, effects, and transform pointers. _state = DCAST(RenderState, p_list[pi++]); _effects = DCAST(RenderEffects, p_list[pi++]); _transform = DCAST(TransformState, p_list[pi++]); _prev_transform = _transform; // Finalize these pointers now to decrement their artificially-held // reference counts. We do this now, rather than later, in case // some other object reassigns them a little later on during // initialization, before they can finalize themselves normally (for // instance, the character may change the node's transform). If // that happens, the pointer may discover that no one else holds its // reference count when it finalizes, which will constitute a memory // leak (see the comments in TransformState::finalize(), etc.). manager->finalize_now((RenderState *)_state.p()); manager->finalize_now((RenderEffects *)_effects.p()); manager->finalize_now((TransformState *)_transform.p()); // Get the parent and child pointers. pi += complete_up_list(_up, p_list + pi, manager); pi += complete_down_list(_down, p_list + pi, manager); pi += complete_down_list(_stashed, p_list + pi, manager); return pi; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::fillin // Access: Public, Virtual // Description: This internal function is called by make_from_bam to // read in all of the relevant data from the BamFile for // the new PandaNode. //////////////////////////////////////////////////////////////////// void PandaNode::CData:: fillin(DatagramIterator &scan, BamReader *manager) { // Read the state, effects, and transform pointers. manager->read_pointer(scan); manager->read_pointer(scan); manager->read_pointer(scan); _draw_mask.set_word(scan.get_uint32()); // Read the parent and child pointers. fillin_up_list(_up, scan, manager); fillin_down_list(_down, scan, manager); fillin_down_list(_stashed, scan, manager); // Read in the tag list. if (manager->get_file_minor_ver() >= 4) { int num_tags = scan.get_uint32(); for (int i = 0; i < num_tags; i++) { string key = scan.get_string(); string value = scan.get_string(); _tag_data[key] = value; } } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::write_up_list // Access: Private // Description: Writes the indicated list of parent node pointers to // the datagram. //////////////////////////////////////////////////////////////////// void PandaNode::CData:: write_up_list(const PandaNode::Up &up_list, BamWriter *manager, Datagram &dg) const { // When we write a PandaNode, we write out its complete list of // child node pointers, but we only write out the parent node // pointers that have already been added to the bam file by a // previous write operation. This is a bit of trickery that allows // us to write out just a subgraph (instead of the complete graph) // when we write out an arbitrary node in the graph, yet also allows // us to keep nodes completely in sync when we use the bam format // for streaming scene graph operations over the network. int num_parents = 0; Up::const_iterator ui; for (ui = up_list.begin(); ui != up_list.end(); ++ui) { PandaNode *parent_node = (*ui).get_parent(); if (manager->has_object(parent_node)) { num_parents++; } } nassertv(num_parents == (int)(PN_uint16)num_parents); dg.add_uint16(num_parents); for (ui = up_list.begin(); ui != up_list.end(); ++ui) { PandaNode *parent_node = (*ui).get_parent(); if (manager->has_object(parent_node)) { manager->write_pointer(dg, parent_node); } } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::write_down_list // Access: Private // Description: Writes the indicated list of child node pointers to // the datagram. //////////////////////////////////////////////////////////////////// void PandaNode::CData:: write_down_list(const PandaNode::Down &down_list, BamWriter *manager, Datagram &dg) const { int num_children = down_list.size(); nassertv(num_children == (int)(PN_uint16)num_children); dg.add_uint16(num_children); // Should we smarten up the writing of the sort number? Most of the // time these will all be zero. Down::const_iterator di; for (di = down_list.begin(); di != down_list.end(); ++di) { PandaNode *child_node = (*di).get_child(); int sort = (*di).get_sort(); manager->write_pointer(dg, child_node); dg.add_int32(sort); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::complete_up_list // Access: Private // Description: Calls complete_pointers() on the list of parent node // pointers. //////////////////////////////////////////////////////////////////// int PandaNode::CData:: complete_up_list(PandaNode::Up &up_list, TypedWritable **p_list, BamReader *manager) { int pi = 0; // Get the parent pointers. Up::iterator ui; for (ui = up_list.begin(); ui != up_list.end(); ++ui) { PandaNode *parent_node = DCAST(PandaNode, p_list[pi++]); // For some reason, VC++ won't accept UpConnection as an inline // temporary constructor here ("C2226: unexpected type // PandaNode::UpConnection"), so we must make this assignment // using an explicit temporary variable. UpConnection connection(parent_node); (*ui) = connection; } // Now we should sort the list, since the sorting is based on // pointer order, which might be different from one session to the // next. up_list.sort(); return pi; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::complete_down_list // Access: Private // Description: Calls complete_pointers() on the list of child node // pointers. //////////////////////////////////////////////////////////////////// int PandaNode::CData:: complete_down_list(PandaNode::Down &down_list, TypedWritable **p_list, BamReader *manager) { int pi = 0; Down::iterator di; for (di = down_list.begin(); di != down_list.end(); ++di) { int sort = (*di).get_sort(); PT(PandaNode) child_node = DCAST(PandaNode, p_list[pi++]); (*di) = DownConnection(child_node, sort); } // Unlike the up list, we should *not* sort the down list. The down // list is stored in a specific order, not related to pointer order; // and this order should be preserved from one session to the next. return pi; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::fillin_up_list // Access: Private // Description: Reads the indicated list parent node pointers from // the datagram (or at least calls read_pointer() for // each one). //////////////////////////////////////////////////////////////////// void PandaNode::CData:: fillin_up_list(PandaNode::Up &up_list, DatagramIterator &scan, BamReader *manager) { int num_parents = scan.get_uint16(); // Read the list of parent nodes. Push back a NULL for each one. _up.reserve(num_parents); for (int i = 0; i < num_parents; i++) { manager->read_pointer(scan); _up.push_back(UpConnection(NULL)); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::CData::fillin_down_list // Access: Private // Description: Reads the indicated list child node pointers from // the datagram (or at least calls read_pointer() for // each one). //////////////////////////////////////////////////////////////////// void PandaNode::CData:: fillin_down_list(PandaNode::Down &down_list, DatagramIterator &scan, BamReader *manager) { int num_children = scan.get_uint16(); // Read the list of child nodes. Push back a NULL for each one. down_list.reserve(num_children); for (int i = 0; i < num_children; i++) { manager->read_pointer(scan); int sort = scan.get_int32(); down_list.push_back(DownConnection(NULL, sort)); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::ChildrenCopy::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PandaNode::ChildrenCopy:: ChildrenCopy(const PandaNode::CDReader &cdata) { Children cr(cdata); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { _list.push_back(cr.get_child(i)); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// PandaNode:: PandaNode(const string &name) : Namable(name) { } //////////////////////////////////////////////////////////////////// // Function: PandaNode::Destructor // Access: Published, Virtual // Description: //////////////////////////////////////////////////////////////////// PandaNode:: ~PandaNode() { // We shouldn't have any parents left by the time we destruct, or // there's a refcount fault somewhere. #ifndef NDEBUG { CDReader cdata(_cycler); nassertv(cdata->_up.empty()); } #endif // NDEBUG remove_all_children(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::Copy Constructor // Access: Protected // Description: Do not call the copy constructor directly; instead, // use make_copy() or copy_subgraph() to make a copy of // a node. //////////////////////////////////////////////////////////////////// PandaNode:: PandaNode(const PandaNode ©) : TypedWritable(copy), Namable(copy), ReferenceCount(copy) { // Copying a node does not copy its children. // Copy the other node's state. CDReader copy_cdata(copy._cycler); CDWriter cdata(_cycler); cdata->_state = copy_cdata->_state; cdata->_effects = copy_cdata->_effects; cdata->_transform = copy_cdata->_transform; cdata->_prev_transform = copy_cdata->_prev_transform; cdata->_tag_data = copy_cdata->_tag_data; cdata->_draw_mask = copy_cdata->_draw_mask; cdata->_fixed_internal_bound = copy_cdata->_fixed_internal_bound; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::Copy Assignment Operator // Access: Private // Description: Do not call the copy assignment operator at all. Use // make_copy() or copy_subgraph() to make a copy of a // node. //////////////////////////////////////////////////////////////////// void PandaNode:: operator = (const PandaNode ©) { nassertv(false); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::make_copy // Access: Public, Virtual // Description: Returns a newly-allocated PandaNode that is a shallow // copy of this one. It will be a different pointer, // but its internal data may or may not be shared with // that of the original PandaNode. No children will be // copied. //////////////////////////////////////////////////////////////////// PandaNode *PandaNode:: make_copy() const { return new PandaNode(*this); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::safe_to_flatten // Access: Public, Virtual // Description: Returns true if it is generally safe to flatten out // this particular kind of PandaNode by duplicating // instances, false otherwise (for instance, a Camera // cannot be safely flattened, because the Camera // pointer itself is meaningful). //////////////////////////////////////////////////////////////////// bool PandaNode:: safe_to_flatten() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::safe_to_transform // Access: Public, Virtual // Description: Returns true if it is generally safe to transform // this particular kind of PandaNode by calling the // xform() method, false otherwise. For instance, it's // usually a bad idea to attempt to xform a Character. //////////////////////////////////////////////////////////////////// bool PandaNode:: safe_to_transform() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::safe_to_modify_transform // Access: Public, Virtual // Description: Returns true if it is safe to automatically adjust // the transform on this kind of node. Usually, this is // only a bad idea if the user expects to find a // particular transform on the node. // // ModelNodes with the preserve_transform flag set are // presently the only kinds of nodes that should not // have their transform even adjusted. //////////////////////////////////////////////////////////////////// bool PandaNode:: safe_to_modify_transform() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::safe_to_combine // Access: Public, Virtual // Description: Returns true if it is generally safe to combine this // particular kind of PandaNode with other kinds of // PandaNodes, adding children or whatever. For // instance, an LODNode should not be combined with any // other PandaNode, because its set of children is // meaningful. //////////////////////////////////////////////////////////////////// bool PandaNode:: safe_to_combine() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::safe_to_flatten_below // Access: Public, Virtual // Description: Returns true if a flatten operation may safely // continue past this node, or false if it should drop // all attributes here and stop. //////////////////////////////////////////////////////////////////// bool PandaNode:: safe_to_flatten_below() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::preserve_name // Access: Public, Virtual // Description: Returns true if the node's name has extrinsic meaning // and must be preserved across a flatten operation, // false otherwise. //////////////////////////////////////////////////////////////////// bool PandaNode:: preserve_name() const { return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_unsafe_to_apply_attribs // Access: Public, Virtual // Description: Returns the union of all attributes from // SceneGraphReducer::AttribTypes that may not safely be // applied to the vertices of this node. If this is // nonzero, these attributes must be dropped at this // node as a state change. // // This is a generalization of safe_to_transform(). //////////////////////////////////////////////////////////////////// int PandaNode:: get_unsafe_to_apply_attribs() const { return 0; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::apply_attribs_to_vertices // Access: Public, Virtual // Description: Applies whatever attributes are specified in the // AccumulatedAttribs object (and by the attrib_types // bitmask) to the vertices on this node, if // appropriate. If this node uses geom arrays like a // GeomNode, the supplied GeomTransformer may be used to // unify shared arrays across multiple different nodes. // // This is a generalization of xform(). //////////////////////////////////////////////////////////////////// void PandaNode:: apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types, GeomTransformer &transformer) { if ((attrib_types & SceneGraphReducer::TT_transform) != 0) { xform(attribs._transform->get_mat()); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::xform // Access: Public, Virtual // Description: Transforms the contents of this PandaNode by the // indicated matrix, if it means anything to do so. For // most kinds of PandaNodes, this does nothing. //////////////////////////////////////////////////////////////////// void PandaNode:: xform(const LMatrix4f &) { } //////////////////////////////////////////////////////////////////// // Function: PandaNode::combine_with // Access: Public, Virtual // Description: Collapses this PandaNode with the other PandaNode, if // possible, and returns a pointer to the combined // PandaNode, or NULL if the two PandaNodes cannot // safely be combined. // // The return value may be this, other, or a new // PandaNode altogether. // // This function is called from GraphReducer::flatten(), // and need not deal with children; its job is just to // decide whether to collapse the two PandaNodes and // what the collapsed PandaNode should look like. //////////////////////////////////////////////////////////////////// PandaNode *PandaNode:: combine_with(PandaNode *other) { // This is a little bit broken right now w.r.t. NodePaths, since any // NodePaths attached to the lost node will simply be disconnected. // This isn't the right thing to do; we should collapse those // NodePaths with these NodePaths instead. To do this properly, we // will need to combine this functionality with that of stealing the // other node's children into one method. Not too difficult, but // there are more pressing problems to work on right now. // An unadorned PandaNode always combines with any other PandaNodes by // yielding completely. However, if we are actually some fancy PandaNode // type that derives from PandaNode but didn't redefine this function, we // should refuse to combine. if (is_exact_type(get_class_type())) { // No, we're an ordinary PandaNode. return other; } else if (other->is_exact_type(get_class_type())) { // We're not an ordinary PandaNode, but the other one is. return this; } // We're something other than an ordinary PandaNode. Don't combine. return (PandaNode *)NULL; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::calc_tight_bounds // Access: Public, Virtual // Description: This is used to support // NodePath::calc_tight_bounds(). It is not intended to // be called directly, and it has nothing to do with the // normal Panda bounding-volume computation. // // If the node contains any geometry, this updates // min_point and max_point to enclose its bounding box. // found_any is to be set true if the node has any // geometry at all, or left alone if it has none. This // method may be called over several nodes, so it may // enter with min_point, max_point, and found_any // already set. // // This function is recursive, and the return value is // the transform after it has been modified by this // node's transform. //////////////////////////////////////////////////////////////////// CPT(TransformState) PandaNode:: calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, bool &found_any, const TransformState *transform) const { CPT(TransformState) next_transform = transform->compose(get_transform()); Children cr = get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { cr.get_child(i)->calc_tight_bounds(min_point, max_point, found_any, next_transform); } return next_transform; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::has_cull_callback // Access: Public, Virtual // Description: Should be overridden by derived classes to return // true if cull_callback() has been defined. Otherwise, // returns false to indicate cull_callback() does not // need to be called for this node during the cull // traversal. //////////////////////////////////////////////////////////////////// bool PandaNode:: has_cull_callback() const { return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::cull_callback // Access: Public, Virtual // Description: If has_cull_callback() returns true, this function // will be called during the cull traversal to perform // any additional operations that should be performed at // cull time. This may include additional manipulation // of render state or additional visible/invisible // decisions, or any other arbitrary operation. // // By the time this function is called, the node has // already passed the bounding-volume test for the // viewing frustum, and the node's transform and state // have already been applied to the indicated // CullTraverserData object. // // The return value is true if this node should be // visible, or false if it should be culled. //////////////////////////////////////////////////////////////////// bool PandaNode:: cull_callback(CullTraverser *, CullTraverserData &) { return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::has_selective_visibility // Access: Public, Virtual // Description: Should be overridden by derived classes to return // true if this kind of node has some restrictions on // the set of children that should be rendered. Node // with this property include LODNodes, SwitchNodes, and // SequenceNodes. // // If this function returns true, // get_first_visible_child() and // get_next_visible_child() will be called to walk // through the list of children during cull, instead of // iterating through the entire list. This method is // called after cull_callback(), so cull_callback() may // be responsible for the decisions as to which children // are visible at the moment. //////////////////////////////////////////////////////////////////// bool PandaNode:: has_selective_visibility() const { return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_first_visible_child // Access: Public, Virtual // Description: Returns the index number of the first visible child // of this node, or a number >= get_num_children() if // there are no visible children of this node. This is // called during the cull traversal, but only if // has_selective_visibility() has already returned true. // See has_selective_visibility(). //////////////////////////////////////////////////////////////////// int PandaNode:: get_first_visible_child() const { return 0; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_next_visible_child // Access: Public, Virtual // Description: Returns the index number of the next visible child // of this node following the indicated child, or a // number >= get_num_children() if there are no more // visible children of this node. See // has_selective_visibility() and // get_first_visible_child(). //////////////////////////////////////////////////////////////////// int PandaNode:: get_next_visible_child(int n) const { return n + 1; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::has_single_child_visibility // Access: Public, Virtual // Description: Should be overridden by derived classes to return // true if this kind of node has the special property // that just one of its children is visible at any given // time, and furthermore that the particular visible // child can be determined without reference to any // external information (such as a camera). At present, // only SequenceNodes and SwitchNodes fall into this // category. // // If this function returns true, get_visible_child() // can be called to return the index of the // currently-visible child. //////////////////////////////////////////////////////////////////// bool PandaNode:: has_single_child_visibility() const { return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_visible_child // Access: Public, Virtual // Description: Returns the index number of the currently visible // child of this node. This is only meaningful if // has_single_child_visibility() has returned true. //////////////////////////////////////////////////////////////////// int PandaNode:: get_visible_child() const { return 0; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::copy_subgraph // Access: Published // Description: Allocates and returns a complete copy of this // PandaNode and the entire scene graph rooted at this // PandaNode. Some data may still be shared from the // original (e.g. vertex index tables), but nothing that // will impede normal use of the PandaNode. //////////////////////////////////////////////////////////////////// PT(PandaNode) PandaNode:: copy_subgraph() const { InstanceMap inst_map; return r_copy_subgraph(inst_map); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::find_child // Access: Published // Description: Returns the index of the indicated child node, if it // is a child, or -1 if it is not. //////////////////////////////////////////////////////////////////// int PandaNode:: find_child(PandaNode *node) const { nassertr(node != (PandaNode *)NULL, -1); CDReader cdata(_cycler); // We have to search for the child by brute force, since we don't // know what sort index it was added as. Down::const_iterator di; for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) { if ((*di).get_child() == node) { return di - cdata->_down.begin(); } } return -1; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::add_child // Access: Published // Description: Adds a new child to the node. The child is added in // the relative position indicated by sort; if all // children have the same sort index, the child is added // at the end. // // If the same child is added to a node more than once, // the previous instance is first removed. //////////////////////////////////////////////////////////////////// void PandaNode:: add_child(PandaNode *child_node, int sort) { nassertv(child_node != (PandaNode *)NULL); // Ensure the child_node is not deleted while we do this. PT(PandaNode) keep_child = child_node; remove_child(child_node); CDWriter cdata(_cycler); CDWriter cdata_child(child_node->_cycler); cdata->_down.insert(DownConnection(child_node, sort)); cdata_child->_up.insert(UpConnection(this)); new_connection(this, child_node); // Mark the bounding volumes stale. force_bound_stale(); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::remove_child // Access: Published // Description: Removes the nth child from the node. //////////////////////////////////////////////////////////////////// void PandaNode:: remove_child(int n) { CDWriter cdata(_cycler); nassertv(n >= 0 && n < (int)cdata->_down.size()); PT(PandaNode) child_node = cdata->_down[n].get_child(); CDWriter cdata_child(child_node->_cycler); cdata->_down.erase(cdata->_down.begin() + n); int num_erased = cdata_child->_up.erase(UpConnection(this)); nassertv(num_erased == 1); sever_connection(this, child_node); // Mark the bounding volumes stale. force_bound_stale(); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::remove_child // Access: Published // Description: Removes the indicated child from the node. Returns // true if the child was removed, false if it was not // already a child of the node. This will also // successfully remove the child if it had been stashed. //////////////////////////////////////////////////////////////////// bool PandaNode:: remove_child(PandaNode *child_node) { nassertr(child_node != (PandaNode *)NULL, false); // First, look for the parent in the child's up list, to ensure the // child is known. int parent_index = child_node->find_parent(this); if (parent_index < 0) { // Nope, no relation. return false; } int child_index = find_child(child_node); if (child_index >= 0) { // The child exists; remove it. remove_child(child_index); return true; } int stashed_index = find_stashed(child_node); if (stashed_index >= 0) { // The child has been stashed; remove it. remove_stashed(stashed_index); return true; } // Never heard of this child. This shouldn't be possible, because // the parent was in the child's up list, above. Must be some // internal error. nassertr(false, false); return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::replace_child // Access: Published // Description: Searches for the orig_child node in the node's list // of children, and replaces it with the new_child // instead. Returns true if the replacement is made, or // false if the node is not a child. //////////////////////////////////////////////////////////////////// bool PandaNode:: replace_child(PandaNode *orig_child, PandaNode *new_child) { nassertr(orig_child != (PandaNode *)NULL, false); nassertr(new_child != (PandaNode *)NULL, false); // First, look for the parent in the child's up list, to ensure the // child is known. int parent_index = orig_child->find_parent(this); if (parent_index < 0) { // Nope, no relation. return false; } if (orig_child == new_child) { // Trivial no-op. return true; } // Don't let orig_child be destructed yet. PT(PandaNode) keep_orig_child = orig_child; int child_index = find_child(orig_child); if (child_index >= 0) { // The child exists; replace it. CDWriter cdata(_cycler); DownConnection &down = cdata->_down[child_index]; nassertr(down.get_child() == orig_child, false); down.set_child(new_child); } else { int stashed_index = find_stashed(orig_child); if (stashed_index >= 0) { // The child has been stashed; remove it. CDWriter cdata(_cycler); DownConnection &down = cdata->_stashed[stashed_index]; nassertr(down.get_child() == orig_child, false); down.set_child(new_child); } else { // Never heard of this child. This shouldn't be possible, because // the parent was in the child's up list, above. Must be some // internal error. nassertr(false, false); return false; } } // Now adjust the bookkeeping on both children. CDWriter cdata_orig_child(orig_child->_cycler); CDWriter cdata_new_child(new_child->_cycler); cdata_new_child->_up.insert(UpConnection(this)); int num_erased = cdata_orig_child->_up.erase(UpConnection(this)); nassertr(num_erased == 1, false); sever_connection(this, orig_child); orig_child->parents_changed(); new_connection(this, new_child); new_child->parents_changed(); // Mark the bounding volumes stale. force_bound_stale(); children_changed(); return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::stash_child // Access: Published // Description: Stashes the indicated child node. This removes the // child from the list of active children and puts it on // a special list of stashed children. This child node // no longer contributes to the bounding volume of the // PandaNode, and is not visited in normal traversals. // It is invisible and uncollidable. The child may // later be restored by calling unstash_child(). //////////////////////////////////////////////////////////////////// void PandaNode:: stash_child(int child_index) { nassertv(child_index >= 0 && child_index < get_num_children()); // Save a reference count for ourselves. I don't think this should // be necessary, but there are occasional crashes in stash() during // furniture moving mode. Perhaps this will eliminate those // crashes. PT(PandaNode) self = this; PT(PandaNode) child_node = get_child(child_index); int sort = get_child_sort(child_index); remove_child(child_index); CDWriter cdata(_cycler); CDWriter cdata_child(child_node->_cycler); cdata->_stashed.insert(DownConnection(child_node, sort)); cdata_child->_up.insert(UpConnection(this)); new_connection(this, child_node); // Mark the bounding volumes stale. force_bound_stale(); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::unstash_child // Access: Published // Description: Returns the indicated stashed node to normal child // status. This removes the child from the list of // stashed children and puts it on the normal list of // active children. This child node once again // contributes to the bounding volume of the PandaNode, // and will be visited in normal traversals. It is // visible and collidable. //////////////////////////////////////////////////////////////////// void PandaNode:: unstash_child(int stashed_index) { nassertv(stashed_index >= 0 && stashed_index < get_num_stashed()); // Save a reference count for ourselves. I don't think this should // be necessary, but there are occasional crashes in stash() during // furniture moving mode. Perhaps this will eliminate those // crashes. PT(PandaNode) self = this; PT(PandaNode) child_node = get_stashed(stashed_index); int sort = get_stashed_sort(stashed_index); remove_stashed(stashed_index); CDWriter cdata(_cycler); CDWriter cdata_child(child_node->_cycler); cdata->_down.insert(DownConnection(child_node, sort)); cdata_child->_up.insert(UpConnection(this)); new_connection(this, child_node); // Mark the bounding volumes stale. force_bound_stale(); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::find_stashed // Access: Published // Description: Returns the index of the indicated stashed node, if // it is a stashed child, or -1 if it is not. //////////////////////////////////////////////////////////////////// int PandaNode:: find_stashed(PandaNode *node) const { CDReader cdata(_cycler); // We have to search for the child by brute force, since we don't // know what sort index it was added as. Down::const_iterator di; for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) { if ((*di).get_child() == node) { return di - cdata->_stashed.begin(); } } return -1; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::add_stashed // Access: Published // Description: Adds a new child to the node, directly as a stashed // child. The child is not added in the normal sense, // but will be revealed if unstash_child() is called on // it later. // // If the same child is added to a node more than once, // the previous instance is first removed. //////////////////////////////////////////////////////////////////// void PandaNode:: add_stashed(PandaNode *child_node, int sort) { // Ensure the child_node is not deleted while we do this. PT(PandaNode) keep_child = child_node; remove_child(child_node); CDWriter cdata(_cycler); CDWriter cdata_child(child_node->_cycler); cdata->_stashed.insert(DownConnection(child_node, sort)); cdata_child->_up.insert(UpConnection(this)); new_connection(this, child_node); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::remove_stashed // Access: Published // Description: Removes the nth stashed child from the node. //////////////////////////////////////////////////////////////////// void PandaNode:: remove_stashed(int n) { CDWriter cdata(_cycler); nassertv(n >= 0 && n < (int)cdata->_stashed.size()); PT(PandaNode) child_node = cdata->_stashed[n].get_child(); CDWriter cdata_child(child_node->_cycler); cdata->_stashed.erase(cdata->_stashed.begin() + n); int num_erased = cdata_child->_up.erase(UpConnection(this)); nassertv(num_erased == 1); sever_connection(this, child_node); // Mark the bounding volumes stale. force_bound_stale(); // Call callback hooks. children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::remove_all_children // Access: Published // Description: Removes all the children from the node at once, // including stashed children. //////////////////////////////////////////////////////////////////// void PandaNode:: remove_all_children() { CDWriter cdata(_cycler); Down::iterator di; for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) { PT(PandaNode) child_node = (*di).get_child(); CDWriter cdata_child(child_node->_cycler); cdata_child->_up.erase(UpConnection(this)); sever_connection(this, child_node); child_node->parents_changed(); } cdata->_down.clear(); for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) { PT(PandaNode) child_node = (*di).get_child(); CDWriter cdata_child(child_node->_cycler); cdata_child->_up.erase(UpConnection(this)); sever_connection(this, child_node); child_node->parents_changed(); } cdata->_stashed.clear(); // Mark the bounding volumes stale. force_bound_stale(); children_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::steal_children // Access: Published // Description: Moves all the children from the other node onto this // node. //////////////////////////////////////////////////////////////////// void PandaNode:: steal_children(PandaNode *other) { if (other == this) { // Trivial. return; } // We do this through the high-level interface for convenience. // This could begin to be a problem if we have a node with hundreds // of children to copy; this could break down the ov_set.insert() // method, which is an O(n^2) operation. If this happens, we should // rewrite this to do a simpler add_child() operation that involves // push_back() instead of insert(), and then sort the down list at // the end. int num_children = other->get_num_children(); int i; for (i = 0; i < num_children; i++) { PandaNode *child_node = other->get_child(i); int sort = other->get_child_sort(i); add_child(child_node, sort); } int num_stashed = other->get_num_stashed(); for (i = 0; i < num_stashed; i++) { PandaNode *child_node = other->get_stashed(i); int sort = other->get_stashed_sort(i); add_stashed(child_node, sort); } other->remove_all_children(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::copy_children // Access: Published // Description: Makes another instance of all the children of the // other node, copying them to this node. //////////////////////////////////////////////////////////////////// void PandaNode:: copy_children(PandaNode *other) { if (other == this) { // Trivial. return; } int num_children = other->get_num_children(); int i; for (i = 0; i < num_children; i++) { PandaNode *child_node = other->get_child(i); int sort = other->get_child_sort(i); add_child(child_node, sort); } int num_stashed = other->get_num_stashed(); for (i = 0; i < num_stashed; i++) { PandaNode *child_node = other->get_stashed(i); int sort = other->get_stashed_sort(i); add_stashed(child_node, sort); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::copy_tags // Access: Published // Description: Copies all of the tags stored on the other node onto // this node. If a particular tag exists on both nodes, // the contents of this node's value is replaced by that // of the other. //////////////////////////////////////////////////////////////////// void PandaNode:: copy_tags(PandaNode *other) { if (other == this) { // Trivial. return; } CDWriter cdataw(_cycler); CDReader cdatar(other->_cycler); TagData::const_iterator ti; for (ti = cdatar->_tag_data.begin(); ti != cdatar->_tag_data.end(); ++ti) { cdataw->_tag_data[(*ti).first] = (*ti).second; } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::list_tags // Access: Published // Description: Writes a list of all the tag keys assigned to the // node to the indicated stream. Writes one instance of // the separator following each key (but does not write // a terminal separator). The value associated with // each key is not written. // // This is mainly for the benefit of the realtime user, // to see the list of all of the associated tag keys. //////////////////////////////////////////////////////////////////// void PandaNode:: list_tags(ostream &out, const string &separator) const { CDReader cdata(_cycler); if (!cdata->_tag_data.empty()) { TagData::const_iterator ti = cdata->_tag_data.begin(); out << (*ti).first; ++ti; while (ti != cdata->_tag_data.end()) { out << separator << (*ti).first; ++ti; } } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::output // Access: Published, Virtual // Description: //////////////////////////////////////////////////////////////////// void PandaNode:: output(ostream &out) const { out << get_type() << " " << get_name(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::write // Access: Published, Virtual // Description: //////////////////////////////////////////////////////////////////// void PandaNode:: write(ostream &out, int indent_level) const { indent(out, indent_level) << *this; CDReader cdata(_cycler); if (!cdata->_tag_data.empty()) { out << " ["; list_tags(out, " "); out << "]"; } if (!cdata->_transform->is_identity()) { out << " " << *cdata->_transform; } if (!cdata->_state->is_empty()) { out << " " << *cdata->_state; } if (!cdata->_effects->is_empty()) { out << " " << *cdata->_effects; } out << "\n"; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::is_geom_node // Access: Published, Virtual // Description: A simple downcast check. Returns true if this kind // of node happens to inherit from GeomNode, false // otherwise. // // This is provided as a a faster alternative to calling // is_of_type(GeomNode::get_class_type()), since this // test is so important to rendering. //////////////////////////////////////////////////////////////////// bool PandaNode:: is_geom_node() const { return false; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::as_light // Access: Published, Virtual // Description: Cross-casts the node to a Light pointer, if it is one // of the four kinds of Light nodes, or returns NULL if // it is not. //////////////////////////////////////////////////////////////////// Light *PandaNode:: as_light() { return NULL; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::propagate_stale_bound // Access: Protected, Virtual // Description: Called by BoundedObject::mark_bound_stale(), this // should make sure that all bounding volumes that // depend on this one are marked stale also. //////////////////////////////////////////////////////////////////// void PandaNode:: propagate_stale_bound() { // Mark all of our parent nodes stale as well. CDWriter cdata(_cycler); Up::const_iterator ui; for (ui = cdata->_up.begin(); ui != cdata->_up.end(); ++ui) { PandaNode *parent_node = (*ui).get_parent(); parent_node->mark_bound_stale(); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::recompute_bound // Access: Protected, Virtual // Description: Recomputes the dynamic bounding volume for this // object. The default behavior is the compute an empty // bounding volume; this may be overridden to extend it // to create a nonempty bounding volume. However, after // calling this function, it is guaranteed that the // _bound pointer will not be shared with any other // stage of the pipeline, and this new pointer is // returned. //////////////////////////////////////////////////////////////////// BoundingVolume *PandaNode:: recompute_bound() { // Get the internal bound before we do anything else, since the // is_bound_stale() flag may also apply to this. const BoundingVolume *internal_bound = &get_internal_bound(); // Now, get ourselves a fresh, empty bounding volume. This will // reset the is_bound_stale() flag if it was set. BoundingVolume *bound = BoundedObject::recompute_bound(); nassertr(bound != (BoundingVolume*)NULL, bound); // Also, recompute the net_collide_mask bits while we do this. CDWriter cdata(_cycler); cdata->_net_collide_mask = CollideMask::all_off(); // Now actually compute the bounding volume by putting it around all // of our child bounding volumes. pvector child_volumes; // It goes around this node's internal bounding volume . . . child_volumes.push_back(internal_bound); Down::const_iterator di; for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) { // . . . plus each node's external bounding volume. PandaNode *child = (*di).get_child(); const BoundingVolume &child_bound = child->get_bound(); child_volumes.push_back(&child_bound); cdata->_net_collide_mask |= child->get_net_collide_mask(); } const BoundingVolume **child_begin = &child_volumes[0]; const BoundingVolume **child_end = child_begin + child_volumes.size(); bool success = bound->around(child_begin, child_end); #ifndef NDEBUG if (!success) { pgraph_cat.error() << "Unable to recompute bounding volume for " << *this << ":\n" << "Cannot put " << bound->get_type() << " around:\n"; for (int i = 0; i < (int)child_volumes.size(); i++) { pgraph_cat.error(false) << " " << *child_volumes[i] << "\n"; } } #endif // Now, if we have a transform, apply it to the bounding volume we // just computed. const TransformState *transform = get_transform(); if (!transform->is_identity()) { GeometricBoundingVolume *gbv; DCAST_INTO_R(gbv, bound, bound); gbv->xform(transform->get_mat()); } return bound; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::recompute_internal_bound // Access: Protected, Virtual // Description: Called when needed to recompute the node's // _internal_bound object. Nodes that contain anything // of substance should redefine this to do the right // thing. //////////////////////////////////////////////////////////////////// BoundingVolume *PandaNode:: recompute_internal_bound() { BoundingVolume *result = _internal_bound.recompute_bound(); return result; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::parents_changed // Access: Protected, Virtual // Description: Called after a scene graph update that either adds or // remove parents from this node, this just provides a // hook for derived PandaNode objects that need to // update themselves based on the set of parents the // node has. //////////////////////////////////////////////////////////////////// void PandaNode:: parents_changed() { } //////////////////////////////////////////////////////////////////// // Function: PandaNode::children_changed // Access: Protected, Virtual // Description: Called after a scene graph update that either adds or // remove children from this node, this just provides a // hook for derived PandaNode objects that need to // update themselves based on the set of children the // node has. //////////////////////////////////////////////////////////////////// void PandaNode:: children_changed() { } //////////////////////////////////////////////////////////////////// // Function: PandaNode::transform_changed // Access: Protected, Virtual // Description: Called after the node's transform has been changed // for any reason, this just provides a hook so derived // classes can do something special in this case. //////////////////////////////////////////////////////////////////// void PandaNode:: transform_changed() { } //////////////////////////////////////////////////////////////////// // Function: PandaNode::r_copy_subgraph // Access: Protected, Virtual // Description: This is the recursive implementation of copy_subgraph(). // It returns a copy of the entire subgraph rooted at // this node. // // Note that it includes the parameter inst_map, which // is a map type, and is not (and cannot be) exported // from PANDA.DLL. Thus, any derivative of PandaNode // that is not also a member of PANDA.DLL *cannot* // access this map. //////////////////////////////////////////////////////////////////// PT(PandaNode) PandaNode:: r_copy_subgraph(PandaNode::InstanceMap &inst_map) const { PT(PandaNode) copy = make_copy(); nassertr(copy != (PandaNode *)NULL, NULL); if (copy->get_type() != get_type()) { pgraph_cat.warning() << "Don't know how to copy nodes of type " << get_type() << "\n"; } copy->r_copy_children(this, inst_map); return copy; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::r_copy_children // Access: Protected, Virtual // Description: This is called by r_copy_subgraph(); the copy has // already been made of this particular node (and this // is the copy); this function's job is to copy all of // the children from the original. // // Note that it includes the parameter inst_map, which // is a map type, and is not (and cannot be) exported // from PANDA.DLL. Thus, any derivative of PandaNode // that is not also a member of PANDA.DLL *cannot* // access this map, and probably should not even // override this function. //////////////////////////////////////////////////////////////////// void PandaNode:: r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) { CDReader from_cdata(from->_cycler); Down::const_iterator di; for (di = from_cdata->_down.begin(); di != from_cdata->_down.end(); ++di) { int sort = (*di).get_sort(); PandaNode *source_child = (*di).get_child(); PT(PandaNode) dest_child; // Check to see if we have already copied this child. If we // have, use the copy. In this way, a subgraph that contains // instances will be correctly duplicated into another subgraph // that also contains its own instances. InstanceMap::const_iterator ci; ci = inst_map.find(source_child); if (ci != inst_map.end()) { dest_child = (*ci).second; } else { dest_child = source_child->r_copy_subgraph(inst_map); inst_map[source_child] = dest_child; } add_child(dest_child, sort); } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::attach // Access: Private, Static // Description: Creates a new parent-child relationship, and returns // the new NodePathComponent. If the child was already // attached to the indicated parent, repositions it and // returns the original NodePathComponent. //////////////////////////////////////////////////////////////////// PT(NodePathComponent) PandaNode:: attach(NodePathComponent *parent, PandaNode *child_node, int sort) { if (parent == (NodePathComponent *)NULL) { // Attaching to NULL means to create a new "instance" with no // attachments, and no questions asked. PT(NodePathComponent) child = new NodePathComponent(child_node, (NodePathComponent *)NULL); CDWriter cdata_child(child_node->_cycler); cdata_child->_paths.insert(child); return child; } // See if the child was already attached to the parent. If it was, // we'll use that same NodePathComponent. PT(NodePathComponent) child = get_component(parent, child_node); if (child == (NodePathComponent *)NULL) { // The child was not already attached to the parent, so get a new // component. child = get_top_component(child_node, true); } reparent(parent, child, sort, false); return child; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::detach // Access: Private, Static // Description: Breaks a parent-child relationship. //////////////////////////////////////////////////////////////////// void PandaNode:: detach(NodePathComponent *child) { nassertv(child != (NodePathComponent *)NULL); nassertv(!child->is_top_node()); PT(PandaNode) child_node = child->get_node(); PT(PandaNode) parent_node = child->get_next()->get_node(); // We should actually have a parent-child relationship, since this // came from a NodePathComponent that ought to know about this // sort of thing. nassertv(child_node->find_parent(parent_node) >= 0); CDWriter cdata_child(child_node->_cycler); CDWriter cdata_parent(parent_node->_cycler); // Now look for the child and break the actual connection. // First, look for and remove the parent node from the child's up // list. int num_erased = cdata_child->_up.erase(UpConnection(parent_node)); nassertv(num_erased == 1); // Now, look for and remove the child node from the parent's down // list. We also check in the stashed list, in case the child node // has been stashed. Down::iterator di; bool found = false; for (di = cdata_parent->_down.begin(); di != cdata_parent->_down.end() && !found; ++di) { if ((*di).get_child() == child_node) { cdata_parent->_down.erase(di); found = true; } } for (di = cdata_parent->_stashed.begin(); di != cdata_parent->_stashed.end() && !found; ++di) { if ((*di).get_child() == child_node) { cdata_parent->_stashed.erase(di); found = true; } } nassertv(found); // Finally, break the NodePathComponent connection. sever_connection(parent_node, child_node); // Mark the bounding volumes stale. parent_node->force_bound_stale(); // Call callback hooks. parent_node->children_changed(); child_node->parents_changed(); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::reparent // Access: Private, Static // Description: Switches a node from one parent to another. Returns // true if the new connection is allowed, or false if it // conflicts with another instance (that is, another // instance of the child is already attached to the // indicated parent). //////////////////////////////////////////////////////////////////// bool PandaNode:: reparent(NodePathComponent *new_parent, NodePathComponent *child, int sort, bool as_stashed) { nassertr(child != (NodePathComponent *)NULL, false); // Keep a reference count to the new parent, since detaching the // child might lose the count. PT(NodePathComponent) keep_parent = new_parent; if (!child->is_top_node()) { detach(child); } if (new_parent != (NodePathComponent *)NULL) { PandaNode *child_node = child->get_node(); PandaNode *parent_node = new_parent->get_node(); if (child_node->find_parent(parent_node) >= 0) { // Whoops, there's already another instance of the child there. return false; } // Redirect the connection to the indicated new parent. child->set_next(new_parent); // Now reattach the child node at the indicated sort position. CDWriter cdata_parent(parent_node->_cycler); CDWriter cdata_child(child_node->_cycler); if (as_stashed) { cdata_parent->_stashed.insert(DownConnection(child_node, sort)); } else { cdata_parent->_down.insert(DownConnection(child_node, sort)); } cdata_child->_up.insert(UpConnection(parent_node)); cdata_child->_paths.insert(child); child_node->fix_path_lengths(cdata_child); // Mark the bounding volumes stale. if (!as_stashed) { parent_node->force_bound_stale(); } // Call callback hooks. parent_node->children_changed(); child_node->parents_changed(); } return true; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_component // Access: Private, Static // Description: Returns the NodePathComponent based on the indicated // child of the given parent, or NULL if there is no // such parent-child relationship. //////////////////////////////////////////////////////////////////// PT(NodePathComponent) PandaNode:: get_component(NodePathComponent *parent, PandaNode *child_node) { nassertr(parent != (NodePathComponent *)NULL, (NodePathComponent *)NULL); PandaNode *parent_node = parent->get_node(); { CDReader cdata_child(child_node->_cycler); // First, walk through the list of NodePathComponents we already // have on the child, looking for one that already exists, // referencing the indicated parent component. Paths::const_iterator pi; for (pi = cdata_child->_paths.begin(); pi != cdata_child->_paths.end(); ++pi) { if ((*pi)->get_next() == parent) { // If we already have such a component, just return it. return (*pi); } } } // We don't already have a NodePathComponent referring to this // parent-child relationship. Are they actually related? int child_index = child_node->find_parent(parent_node); if (child_index >= 0) { // They are. Create and return a new one. PT(NodePathComponent) child = new NodePathComponent(child_node, parent); CDWriter cdata_child(child_node->_cycler); cdata_child->_paths.insert(child); return child; } else { // They aren't related. Return NULL. return NULL; } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_top_component // Access: Private, Static // Description: Returns a NodePathComponent referencing the // indicated node as a singleton. It is invalid to call // this for a node that has parents, unless you are // about to create a new instance (and immediately // reconnect the NodePathComponent elsewhere). // // If force is true, this will always return something, // even if it needs to create a new top component; // otherwise, if force is false, it will return NULL if // there is not already a top component available. //////////////////////////////////////////////////////////////////// PT(NodePathComponent) PandaNode:: get_top_component(PandaNode *child_node, bool force) { { CDReader cdata_child(child_node->_cycler); // Walk through the list of NodePathComponents we already have on // the child, looking for one that already exists as a top node. Paths::const_iterator pi; for (pi = cdata_child->_paths.begin(); pi != cdata_child->_paths.end(); ++pi) { if ((*pi)->is_top_node()) { // If we already have such a component, just return it. return (*pi); } } } if (!force) { // If we don't care to force the point, return NULL to indicate // there's not already a top component. return NULL; } // We don't already have such a NodePathComponent; create and // return a new one. PT(NodePathComponent) child = new NodePathComponent(child_node, (NodePathComponent *)NULL); CDWriter cdata_child(child_node->_cycler); cdata_child->_paths.insert(child); return child; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::get_generic_component // Access: Private // Description: Returns a NodePathComponent referencing this node as // a path from the root. // // Unless accept_ambiguity is true, it is only valid to // call this if there is an unambiguous path from the // root; otherwise, a warning will be issued and one // path will be chosen arbitrarily. //////////////////////////////////////////////////////////////////// PT(NodePathComponent) PandaNode:: get_generic_component(bool accept_ambiguity) { bool ambiguity_detected = false; PT(NodePathComponent) result = r_get_generic_component(accept_ambiguity, ambiguity_detected); if (!accept_ambiguity && ambiguity_detected) { pgraph_cat.warning() << "Chose: " << *result << "\n"; nassertr(!unambiguous_graph, result); } return result; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::r_get_generic_component // Access: Private // Description: The recursive implementation of // get_generic_component, this simply sets the flag when // the ambiguity is detected (so we can report the // bottom node that started the ambiguous search). //////////////////////////////////////////////////////////////////// PT(NodePathComponent) PandaNode:: r_get_generic_component(bool accept_ambiguity, bool &ambiguity_detected) { int num_parents = get_num_parents(); if (num_parents == 0) { // No parents; no ambiguity. This is the root. return get_top_component(this, true); } PT(NodePathComponent) result; if (num_parents == 1) { // Only one parent; no ambiguity. PT(NodePathComponent) parent = get_parent(0)->r_get_generic_component(accept_ambiguity, ambiguity_detected); result = get_component(parent, this); } else { // Oops, multiple parents; the NodePath is ambiguous. if (!accept_ambiguity) { pgraph_cat.warning() << *this << " has " << num_parents << " parents; choosing arbitrary path to root.\n"; } ambiguity_detected = true; PT(NodePathComponent) parent = get_parent(0)->r_get_generic_component(accept_ambiguity, ambiguity_detected); result = get_component(parent, this); } return result; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::delete_component // Access: Private // Description: Removes a NodePathComponent from the set prior to // its deletion. This should only be called by the // NodePathComponent destructor. //////////////////////////////////////////////////////////////////// void PandaNode:: delete_component(NodePathComponent *component) { // We have to remove the component from all of the pipeline stages, // not just the current one. int max_num_erased = 0; int num_stages = _cycler.get_num_stages(); for (int i = 0; i < num_stages; i++) { if (_cycler.is_stage_unique(i)) { CData *cdata = _cycler.write_stage(i); int num_erased = cdata->_paths.erase(component); max_num_erased = max(max_num_erased, num_erased); _cycler.release_write_stage(i, cdata); } } nassertv(max_num_erased == 1); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::sever_connection // Access: Private, Static // Description: This is called internally when a parent-child // connection is broken to update the NodePathComponents // that reflected this connection. // // It severs any NodePathComponents on the child node // that reference the indicated parent node. These // components remain unattached; there may therefore be // multiple "instances" of a node that all have no // parent, even while there are other instances that do // have parents. //////////////////////////////////////////////////////////////////// void PandaNode:: sever_connection(PandaNode *parent_node, PandaNode *child_node) { CDWriter cdata_child(child_node->_cycler); Paths::iterator pi; for (pi = cdata_child->_paths.begin(); pi != cdata_child->_paths.end(); ++pi) { if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == parent_node) { // Sever the component here. (*pi)->set_top_node(); } } child_node->fix_path_lengths(cdata_child); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::new_connection // Access: Private, Static // Description: This is called internally when a parent-child // connection is established to update the // NodePathComponents that might be involved. // // It adjusts any NodePathComponents the child has that // reference the child as a top node. Any other // components we can leave alone, because we are making // a new instance of the child. //////////////////////////////////////////////////////////////////// void PandaNode:: new_connection(PandaNode *parent_node, PandaNode *child_node) { CDWriter cdata_child(child_node->_cycler); Paths::iterator pi; for (pi = cdata_child->_paths.begin(); pi != cdata_child->_paths.end(); ++pi) { if ((*pi)->is_top_node()) { (*pi)->set_next(parent_node->get_generic_component(false)); } } child_node->fix_path_lengths(cdata_child); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::fix_path_lengths // Access: Private // Description: Recursively fixes the _length member of each // NodePathComponent at this level and below, after an // add or delete child operation that might have messed // these up. //////////////////////////////////////////////////////////////////// void PandaNode:: fix_path_lengths(const CData *cdata) { bool any_wrong = false; Paths::const_iterator pi; for (pi = cdata->_paths.begin(); pi != cdata->_paths.end(); ++pi) { if ((*pi)->fix_length()) { any_wrong = true; } } // If any paths were updated, we have to recurse on all of our // children, since any one of those paths might be shared by any of // our child nodes. if (any_wrong) { Down::const_iterator di; for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) { PandaNode *child_node = (*di).get_child(); CDReader cdata_child(child_node->_cycler); child_node->fix_path_lengths(cdata_child); } for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) { PandaNode *child_node = (*di).get_child(); CDReader cdata_child(child_node->_cycler); child_node->fix_path_lengths(cdata_child); } } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::r_list_descendants // Access: Private // Description: The recursive implementation of ls(). //////////////////////////////////////////////////////////////////// void PandaNode:: r_list_descendants(ostream &out, int indent_level) const { CDReader cdata(_cycler); indent(out, indent_level) << *this; if (!cdata->_tag_data.empty()) { out << " ["; list_tags(out, " "); out << "]"; } if (!cdata->_transform->is_identity()) { out << " " << *cdata->_transform; } if (!cdata->_state->is_empty()) { out << " " << *cdata->_state; } if (!cdata->_effects->is_empty()) { out << " " << *cdata->_effects; } out << "\n"; Down::const_iterator di; for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) { (*di).get_child()->r_list_descendants(out, indent_level + 2); } // Also report the number of stashed nodes at this level. int num_stashed = get_num_stashed(); if (num_stashed != 0) { indent(out, indent_level) << "(" << num_stashed << " stashed)\n"; } } //////////////////////////////////////////////////////////////////// // Function: PandaNode::register_with_read_factory // Access: Public, Static // Description: Tells the BamReader how to create objects of type // PandaNode. //////////////////////////////////////////////////////////////////// void PandaNode:: register_with_read_factory() { BamReader::get_factory()->register_factory(get_class_type(), make_from_bam); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::write_datagram // Access: Public, Virtual // Description: Writes the contents of this object to the datagram // for shipping out to a Bam file. //////////////////////////////////////////////////////////////////// void PandaNode:: write_datagram(BamWriter *manager, Datagram &dg) { TypedWritable::write_datagram(manager, dg); dg.add_string(get_name()); manager->write_cdata(dg, _cycler); } //////////////////////////////////////////////////////////////////// // Function: PandaNode::make_from_bam // Access: Protected, Static // Description: This function is called by the BamReader's factory // when a new object of type PandaNode is encountered // in the Bam file. It should create the PandaNode // and extract its information from the file. //////////////////////////////////////////////////////////////////// TypedWritable *PandaNode:: make_from_bam(const FactoryParams ¶ms) { PandaNode *node = new PandaNode(""); DatagramIterator scan; BamReader *manager; parse_params(params, scan, manager); node->fillin(scan, manager); return node; } //////////////////////////////////////////////////////////////////// // Function: PandaNode::fillin // Access: Protected // Description: This internal function is called by make_from_bam to // read in all of the relevant data from the BamFile for // the new PandaNode. //////////////////////////////////////////////////////////////////// void PandaNode:: fillin(DatagramIterator &scan, BamReader *manager) { TypedWritable::fillin(scan, manager); string name = scan.get_string(); set_name(name); manager->read_cdata(scan, _cycler); }