/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoGroup SoGroup.h Inventor/nodes/SoGroup.h \brief The SoGroup class is a node which managed other node instances. \ingroup nodes The internal scene data structures in Coin are managed as directed graphs. The graphs are built by setting up a hierarchy through the use of group nodes (either of this type, or from subclasses like SoSeparator) which is then traversed when applying actions (like SoGLRenderAction) to it. SoGroup, SoSeparator, and other classes derived from SoGroup, are the "tools" the application programmer uses when making the layout of the scene graph. An often asked question about SoGroup nodes is: "Why is there no SoGroup::getParent() method?" The answer to this is that nodes in the scene graph can have multiple parents, so a simple getParent() method wouldn't work. If you have a node pointer (or other node identification) of a node that you want to remove from the scene graph, what you need to do is to use an SoSearchAction to find all paths down to the node, and then invoke SoGroup::removeChild() from all found parents of the node. The function would look something like this: \code void getParents(SoNode * node, SoNode * root, SoPathList & parents) { SoSearchAction sa; sa.setNode(node); sa.setInterest(SoSearchAction::ALL); sa.apply(root); parents = sa.getPaths(); } \endcode Or if you \e know that your node of interest has only a single parent: \code SoGroup * getParent(SoNode * node, SoNode * root) { SoSearchAction sa; sa.setNode(node); sa.setInterest(SoSearchAction::FIRST); sa.apply(root); SoPath * p = sa.getPath(); assert(p && "not found"); if (p->getLength() < 2) { return NULL; } // no parent return (SoGroup *)p->getNodeFromTail(1); } \endcode An important note about a potential problem using SoGroup nodes which it is not common to stumble on, but which makes hard to find bugs when one does: you should not change the scene graph layout during any action traversal, as that is not allowed by the internal Coin code. I.e. do not use addChild(), removeChild(), insertChild() or replaceChild() from any callback that is triggered directly or indirectly from an action traversal. The most common way of getting hit by this error, would be something like the following (simplified) example: \code #include #include #include #include #include #include #include SoPointLightManip * global_pointlightmanip; SoSeparator * global_root; // Remove pointlight when clicking right mouse button. static void mySelectionC(void * ud, SoEventCallback * n) { const SoMouseButtonEvent * mbe = (SoMouseButtonEvent*) n->getEvent(); if ((mbe->getButton() == SoMouseButtonEvent::BUTTON2) && (mbe->getState() == SoButtonEvent::DOWN)) { if (global_pointlightmanip) { global_root->removeChild(global_pointlightmanip); global_pointlightmanip = NULL; } } } int main(int argc, char ** argv) { QWidget * window = SoQt::init(argv[0]); global_root = new SoSeparator; global_root->ref(); SoEventCallback * ecb = new SoEventCallback; ecb->addEventCallback(SoMouseButtonEvent::getClassTypeId(), mySelectionC, 0); global_root->addChild(ecb); global_root->addChild(new SoCone); global_pointlightmanip = new SoPointLightManip; global_root->addChild(global_pointlightmanip); SoQtExaminerViewer * viewer = new SoQtExaminerViewer(window); viewer->setSceneGraph(global_root); viewer->show(); SoQt::show(window); SoQt::mainLoop(); global_root->unref(); delete viewer; return 0; } \endcode What happens in the above case is this: when clicking with the right mouse button, the SoQtExaminerViewer converts the Qt event to a Coin event, which is sent down the scene graph with an SoHandleEventAction. The action traversal reaches the "global_root" SoSeparator node, where it sees that it should further traverse 3 child nodes (first the SoEventCallback, then the SoCone, then the SoPointLightManip). When it then traverses the SoEventCallback, the mySelectionC() callback will be invoked, which removes the last child. But the SoHandleEventAction will still continue it's traversal as if the global_root node has 3 children -- and the code will crash. (This exact example would perhaps be straight-forward to handle internally in Coin, but there are other ways to change the scene graph layout that are very difficult to handle properly. So in general, changing layout during action traversal is not allowed.) What to do in these cases is to change the code inside the callback to not do any operations that immediately changes the layout of the scene graph, but to delay it for after the traversal is done. This can e.g. be done by using a Coin sensor. FILE FORMAT/DEFAULTS: \code Group { } \endcode */ // ************************************************************************* #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../io/SoWriterefCounter.h" #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include // ************************************************************************* /*! \var SoChildList * SoGroup::children List of managed child nodes. */ // ************************************************************************* SO_NODE_SOURCE(SoGroup); /*! Default constructor. */ SoGroup::SoGroup(void) { this->pimpl = NULL; // just set to NULL for now SO_NODE_INTERNAL_CONSTRUCTOR(SoGroup); this->children = new SoChildList(this); } /*! Constructor. The argument should be the approximate number of children which is expected to be inserted below this node. The number need not be exact, as it is only used as a hint for better memory resource allocation. */ SoGroup::SoGroup(int nchildren) { SO_NODE_INTERNAL_CONSTRUCTOR(SoGroup); this->children = new SoChildList(this, nchildren); } /*! Destructor. */ SoGroup::~SoGroup() { delete this->children; } /*! Returns pointer to child node at \a index. Please note that this method is not virtual in the original SGI Inventor API. */ SoNode * SoGroup::getChild(int index) const { assert((index >= 0) && (index < this->getNumChildren())); return (SoNode*) this->getChildren()->getArrayPtr()[index]; } /*! Returns number of child nodes managed by this group. Please note that this method is not virtual in the original SGI Inventor API. */ int SoGroup::getNumChildren(void) const { return this->getChildren()->getLength(); } // Doc from superclass. SbBool SoGroup::readInstance(SoInput * in, unsigned short flags) { SbBool readfields = TRUE; // Make sure we're compatible with binary format Inventor 1.0 and // 2.0 files. if (in->isBinary() && (in->getIVVersion() < 2.1f) && this->getTypeId() == SoGroup::getClassTypeId()) { readfields = FALSE; } // Make sure we're compatible with binary format Inventor 1.0 files. if (in->isBinary() && (in->getIVVersion() < 2.0f) && // For Inventor V1.0 files, no fields should be attempted read // from SoSeparator nodes when reading from binary format files, // or the input parsing will go wrong. I have just assumed this // goes for all SoGroup-derived nodes -- which may not be // correct. 20050706 mortene. this->isOfType(SoGroup::getClassTypeId())) { readfields = FALSE; } // This influences how SoFieldContainer::readInstance() handles // unknown field names inside the node: if it's a group, ignore, as // it can be the name of a node type. flags |= SoBase::IS_GROUP; // For nodes with fields inheriting SoGroup, the fields must come // before the children, according to the file format specification. if (readfields && !inherited::readInstance(in, flags)) return FALSE; return this->readChildren(in); } /*! Read all children of this node from \a in and attach them below this group in left-to-right order. Returns \c FALSE upon read error. */ SbBool SoGroup::readChildren(SoInput * in) { unsigned int numchildren = 0; // used by binary format import if (in->isBinary() && !in->read(numchildren)) { SoReadError::post(in, "Premature end of file"); return FALSE; } for (unsigned int i=0; !in->isBinary() || (i < numchildren); i++) { SoBase * child; if (SoBase::read(in, child, SoNode::getClassTypeId())) { if (child == NULL) { if (in->eof()) { SoReadError::post(in, "Premature end of file"); return FALSE; } else { if (in->isBinary()) { SoReadError::post(in, "Couldn't read valid identifier name"); return FALSE; } #if COIN_DEBUG && 0 // debug char m; if (in->read(m)) { SoDebugError::postInfo("SoGroup::readChildren", "next char: '%c'", m); } #endif // debug // Completed reading of children for ASCII format import. return TRUE; } } else { this->addChild((SoNode *)child); } } else { // SoReadError::post() is called within the SoBase::read() // frame upon error conditions, so don't duplicate with // another error message here. mortene. return FALSE; } } // A successful import operation for binary format reading of child // nodes will exit here. return TRUE; } // Overridden from parent. void SoGroup::copyContents(const SoFieldContainer * from, SbBool copyconnections) { this->removeAllChildren(); inherited::copyContents(from, copyconnections); SoGroup * g = (SoGroup *)from; // Add children of "from" group node. for (int i=0 ; i < g->getNumChildren(); i++) { SoNode * cp = (SoNode *) SoFieldContainer::findCopy(g->getChild(i), copyconnections); this->addChild(cp); } } /*! Append a child \a node to the list of children nodes this group node is managing. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::addChild(SoNode * node) { assert(node != NULL); this->getChildren()->append(node); } /*! Insert a \a child node at position \a newchildindex. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::insertChild(SoNode * child, int newchildindex) { #if COIN_DEBUG if (newchildindex < 0 || newchildindex > this->getNumChildren()) { SoDebugError::post("SoGroup::insertChild", "idx %d is out of bounds (groupnode # children == %d)", newchildindex, this->getNumChildren()); return; } #endif // COIN_DEBUG this->getChildren()->insert(child, newchildindex); } /*! Remove node at \a childindex in our list of children. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::removeChild(int childindex) { #if COIN_DEBUG if (childindex < 0 || childindex >= this->getNumChildren()) { SoDebugError::post("SoGroup::removeChild", "idx %d is out of bounds (groupnode # children == %d)", childindex, this->getNumChildren()); return; } #endif // COIN_DEBUG this->getChildren()->remove(childindex); } /*! Returns index in our list of children for child \a node, or -1 if \a node is not a child of this group node. Please note that this method is not virtual in the original SGI Inventor API. */ int SoGroup::findChild(const SoNode * node) const { return this->getChildren()->find((SoNode *) node); } // Doc from superclass. void SoGroup::initClass(void) { SO_NODE_INTERNAL_INIT_CLASS(SoGroup, SO_FROM_INVENTOR_1); } // Doc from superclass. void SoGroup::doAction(SoAction * action) { int numindices; const int * indices; if (action->getPathCode(numindices, indices) == SoAction::IN_PATH) { this->getChildren()->traverseInPath(action, numindices, indices); } else { this->getChildren()->traverse(action); // traverse all children } } // Doc from superclass. void SoGroup::getBoundingBox(SoGetBoundingBoxAction * action) { // Sanity check. This has caught bugs. assert(this->getNumChildren() == this->getChildren()->getLength()); int numindices; const int * indices; int lastchildindex; if (action->getPathCode(numindices, indices) == SoAction::IN_PATH) lastchildindex = indices[numindices-1]; else lastchildindex = this->getNumChildren() - 1; assert(lastchildindex < this->getNumChildren()); // Initialize accumulation variables. SbVec3f acccenter(0.0f, 0.0f, 0.0f); int numcenters = 0; for (int i = 0; i <= lastchildindex; i++) { this->getChildren()->traverse(action, i); // If center point is set, accumulate. if (action->isCenterSet()) { acccenter += action->getCenter(); numcenters++; action->resetCenter(); } } if (numcenters != 0) action->setCenter(acccenter / float(numcenters), FALSE); } // Doc from superclass. void SoGroup::GLRender(SoGLRenderAction * action) { int numindices; const int * indices; SoAction::PathCode pathcode = action->getPathCode(numindices, indices); SoNode ** childarray = (SoNode**) this->getChildren()->getArrayPtr(); SoState * state = action->getState(); if (pathcode == SoAction::IN_PATH) { int lastchild = indices[numindices - 1]; for (int i = 0; i <= lastchild && !action->hasTerminated(); i++) { SoNode * child = childarray[i]; action->pushCurPath(i, child); if (action->getCurPathCode() != SoAction::OFF_PATH || child->affectsState()) { if (!action->abortNow()) { child->GLRender(action); } else { SoCacheElement::invalidate(state); } } action->popCurPath(pathcode); } } else { action->pushCurPath(); int n = this->getChildren()->getLength(); for (int i = 0; i < n && !action->hasTerminated(); i++) { action->popPushCurPath(i, childarray[i]); if (pathcode == SoAction::OFF_PATH && !childarray[i]->affectsState()) { continue; } if (action->abortNow()) { // only cache if we do a full traversal SoCacheElement::invalidate(state); break; } childarray[i]->GLRender(action); #if COIN_DEBUG // The GL error test is default disabled for this optimized // path. If you get a GL error reporting an error in the // Separator node, enable this code by setting the environment // variable COIN_GLERROR_DEBUGGING to "1" to see exactly which // node caused the error. static SbBool chkglerr = sogl_glerror_debugging(); if (chkglerr) { cc_string str; cc_string_construct(&str); const unsigned int errs = coin_catch_gl_errors(&str); if (errs > 0) { SoDebugError::post("SoGroup::GLRenderBelowPath", "glGetError()s => '%s', nodetype: '%s'", cc_string_get_text(&str), (*this->getChildren())[i]->getTypeId().getName().getString()); } cc_string_clean(&str); } #endif // COIN_DEBUG } action->popCurPath(); } } // Doc from superclass. void SoGroup::callback(SoCallbackAction * action) { SoGroup::doAction((SoAction *)action); } // Doc from superclass. void SoGroup::getMatrix(SoGetMatrixAction * action) { switch (action->getCurPathCode()) { case SoAction::NO_PATH: case SoAction::BELOW_PATH: break; case SoAction::OFF_PATH: case SoAction::IN_PATH: SoGroup::doAction((SoAction *)action); break; } } // Doc from superclass. void SoGroup::pick(SoPickAction * action) { SoGroup::doAction((SoAction *)action); } // Doc from superclass. void SoGroup::handleEvent(SoHandleEventAction * action) { SoGroup::doAction((SoAction *)action); } // Doc from superclass void SoGroup::audioRender(SoAudioRenderAction * action) { SoGroup::doAction(action); } // Doc from superclass. void SoGroup::write(SoWriteAction * action) { SoOutput * out = action->getOutput(); if (out->getStage() == SoOutput::COUNT_REFS) { int ref = SoWriterefCounter::instance(out)->getWriteref(this); this->addWriteReference(out); // Traverse hierarchy only first time around if (ref == 0) SoGroup::doAction((SoAction *)action); } else if (out->getStage() == SoOutput::WRITE) { if (this->writeHeader(out, TRUE, FALSE)) return; this->getFieldData()->write(out, this); if (out->isBinary()) out->write(this->getNumChildren()); SoGroup::doAction((SoAction *)action); this->writeFooter(out); } else assert(0 && "unknown stage"); } // Doc from superclass. void SoGroup::search(SoSearchAction * action) { // Include this node in the search. inherited::search(action); if (action->isFound()) return; // If we're not the one being sought after, try child subgraphs. SoGroup::doAction((SoAction *)action); } /*! Returns list of children. */ SoChildList * SoGroup::getChildren(void) const { return ((SoGroup *)this)->children; } /*! Remove \a child from the set of children managed by this group node. Will decrease the reference count of \a child by 1. This is a convenience method. It will simply call findChild() with \a child as argument, and then call removeChild(int) if the child is found. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::removeChild(SoNode * child) { int idx = this->findChild(child); if (idx < 0) { #if COIN_DEBUG SoDebugError::post("SoGroup::removeChild", "tried to remove non-existent child %p (%s)", child, child ? child->getTypeId().getName().getString() : ""); #endif // COIN_DEBUG return; } this->removeChild(idx); } /*! Do not manage the children anymore. Will dereference all children by 1 as they are removed. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::removeAllChildren(void) { this->getChildren()->truncate(0); } /*! Replace child at \a index with \a newChild. Dereferences the child previously at \a index, and increases the reference count of \a newChild by 1. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::replaceChild(int index, SoNode * newchild) { // Note: its imperative that we use set() here, and not a // remove+insert pair of calls as that would puck up SoChildList // auditing from SoPath instances. this->getChildren()->set(index, newchild); } /*! Replace \a oldchild with \a newchild. Dereferences \a oldchild by 1, and increases the reference count of \a newchild by 1. This is a convenience method. It will simply call findChild() with \a oldchild as argument, and call replaceChild(int, SoNode*) if the child is found. Please note that this method is not virtual in the original SGI Inventor API. */ void SoGroup::replaceChild(SoNode * oldchild, SoNode * newchild) { #if COIN_DEBUG && 0 // debug SoDebugError::postInfo("SoGroup::replaceChild", "(%p) from %p (%s) to %p (%s)", this, oldchild, oldchild->getTypeId().getName().getString(), newchild, newchild->getTypeId().getName().getString()); #endif // debug int idx = this->findChild(oldchild); #if COIN_DEBUG if (idx < 0 || idx > this->getNumChildren()) { SoDebugError::post("SoGroup::replaceChild", "(%p) Tried to remove non-existent child %p (%s)", this, oldchild, oldchild->getTypeId().getName().getString()); return; } #endif // COIN_DEBUG this->replaceChild(idx, newchild); } // Doc from parent class. void SoGroup::getPrimitiveCount(SoGetPrimitiveCountAction * action) { SoGroup::doAction((SoAction *)action); }