panda3d/panda/src/pgraph/pandaNode.cxx

4921 lines
187 KiB
C++

// Filename: pandaNode.cxx
// Created by: drose (20Feb02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#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"
#include "clipPlaneAttrib.h"
#include "boundingSphere.h"
#include "boundingBox.h"
#include "pStatTimer.h"
#include "config_mathutil.h"
#include "lightReMutexHolder.h"
#include "graphicsStateGuardianBase.h"
#include "py_panda.h"
// This category is just temporary for debugging convenience.
NotifyCategoryDecl(drawmask, EXPCL_PANDA_PGRAPH, EXPTP_PANDA_PGRAPH);
NotifyCategoryDef(drawmask, "");
TypeHandle PandaNode::BamReaderAuxDataDown::_type_handle;
PandaNode::SceneRootFunc *PandaNode::_scene_root_func;
PandaNodeChain PandaNode::_dirty_prev_transforms;
DrawMask PandaNode::_overall_bit = DrawMask::bit(31);
PStatCollector PandaNode::_reset_prev_pcollector("App:Collisions:Reset");
PStatCollector PandaNode::_update_bounds_pcollector("*:Bounds");
TypeHandle PandaNode::_type_handle;
TypeHandle PandaNode::CData::_type_handle;
TypeHandle PandaNodePipelineReader::_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::Constructor
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
PandaNode::
PandaNode(const string &name) :
Namable(name),
_paths_lock("PandaNode::_paths_lock"),
_dirty_prev_transform(false)
{
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
<< "Constructing " << (void *)this << ", " << get_name() << "\n";
}
#ifndef NDEBUG
_unexpected_change_flags = 0;
#endif // !NDEBUG
#ifdef DO_MEMORY_USAGE
MemoryUsage::update_type(this, this);
#endif
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::Destructor
// Access: Published, Virtual
// Description:
////////////////////////////////////////////////////////////////////
PandaNode::
~PandaNode() {
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
<< "Destructing " << (void *)this << ", " << get_name() << "\n";
}
clear_dirty_prev_transform();
// We shouldn't have any parents left by the time we destruct, or
// there's a refcount fault somewhere.
// Actually, that's not necessarily true anymore, since we might be
// updating a node dynamically via the bam reader, which doesn't
// necessarily keep related pairs of nodes in sync with each other.
/*
#ifndef NDEBUG
{
CDReader cdata(_cycler);
nassertv(cdata->get_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 &copy) :
ReferenceCount(copy),
TypedWritable(copy),
Namable(copy),
_paths_lock("PandaNode::_paths_lock"),
_dirty_prev_transform(false)
{
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
<< "Copying " << (void *)this << ", " << get_name() << "\n";
}
#ifdef DO_MEMORY_USAGE
MemoryUsage::update_type(this, this);
#endif
// Copying a node does not copy its children.
#ifndef NDEBUG
_unexpected_change_flags = 0;
#endif // !NDEBUG
// Copy the other node's state.
{
CDReader copy_cdata(copy._cycler);
CDWriter cdata(_cycler, true);
cdata->_state = copy_cdata->_state;
cdata->_transform = copy_cdata->_transform;
cdata->_prev_transform = copy_cdata->_prev_transform;
if (cdata->_transform != cdata->_prev_transform) {
set_dirty_prev_transform();
}
cdata->_effects = copy_cdata->_effects;
cdata->_tag_data = copy_cdata->_tag_data;
cdata->_draw_control_mask = copy_cdata->_draw_control_mask;
cdata->_draw_show_mask = copy_cdata->_draw_show_mask;
cdata->_into_collide_mask = copy_cdata->_into_collide_mask;
cdata->_bounds_type = copy_cdata->_bounds_type;
cdata->_user_bounds = copy_cdata->_user_bounds;
cdata->_internal_bounds = NULL;
cdata->_internal_bounds_computed = UpdateSeq::initial();
cdata->_internal_bounds_mark = UpdateSeq::initial();
++cdata->_internal_bounds_mark;
cdata->_final_bounds = copy_cdata->_final_bounds;
cdata->_fancy_bits = copy_cdata->_fancy_bits;
#ifdef HAVE_PYTHON
// Copy and increment all of the Python objects held by the other
// node.
cdata->_python_tag_data = copy_cdata->_python_tag_data;
cdata->inc_py_refs();
#endif // HAVE_PYTHON
}
}
////////////////////////////////////////////////////////////////////
// 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 &copy) {
nassertv(false);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::as_reference_count
// Access: Public, Virtual
// Description: Returns the pointer cast to a ReferenceCount pointer,
// if it is in fact of that type.
////////////////////////////////////////////////////////////////////
ReferenceCount *PandaNode::
as_reference_count() {
return this;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::dupe_for_flatten
// Access: Public, Virtual
// Description: This is similar to make_copy(), but it makes a copy
// for the specific purpose of flatten. Typically, this
// will be a new PandaNode with a new pointer, but all
// of the internal data will always be shared with the
// original; whereas the new node returned by
// make_copy() might not share the internal data.
////////////////////////////////////////////////////////////////////
PandaNode *PandaNode::
dupe_for_flatten() const {
return make_copy();
}
////////////////////////////////////////////////////////////////////
// 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 (by calling dupe_for_flatten()), 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.
////////////////////////////////////////////////////////////////////
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 of compatible type, 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_combine_children
// Access: Public, Virtual
// Description: Returns true if it is generally safe to combine the
// children of this PandaNode with each other. For
// instance, an LODNode's children should not be
// combined with each other, because the set of children
// is meaningful.
////////////////////////////////////////////////////////////////////
bool PandaNode::
safe_to_combine_children() 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 nodes below this
// node may not be molested.
////////////////////////////////////////////////////////////////////
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) {
const LMatrix4f &mat = attribs._transform->get_mat();
xform(mat);
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = cdata->_effects->xform(mat);
cdata->set_fancy_bit(FB_effects, !cdata->_effects->is_empty());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
}
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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) {
// 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, Thread *current_thread) const {
CPT(TransformState) next_transform = transform->compose(get_transform());
Children cr = get_children(current_thread);
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,
current_thread);
}
return next_transform;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::cull_callback
// Access: Public, Virtual
// Description: 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.
//
// Note that this function will *not* be called unless
// set_cull_callback() is called in the constructor of
// the derived class. It is necessary to call
// set_cull_callback() to indicated that we require
// cull_callback() to be called.
//
// 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::is_renderable
// Access: Public, Virtual
// Description: Returns true if there is some value to visiting this
// particular node during the cull traversal for any
// camera, false otherwise. This will be used to
// optimize the result of get_net_draw_show_mask(), so
// that any subtrees that contain only nodes for which
// is_renderable() is false need not be visited.
////////////////////////////////////////////////////////////////////
bool PandaNode::
is_renderable() const {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::add_for_draw
// Access: Public, Virtual
// Description: Adds the node's contents to the CullResult we are
// building up during the cull traversal, so that it
// will be drawn at render time. For most nodes other
// than GeomNodes, this is a do-nothing operation.
////////////////////////////////////////////////////////////////////
void PandaNode::
add_for_draw(CullTraverser *, CullTraverserData &) {
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::make_copy
// Access: Published, 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::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(Thread *current_thread) const {
InstanceMap inst_map;
return r_copy_subgraph(inst_map, current_thread);
}
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::__copy__
// Access: Published
// Description: A special Python method that is invoked by
// copy.copy(node). Unlike the PandaNode copy
// constructor, which creates a new node without
// children, this shares child pointers (essentially
// making every child an instance). This is intended to
// simulate the behavior of copy.copy() for other
// objects.
////////////////////////////////////////////////////////////////////
PT(PandaNode) PandaNode::
__copy__() const {
Thread *current_thread = Thread::get_current_thread();
PT(PandaNode) node_dupe = make_copy();
Children children = get_children(current_thread);
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
PandaNode *child = children.get_child(i);
node_dupe->add_child(children.get_child(i), children.get_child_sort(i));
}
return node_dupe;
}
#endif // HAVE_PYTHON
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::__deepcopy__
// Access: Published
// Description: A special Python method that is invoked by
// copy.deepcopy(node). This calls copy_subgraph()
// unless the node is already present in the provided
// dictionary.
////////////////////////////////////////////////////////////////////
PyObject *PandaNode::
__deepcopy__(PyObject *self, PyObject *memo) const {
IMPORT_THIS struct Dtool_PyTypedObject Dtool_PandaNode;
// Borrowed reference.
PyObject *dupe = PyDict_GetItem(memo, self);
if (dupe != NULL) {
// Already in the memo dictionary.
Py_INCREF(dupe);
return dupe;
}
PT(PandaNode) node_dupe = copy_subgraph();
// DTool_CreatePyInstanceTyped() steals a C++ reference.
node_dupe->ref();
dupe = DTool_CreatePyInstanceTyped
((void *)node_dupe.p(), Dtool_PandaNode, true, false,
node_dupe->get_type_index());
if (PyDict_SetItem(memo, self, dupe) != 0) {
Py_DECREF(dupe);
return NULL;
}
return dupe;
}
#endif // HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::count_num_descendants
// Access: Published
// Description: Returns the number of nodes at and below this level.
////////////////////////////////////////////////////////////////////
int PandaNode::
count_num_descendants() const {
int count = 1;
Children children = get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
PandaNode *child = children.get_child(i);
count += child->count_num_descendants();
}
return count;
}
////////////////////////////////////////////////////////////////////
// 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, Thread *current_thread) {
nassertv(child_node != (PandaNode *)NULL);
if (!verify_child_no_cycles(child_node)) {
// Whoops, adding this child node would introduce a cycle in the
// scene graph.
return;
}
// Ensure the child_node is not deleted while we do this.
PT(PandaNode) keep_child = child_node;
remove_child(child_node);
// Apply this operation to the current stage as well as to all
// upstream stages.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
cdata->modify_down()->insert(DownConnection(child_node, sort));
cdata_child->modify_up()->insert(UpConnection(this));
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler, current_thread) {
new_connection(this, child_node, pipeline_stage, current_thread);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler);
force_bounds_stale();
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::remove_child
// Access: Published
// Description: Removes the nth child from the node.
////////////////////////////////////////////////////////////////////
void PandaNode::
remove_child(int child_index, Thread *current_thread) {
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
PT(Down) down = cdata->modify_down();
nassertv(child_index >= 0 && child_index < (int)down->size());
PT(PandaNode) child_node = (*down)[child_index].get_child();
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage,
current_thread);
PT(Up) up = cdata_child->modify_up();
down->erase(down->begin() + child_index);
int num_erased = up->erase(UpConnection(this));
nassertv(num_erased == 1);
sever_connection(this, child_node, pipeline_stage, current_thread);
force_bounds_stale(pipeline_stage, current_thread);
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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, Thread *current_thread) {
nassertr(child_node != (PandaNode *)NULL, false);
// Make sure the child node is not destructed during the execution
// of this method.
PT(PandaNode) keep_child = child_node;
// We have to do this for each upstream pipeline stage.
bool any_removed = false;
OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler, current_thread) {
if (stage_remove_child(child_node, pipeline_stage, current_thread)) {
any_removed = true;
sever_connection(this, child_node, pipeline_stage, current_thread);
force_bounds_stale(pipeline_stage, current_thread);
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler);
if (any_removed) {
// Call callback hooks.
children_changed();
child_node->parents_changed();
}
return any_removed;
}
////////////////////////////////////////////////////////////////////
// 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 or if there is some
// other problem.
////////////////////////////////////////////////////////////////////
bool PandaNode::
replace_child(PandaNode *orig_child, PandaNode *new_child,
Thread *current_thread) {
nassertr(orig_child != (PandaNode *)NULL, false);
nassertr(new_child != (PandaNode *)NULL, false);
if (orig_child == new_child) {
// Trivial no-op.
return true;
}
if (!verify_child_no_cycles(new_child)) {
// Whoops, adding this child node would introduce a cycle in the
// scene graph.
return false;
}
// Make sure the orig_child node is not destructed during the
// execution of this method.
PT(PandaNode) keep_orig_child = orig_child;
// We have to do this for each upstream pipeline stage.
bool any_replaced = false;
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
if (stage_replace_child(orig_child, new_child, pipeline_stage, current_thread)) {
any_replaced = true;
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
if (any_replaced) {
children_changed();
orig_child->parents_changed();
new_child->parents_changed();
}
return any_replaced;
}
////////////////////////////////////////////////////////////////////
// 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().
//
// This can only be called from the top pipeline stage
// (i.e. from App).
////////////////////////////////////////////////////////////////////
void PandaNode::
stash_child(int child_index, Thread *current_thread) {
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
nassertv(child_index >= 0 && child_index < get_num_children());
// Save a reference count for ourselves.
PT(PandaNode) self = this;
PT(PandaNode) child_node = get_child(child_index);
int sort = get_child_sort(child_index);
remove_child(child_index);
{
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
cdata->modify_stashed()->insert(DownConnection(child_node, sort));
cdata_child->modify_up()->insert(UpConnection(this));
}
new_connection(this, child_node, pipeline_stage, current_thread);
force_bounds_stale(pipeline_stage, current_thread);
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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.
//
// This can only be called from the top pipeline stage
// (i.e. from App).
////////////////////////////////////////////////////////////////////
void PandaNode::
unstash_child(int stashed_index, Thread *current_thread) {
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
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->modify_down()->insert(DownConnection(child_node, sort));
cdata_child->modify_up()->insert(UpConnection(this));
}
new_connection(this, child_node, pipeline_stage, current_thread);
force_bounds_stale();
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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.
//
// This can only be called from the top pipeline stage
// (i.e. from App).
////////////////////////////////////////////////////////////////////
void PandaNode::
add_stashed(PandaNode *child_node, int sort, Thread *current_thread) {
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
if (!verify_child_no_cycles(child_node)) {
// Whoops, adding this child node would introduce a cycle in the
// scene graph.
return;
}
// 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->modify_stashed()->insert(DownConnection(child_node, sort));
cdata_child->modify_up()->insert(UpConnection(this));
}
new_connection(this, child_node, pipeline_stage, current_thread);
// Call callback hooks.
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::remove_stashed
// Access: Published
// Description: Removes the nth stashed child from the node.
////////////////////////////////////////////////////////////////////
void PandaNode::
remove_stashed(int child_index, Thread *current_thread) {
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
Down &stashed = *cdata->modify_stashed();
nassertv(child_index >= 0 && child_index < (int)stashed.size());
PT(PandaNode) child_node = stashed[child_index].get_child();
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
stashed.erase(stashed.begin() + child_index);
int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
nassertv(num_erased == 1);
sever_connection(this, child_node, pipeline_stage, current_thread);
force_bounds_stale(pipeline_stage, current_thread);
children_changed();
child_node->parents_changed();
mark_bam_modified();
child_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::remove_all_children
// Access: Published
// Description: Removes all the children from the node at once,
// including stashed children.
//
// This can only be called from the top pipeline stage
// (i.e. from App).
////////////////////////////////////////////////////////////////////
void PandaNode::
remove_all_children(Thread *current_thread) {
// We have to do this for each upstream pipeline stage.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
PT(Down) down = cdata->modify_down();
Down::iterator di;
for (di = down->begin(); di != down->end(); ++di) {
PT(PandaNode) child_node = (*di).get_child();
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage,
current_thread);
cdata_child->modify_up()->erase(UpConnection(this));
sever_connection(this, child_node, pipeline_stage, current_thread);
child_node->parents_changed();
child_node->mark_bam_modified();
}
down->clear();
Down &stashed = *cdata->modify_stashed();
for (di = stashed.begin(); di != stashed.end(); ++di) {
PT(PandaNode) child_node = (*di).get_child();
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage,
current_thread);
cdata_child->modify_up()->erase(UpConnection(this));
sever_connection(this, child_node, pipeline_stage, current_thread);
child_node->parents_changed();
child_node->mark_bam_modified();
}
stashed.clear();
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
force_bounds_stale();
children_changed();
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::steal_children
// Access: Published
// Description: Moves all the children from the other node onto this
// node.
//
// Any NodePaths to child nodes of the other node are
// truncated, rather than moved to the new parent.
////////////////////////////////////////////////////////////////////
void PandaNode::
steal_children(PandaNode *other, Thread *current_thread) {
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, current_thread);
}
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, current_thread);
}
other->remove_all_children(current_thread);
}
////////////////////////////////////////////////////////////////////
// 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, Thread *current_thread) {
if (other == this) {
// Trivial.
return;
}
Children children = other->get_children(current_thread);
Stashed stashed = other->get_stashed(current_thread);
int num_children = children.get_num_children();
int i;
for (i = 0; i < num_children; i++) {
PandaNode *child_node = children.get_child(i);
int sort = children.get_child_sort(i);
add_child(child_node, sort, current_thread);
}
int num_stashed = stashed.get_num_stashed();
for (i = 0; i < num_stashed; i++) {
PandaNode *child_node = stashed.get_stashed(i);
int sort = stashed.get_stashed_sort(i);
add_stashed(child_node, sort, current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_attrib
// Access: Published
// Description: Adds the indicated render attribute to the scene
// graph on this node. This attribute will now apply to
// this node and everything below. If there was already
// an attribute of the same type, it is replaced.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_attrib(const RenderAttrib *attrib, int override) {
// Apply this operation to the current stage as well as to all
// upstream stages.
bool any_changed = false;
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CPT(RenderState) new_state = cdata->_state->set_attrib(attrib, override);
if (cdata->_state != new_state) {
cdata->_state = new_state;
cdata->set_fancy_bit(FB_state, true);
any_changed = true;
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
// Maybe we changed a ClipPlaneAttrib.
if (any_changed) {
mark_bounds_stale(current_thread);
state_changed();
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::clear_attrib
// Access: Published
// Description: Removes the render attribute of the given type from
// this node. This node, and the subgraph below, will
// now inherit the indicated render attribute from the
// nodes above this one.
////////////////////////////////////////////////////////////////////
void PandaNode::
clear_attrib(int slot) {
bool any_changed = false;
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CPT(RenderState) new_state = cdata->_state->remove_attrib(slot);
if (cdata->_state != new_state) {
cdata->_state = new_state;
cdata->set_fancy_bit(FB_state, !new_state->is_empty());
any_changed = true;
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
// We mark the bounds stale when the state changes, in case
// we have changed a ClipPlaneAttrib.
if (any_changed) {
mark_bounds_stale(current_thread);
state_changed();
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_effect
// Access: Published
// Description: Adds the indicated render effect to the scene
// graph on this node. If there was already an effect
// of the same type, it is replaced.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_effect(const RenderEffect *effect) {
// Apply this operation to the current stage as well as to all
// upstream stages.
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = cdata->_effects->add_effect(effect);
cdata->set_fancy_bit(FB_effects, true);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::clear_effect
// Access: Published
// Description: Removes the render effect of the given type from
// this node.
////////////////////////////////////////////////////////////////////
void PandaNode::
clear_effect(TypeHandle type) {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = cdata->_effects->remove_effect(type);
cdata->set_fancy_bit(FB_effects, !cdata->_effects->is_empty());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_state
// Access: Published
// Description: Sets the complete RenderState that will be applied to
// all nodes at this level and below. (The actual state
// that will be applied to lower nodes is based on the
// composition of RenderStates from above this node as
// well). This completely replaces whatever has been
// set on this node via repeated calls to set_attrib().
////////////////////////////////////////////////////////////////////
void PandaNode::
set_state(const RenderState *state, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
bool any_changed = false;
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_state != state) {
cdata->_state = state;
cdata->set_fancy_bit(FB_state, !state->is_empty());
any_changed = true;
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
// Maybe we have changed a ClipPlaneAttrib.
if (any_changed) {
mark_bounds_stale(current_thread);
state_changed();
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_effects
// Access: Published
// Description: Sets the complete RenderEffects that will be applied
// this node. This completely replaces whatever has
// been set on this node via repeated calls to
// set_attrib().
////////////////////////////////////////////////////////////////////
void PandaNode::
set_effects(const RenderEffects *effects, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_effects = effects;
cdata->set_fancy_bit(FB_effects, !effects->is_empty());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_transform
// Access: Published
// Description: Sets the transform that will be applied to this node
// and below. This defines a new coordinate space at
// this point in the scene graph and below.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_transform(const TransformState *transform, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
bool any_changed = false;
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_transform != transform) {
cdata->_transform = transform;
cdata->set_fancy_bit(FB_transform, !transform->is_identity());
any_changed = true;
if (pipeline_stage == 0) {
if (cdata->_transform != cdata->_prev_transform) {
set_dirty_prev_transform();
}
}
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
if (any_changed) {
mark_bounds_stale(current_thread);
transform_changed();
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_prev_transform
// Access: Published
// Description: Sets the transform that represents this node's
// "previous" position, one frame ago, for the purposes
// of detecting motion for accurate collision
// calculations.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_prev_transform(const TransformState *transform, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_prev_transform = transform;
if (pipeline_stage == 0) {
if (cdata->_transform != cdata->_prev_transform) {
set_dirty_prev_transform();
} else {
clear_dirty_prev_transform();
}
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::reset_prev_transform
// Access: Published
// Description: Resets the transform that represents this node's
// "previous" position to the same as the current
// transform. This is not the same thing as clearing it
// to identity.
////////////////////////////////////////////////////////////////////
void PandaNode::
reset_prev_transform(Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
clear_dirty_prev_transform();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_prev_transform = cdata->_transform;
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::reset_all_prev_transform
// Access: Published, Static
// Description: Visits all nodes in the world with the
// _dirty_prev_transform flag--that is, all nodes whose
// _prev_transform is different from the _transform in
// pipeline stage 0--and resets the _prev_transform to
// be the same as _transform.
////////////////////////////////////////////////////////////////////
void PandaNode::
reset_all_prev_transform(Thread *current_thread) {
nassertv(current_thread->get_pipeline_stage() == 0);
PStatTimer timer(_reset_prev_pcollector, current_thread);
LightMutexHolder holder(_dirty_prev_transforms._lock);
LinkedListNode *list_node = _dirty_prev_transforms._next;
while (list_node != &_dirty_prev_transforms) {
PandaNode *panda_node = (PandaNode *)list_node;
nassertv(panda_node->_dirty_prev_transform);
panda_node->_dirty_prev_transform = false;
CDStageWriter cdata(panda_node->_cycler, 0, current_thread);
cdata->_prev_transform = cdata->_transform;
list_node = panda_node->_next;
#ifndef NDEBUG
panda_node->_prev = NULL;
panda_node->_next = NULL;
#endif // NDEBUG
panda_node->mark_bam_modified();
}
_dirty_prev_transforms._prev = &_dirty_prev_transforms;
_dirty_prev_transforms._next = &_dirty_prev_transforms;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_tag
// Access: Published
// Description: Associates a user-defined value with a user-defined
// key which is stored on the node. This value has no
// meaning to Panda; but it is stored indefinitely on
// the node until it is requested again.
//
// Each unique key stores a different string value.
// There is no effective limit on the number of
// different keys that may be stored or on the length of
// any one key's value.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_tag(const string &key, const string &value, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_tag_data[key] = value;
cdata->set_fancy_bit(FB_tag, true);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::clear_tag
// Access: Published
// Description: Removes the value defined for this key on this
// particular node. After a call to clear_tag(),
// has_tag() will return false for the indicated key.
////////////////////////////////////////////////////////////////////
void PandaNode::
clear_tag(const string &key, Thread *current_thread) {
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_tag_data.erase(key);
cdata->set_fancy_bit(FB_tag, !cdata->_tag_data.empty());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_python_tag
// Access: Published
// Description: Associates an arbitrary Python object with a
// user-defined key which is stored on the node. This
// is similar to set_tag(), except it can store any
// Python object instead of just a string. However, the
// Python object is not recorded to a bam file.
//
// Each unique key stores a different string value.
// There is no effective limit on the number of
// different keys that may be stored or on the length of
// any one key's value.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_python_tag(const string &key, PyObject *value) {
Thread *current_thread = Thread::get_current_thread();
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
CDWriter cdata(_cycler);
Py_XINCREF(value);
pair<PythonTagData::iterator, bool> result;
result = cdata->_python_tag_data.insert(PythonTagData::value_type(key, value));
if (!result.second) {
// The insert was unsuccessful; that means the key was already
// present in the map. In this case, we should decrement the
// original value's reference count and replace it with the new
// object.
PythonTagData::iterator ti = result.first;
PyObject *old_value = (*ti).second;
Py_XDECREF(old_value);
(*ti).second = value;
}
// Even though the python tag isn't recorded in the bam stream?
mark_bam_modified();
}
#endif // HAVE_PYTHON
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_python_tag
// Access: Published
// Description: Retrieves the Python object that was previously
// set on this node for the particular key, if any. If
// no value has been previously set, returns None.
////////////////////////////////////////////////////////////////////
PyObject *PandaNode::
get_python_tag(const string &key) const {
CDReader cdata(_cycler);
PythonTagData::const_iterator ti;
ti = cdata->_python_tag_data.find(key);
if (ti != cdata->_python_tag_data.end()) {
PyObject *result = (*ti).second;
Py_XINCREF(result);
return result;
}
Py_INCREF(Py_None);
return Py_None;
}
#endif // HAVE_PYTHON
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::has_python_tag
// Access: Published
// Description: Returns true if a Python object has been defined on
// this node for the particular key (even if that object
// is None), or false if no object has been set.
////////////////////////////////////////////////////////////////////
bool PandaNode::
has_python_tag(const string &key) const {
CDReader cdata(_cycler);
PythonTagData::const_iterator ti;
ti = cdata->_python_tag_data.find(key);
return (ti != cdata->_python_tag_data.end());
}
#endif // HAVE_PYTHON
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::clear_python_tag
// Access: Published
// Description: Removes the Python object defined for this key on
// this particular node. After a call to
// clear_python_tag(), has_python_tag() will return
// false for the indicated key.
////////////////////////////////////////////////////////////////////
void PandaNode::
clear_python_tag(const string &key) {
Thread *current_thread = Thread::get_current_thread();
int pipeline_stage = current_thread->get_pipeline_stage();
nassertv(pipeline_stage == 0);
CDWriter cdata(_cycler, current_thread);
PythonTagData::iterator ti;
ti = cdata->_python_tag_data.find(key);
if (ti != cdata->_python_tag_data.end()) {
PyObject *value = (*ti).second;
Py_XDECREF(value);
cdata->_python_tag_data.erase(ti);
}
// Even though the python tag isn't recorded in the bam stream?
mark_bam_modified();
}
#endif // HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// 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;
}
// Apply this operation to the current stage as well as to all
// upstream stages.
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
CDStageReader cdatar(other->_cycler, pipeline_stage, current_thread);
TagData::const_iterator ti;
for (ti = cdatar->_tag_data.begin();
ti != cdatar->_tag_data.end();
++ti) {
cdataw->_tag_data[(*ti).first] = (*ti).second;
}
cdataw->set_fancy_bit(FB_tag, !cdataw->_tag_data.empty());
#ifdef HAVE_PYTHON
PythonTagData::const_iterator pti;
for (pti = cdatar->_python_tag_data.begin();
pti != cdatar->_python_tag_data.end();
++pti) {
const string &key = (*pti).first;
PyObject *value = (*pti).second;
Py_XINCREF(value);
pair<PythonTagData::iterator, bool> result;
result = cdataw->_python_tag_data.insert(PythonTagData::value_type(key, value));
if (!result.second) {
// The insert was unsuccessful; that means the key was already
// present in the map. In this case, we should decrement the
// original value's reference count and replace it with the new
// object.
PythonTagData::iterator wpti = result.first;
PyObject *old_value = (*wpti).second;
Py_XDECREF(old_value);
(*wpti).second = value;
}
}
#endif // HAVE_PYTHON
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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;
}
}
#ifdef HAVE_PYTHON
if (!cdata->_python_tag_data.empty()) {
if (!cdata->_tag_data.empty()) {
out << separator;
}
PythonTagData::const_iterator ti = cdata->_python_tag_data.begin();
out << (*ti).first;
++ti;
while (ti != cdata->_python_tag_data.end()) {
out << separator << (*ti).first;
++ti;
}
}
#endif // HAVE_PYTHON
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::compare_tags
// Access: Published
// Description: Returns a number less than 0, 0, or greater than 0,
// to indicate the similarity of tags between this node
// and the other one. If this returns 0, the tags are
// identical. If it returns other than 0, then the tags
// are different; and the nodes may be sorted into a
// consistent (but arbitrary) ordering based on this
// number.
////////////////////////////////////////////////////////////////////
int PandaNode::
compare_tags(const PandaNode *other) const {
CDReader cdata(_cycler);
CDReader cdata_other(other->_cycler);
TagData::const_iterator ati = cdata->_tag_data.begin();
TagData::const_iterator bti = cdata_other->_tag_data.begin();
while (ati != cdata->_tag_data.end() &&
bti != cdata_other->_tag_data.end()) {
int cmp = strcmp((*ati).first.c_str(), (*bti).first.c_str());
if (cmp != 0) {
return cmp;
}
cmp = strcmp((*ati).second.c_str(), (*bti).second.c_str());
if (cmp != 0) {
return cmp;
}
++ati;
++bti;
}
if (ati != cdata->_tag_data.end()) {
// list A is longer.
return 1;
}
if (bti != cdata_other->_tag_data.end()) {
// list B is longer.
return -1;
}
#ifdef HAVE_PYTHON
PythonTagData::const_iterator api = cdata->_python_tag_data.begin();
PythonTagData::const_iterator bpi = cdata_other->_python_tag_data.begin();
while (api != cdata->_python_tag_data.end() &&
bpi != cdata_other->_python_tag_data.end()) {
int cmp = strcmp((*api).first.c_str(), (*bpi).first.c_str());
if (cmp != 0) {
return cmp;
}
if (PyObject_Cmp((*api).second, (*bpi).second, &cmp) == -1) {
// Unable to compare objects; just compare pointers.
if ((*api).second != (*bpi).second) {
cmp = (*api).second < (*bpi).second ? -1 : 1;
} else {
cmp = 0;
}
}
if (cmp != 0) {
return cmp;
}
++api;
++bpi;
}
if (api != cdata->_python_tag_data.end()) {
// list A is longer.
return 1;
}
if (bpi != cdata_other->_python_tag_data.end()) {
// list B is longer.
return -1;
}
#endif // HAVE_PYTHON
return 0;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::copy_all_properties
// Access: Published
// Description: Copies the TransformState, RenderState,
// RenderEffects, tags, Python tags, and the show/hide
// state from the other node onto this one. Typically
// this is used to prepare a node to replace another
// node in the scene graph (also see replace_node()).
////////////////////////////////////////////////////////////////////
void PandaNode::
copy_all_properties(PandaNode *other) {
if (other == this) {
// Trivial.
return;
}
bool any_transform_changed = false;
bool any_state_changed = false;
bool any_draw_mask_changed = false;
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
CDStageReader cdatar(other->_cycler, pipeline_stage, current_thread);
if (cdataw->_transform != cdatar->_transform) {
any_transform_changed = true;
}
if (cdataw->_state != cdatar->_state) {
any_state_changed = true;
}
if (cdataw->_draw_control_mask != cdatar->_draw_control_mask ||
cdataw->_draw_show_mask != cdatar->_draw_show_mask) {
any_draw_mask_changed = true;
}
cdataw->_transform = cdatar->_transform;
cdataw->_prev_transform = cdatar->_prev_transform;
cdataw->_state = cdatar->_state;
cdataw->_effects = cdatar->_effects;
cdataw->_draw_control_mask = cdatar->_draw_control_mask;
cdataw->_draw_show_mask = cdatar->_draw_show_mask;
// The collide mask becomes the union of the two masks. This is
// important to preserve properties such as the default GeomNode
// bitmask.
cdataw->_into_collide_mask |= cdatar->_into_collide_mask;
TagData::const_iterator ti;
for (ti = cdatar->_tag_data.begin();
ti != cdatar->_tag_data.end();
++ti) {
cdataw->_tag_data[(*ti).first] = (*ti).second;
}
#ifdef HAVE_PYTHON
PythonTagData::const_iterator pti;
for (pti = cdatar->_python_tag_data.begin();
pti != cdatar->_python_tag_data.end();
++pti) {
const string &key = (*pti).first;
PyObject *value = (*pti).second;
Py_XINCREF(value);
pair<PythonTagData::iterator, bool> result;
result = cdataw->_python_tag_data.insert(PythonTagData::value_type(key, value));
if (!result.second) {
// The insert was unsuccessful; that means the key was already
// present in the map. In this case, we should decrement the
// original value's reference count and replace it with the new
// object.
PythonTagData::iterator wpti = result.first;
PyObject *old_value = (*wpti).second;
Py_XDECREF(old_value);
(*wpti).second = value;
}
}
#endif // HAVE_PYTHON
static const int change_bits = (FB_transform | FB_state | FB_effects |
FB_tag | FB_draw_mask);
cdataw->_fancy_bits =
(cdataw->_fancy_bits & ~change_bits) |
(cdatar->_fancy_bits & change_bits);
if (pipeline_stage == 0) {
if (cdataw->_transform != cdataw->_prev_transform) {
set_dirty_prev_transform();
}
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
if (any_transform_changed || any_state_changed || any_draw_mask_changed) {
mark_bounds_stale(current_thread);
if (any_transform_changed) {
transform_changed();
}
if (any_state_changed) {
state_changed();
}
if (any_draw_mask_changed) {
draw_mask_changed();
}
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::replace_node
// Access: Published
// Description: Inserts this node into the scene graph in place of
// the other one, and removes the other node. All scene
// graph attributes (TransformState, RenderState, etc.)
// are copied to this node.
//
// All children are moved to this node, and removed from
// the old node. The new node is left in the same place
// in the old node's parent's list of children.
//
// Even NodePaths that reference the old node are
// updated in-place to reference the new node instead.
//
// This method is intended to be used to replace a node
// of a given type in the scene graph with a node of a
// different type.
////////////////////////////////////////////////////////////////////
void PandaNode::
replace_node(PandaNode *other) {
// nassertv(Thread::get_current_pipeline_stage() == 0);
if (other == this) {
// Trivial.
return;
}
// Make sure the other node is not destructed during the
// execution of this method.
PT(PandaNode) keep_other = other;
// Get all the important scene graph properties.
copy_all_properties(other);
// Fix up the NodePaths.
{
LightReMutexHolder holder1(other->_paths_lock);
LightReMutexHolder holder2(_paths_lock);
Paths::iterator pi;
for (pi = other->_paths.begin(); pi != other->_paths.end(); ++pi) {
(*pi)->_node = this;
_paths.insert(*pi);
}
other->_paths.clear();
}
// Get the children.
steal_children(other);
// Switch the parents.
Thread *current_thread = Thread::get_current_thread();
Parents other_parents = other->get_parents();
for (int i = 0; i < other_parents.get_num_parents(); ++i) {
PandaNode *parent = other_parents.get_parent(i);
if (find_parent(parent) != -1) {
// This node was already a child of this parent; don't change
// it.
parent->remove_child(other);
} else {
// This node was not yet a child of this parent; now it is.
parent->replace_child(other, this, current_thread);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_unexpected_change
// Access: Published
// Description: Sets one or more of the PandaNode::UnexpectedChange
// bits on, indicating that the corresponding property
// should not change again on this node. Once one of
// these bits has been set, if the property changes, an
// assertion failure will be raised, which is designed
// to assist the developer in identifying the
// troublesome code that modified the property
// unexpectedly.
//
// The input parameter is the union of bits that are to
// be set. To clear these bits later, use
// clear_unexpected_change().
//
// Since this is a developer debugging tool only, this
// function does nothing in a production (NDEBUG) build.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_unexpected_change(unsigned int flags) {
#ifndef NDEBUG
_unexpected_change_flags |= flags;
#endif // !NDEBUG
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_unexpected_change
// Access: Published
// Description: Returns nonzero if any of the bits in the input
// parameter are set on this node, or zero if none of
// them are set. More specifically, this returns the
// particular set of bits (masked by the input
// parameter) that have been set on this node. See
// set_unexpected_change().
//
// Since this is a developer debugging tool only, this
// function always returns zero in a production (NDEBUG)
// build.
////////////////////////////////////////////////////////////////////
unsigned int PandaNode::
get_unexpected_change(unsigned int flags) const {
#ifndef NDEBUG
return _unexpected_change_flags & flags;
#else
return 0;
#endif // !NDEBUG
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::clear_unexpected_change
// Access: Published
// Description: Sets one or more of the PandaNode::UnexpectedChange
// bits off, indicating that the corresponding property
// may once again change on this node. See
// set_unexpected_change().
//
// The input parameter is the union of bits that are to
// be cleared.
//
// Since this is a developer debugging tool only, this
// function does nothing in a production (NDEBUG) build.
////////////////////////////////////////////////////////////////////
void PandaNode::
clear_unexpected_change(unsigned int flags) {
#ifndef NDEBUG
_unexpected_change_flags &= ~flags;
#endif // !NDEBUG
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::adjust_draw_mask
// Access: Published
// Description: Adjusts the hide/show bits of this particular node.
//
// These three parameters can be used to adjust the
// _draw_control_mask and _draw_show_mask independently,
// which work together to provide per-camera visibility
// for the node and its descendents.
//
// _draw_control_mask indicates the bits in
// _draw_show_mask that are significant. Each different
// bit corresponds to a different camera (and these bits
// are assigned via Camera::set_camera_mask()).
//
// Where _draw_control_mask has a 1 bit, a 1 bit in
// _draw_show_mask indicates the node is visible to that
// camera, and a 0 bit indicates the node is hidden to
// that camera. Where _draw_control_mask is 0, the node
// is hidden only if a parent node is hidden.
//
// The meaning of the three parameters is as follows:
//
// * Wherever show_mask is 1, _draw_show_mask and
// _draw_control_mask will be set 1. Thus, show_mask
// indicates the set of cameras to which the node should
// be shown.
//
// * Wherever hide_mask is 1, _draw_show_mask will be
// set 0 and _draw_control_mask will be set 1. Thus,
// hide_mask indicates the set of cameras from which the
// node should be hidden.
//
// * Wherever clear_mask is 1, _draw_control_mask will
// be set 0. Thus, clear_mask indicates the set of
// cameras from which the hidden state should be
// inherited from a parent.
////////////////////////////////////////////////////////////////////
void PandaNode::
adjust_draw_mask(DrawMask show_mask, DrawMask hide_mask, DrawMask clear_mask) {
bool any_changed = false;
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
DrawMask draw_control_mask = (cdata->_draw_control_mask | show_mask | hide_mask) & ~clear_mask;
DrawMask draw_show_mask = (cdata->_draw_show_mask | show_mask) & ~hide_mask;
// The uncontrolled bits are implicitly on.
draw_show_mask |= ~draw_control_mask;
if (cdata->_draw_control_mask != draw_control_mask ||
cdata->_draw_show_mask != draw_show_mask) {
cdata->_draw_control_mask = draw_control_mask;
cdata->_draw_show_mask = draw_show_mask;
any_changed = true;
}
cdata->set_fancy_bit(FB_draw_mask, !draw_control_mask.is_zero());
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
if (any_changed) {
mark_bounds_stale(current_thread);
draw_mask_changed();
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_net_draw_control_mask
// Access: Published
// Description: Returns the set of bits in get_net_draw_show_mask()
// that have been explicitly set via adjust_draw_mask(),
// rather than implicitly inherited.
//
// A 1 bit in any position of this mask indicates that
// (a) this node has renderable children, and (b) some
// child of this node has made an explicit hide() or
// show_through() call for the corresponding bit.
////////////////////////////////////////////////////////////////////
DrawMask PandaNode::
get_net_draw_control_mask() const {
Thread *current_thread = Thread::get_current_thread();
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
return cdataw->_net_draw_control_mask;
}
return cdata->_net_draw_control_mask;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_net_draw_show_mask
// Access: Published
// Description: Returns the union of all draw_show_mask values--of
// renderable nodes only--at this level and below. If
// any bit in this mask is 0, there is no reason to
// traverse below this node for a camera with the
// corresponding camera_mask.
//
// The bits in this mask that do not correspond to a 1
// bit in the net_draw_control_mask are meaningless (and
// will be set to 1). For bits that *do* correspond to
// a 1 bit in the net_draw_control_mask, a 1 bit
// indicates that at least one child should be visible,
// while a 0 bit indicates that all children are hidden.
////////////////////////////////////////////////////////////////////
DrawMask PandaNode::
get_net_draw_show_mask() const {
Thread *current_thread = Thread::get_current_thread();
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
return cdataw->_net_draw_show_mask;
}
return cdata->_net_draw_show_mask;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_into_collide_mask
// Access: Published
// Description: Sets the "into" CollideMask.
//
// This specifies the set of bits that must be shared
// with a CollisionNode's "from" CollideMask in order
// for the CollisionNode to detect a collision with this
// particular node.
//
// The actual CollideMask that will be set is masked by
// the return value from get_legal_collide_mask().
// Thus, the into_collide_mask cannot be set to anything
// other than nonzero except for those types of nodes
// that can be collided into, such as CollisionNodes and
// GeomNodes.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_into_collide_mask(CollideMask mask) {
mask &= get_legal_collide_mask();
bool any_changed = false;
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_into_collide_mask != mask) {
cdata->_into_collide_mask = mask;
any_changed = true;
}
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
if (any_changed) {
mark_bounds_stale(current_thread);
mark_bam_modified();
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_legal_collide_mask
// Access: Published, Virtual
// Description: Returns the subset of CollideMask bits that may be
// set for this particular type of PandaNode. For most
// nodes, this is 0; it doesn't make sense to set a
// CollideMask for most kinds of nodes.
//
// For nodes that can be collided with, such as GeomNode
// and CollisionNode, this returns all bits on.
////////////////////////////////////////////////////////////////////
CollideMask PandaNode::
get_legal_collide_mask() const {
return CollideMask::all_off();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_net_collide_mask
// Access: Published
// Description: Returns the union of all into_collide_mask() values
// set at CollisionNodes at this level and below.
////////////////////////////////////////////////////////////////////
CollideMask PandaNode::
get_net_collide_mask(Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
return cdataw->_net_collide_mask;
}
return cdata->_net_collide_mask;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_off_clip_planes
// Access: Published
// Description: Returns a ClipPlaneAttrib which represents the union
// of all of the clip planes that have been turned *off*
// at this level and below.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) PandaNode::
get_off_clip_planes(Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
return cdataw->_off_clip_planes;
}
return cdata->_off_clip_planes;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::prepare_scene
// Access: Published
// Description: Walks through the scene graph beginning at this node,
// and does whatever initialization is required to
// render the scene properly with the indicated GSG. It
// is not strictly necessary to call this, since the GSG
// will initialize itself when the scene is rendered,
// but this may take some of the overhead away from that
// process.
//
// In particular, this will ensure that textures within
// the scene are loaded in texture memory, and display
// lists are built up from static geometry.
////////////////////////////////////////////////////////////////////
void PandaNode::
prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *net_state) {
Thread *current_thread = Thread::get_current_thread();
PreparedGraphicsObjects *prepared_objects = gsg->get_prepared_objects();
r_prepare_scene(net_state, prepared_objects, current_thread);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::is_scene_root
// Access: Published
// Description: Returns true if this particular node is known to be
// the render root of some active DisplayRegion
// associated with the global GraphicsEngine, false
// otherwise.
////////////////////////////////////////////////////////////////////
bool PandaNode::
is_scene_root() const {
// This function pointer has to be filled in when the global
// GraphicsEngine is created, because we can't link with the
// GraphicsEngine functions directly.
if (_scene_root_func != (SceneRootFunc *)NULL) {
return (*_scene_root_func)(this);
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::is_under_scene_root
// Access: Published
// Description: Returns true if this particular node is in a live
// scene graph: that is, it is a child or descendent of
// a node that is itself a scene root. If this is true,
// this node may potentially be traversed by the render
// traverser. Stashed nodes don't count for this
// purpose, but hidden nodes do.
////////////////////////////////////////////////////////////////////
bool PandaNode::
is_under_scene_root() const {
if (is_scene_root()) {
return true;
}
Parents parents = get_parents();
for (int i = 0; i < parents.get_num_parents(); ++i) {
PandaNode *parent = parents.get_parent(i);
if (parent->find_stashed((PandaNode *)this) == -1) {
if (parent->is_under_scene_root()) {
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// 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;
if (has_tags()) {
out << " [";
list_tags(out, " ");
out << "]";
}
CPT(TransformState) transform = get_transform();
if (!transform->is_identity()) {
out << " " << *transform;
}
CPT(RenderState) state = get_state();
if (!state->is_empty()) {
out << " " << *state;
}
CPT(RenderEffects) effects = get_effects();
if (!effects->is_empty()) {
out << " " << *effects;
}
DrawMask draw_control_mask = get_draw_control_mask();
if (!draw_control_mask.is_zero()) {
DrawMask draw_show_mask = get_draw_show_mask();
if (!(draw_control_mask & _overall_bit).is_zero()) {
if (!(draw_show_mask & _overall_bit).is_zero()) {
out << " (show_through)";
} else {
out << " (hidden)";
}
}
if (!(draw_control_mask & ~_overall_bit).is_zero()) {
draw_control_mask &= ~_overall_bit;
if (!(draw_show_mask & draw_control_mask).is_zero()) {
out << " (per-camera show_through)";
}
if (!(~draw_show_mask & draw_control_mask).is_zero()) {
out << " (per-camera hidden)";
}
}
}
out << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_bounds_type
// Access: Published
// Description: Specifies the desired type of bounding volume that
// will be created for this node. This is normally
// BoundingVolume::BT_default, which means to set the
// type according to the config variable "bounds-type".
//
// If this is BT_sphere or BT_box, a BoundingSphere or
// BoundingBox is explicitly created. If it is BT_best,
// the appropriate type to best enclose the node's
// children is created.
//
// This affects the bounding volume returned by
// get_bounds(), which is not exactly the same bounding
// volume modified by set_bounds(), because a new
// bounding volume has to be created that includes this
// node and all of its children.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_bounds_type(BoundingVolume::BoundsType bounds_type) {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->_bounds_type = bounds_type;
mark_bounds_stale(pipeline_stage, current_thread);
// GeomNodes, CollisionNodes, and PGItems all have an internal
// bounds that may need to be updated when the bounds_type
// changes.
mark_internal_bounds_stale(pipeline_stage, current_thread);
mark_bam_modified();
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_bounds_type
// Access: Published
// Description: Returns the bounding volume type set with
// set_bounds_type().
////////////////////////////////////////////////////////////////////
BoundingVolume::BoundsType PandaNode::
get_bounds_type() const {
CDReader cdata(_cycler);
return cdata->_bounds_type;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_bounds
// Access: Published
// Description: Resets the bounding volume so that it is the
// indicated volume. When it is explicitly set, the
// bounding volume will no longer be automatically
// computed according to the contents of the node
// itself, for nodes like GeomNodes and TextNodes that
// contain substance (but the bounding volume will still
// be automatically expanded to include its children).
//
// Call clear_bounds() if you would like to return the
// bounding volume to its default behavior later.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_bounds(const BoundingVolume *volume) {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
if (volume == NULL) {
cdata->_user_bounds = NULL;
} else {
cdata->_user_bounds = volume->make_copy();
}
mark_bounds_stale(pipeline_stage, current_thread);
mark_bam_modified();
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_bound
// Access: Published
// Description: Deprecated. Use set_bounds() instead.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_bound(const BoundingVolume *volume) {
pgraph_cat.warning()
<< "Deprecated PandaNode::set_bound() called. Use set_bounds() instead.\n";
set_bounds(volume);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_bounds
// Access: Published
// Description: Returns the external bounding volume of this node: a
// bounding volume that contains the user bounding
// volume, the internal bounding volume, and all of the
// children's bounding volumes.
////////////////////////////////////////////////////////////////////
CPT(BoundingVolume) PandaNode::
get_bounds(Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
CPT(BoundingVolume) result;
{
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
result = cdataw->_external_bounds;
}
return result;
}
return cdata->_external_bounds;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_bounds
// Access: Published
// Description: This flavor of get_bounds() return the external
// bounding volume, and also fills in seq with the
// bounding volume's current sequence number. When this
// sequence number changes, it indicates that the
// bounding volume might have changed, e.g. because some
// nested child's bounding volume has changed.
//
// Although this might occasionally increment without
// changing the bounding volume, the bounding volume
// will never change without incrementing this counter,
// so as long as this counter remains unchanged you can
// be confident the bounding volume is also unchanged.
////////////////////////////////////////////////////////////////////
CPT(BoundingVolume) PandaNode::
get_bounds(UpdateSeq &seq, Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
CPT(BoundingVolume) result;
{
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
result = cdataw->_external_bounds;
seq = cdataw->_last_update;
}
return result;
}
seq = cdata->_last_update;
return cdata->_external_bounds;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_nested_vertices
// Access: Published
// Description: Returns the total number of vertices that will be
// rendered by this node and all of its descendents.
//
// This is not necessarily an accurate count of vertices
// that will actually be rendered, since this will
// include all vertices of all LOD's, and it will also
// include hidden nodes. It may also omit or only
// approximate certain kinds of dynamic geometry.
// However, it will not include stashed nodes.
////////////////////////////////////////////////////////////////////
int PandaNode::
get_nested_vertices(Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
int result;
{
PStatTimer timer(_update_bounds_pcollector);
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
result = cdataw->_nested_vertices;
}
return result;
}
return cdata->_nested_vertices;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::mark_bounds_stale
// Access: Published
// Description: Indicates that the bounding volume, or something that
// influences the bounding volume (or any of the other
// things stored in CData, like net_collide_mask),
// may have changed for this node, and that it must be
// recomputed.
//
// With no parameters, this means to iterate through all
// stages including and upstream of the current pipeline
// stage.
//
// This method is intended for internal use; usually it
// is not necessary for a user to call this directly.
// It will be called automatically by derived classes
// when appropriate.
////////////////////////////////////////////////////////////////////
void PandaNode::
mark_bounds_stale(Thread *current_thread) const {
OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler, current_thread) {
mark_bounds_stale(pipeline_stage, current_thread);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::mark_internal_bounds_stale
// Access: Published
// Description: Should be called by a derived class to mark the
// internal bounding volume stale, so that
// compute_internal_bounds() will be called when the
// bounding volume is next requested.
//
// With no parameters, this means to iterate through all
// stages including and upstream of the current pipeline
// stage.
//
// It is normally not necessary to call this method
// directly; each node should be responsible for calling
// it when its internals have changed.
////////////////////////////////////////////////////////////////////
void PandaNode::
mark_internal_bounds_stale(Thread *current_thread) {
OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler, current_thread) {
mark_internal_bounds_stale(pipeline_stage, current_thread);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler);
}
////////////////////////////////////////////////////////////////////
// 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::is_lod_node
// Access: Published, Virtual
// Description: A simple downcast check. Returns true if this kind
// of node happens to inherit from LODNode, false
// otherwise.
//
// This is provided as a a faster alternative to calling
// is_of_type(LODNode::get_class_type()).
////////////////////////////////////////////////////////////////////
bool PandaNode::
is_lod_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::is_ambient_light
// Access: Published, Virtual
// Description: Returns true if this is an AmbientLight, false if it
// is not a light, or it is some other kind of light.
////////////////////////////////////////////////////////////////////
bool PandaNode::
is_ambient_light() const {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::decode_from_bam_stream
// Access: Published, Static
// Description: Reads the string created by a previous call to
// encode_to_bam_stream(), and extracts and returns the
// single object on that string. Returns NULL on error.
//
// This method is intended to replace
// decode_raw_from_bam_stream() when you know the stream
// in question returns an object of type PandaNode,
// allowing for easier reference count management. Note
// that the caller is still responsible for maintaining
// the reference count on the return value.
////////////////////////////////////////////////////////////////////
PT(PandaNode) PandaNode::
decode_from_bam_stream(const string &data) {
TypedWritable *object;
ReferenceCount *ref_ptr;
if (!TypedWritable::decode_raw_from_bam_stream(object, ref_ptr, data)) {
return NULL;
}
return DCAST(PandaNode, object);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_internal_bounds
// Access: Protected
// Description: Returns the node's internal bounding volume. This is
// the bounding volume around the node alone, without
// including children.
////////////////////////////////////////////////////////////////////
CPT(BoundingVolume) PandaNode::
get_internal_bounds(int pipeline_stage, Thread *current_thread) const {
while (true) {
UpdateSeq mark;
{
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_user_bounds != (BoundingVolume *)NULL) {
return cdata->_user_bounds;
}
if (cdata->_internal_bounds_mark == cdata->_internal_bounds_computed) {
return cdata->_internal_bounds;
}
mark = cdata->_internal_bounds_mark;
}
// First, call compute_internal_bounds without acquiring the lock.
// This avoids a deadlock condition.
CPT(BoundingVolume) internal_bounds;
int internal_vertices;
compute_internal_bounds(internal_bounds, internal_vertices,
pipeline_stage, current_thread);
nassertr(!internal_bounds.is_null(), NULL);
// Now, acquire the lock, and apply the above-computed bounds.
CDStageWriter cdataw(((PandaNode *)this)->_cycler, pipeline_stage);
if (cdataw->_internal_bounds_mark == mark) {
cdataw->_internal_bounds_computed = mark;
cdataw->_internal_bounds = internal_bounds;
cdataw->_internal_vertices = internal_vertices;
((PandaNode *)this)->mark_bam_modified();
return cdataw->_internal_bounds;
}
// Dang, someone in another thread incremented
// _internal_bounds_mark while we weren't holding the lock. That
// means we need to go back and do it again.
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_internal_vertices
// Access: Protected
// Description: Returns the total number of vertices that will be
// rendered by this particular node alone, not
// accounting for its children.
//
// This may not include all vertices for certain dynamic
// effects.
////////////////////////////////////////////////////////////////////
int PandaNode::
get_internal_vertices(int pipeline_stage, Thread *current_thread) const {
while (true) {
UpdateSeq mark;
{
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_internal_bounds_mark == cdata->_internal_bounds_computed) {
return cdata->_internal_vertices;
}
mark = cdata->_internal_bounds_mark;
}
// First, call compute_internal_bounds without acquiring the lock.
// This avoids a deadlock condition.
CPT(BoundingVolume) internal_bounds;
int internal_vertices;
compute_internal_bounds(internal_bounds, internal_vertices,
pipeline_stage, current_thread);
nassertr(!internal_bounds.is_null(), 0);
// Now, acquire the lock, and apply the above-computed bounds.
CDStageWriter cdataw(((PandaNode *)this)->_cycler, pipeline_stage);
if (cdataw->_internal_bounds_mark == mark) {
cdataw->_internal_bounds_computed = mark;
cdataw->_internal_bounds = internal_bounds;
cdataw->_internal_vertices = internal_vertices;
((PandaNode *)this)->mark_bam_modified();
return cdataw->_internal_vertices;
}
// Dang, someone in another thread incremented
// _internal_bounds_mark while we weren't holding the lock. That
// means we need to go back and do it again.
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_internal_bounds
// Access: Protected
// Description: This is provided as an alternate way for a node to
// set its own internal bounds, rather than overloading
// compute_internal_bounds(). If this method is called,
// the internal bounding volume will immediately be set
// to the indicated pointer.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_internal_bounds(const BoundingVolume *volume) {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
cdataw->_internal_bounds = volume;
cdataw->_internal_bounds_computed = cdataw->_internal_bounds_mark;
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bounds_stale(current_thread);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::force_bounds_stale
// Access: Protected
// Description: Similar to mark_bounds_stale(), except that the
// parents of this node marked stale even if this node
// was already considered stale.
//
// With no parameters, this means to iterate through all
// stages including and upstream of the current pipeline
// stage.
////////////////////////////////////////////////////////////////////
void PandaNode::
force_bounds_stale(Thread *current_thread) {
OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler, current_thread) {
force_bounds_stale(pipeline_stage, current_thread);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::force_bounds_stale
// Access: Protected
// Description: Similar to mark_bounds_stale(), except that the
// parents of this node marked stale even if this node
// was already considered stale.
////////////////////////////////////////////////////////////////////
void PandaNode::
force_bounds_stale(int pipeline_stage, Thread *current_thread) {
{
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
++cdata->_next_update;
mark_bam_modified();
// It is important that we allow this lock to be dropped before we
// continue up the graph; otherwise, we risk deadlock from another
// thread walking down the graph.
}
// It is similarly important that we use get_parents() here to copy
// the parents list, instead of keeping the lock open while we walk
// through the parents list directly on the node.
Parents parents;
{
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
parents = Parents(cdata);
}
int num_parents = parents.get_num_parents();
for (int i = 0; i < num_parents; ++i) {
PandaNode *parent = parents.get_parent(i);
parent->mark_bounds_stale(pipeline_stage, current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::r_mark_geom_bounds_stale
// Access: Protected, Virtual
// Description: Recursively calls Geom::mark_bounds_stale() on every
// Geom at this node and below.
////////////////////////////////////////////////////////////////////
void PandaNode::
r_mark_geom_bounds_stale(Thread *current_thread) {
Children children = get_children(current_thread);
int i;
for (i = 0; i < children.get_num_children(); i++) {
PandaNode *child = children.get_child(i);
child->r_mark_geom_bounds_stale(current_thread);
}
Stashed stashed = get_stashed(current_thread);
for (i = 0; i < stashed.get_num_stashed(); i++) {
PandaNode *child = stashed.get_stashed(i);
child->r_mark_geom_bounds_stale(current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::compute_internal_bounds
// Access: Protected, Virtual
// Description: Returns a newly-allocated BoundingVolume that
// represents the internal contents of the node. Should
// be overridden by PandaNode classes that contain
// something internally.
////////////////////////////////////////////////////////////////////
void PandaNode::
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
int &internal_vertices,
int pipeline_stage,
Thread *current_thread) const {
internal_bounds = new BoundingSphere;
internal_vertices = 0;
}
////////////////////////////////////////////////////////////////////
// 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() {
nassertv((_unexpected_change_flags & UC_parents) == 0);
}
////////////////////////////////////////////////////////////////////
// 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() {
nassertv((_unexpected_change_flags & UC_children) == 0);
}
////////////////////////////////////////////////////////////////////
// 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() {
nassertv((_unexpected_change_flags & UC_transform) == 0);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::state_changed
// Access: Protected, Virtual
// Description: Called after the node's RenderState has been changed
// for any reason, this just provides a hook so derived
// classes can do something special in this case.
////////////////////////////////////////////////////////////////////
void PandaNode::
state_changed() {
nassertv((_unexpected_change_flags & UC_state) == 0);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::draw_mask_changed
// Access: Protected, Virtual
// Description: Called after the node's DrawMask has been changed
// for any reason, this just provides a hook so derived
// classes can do something special in this case.
////////////////////////////////////////////////////////////////////
void PandaNode::
draw_mask_changed() {
nassertv((_unexpected_change_flags & UC_draw_mask) == 0);
}
////////////////////////////////////////////////////////////////////
// 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, Thread *current_thread) 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";
if (no_unsupported_copy) {
nassertr(false, NULL);
}
}
copy->r_copy_children(this, inst_map, current_thread);
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,
Thread *current_thread) {
CDReader from_cdata(from->_cycler, current_thread);
CPT(Down) from_down = from_cdata->get_down();
Down::const_iterator di;
for (di = from_down->begin(); di != from_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, current_thread);
inst_map[source_child] = dest_child;
}
quick_add_new_child(dest_child, sort, current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::r_prepare_scene
// Access: Public, Virtual
// Description: The recursive implementation of prepare_scene().
// Don't call this directly; call
// PandaNode::prepare_scene() or
// NodePath::prepare_scene() instead.
////////////////////////////////////////////////////////////////////
void PandaNode::
r_prepare_scene(const RenderState *state,
PreparedGraphicsObjects *prepared_objects,
Thread *current_thread) {
Children children = get_children(current_thread);
// We must call get_num_children() each time through the loop, in
// case we're running SIMPLE_THREADS and we get interrupted.
int i;
for (i = 0; i < children.get_num_children(); i++) {
PandaNode *child = children.get_child(i);
CPT(RenderState) child_state = state->compose(child->get_state());
child->r_prepare_scene(child_state, prepared_objects, current_thread);
}
Stashed stashed = get_stashed(current_thread);
for (i = 0; i < stashed.get_num_stashed(); i++) {
PandaNode *child = stashed.get_stashed(i);
CPT(RenderState) child_state = state->compose(child->get_state());
child->r_prepare_scene(child_state, prepared_objects, current_thread);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_cull_callback
// Access: Protected
// Description: Intended to be called in the constructor by any
// subclass that defines cull_callback(), this sets up
// the flags to indicate that the cullback needs to be
// called.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_cull_callback() {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->set_fancy_bit(FB_cull_callback, true);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::disable_cull_callback
// Access: Protected
// Description: disables the call back
////////////////////////////////////////////////////////////////////
void PandaNode::
disable_cull_callback() {
Thread *current_thread = Thread::get_current_thread();
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
cdata->set_fancy_bit(FB_cull_callback, false);
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::stage_remove_child
// Access: Private
// Description: The private implementation of remove_child(), for a
// particular pipeline stage.
////////////////////////////////////////////////////////////////////
bool PandaNode::
stage_remove_child(PandaNode *child_node, int pipeline_stage,
Thread *current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
// First, look for the parent in the child's up list, to ensure the
// child is known.
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage,
current_thread);
int parent_index = child_node->do_find_parent(this, cdata_child);
if (parent_index < 0) {
// Nope, no relation.
return false;
}
PT(Down) down = cdata->modify_down();
int child_index = do_find_child(child_node, down);
if (child_index >= 0) {
// The child exists; remove it.
down->erase(down->begin() + child_index);
int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
nassertr(num_erased == 1, false);
return true;
}
PT(Down) stashed = cdata->modify_stashed();
int stashed_index = do_find_child(child_node, stashed);
if (stashed_index >= 0) {
// The child has been stashed; remove it.
stashed->erase(stashed->begin() + stashed_index);
int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
nassertr(num_erased == 1, false);
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::stage_replace_child
// Access: Private
// Description: The private implementation of replace_child(), for a
// particular pipeline stage.
////////////////////////////////////////////////////////////////////
bool PandaNode::
stage_replace_child(PandaNode *orig_child, PandaNode *new_child,
int pipeline_stage, Thread *current_thread) {
{
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_orig_child(orig_child->_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_new_child(new_child->_cycler, pipeline_stage, current_thread);
// First, look for the parent in the child's up list, to ensure the
// child is known.
int parent_index = orig_child->do_find_parent(this, cdata_orig_child);
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;
// If we already have new_child as a child, remove it first.
if (stage_remove_child(new_child, pipeline_stage, current_thread)) {
sever_connection(this, new_child, pipeline_stage, current_thread);
}
PT(Down) down = cdata->modify_down();
int child_index = do_find_child(orig_child, down);
if (child_index >= 0) {
// The child exists; replace it.
DownConnection &dc = (*down)[child_index];
nassertr(dc.get_child() == orig_child, false);
dc.set_child(new_child);
} else {
PT(Down) stashed = cdata->modify_stashed();
int stashed_index = do_find_child(orig_child, stashed);
if (stashed_index >= 0) {
// The child has been stashed; remove it.
DownConnection &dc = (*stashed)[stashed_index];
nassertr(dc.get_child() == orig_child, false);
dc.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.
cdata_new_child->modify_up()->insert(UpConnection(this));
int num_erased = cdata_orig_child->modify_up()->erase(UpConnection(this));
nassertr(num_erased == 1, false);
}
sever_connection(this, orig_child, pipeline_stage, current_thread);
new_connection(this, new_child, pipeline_stage, current_thread);
force_bounds_stale(pipeline_stage, current_thread);
orig_child->parents_changed();
new_child->parents_changed();
mark_bam_modified();
orig_child->mark_bam_modified();
new_child->mark_bam_modified();
return true;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::quick_add_new_child
// Access: Private
// Description: Similar to add_child(), but performs fewer checks.
// The purpose of this method is to add a child node
// that was newly constructed, to a parent node that was
// newly constructed, so we know we have to make fewer
// sanity checks. This is a private method; do not call
// it directly.
////////////////////////////////////////////////////////////////////
void PandaNode::
quick_add_new_child(PandaNode *child_node, int sort, Thread *current_thread) {
// Apply this operation to the current stage as well as to all
// upstream stages.
OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
cdata->modify_down()->insert(DownConnection(child_node, sort));
cdata_child->modify_up()->insert(UpConnection(this));
}
CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::report_cycle
// Access: Private
// Description: Raises an assertion when a graph cycle attempt is
// detected (and aborted).
////////////////////////////////////////////////////////////////////
void PandaNode::
report_cycle(PandaNode *child_node) {
ostringstream strm;
strm << "Detected attempt to create a cycle in the scene graph: "
<< NodePath::any_path(this) << " : " << *child_node;
nassert_raise(strm.str());
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::find_node_above
// Access: Private
// Description: Returns true if the indicated node is this node, or
// any ancestor of this node; or false if it is not in
// this node's ancestry.
////////////////////////////////////////////////////////////////////
bool PandaNode::
find_node_above(PandaNode *node) {
if (node == this) {
return true;
}
Parents parents = get_parents();
for (int i = 0; i < parents.get_num_parents(); ++i) {
PandaNode *parent = parents.get_parent(i);
if (parent->find_node_above(node)) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////
// 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.
//
// This operation is automatically propagated back up to
// pipeline 0, from the specified pipeline stage.
////////////////////////////////////////////////////////////////////
PT(NodePathComponent) PandaNode::
attach(NodePathComponent *parent, PandaNode *child_node, int sort,
int pipeline_stage, Thread *current_thread) {
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,
pipeline_stage, current_thread);
LightReMutexHolder holder(child_node->_paths_lock);
child_node->_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, pipeline_stage, current_thread);
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, pipeline_stage, current_thread);
}
reparent(parent, child, sort, false, pipeline_stage, current_thread);
return child;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::detach
// Access: Private, Static
// Description: Breaks a parent-child relationship.
//
// This operation is automatically propagated back up to
// pipeline 0, from the specified pipeline stage.
////////////////////////////////////////////////////////////////////
void PandaNode::
detach(NodePathComponent *child, int pipeline_stage, Thread *current_thread) {
nassertv(child != (NodePathComponent *)NULL);
for (int pipeline_stage_i = pipeline_stage;
pipeline_stage_i >= 0;
--pipeline_stage_i) {
detach_one_stage(child, pipeline_stage_i, current_thread);
}
child->get_node()->parents_changed();
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::detach_one_stage
// Access: Private, Static
// Description: Breaks a parent-child relationship.
//
// This operation is not automatically propagated
// upstream. It is applied to the indicated pipeline
// stage only.
////////////////////////////////////////////////////////////////////
void PandaNode::
detach_one_stage(NodePathComponent *child, int pipeline_stage,
Thread *current_thread) {
nassertv(child != (NodePathComponent *)NULL);
if (child->is_top_node(pipeline_stage, current_thread)) {
return;
}
PT(PandaNode) child_node = child->get_node();
PT(PandaNode) parent_node = child->get_next(pipeline_stage, current_thread)->get_node();
CDStageWriter cdata_parent(parent_node->_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
int parent_index = child_node->do_find_parent(parent_node, cdata_child);
if (parent_index >= 0) {
// 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->modify_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;
PT(Down) down = cdata_parent->modify_down();
for (di = down->begin(); di != down->end(); ++di) {
if ((*di).get_child() == child_node) {
down->erase(di);
found = true;
break;
}
}
if (!found) {
PT(Down) stashed = cdata_parent->modify_stashed();
for (di = stashed->begin(); di != stashed->end(); ++di) {
if ((*di).get_child() == child_node) {
stashed->erase(di);
found = true;
break;
}
}
}
nassertv(found);
}
// Finally, break the NodePathComponent connection.
sever_connection(parent_node, child_node, pipeline_stage, current_thread);
parent_node->force_bounds_stale(pipeline_stage, current_thread);
parent_node->children_changed();
parent_node->mark_bam_modified();
}
////////////////////////////////////////////////////////////////////
// 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).
//
// This operation is automatically propagated back up to
// pipeline 0, from the specified pipeline stage.
////////////////////////////////////////////////////////////////////
bool PandaNode::
reparent(NodePathComponent *new_parent, NodePathComponent *child, int sort,
bool as_stashed, int pipeline_stage, Thread *current_thread) {
bool any_ok = false;
if (new_parent != (NodePathComponent *)NULL &&
!new_parent->get_node()->verify_child_no_cycles(child->get_node())) {
// Whoops, adding this child node would introduce a cycle in the
// scene graph.
return false;
}
for (int pipeline_stage_i = pipeline_stage;
pipeline_stage_i >= 0;
--pipeline_stage_i) {
if (reparent_one_stage(new_parent, child, sort, as_stashed,
pipeline_stage_i, current_thread)) {
any_ok = true;
}
}
if (new_parent != (NodePathComponent *)NULL) {
new_parent->get_node()->children_changed();
new_parent->get_node()->mark_bam_modified();
}
child->get_node()->parents_changed();
child->get_node()->mark_bam_modified();
return any_ok;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::reparent_one_stage
// 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).
//
// This operation is not automatically propagated
// upstream. It is applied to the indicated pipeline
// stage only.
////////////////////////////////////////////////////////////////////
bool PandaNode::
reparent_one_stage(NodePathComponent *new_parent, NodePathComponent *child,
int sort, bool as_stashed, int pipeline_stage,
Thread *current_thread) {
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(pipeline_stage, current_thread)) {
detach(child, pipeline_stage, current_thread);
}
if (new_parent != (NodePathComponent *)NULL) {
PandaNode *child_node = child->get_node();
PandaNode *parent_node = new_parent->get_node();
{
CDStageReader cdata_child(child_node->_cycler, pipeline_stage, current_thread);
int parent_index = child_node->do_find_parent(parent_node, cdata_child);
if (parent_index >= 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, pipeline_stage, current_thread);
// Now reattach the child node at the indicated sort position.
{
CDStageWriter cdata_parent(parent_node->_cycler, pipeline_stage, current_thread);
CDStageWriter cdata_child(child_node->_cycler, pipeline_stage, current_thread);
if (as_stashed) {
cdata_parent->modify_stashed()->insert(DownConnection(child_node, sort));
} else {
cdata_parent->modify_down()->insert(DownConnection(child_node, sort));
}
cdata_child->modify_up()->insert(UpConnection(parent_node));
#ifndef NDEBUG
// The NodePathComponent should already be in the set.
{
LightReMutexHolder holder(child_node->_paths_lock);
nassertr(child_node->_paths.find(child) != child_node->_paths.end(), false);
}
#endif // NDEBUG
}
child_node->fix_path_lengths(pipeline_stage, current_thread);
parent_node->force_bounds_stale(pipeline_stage, current_thread);
}
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,
int pipeline_stage, Thread *current_thread) {
nassertr(parent != (NodePathComponent *)NULL, (NodePathComponent *)NULL);
PandaNode *parent_node = parent->get_node();
LightReMutexHolder holder(child_node->_paths_lock);
// 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 = child_node->_paths.begin(); pi != child_node->_paths.end(); ++pi) {
if ((*pi)->get_next(pipeline_stage, current_thread) == 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?
CDStageReader cdata_child(child_node->_cycler, pipeline_stage, current_thread);
int parent_index = child_node->do_find_parent(parent_node, cdata_child);
if (parent_index >= 0) {
// They are. Create and return a new one.
PT(NodePathComponent) child =
new NodePathComponent(child_node, parent, pipeline_stage, current_thread);
child_node->_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, int pipeline_stage,
Thread *current_thread) {
LightReMutexHolder holder(child_node->_paths_lock);
// 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 = child_node->_paths.begin(); pi != child_node->_paths.end(); ++pi) {
if ((*pi)->is_top_node(pipeline_stage, current_thread)) {
// 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,
pipeline_stage, current_thread);
child_node->_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, int pipeline_stage,
Thread *current_thread) {
bool ambiguity_detected = false;
PT(NodePathComponent) result =
r_get_generic_component(accept_ambiguity, ambiguity_detected,
pipeline_stage, current_thread);
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 pipeline_stage, Thread *current_thread) {
PT(PandaNode) parent_node;
{
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
int num_parents = cdata->get_up()->size();
if (num_parents == 0) {
// No parents; no ambiguity. This is the root.
return get_top_component(this, true, pipeline_stage, current_thread);
}
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, pipeline_stage, current_thread);
return get_component(parent, this, pipeline_stage, current_thread);
}
// 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;
CPT(Up) up = cdata->get_up();
parent_node = (*up)[0].get_parent();
}
// Now that the lock is released, it's safe to recurse.
PT(NodePathComponent) parent =
parent_node->r_get_generic_component(accept_ambiguity, ambiguity_detected, pipeline_stage, current_thread);
return get_component(parent, this, pipeline_stage, current_thread);
}
////////////////////////////////////////////////////////////////////
// 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) {
LightReMutexHolder holder(_paths_lock);
int num_erased = _paths.erase(component);
nassertv(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.
//
// This operation is not automatically propagated
// upstream. It is applied to the indicated pipeline
// stage only.
////////////////////////////////////////////////////////////////////
void PandaNode::
sever_connection(PandaNode *parent_node, PandaNode *child_node,
int pipeline_stage, Thread *current_thread) {
{
LightReMutexHolder holder(child_node->_paths_lock);
Paths::iterator pi;
for (pi = child_node->_paths.begin(); pi != child_node->_paths.end(); ++pi) {
if (!(*pi)->is_top_node(pipeline_stage, current_thread) &&
(*pi)->get_next(pipeline_stage, current_thread)->get_node() == parent_node) {
// Sever the component here.
(*pi)->set_top_node(pipeline_stage, current_thread);
}
}
}
child_node->fix_path_lengths(pipeline_stage, current_thread);
}
////////////////////////////////////////////////////////////////////
// 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.
//
// This operation is not automatically propagated
// upstream. It is applied to the indicated pipeline
// stage only.
////////////////////////////////////////////////////////////////////
void PandaNode::
new_connection(PandaNode *parent_node, PandaNode *child_node,
int pipeline_stage, Thread *current_thread) {
{
LightReMutexHolder holder(child_node->_paths_lock);
Paths::iterator pi;
for (pi = child_node->_paths.begin(); pi != child_node->_paths.end(); ++pi) {
if ((*pi)->is_top_node(pipeline_stage, current_thread)) {
(*pi)->set_next(parent_node->get_generic_component(false, pipeline_stage, current_thread), pipeline_stage, current_thread);
}
}
}
child_node->fix_path_lengths(pipeline_stage, current_thread);
}
////////////////////////////////////////////////////////////////////
// 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.
//
// This operation is not automatically propagated
// upstream. It is applied to the indicated pipeline
// stage only.
////////////////////////////////////////////////////////////////////
void PandaNode::
fix_path_lengths(int pipeline_stage, Thread *current_thread) {
LightReMutexHolder holder(_paths_lock);
bool any_wrong = false;
Paths::const_iterator pi;
for (pi = _paths.begin(); pi != _paths.end(); ++pi) {
if ((*pi)->fix_length(pipeline_stage, current_thread)) {
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. Don't hold any locks while we recurse.
if (any_wrong) {
Children children;
Stashed stashed;
{
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
children = Children(cdata);
stashed = Stashed(cdata);
}
int num_children = children.get_num_children();
int i;
for (i = 0; i < num_children; ++i) {
PandaNode *child_node = children.get_child(i);
child_node->fix_path_lengths(pipeline_stage, current_thread);
}
int num_stashed = stashed.get_num_stashed();
for (i = 0; i < num_stashed; ++i) {
PandaNode *child_node = stashed.get_stashed(i);
child_node->fix_path_lengths(pipeline_stage, current_thread);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::r_list_descendants
// Access: Private
// Description: The recursive implementation of ls().
////////////////////////////////////////////////////////////////////
void PandaNode::
r_list_descendants(ostream &out, int indent_level) const {
write(out, indent_level);
Children children = get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
PandaNode *child = children.get_child(i);
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::do_find_child
// Access: Private
// Description: The private implementation of find_child().
////////////////////////////////////////////////////////////////////
int PandaNode::
do_find_child(PandaNode *node, const PandaNode::Down *down) const {
nassertr(node != (PandaNode *)NULL, -1);
// 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 = down->begin(); di != down->end(); ++di) {
if ((*di).get_child() == node) {
return di - down->begin();
}
}
return -1;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::update_bounds
// Access: Private
// Description: Updates the cached values of the node that are
// dependent on its children, such as the
// external bounding volume, the _net_collide_mask, and
// the _off_clip_planes.
//
// The old value should be passed in; it will be
// released. The new value is returned.
////////////////////////////////////////////////////////////////////
PandaNode::CDStageWriter PandaNode::
update_bounds(int pipeline_stage, PandaNode::CDLockedStageReader &cdata) {
// We might need to try this a couple of times, in case someone else
// steps on our result.
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< *this << "::update_bounds() {\n";
}
Thread *current_thread = cdata.get_current_thread();
do {
// Grab the last_update counter.
UpdateSeq last_update = cdata->_last_update;
UpdateSeq next_update = cdata->_next_update;
nassertr(last_update != next_update, CDStageWriter(_cycler, pipeline_stage, cdata));
// Start with a clean slate.
CollideMask net_collide_mask = cdata->_into_collide_mask;
DrawMask net_draw_control_mask, net_draw_show_mask;
bool renderable = is_renderable();
if (renderable) {
// If this node is itself renderable, it contributes to the net
// draw mask.
net_draw_control_mask = cdata->_draw_control_mask;
net_draw_show_mask = cdata->_draw_show_mask;
}
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "net_draw_control_mask = " << net_draw_control_mask
<< "\nnet_draw_show_mask = " << net_draw_show_mask
<< "\n";
}
CPT(RenderAttrib) off_clip_planes = cdata->_state->get_attrib(ClipPlaneAttrib::get_class_slot());
if (off_clip_planes == (RenderAttrib *)NULL) {
off_clip_planes = ClipPlaneAttrib::make();
}
// Also get the list of the node's children.
Children children(cdata);
int num_vertices = cdata->_internal_vertices;
// Now that we've got all the data we need from the node, we can
// release the lock.
_cycler.release_read_stage(pipeline_stage, cdata.take_pointer());
int num_children = children.get_num_children();
// We need to keep references to the bounding volumes, since in a
// threaded environment the pointers might go away while we're
// working (since we're not holding a lock on our set of children
// right now). But we also need the regular pointers, to pass to
// BoundingVolume::around().
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
pvector<CPT(BoundingVolume) > child_volumes_ref;
child_volumes_ref.reserve(num_children + 1);
#endif
const BoundingVolume **child_volumes = (const BoundingVolume **)alloca(sizeof(BoundingVolume *) * (num_children + 1));
int child_volumes_i = 0;
bool all_box = true;
CPT(BoundingVolume) internal_bounds =
get_internal_bounds(pipeline_stage, current_thread);
if (!internal_bounds->is_empty()) {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
child_volumes_ref.push_back(internal_bounds);
#endif
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
child_volumes[child_volumes_i++] = internal_bounds;
if (internal_bounds->as_bounding_box() == NULL) {
all_box = false;
}
}
// Now expand those contents to include all of our children.
for (int i = 0; i < num_children; ++i) {
PandaNode *child = children.get_child(i);
const ClipPlaneAttrib *orig_cp = DCAST(ClipPlaneAttrib, off_clip_planes);
CDLockedStageReader child_cdata(child->_cycler, pipeline_stage, current_thread);
if (child_cdata->_last_update != child_cdata->_next_update) {
// Child needs update.
CDStageWriter child_cdataw = child->update_bounds(pipeline_stage, child_cdata);
net_collide_mask |= child_cdataw->_net_collide_mask;
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "\nchild update " << *child << ":\n";
}
DrawMask child_control_mask = child_cdataw->_net_draw_control_mask;
DrawMask child_show_mask = child_cdataw->_net_draw_show_mask;
if (!(child_control_mask | child_show_mask).is_zero()) {
// This child includes a renderable node or subtree. Thus,
// we should propagate its draw masks.
renderable = true;
// For each bit position in the masks, we have assigned the
// following semantic meaning. The number on the left
// represents the pairing of the corresponding bit from the
// control mask and from the show mask:
// 00 : not a renderable node (control 0, show 0)
// 01 : a normally visible node (control 0, show 1)
// 10 : a hidden node (control 1, show 0)
// 11 : a show-through node (control 1, show 1)
// Now, when we accumulate these masks, we want to do so
// according to the following table, for each bit position:
// 00 01 10 11 (child)
// ---------------------
// 00 | 00 01 10 11
// 01 | 01 01 01* 11
// 10 | 10 01* 10 11
// 11 | 11 11 11 11
// (parent)
// This table is almost the same as the union of both masks,
// with one exception, marked with a * in the above table:
// if one is 10 and the other is 01--that is, one is hidden
// and the other is normally visible--then the result should
// be 01, normally visible. This is because we only want to
// propagate the hidden bit upwards if *all* renderable
// nodes are hidden.
// Get the set of exception bits for which the above rule
// applies. These are the bits for which both bits have
// flipped, but which were not the same in the original.
DrawMask exception_mask = (net_draw_control_mask ^ child_control_mask) & (net_draw_show_mask ^ child_show_mask);
exception_mask &= (net_draw_control_mask ^ net_draw_show_mask);
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "exception_mask = " << exception_mask << "\n";
}
// Now compute the union, applying the above exception.
net_draw_control_mask |= child_control_mask;
net_draw_show_mask |= child_show_mask;
net_draw_control_mask &= ~exception_mask;
net_draw_show_mask |= exception_mask;
}
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "child_control_mask = " << child_control_mask
<< "\nchild_show_mask = " << child_show_mask
<< "\nnet_draw_control_mask = " << net_draw_control_mask
<< "\nnet_draw_show_mask = " << net_draw_show_mask
<< "\n";
}
off_clip_planes = orig_cp->compose_off(child_cdataw->_off_clip_planes);
if (!child_cdataw->_external_bounds->is_empty()) {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
child_volumes_ref.push_back(child_cdataw->_external_bounds);
#endif
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
child_volumes[child_volumes_i++] = child_cdataw->_external_bounds;
if (child_cdataw->_external_bounds->as_bounding_box() == NULL) {
all_box = false;
}
}
num_vertices += child_cdataw->_nested_vertices;
} else {
// Child is good.
net_collide_mask |= child_cdata->_net_collide_mask;
// See comments in similar block above.
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "\nchild fresh " << *child << ":\n";
}
DrawMask child_control_mask = child_cdata->_net_draw_control_mask;
DrawMask child_show_mask = child_cdata->_net_draw_show_mask;
if (!(child_control_mask | child_show_mask).is_zero()) {
renderable = true;
DrawMask exception_mask = (net_draw_control_mask ^ child_control_mask) & (net_draw_show_mask ^ child_show_mask);
exception_mask &= (net_draw_control_mask ^ net_draw_show_mask);
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "exception_mask = " << exception_mask << "\n";
}
// Now compute the union, applying the above exception.
net_draw_control_mask |= child_control_mask;
net_draw_show_mask |= child_show_mask;
net_draw_control_mask &= ~exception_mask;
net_draw_show_mask |= exception_mask;
}
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "child_control_mask = " << child_control_mask
<< "\nchild_show_mask = " << child_show_mask
<< "\nnet_draw_control_mask = " << net_draw_control_mask
<< "\nnet_draw_show_mask = " << net_draw_show_mask
<< "\n";
}
off_clip_planes = orig_cp->compose_off(child_cdata->_off_clip_planes);
if (!child_cdata->_external_bounds->is_empty()) {
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
child_volumes_ref.push_back(child_cdata->_external_bounds);
#endif
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
child_volumes[child_volumes_i++] = child_cdata->_external_bounds;
if (child_cdata->_external_bounds->as_bounding_box() == NULL) {
all_box = false;
}
}
num_vertices += child_cdata->_nested_vertices;
}
}
{
// Now grab the write lock on this node.
CDStageWriter cdataw(_cycler, pipeline_stage, current_thread);
if (last_update == cdataw->_last_update &&
next_update == cdataw->_next_update) {
// Great, no one has monkeyed with these while we were computing
// the cache. Safe to store the computed values and return.
cdataw->_net_collide_mask = net_collide_mask;
if (renderable) {
// Any explicit draw control mask on this node trumps anything
// inherited from below, except a show-through.
DrawMask draw_control_mask = cdataw->_draw_control_mask;
DrawMask draw_show_mask = cdataw->_draw_show_mask;
DrawMask show_through_mask = net_draw_control_mask & net_draw_show_mask;
net_draw_control_mask |= draw_control_mask;
net_draw_show_mask = (net_draw_show_mask & ~draw_control_mask) | (draw_show_mask & draw_control_mask);
net_draw_show_mask |= show_through_mask;
// There are renderable nodes below, so the implicit draw
// bits are all on.
cdataw->_net_draw_control_mask = net_draw_control_mask;
cdataw->_net_draw_show_mask = net_draw_show_mask | ~net_draw_control_mask;
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "renderable, set mask " << cdataw->_net_draw_show_mask << "\n";
}
} else {
// There are no renderable nodes below, so the implicit draw
// bits are all off. Also, we don't care about the draw
// mask on this particular node (since nothing below it is
// renderable anyway).
cdataw->_net_draw_control_mask = net_draw_control_mask;
cdataw->_net_draw_show_mask = net_draw_show_mask;
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "not renderable, set mask " << cdataw->_net_draw_show_mask << "\n";
}
}
cdataw->_off_clip_planes = off_clip_planes;
cdataw->_nested_vertices = num_vertices;
CPT(TransformState) transform = get_transform(current_thread);
PT(GeometricBoundingVolume) gbv;
BoundingVolume::BoundsType btype = cdataw->_bounds_type;
if (btype == BoundingVolume::BT_default) {
btype = bounds_type;
}
if (btype == BoundingVolume::BT_box ||
(btype != BoundingVolume::BT_sphere && all_box && transform->is_identity())) {
// If all of the child volumes are a BoundingBox, and we
// have no transform, then our volume is also a
// BoundingBox.
gbv = new BoundingBox;
} else {
// Otherwise, it's a sphere.
gbv = new BoundingSphere;
}
if (child_volumes_i > 0) {
const BoundingVolume **child_begin = &child_volumes[0];
const BoundingVolume **child_end = child_begin + child_volumes_i;
((BoundingVolume *)gbv)->around(child_begin, child_end);
}
// If we have a transform, apply it to the bounding volume we
// just computed.
if (!transform->is_identity()) {
gbv->xform(transform->get_mat());
}
cdataw->_external_bounds = gbv;
cdataw->_last_update = next_update;
if (drawmask_cat.is_debug()) {
drawmask_cat.debug(false)
<< "} " << *this << "::update_bounds();\n";
}
nassertr(cdataw->_last_update == cdataw->_next_update, cdataw);
// Even though implicit bounding volume is not (yet?) part of
// the bam stream.
mark_bam_modified();
return cdataw;
}
if (cdataw->_last_update == cdataw->_next_update) {
// Someone else has computed the cache for us. OK.
return cdataw;
}
}
// We need to go around again. Release the write lock, and grab
// the read lock back.
cdata = CDLockedStageReader(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update == cdata->_next_update) {
// Someone else has computed the cache for us while we were
// diddling with the locks. OK.
return CDStageWriter(_cycler, pipeline_stage, cdata);
}
} while (true);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::set_scene_root_func
// Access: Public, Static
// Description: This is used by the GraphicsEngine to hook in a
// pointer to the scene_root_func(), the function to
// determine whether the node is an active scene root.
// This back-pointer is necessary because we can't make
// calls directly into GraphicsEngine, which is in the
// display module.
////////////////////////////////////////////////////////////////////
void PandaNode::
set_scene_root_func(SceneRootFunc *func) {
_scene_root_func = func;
}
////////////////////////////////////////////////////////////////////
// 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::update_bam_nested
// Access: Public, Virtual
// Description: Called by the BamWriter when this object has not
// itself been modified recently, but it should check
// its nested objects for updates.
////////////////////////////////////////////////////////////////////
void PandaNode::
update_bam_nested(BamWriter *manager) {
CDReader cdata(_cycler);
cdata->update_bam_nested(manager);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::write_recorder
// Access: Public
// Description: This method is provided for the benefit of classes
// (like MouseRecorder) that inherit from PandaMode and
// also RecorderBase. It's not virtual at this level
// since it doesn't need to be (it's called up from the
// derived class).
//
// This method acts very like write_datagram, but it
// writes the node as appropriate for writing a
// RecorderBase object as described in the beginning of
// a session file, meaning it doesn't need to write
// things such as children. It balances with
// fillin_recorder().
////////////////////////////////////////////////////////////////////
void PandaNode::
write_recorder(BamWriter *, Datagram &dg) {
dg.add_string(get_name());
}
////////////////////////////////////////////////////////////////////
// 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 &params) {
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);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::fillin_recorder
// Access: Protected
// Description: This internal function is called by make_recorder (in
// classes derived from RecorderBase, such as
// MouseRecorder) to read in all of the relevant data
// from the session file. It balances with
// write_recorder().
////////////////////////////////////////////////////////////////////
void PandaNode::
fillin_recorder(DatagramIterator &scan, BamReader *) {
string name = scan.get_string();
set_name(name);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
PandaNode::CData::
CData() :
_state(RenderState::make_empty()),
_transform(TransformState::make_identity()),
_prev_transform(TransformState::make_identity()),
_effects(RenderEffects::make_empty()),
_draw_control_mask(DrawMask::all_off()),
_draw_show_mask(DrawMask::all_on()),
_into_collide_mask(CollideMask::all_off()),
_bounds_type(BoundingVolume::BT_default),
_user_bounds(NULL),
_final_bounds(false),
_fancy_bits(0),
_net_collide_mask(CollideMask::all_off()),
_net_draw_control_mask(DrawMask::all_off()),
_net_draw_show_mask(DrawMask::all_off()),
_down(new PandaNode::Down(PandaNode::get_class_type())),
_stashed(new PandaNode::Down(PandaNode::get_class_type())),
_up(new PandaNode::Up(PandaNode::get_class_type()))
{
++_next_update;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
PandaNode::CData::
CData(const PandaNode::CData &copy) :
BoundsData(copy),
_state(copy._state),
_transform(copy._transform),
_prev_transform(copy._prev_transform),
_effects(copy._effects),
_tag_data(copy._tag_data),
_draw_control_mask(copy._draw_control_mask),
_draw_show_mask(copy._draw_show_mask),
_into_collide_mask(copy._into_collide_mask),
_bounds_type(copy._bounds_type),
_user_bounds(copy._user_bounds),
_final_bounds(copy._final_bounds),
_fancy_bits(copy._fancy_bits),
_net_collide_mask(copy._net_collide_mask),
_net_draw_control_mask(copy._net_draw_control_mask),
_net_draw_show_mask(copy._net_draw_show_mask),
_off_clip_planes(copy._off_clip_planes),
_nested_vertices(copy._nested_vertices),
_external_bounds(copy._external_bounds),
_last_update(copy._last_update),
_next_update(copy._next_update),
_down(copy._down),
_stashed(copy._stashed),
_up(copy._up)
{
// Note that this copy constructor is not used by the PandaNode copy
// constructor! Any elements that must be copied between nodes
// should also be explicitly copied there.
#ifdef HAVE_PYTHON
// Copy and increment all of the Python objects held by the other
// node.
_python_tag_data = _python_tag_data;
inc_py_refs();
#endif // HAVE_PYTHON
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
PandaNode::CData::
~CData() {
#ifdef HAVE_PYTHON
// Free all of the Python objects held by this node.
dec_py_refs();
#endif // HAVE_PYTHON
}
////////////////////////////////////////////////////////////////////
// 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, _transform);
//
manager->write_pointer(dg, _effects);
dg.add_uint32(_draw_control_mask.get_word());
dg.add_uint32(_draw_show_mask.get_word());
dg.add_uint32(_into_collide_mask.get_word());
dg.add_uint8(_bounds_type);
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);
}
//
write_up_list(*get_up(), manager, dg);
write_down_list(*get_down(), manager, dg);
write_down_list(*get_stashed(), manager, dg);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::update_bam_nested
// Access: Public
// Description: Called by the BamWriter when this object has not
// itself been modified recently, but it should check
// its nested objects for updates.
////////////////////////////////////////////////////////////////////
void PandaNode::CData::
update_bam_nested(BamWriter *manager) const {
// No need to check the state pointers for updates, since they're
// all immutable objects.
//manager->consider_update(_state);
//manager->consider_update(_transform);
//manager->consider_update(_effects);
update_up_list(*get_up(), manager);
update_down_list(*get_down(), manager);
update_down_list(*get_stashed(), manager);
}
////////////////////////////////////////////////////////////////////
// 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 and transform pointers.
_state = DCAST(RenderState, 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((TransformState *)_transform.p());
//
// Get the effects pointer.
_effects = DCAST(RenderEffects, p_list[pi++]);
// 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((RenderEffects *)_effects.p());
//
// Get the parent and child pointers.
pi += complete_up_list(*modify_up(), "up", p_list + pi, manager);
pi += complete_down_list(*modify_down(), "down", p_list + pi, manager);
pi += complete_down_list(*modify_stashed(), "stashed", p_list + pi, manager);
// Since the _effects and _states members have been finalized by
// now, this should be safe.
set_fancy_bit(FB_transform, !_transform->is_identity());
set_fancy_bit(FB_state, !_state->is_empty());
set_fancy_bit(FB_effects, !_effects->is_empty());
set_fancy_bit(FB_tag, !_tag_data.empty());
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 and transform pointers.
manager->read_pointer(scan);
manager->read_pointer(scan);
//
// Read the effects pointer.
manager->read_pointer(scan);
if (manager->get_file_minor_ver() < 2) {
DrawMask draw_mask;
draw_mask.set_word(scan.get_uint32());
if (draw_mask == DrawMask::all_off()) {
// Hidden.
_draw_control_mask = _overall_bit;
_draw_show_mask = ~_overall_bit;
} else if (draw_mask == DrawMask::all_on()) {
// Normally visible.
_draw_control_mask = DrawMask::all_off();
_draw_show_mask = DrawMask::all_on();
} else {
// Some per-camera combination.
draw_mask &= ~_overall_bit;
_draw_control_mask = ~draw_mask;
_draw_show_mask = draw_mask;
}
} else {
_draw_control_mask.set_word(scan.get_uint32());
_draw_show_mask.set_word(scan.get_uint32());
}
_into_collide_mask.set_word(scan.get_uint32());
_bounds_type = BoundingVolume::BT_default;
if (manager->get_file_minor_ver() >= 19) {
_bounds_type = (BoundingVolume::BoundsType)scan.get_uint8();
}
// Read in the tag list.
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;
}
//
fillin_up_list(*modify_up(), "up", scan, manager);
fillin_down_list(*modify_down(), "down", scan, manager);
fillin_down_list(*modify_stashed(), "stashed", scan, manager);
}
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::inc_py_refs
// Access: Public
// Description: Increments the reference counts on all held Python
// objects.
////////////////////////////////////////////////////////////////////
void PandaNode::CData::
inc_py_refs() {
PythonTagData::const_iterator ti;
for (ti = _python_tag_data.begin();
ti != _python_tag_data.end();
++ti) {
PyObject *value = (*ti).second;
Py_XINCREF(value);
}
}
#endif // HAVE_PYTHON
#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::dec_py_refs
// Access: Public
// Description: Decrements the reference counts on all held Python
// objects.
////////////////////////////////////////////////////////////////////
void PandaNode::CData::
dec_py_refs() {
PythonTagData::const_iterator ti;
for (ti = _python_tag_data.begin();
ti != _python_tag_data.end();
++ti) {
PyObject *value = (*ti).second;
Py_XDECREF(value);
}
}
#endif // HAVE_PYTHON
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::write_up_list
// Access: Public
// 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: Public
// 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::update_up_list
// Access: Public
// Description: Calls consider_update on each node of the indicated
// up list.
////////////////////////////////////////////////////////////////////
void PandaNode::CData::
update_up_list(const PandaNode::Up &up_list, BamWriter *manager) const {
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)) {
manager->consider_update(parent_node);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::update_down_list
// Access: Public
// Description: Calls consider_update on each node of the indicated
// up list.
////////////////////////////////////////////////////////////////////
void PandaNode::CData::
update_down_list(const PandaNode::Down &down_list, BamWriter *manager) const {
Down::const_iterator di;
for (di = down_list.begin(); di != down_list.end(); ++di) {
PandaNode *child_node = (*di).get_child();
manager->consider_update(child_node);
}
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::complete_up_list
// Access: Public
// Description: Calls complete_pointers() on the list of parent node
// pointers.
////////////////////////////////////////////////////////////////////
int PandaNode::CData::
complete_up_list(PandaNode::Up &up_list, const string &tag,
TypedWritable **p_list, BamReader *manager) {
int pi = 0;
int num_parents = manager->get_int_tag(tag);
Up new_up_list(PandaNode::get_class_type());
new_up_list.reserve(num_parents);
for (int i = 0; i < num_parents; i++) {
PandaNode *parent_node = DCAST(PandaNode, p_list[pi++]);
UpConnection connection(parent_node);
new_up_list.push_back(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.
new_up_list.sort();
// Make it permanent.
up_list.swap(new_up_list);
new_up_list.clear();
return pi;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::complete_down_list
// Access: Public
// Description: Calls complete_pointers() on the list of child node
// pointers.
////////////////////////////////////////////////////////////////////
int PandaNode::CData::
complete_down_list(PandaNode::Down &down_list, const string &tag,
TypedWritable **p_list, BamReader *manager) {
int pi = 0;
BamReaderAuxDataDown *aux;
DCAST_INTO_R(aux, manager->get_aux_tag(tag), pi);
Down &new_down_list = aux->_down_list;
for (Down::iterator di = new_down_list.begin();
di != new_down_list.end();
++di) {
PandaNode *child_node = DCAST(PandaNode, p_list[pi++]);
(*di).set_child(child_node);
}
// 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.
// Make it permanent.
down_list.swap(new_down_list);
new_down_list.clear();
return pi;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::fillin_up_list
// Access: Public
// 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, const string &tag,
DatagramIterator &scan, BamReader *manager) {
int num_parents = scan.get_uint16();
manager->set_int_tag(tag, num_parents);
manager->read_pointers(scan, num_parents);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::CData::fillin_down_list
// Access: Public
// 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, const string &tag,
DatagramIterator &scan, BamReader *manager) {
int num_children = scan.get_uint16();
// Create a temporary down_list, with the right number of elements,
// but a NULL value for each pointer (we'll fill in the pointers
// later). We need to do this to associate the sort values with
// their pointers.
Down new_down_list(PandaNode::get_class_type());
new_down_list.reserve(num_children);
for (int i = 0; i < num_children; i++) {
manager->read_pointer(scan);
int sort = scan.get_int32();
DownConnection connection(NULL, sort);
new_down_list.push_back(connection);
}
// Now store the temporary down_list in the BamReader, so we can get
// it during the call to complete_down_list().
PT(BamReaderAuxDataDown) aux = new BamReaderAuxDataDown;
aux->_down_list.swap(new_down_list);
manager->set_aux_tag(tag, aux);
}
////////////////////////////////////////////////////////////////////
// Function: PandaNodePipelineReader::check_bounds
// Access: Public
// Description: Ensures that the bounding volume is properly computed
// on this node.
////////////////////////////////////////////////////////////////////
void PandaNodePipelineReader::
check_bounds() const {
if (_cdata->_last_update != _cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
// We'll need to get a fresh read pointer, since another thread
// might already have modified the pointer on the object since we
// queried it.
#ifdef DO_PIPELINING
node_unref_delete((CycleData *)_cdata);
#endif // DO_PIPELINING
((PandaNodePipelineReader *)this)->_cdata = NULL;
int pipeline_stage = _current_thread->get_pipeline_stage();
PandaNode::CDLockedStageReader fresh_cdata(_object->_cycler, pipeline_stage, _current_thread);
if (fresh_cdata->_last_update == fresh_cdata->_next_update) {
// What luck, some other thread has already freshened the
// cache for us. Save the new pointer, and let the lock
// release itself.
if (_cdata != (const PandaNode::CData *)fresh_cdata) {
((PandaNodePipelineReader *)this)->_cdata = fresh_cdata;
#ifdef DO_PIPELINING
_cdata->node_ref();
#endif // DO_PIPELINING
}
} else {
// No, the cache is still stale. We have to do the work of
// freshening it.
PStatTimer timer(PandaNode::_update_bounds_pcollector);
PandaNode::CDStageWriter cdataw = ((PandaNode *)_object)->update_bounds(pipeline_stage, fresh_cdata);
nassertv(cdataw->_last_update == cdataw->_next_update);
// As above, we save the new pointer, and then let the lock
// release itself.
if (_cdata != (const PandaNode::CData *)cdataw) {
((PandaNodePipelineReader *)this)->_cdata = cdataw;
#ifdef DO_PIPELINING
_cdata->node_ref();
#endif // DO_PIPELINING
}
}
}
nassertv(_cdata->_last_update == _cdata->_next_update);
}