/**************************************************************************\
*
* 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 SoSeparator SoSeparator.h Inventor/nodes/SoSeparator.h
\brief The SoSeparator class is a state-preserving group node.
\ingroup nodes
Subgraphs parented by SoSeparator nodes will not affect the state,
as they push and pop the traversal state before and after traversal
of its children.
SoSeparator nodes also provides options for traversal optimalization
through the use of caching.
FILE FORMAT/DEFAULTS:
\code
Separator {
renderCaching AUTO
boundingBoxCaching AUTO
renderCulling AUTO
pickCulling AUTO
}
\endcode
\sa SoTransformSeparator
*/
// *************************************************************************
#include
#include // strtol(), rand()
#include // LONG_MIN, LONG_MAX
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // coin_getenv()
#include
#ifdef HAVE_CONFIG_H
#include
#endif // HAVE_CONFIG_H
#include
#include // COIN_OBSOLETED()
#include
#ifdef COIN_THREADSAFE
#include
#endif // COIN_THREADSAFE
// *************************************************************************
#if COIN_DEBUG
#define GLCACHE_DEBUG 0 // set to 1 to debug caching
#endif
// *************************************************************************
// environment variable
static int COIN_RANDOMIZE_RENDER_CACHING = -1;
// Maximum number of caches available for allocation for the
// rendercaching.
int SoSeparator::numrendercaches = 2;
// *************************************************************************
/*!
\enum SoSeparator::CacheEnabled
Enumeration of flags for the fields deciding which optimalizations
to do in SoSeparator nodes. There are two types of settings
available: caching policies or culling policies. See doumentation of
fields.
*/
/*!
\var SoSeparator::CacheEnabled SoSeparator::OFF
No caching.
*/
/*!
\var SoSeparator::CacheEnabled SoSeparator::ON
Always try to cache state.
*/
/*!
\var SoSeparator::CacheEnabled SoSeparator::AUTO
Use heuristics to try to figure out the optimal caching policy.
*/
/*!
\var SoSFEnum SoSeparator::renderCaching
Policy for caching of rendering instructions for faster
execution. This will typically use the OpenGL \e displaylist
mechanism.
Default value is SoSeparator::AUTO.
If you know that some parts of your scene will never change,
rendering might happen faster if you explicitly set this field to
SoSeparator::ON. If you on the other hand know that parts of the
scene will change a lot (like for every redraw), it will be
beneficial to set this field to SoSeparator::OFF for the top-level
separator node of this (sub)graph.
Usually the default setting of \c AUTO will handle any scene very
well. The advantages that \e can be had from setting
SoSeparator::renderCaching to \c ON are:
- If you positively know that the geometry under the SoSeparator is
static, you get the cache set up right away.
Otherwise, the code in Coin will do a bit of testing and decide
by some heuristics whether or not to enable it. That will make
the rendering be a tiny bit slower right after start-up than with
renderCaching set to \c ON.
(The slow-down should hardly be noticable, though, so we don't
advice application programmers to do this.)
- For many of the shape nodes that can contain many basic
primitives, like e.g. SoFaceSet, SoIndexedFaceSet, SoLineSet, etc
etc, there is an internal threshold for how many primitives a
node can contain before we don't do caching when
SoSeparator::renderCaching is set to \c AUTO.
The reason we do this is because OpenGL renderlists can
potentially suck up a lot of memory resources on the graphics
card.
But if you know that it will be advantageous on your particular
platform, you can override this by setting
SoSeparator::renderCaching equal to \c ON.
(We don't advice application programmers to do this either. A
better solution in these cases would simply be to get in touch
with SIM and describe the platform and the problem, and we could
integrate a proper fix into Coin.)
There are good reasons for setting renderCaching to \c OFF, like
when you know the geometry will be changing a lot. Still, Coin
should work fairly well without even this optimization. (If
renderCaching is \c AUTO over a sub-graph with changing geometry or
other cache smashing nodes, the caching heuristics will stop the
SoSeparator node from trying to make caches -- at least after a few
tries has been made and failed.)
The short story about how auto-caching works is as follows:
- For vertex-based shapes with fewer than 100 triangles and
where the geometry is detected to be fairly static, caching is
enabled.
- For shapes with more than 1000 trangles, it is disabled, to
avoid spending too much of the on-board graphics card's memory
resources.
- For shapes with between 100 and 1000 shapes, displaylist
caching will be turned on if our heuristics decides that the
geometry can be considered static.
The maximum threshold (of 1000) is higher when doing remote
rendering (as when rendering from one X11-based system to another).
Disabling the displaylist caching takes precedence over enabling, so
if you have an SoSeparator with a shape with more than 1000
triangles and a shape with fewer than 100 triangles, caching will be
disabled for the SoSeparator.
It's possible to tune the limits using some environment variables:
- \c COIN_AUTOCACHE_LOCAL_MIN can be used to change the
enable-caching limit, while \c COIN_AUTOCACHE_LOCAL_MAX
controls the disable-caching limit.
- The corresponding variables for remote rendering are \c
COIN_AUTOCACHE_REMOTE_MIN and \c
COIN_AUTOCACHE_REMOTE_MAX.
*/
/*!
\var SoSFEnum SoSeparator::boundingBoxCaching
Policy for caching bounding box calculations. Default value is
SoSeparator::AUTO.
See also documentation for SoSeparator::renderCaching.
*/
/*!
\var SoSFEnum SoSeparator::renderCulling
Policy for doing viewport culling during rendering
traversals. Default value is SoSeparator::AUTO.
When the render culling is turned off for Coin, it will be left to
be done for the underlying immediate mode rendering library. This
will often be faster than doing culling from within Coin, so be
careful to monitor the change in execution speed if setting this
field to SoSeparator::ON.
See also documentation for SoSeparator::renderCaching.
*/
/*!
\var SoSFEnum SoSeparator::pickCulling
Policy for doing viewport culling during pick traversals. Default
value is SoSeparator::AUTO.
See documentation for SoSeparator::renderCulling.
*/
// *************************************************************************
// when doing threadsafe rendering, each thread needs its own
// glcachelist
typedef struct {
SoGLCacheList * glcachelist;
} soseparator_storage;
static void
soseparator_storage_construct(void * data)
{
soseparator_storage * ptr = (soseparator_storage*) data;
ptr->glcachelist = NULL;
}
static void
soseparator_storage_destruct(void * data)
{
soseparator_storage * ptr = (soseparator_storage*) data;
delete ptr->glcachelist;
}
// *************************************************************************
class SoSeparatorP {
public:
SoSeparatorP(void) {
this->glcachestorage =
new SbStorage(sizeof(soseparator_storage),
soseparator_storage_construct,
soseparator_storage_destruct);
}
~SoSeparatorP() {
delete this->glcachestorage;
}
SoBoundingBoxCache * bboxcache;
#ifdef COIN_THREADSAFE
SbMutex mutex;
#endif // !COIN_THREADSAFE
SbStorage * glcachestorage;
static void invalidate_gl_cache(void * tls, void *) {
soseparator_storage * ptr = (soseparator_storage*) tls;
if (ptr->glcachelist) {
ptr->glcachelist->invalidateAll();
}
}
enum { YES, NO, MAYBE } hassoundchild;
public:
SoGLCacheList * getGLCacheList(SbBool createifnull);
void invalidateGLCaches(void) {
glcachestorage->applyToAll(invalidate_gl_cache, NULL);
}
void lock(void) {
#ifdef COIN_THREADSAFE
this->mutex.lock();
#endif // COIN_THREADSAFE
}
void unlock(void) {
#ifdef COIN_THREADSAFE
this->mutex.unlock();
#endif // COIN_THREADSAFE
}
};
SoGLCacheList *
SoSeparatorP::getGLCacheList(SbBool createifnull)
{
soseparator_storage * ptr =
(soseparator_storage*) this->glcachestorage->get();
if (createifnull && ptr->glcachelist == NULL) {
ptr->glcachelist = new SoGLCacheList(SoSeparator::getNumRenderCaches());
}
return ptr->glcachelist;
}
// *************************************************************************
#define PRIVATE(obj) ((obj)->pimpl)
SO_NODE_SOURCE(SoSeparator);
/*!
Default constructor.
*/
SoSeparator::SoSeparator(void)
{
this->commonConstructor();
}
/*!
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.
*/
SoSeparator::SoSeparator(const int nchildren)
: SoGroup(nchildren)
{
this->commonConstructor();
}
// private common constructor helper function
void
SoSeparator::commonConstructor(void)
{
PRIVATE(this) = new SoSeparatorP;
SO_NODE_INTERNAL_CONSTRUCTOR(SoSeparator);
SO_NODE_ADD_FIELD(renderCaching, (SoSeparator::AUTO));
SO_NODE_ADD_FIELD(boundingBoxCaching, (SoSeparator::AUTO));
SO_NODE_ADD_FIELD(renderCulling, (SoSeparator::AUTO));
SO_NODE_ADD_FIELD(pickCulling, (SoSeparator::AUTO));
SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, ON);
SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, OFF);
SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, AUTO);
SO_NODE_SET_SF_ENUM_TYPE(renderCaching, CacheEnabled);
SO_NODE_SET_SF_ENUM_TYPE(boundingBoxCaching, CacheEnabled);
SO_NODE_SET_SF_ENUM_TYPE(renderCulling, CacheEnabled);
SO_NODE_SET_SF_ENUM_TYPE(pickCulling, CacheEnabled);
static long int maxcaches = -1;
if (maxcaches == -1) {
maxcaches = -2; // so we don't request the envvar later if it is not set
const char * maxcachesstr = coin_getenv("IV_SEPARATOR_MAX_CACHES");
if (maxcachesstr) {
maxcaches = strtol(maxcachesstr, NULL, 10);
if ((maxcaches == LONG_MIN) || (maxcaches == LONG_MAX) || (maxcaches < 0)) {
SoDebugError::post("SoSeparator::commonConstructor",
"Environment variable IV_SEPARATOR_MAX_CACHES "
"has invalid setting \"%s\"", maxcachesstr);
}
else {
SoSeparator::setNumRenderCaches(maxcaches);
}
}
}
PRIVATE(this)->bboxcache = NULL;
// This environment variable used for local stability / robustness /
// correctness testing of the render caching. If set >= 1,
// renderCaching will be set to "ON" with a probability of 0.5 for
// every SoSeparator instantiated.
if (COIN_RANDOMIZE_RENDER_CACHING < 0) {
const char * env = coin_getenv("COIN_RANDOMIZE_RENDER_CACHING");
if (env) COIN_RANDOMIZE_RENDER_CACHING = atoi(env);
else COIN_RANDOMIZE_RENDER_CACHING = 0;
}
if (COIN_RANDOMIZE_RENDER_CACHING > 0) {
if (rand() > (RAND_MAX/2)) { this->renderCaching = SoSeparator::ON; }
}
PRIVATE(this)->hassoundchild = SoSeparatorP::MAYBE;
}
/*!
Destructor. Frees resources used to implement caches.
*/
SoSeparator::~SoSeparator()
{
if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->unref();
delete PRIVATE(this);
}
// Doc from superclass.
void
SoSeparator::initClass(void)
{
SO_NODE_INTERNAL_INIT_CLASS(SoSeparator, SO_FROM_INVENTOR_1|SoNode::VRML1);
SO_ENABLE(SoGetBoundingBoxAction, SoCacheElement);
SO_ENABLE(SoGLRenderAction, SoCacheElement);
SoSeparator::numrendercaches = 2;
}
// Doc from superclass.
void
SoSeparator::doAction(SoAction * action)
{
action->getState()->push();
inherited::doAction(action);
action->getState()->pop();
}
// Doc from superclass.
void
SoSeparator::getBoundingBox(SoGetBoundingBoxAction * action)
{
SoState * state = action->getState();
SbXfBox3f childrenbbox;
SbBool childrencenterset;
SbVec3f childrencenter;
// FIXME: AUTO is interpreted as ON for the boundingBoxCaching
// field, but we should trigger some heuristics based on scene graph
// "behavior" in the children subgraphs if the value is set to
// AUTO. 19990513 mortene.
SbBool iscaching = this->boundingBoxCaching.getValue() != OFF;
switch (action->getCurPathCode()) {
case SoAction::IN_PATH:
// can't cache if we're not traversing all children
iscaching = FALSE;
break;
case SoAction::OFF_PATH:
return; // no need to do any more work
case SoAction::BELOW_PATH:
case SoAction::NO_PATH:
// check if this is a normal traversal
if (action->isInCameraSpace() || action->isResetPath()) iscaching = FALSE;
break;
default:
iscaching = FALSE;
assert(0 && "unknown path code");
break;
}
SbBool validcache = iscaching && PRIVATE(this)->bboxcache && PRIVATE(this)->bboxcache->isValid(state);
if (iscaching && validcache) {
SoCacheElement::addCacheDependency(state, PRIVATE(this)->bboxcache);
childrenbbox = PRIVATE(this)->bboxcache->getBox();
childrencenterset = PRIVATE(this)->bboxcache->isCenterSet();
childrencenter = PRIVATE(this)->bboxcache->getCenter();
if (PRIVATE(this)->bboxcache->hasLinesOrPoints()) {
SoBoundingBoxCache::setHasLinesOrPoints(state);
}
}
else {
SbXfBox3f abox = action->getXfBoundingBox();
SbBool storedinvalid = FALSE;
if (iscaching) {
storedinvalid = SoCacheElement::setInvalid(FALSE);
}
state->push();
if (iscaching) {
// lock before changing the bboxcache pointer so that the notify()
// function can be used by another thread.
PRIVATE(this)->lock();
// if we get here, we know bbox cache is not created or is invalid
if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->unref();
PRIVATE(this)->bboxcache = new SoBoundingBoxCache(state);
PRIVATE(this)->bboxcache->ref();
PRIVATE(this)->unlock();
// set active cache to record cache dependencies
SoCacheElement::set(state, PRIVATE(this)->bboxcache);
}
SoLocalBBoxMatrixElement::makeIdentity(state);
action->getXfBoundingBox().makeEmpty();
inherited::getBoundingBox(action);
childrenbbox = action->getXfBoundingBox();
childrencenterset = action->isCenterSet();
if (childrencenterset) childrencenter = action->getCenter();
action->getXfBoundingBox() = abox; // reset action bbox
if (iscaching) {
PRIVATE(this)->bboxcache->set(childrenbbox, childrencenterset, childrencenter);
}
state->pop();
if (iscaching) SoCacheElement::setInvalid(storedinvalid);
}
if (!childrenbbox.isEmpty()) {
action->extendBy(childrenbbox);
if (childrencenterset) {
// FIXME: shouldn't this assert() hold up? Investigate. 19990422 mortene.
#if 0 // disabled
assert(!action->isCenterSet());
#else
action->resetCenter();
#endif
action->setCenter(childrencenter, TRUE);
}
}
}
// Doc from superclass.
void
SoSeparator::callback(SoCallbackAction * action)
{
SoState * state = action->getState();
state->push();
// culling planes should normally not be set, but can be set
// manually by the application programmer to optimize callback
// action traversal.
if (!this->cullTest(state)) {
SoGroup::callback(action);
}
state->pop();
}
// Doc from superclass.
void
SoSeparator::GLRender(SoGLRenderAction * action)
{
switch (action->getCurPathCode()) {
case SoAction::NO_PATH:
case SoAction::BELOW_PATH:
this->GLRenderBelowPath(action);
break;
case SoAction::OFF_PATH:
// do nothing. Separator will reset state.
break;
case SoAction::IN_PATH:
this->GLRenderInPath(action);
break;
}
}
/*!
SGI Open Inventor v2.1 obsoleted support for
SoGLRenderAction::addMethod(). Instead, GLRender() might be called
directly, and to optimize traversal, the SoSeparator node calls
GLRenderBelowPath whenever the path code is BELOW_PATH or NO_PATH
(path code is guaranteed not to change). To be compatible with SGI's
Inventor (and thereby also TGS') we have chosen to follow their
implementation in this respect.
SoSeparator::GLRenderBelowPath() do not traverse its children using
SoChildList::traverse(), but calls GLRenderBelowPath() directly
for all its children.
*/
void
SoSeparator::GLRenderBelowPath(SoGLRenderAction * action)
{
SoState * state = action->getState();
state->push();
SbBool didcull = FALSE;
SoGLCacheList * createcache = NULL;
if ((this->renderCaching.getValue() != OFF) &&
(SoSeparator::getNumRenderCaches() > 0)) {
// test if bbox is outside view-volume
if (!state->isCacheOpen()) {
didcull = TRUE;
if (this->cullTest(state)) {
state->pop();
return;
}
}
PRIVATE(this)->lock();
SoGLCacheList * glcachelist = PRIVATE(this)->getGLCacheList(TRUE);
PRIVATE(this)->unlock();
if (glcachelist->call(action)) {
#if GLCACHE_DEBUG // debug
SoDebugError::postInfo("SoSeparator::GLRenderBelowPath",
"%p executed GL cache", this);
#endif // debug
state->pop();
return;
}
if (!SoCacheElement::anyOpen(state)) {
#if GLCACHE_DEBUG // debug
SoDebugError::postInfo("SoSeparator::GLRenderBelowPath",
"%p creating GL cache", this);
#endif // debug
createcache = glcachelist;
}
}
if (createcache) createcache->open(action, this->renderCaching.getValue() == AUTO);
SbBool outsidefrustum =
(createcache || state->isCacheOpen() || didcull) ?
FALSE : this->cullTest(state);
if (createcache || !outsidefrustum) {
int n = this->children->getLength();
SoNode ** childarray = (SoNode**) this->children->getArrayPtr();
action->pushCurPath();
for (int i = 0; i < n && !action->hasTerminated(); i++) {
action->popPushCurPath(i, childarray[i]);
if (action->abortNow()) {
// only cache if we do a full traversal
SoCacheElement::invalidate(state);
break;
}
childarray[i]->GLRenderBelowPath(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("SoSeparator::GLRenderBelowPath",
"GL error: '%s', nodetype: %s",
cc_string_get_text(&str),
(*this->children)[i]->getTypeId().getName().getString());
}
cc_string_clean(&str);
}
#endif // COIN_DEBUG
}
action->popCurPath();
}
state->pop();
if (createcache) {
createcache->close(action);
}
}
// Doc from superclass.
void
SoSeparator::GLRenderInPath(SoGLRenderAction * action)
{
int numindices;
const int * indices;
SoAction::PathCode pathcode = action->getPathCode(numindices, indices);
if (pathcode == SoAction::IN_PATH) {
SoState * state = action->getState();
SoNode ** childarray = (SoNode**) this->children->getArrayPtr();
state->push();
int childidx = 0;
for (int i = 0; i < numindices; i++) {
for (; childidx < indices[i] && !action->hasTerminated(); childidx++) {
SoNode * offpath = childarray[childidx];
if (offpath->affectsState()) {
action->pushCurPath(childidx, offpath);
if (!action->abortNow()) {
offpath->GLRenderOffPath(action);
}
else {
SoCacheElement::invalidate(state);
}
action->popCurPath(pathcode);
}
}
SoNode * inpath = childarray[childidx];
action->pushCurPath(childidx, inpath);
if (!action->abortNow()) {
inpath->GLRenderInPath(action);
}
else {
SoCacheElement::invalidate(state);
}
action->popCurPath(pathcode);
childidx++;
}
state->pop();
}
else if (pathcode == SoAction::BELOW_PATH) {
this->GLRenderBelowPath(action);
}
}
// Doc from superclass.
void
SoSeparator::GLRenderOffPath(SoGLRenderAction *)
{
// do nothing, since all state changes will be reset by the separator
}
// Doc from superclass.
void
SoSeparator::handleEvent(SoHandleEventAction * action)
{
SoSeparator::doAction(action);
}
// Doc from superclass.
void
SoSeparator::audioRender(SoAudioRenderAction * action)
{
// Note: This function is similar to SoVRMLGroup::audioRender().
/* FIXME: how should we handle termination of an action? We should
probably reset PRIVATE(this)->hassoundchild to MAYBE. Investigate
2003-01-31 thammer. */
int numindices;
const int * indices;
SoState * state = action->getState();
if (PRIVATE(this)->hassoundchild != SoSeparatorP::NO) {
if (action->getPathCode(numindices, indices) != SoAction::IN_PATH) {
action->getState()->push();
SoSoundElement::setSceneGraphHasSoundNode(state, this, FALSE);
inherited::doAction(action);
PRIVATE(this)->hassoundchild = SoSoundElement::sceneGraphHasSoundNode(state) ?
SoSeparatorP::YES : SoSeparatorP::NO;
action->getState()->pop();
} else {
SoSeparator::doAction((SoAction*)action);
}
}
}
// compute object space ray and test for intersection
static SbBool
ray_intersect(SoRayPickAction * action, const SbBox3f &box)
{
if (box.isEmpty()) return FALSE;
action->setObjectSpace();
return action->intersect(box, TRUE);
}
// Doc from superclass.
void
SoSeparator::rayPick(SoRayPickAction * action)
{
if (this->pickCulling.getValue() == OFF ||
!PRIVATE(this)->bboxcache || !PRIVATE(this)->bboxcache->isValid(action->getState()) ||
!action->hasWorldSpaceRay() ||
ray_intersect(action, PRIVATE(this)->bboxcache->getProjectedBox())) {
SoSeparator::doAction(action);
}
}
// Doc from superclass.
void
SoSeparator::search(SoSearchAction * action)
{
// Include this node in the search. (_Don't_ use the
// SoGroup::search(), as it will also traverse all children.)
SoNode::search(action);
if (action->isFound()) return;
SoSeparator::doAction(action);
}
// Doc from superclass.
void
SoSeparator::getMatrix(SoGetMatrixAction * action)
{
int numindices;
const int * indices;
if (action->getPathCode(numindices, indices) == SoAction::IN_PATH) {
// need to push/pop to handle SoUnitsElement correctly
action->getState()->push();
this->children->traverseInPath(action, numindices, indices);
action->getState()->pop();
}
}
/*!
Set the maximum number of caches that SoSeparator nodes may allocate
for render caching.
This is a global value which will be used for all SoSeparator nodes,
but the value indicate the maximum number \e per SoSeparator node.
More caches might give better performance, but will use more memory.
The built-in default value is 2.
The value can also be changed globally by setting the host system's
environment variable IV_SEPARATOR_MAX_CACHES to the wanted
number. This is primarily meant as an aid during debugging, to make
it easy to turn off rendercaching completely (by setting
"IV_SEPARATOR_MAX_CACHES=0") without having to change any
application code.
*/
void
SoSeparator::setNumRenderCaches(const int howmany)
{
SoSeparator::numrendercaches = howmany;
}
/*!
Returns maximum number of caches SoSeparator nodes are allowed to
use for render caching.
\sa setNumRenderCaches()
*/
int
SoSeparator::getNumRenderCaches(void)
{
return SoSeparator::numrendercaches;
}
// Doc from superclass.
SbBool
SoSeparator::affectsState(void) const
{
// Subgraphs parented by SoSeparator nodes will not affect the
// state, as they push and pop the traversal state before and after
// traversal of its children.
return FALSE;
}
// Doc from superclass.
void
SoSeparator::getPrimitiveCount(SoGetPrimitiveCountAction * action)
{
SoSeparator::doAction((SoAction *)action);
}
// Doc from superclass.
void
SoSeparator::notify(SoNotList * nl)
{
inherited::notify(nl);
// lock before using the cache pointers so that we know the pointers
// are valid while reading them
PRIVATE(this)->lock();
if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->invalidate();
PRIVATE(this)->invalidateGLCaches();
PRIVATE(this)->hassoundchild = SoSeparatorP::MAYBE;
PRIVATE(this)->unlock();
}
/*!
This is an internal Open Inventor method. We've implemented
view frustum culling in a different manner. Let us know if
you need this function, and we'll consider implementing it.
*/
SbBool
SoSeparator::cullTest(SoGLRenderAction * action, int & cullresults)
{
COIN_OBSOLETED();
return FALSE;
}
// Doc from superclass.
SbBool
SoSeparator::readInstance(SoInput * in, unsigned short flags)
{
return inherited::readInstance(in, flags);
}
/*!
Internal method which do view frustum culling. For now, view frustum
culling is performed if the renderCulling field is \c AUTO or \c ON,
and the bounding box cache is valid.
Returns \c TRUE if this separator is outside view frustum, \c FALSE
if inside.
*/
SbBool
SoSeparator::cullTest(SoState * state)
{
if (this->renderCulling.getValue() == SoSeparator::OFF) return FALSE;
if (SoCullElement::completelyInside(state)) return FALSE;
SbBool outside = FALSE;
if (PRIVATE(this)->bboxcache &&
PRIVATE(this)->bboxcache->isValid(state)) {
const SbBox3f & bbox = PRIVATE(this)->bboxcache->getProjectedBox();
if (!bbox.isEmpty()) {
outside = SoCullElement::cullBox(state, bbox);
}
}
return outside;
}
//
// Performs a cull test on this node. This call will not update
// the SoCullElement, so it can be called without calling
// state->push() first.
//
SbBool
SoSeparator::cullTestNoPush(SoState * state)
{
if (this->renderCulling.getValue() == SoSeparator::OFF) return FALSE;
if (SoCullElement::completelyInside(state)) return FALSE;
SbBool outside = FALSE;
if (PRIVATE(this)->bboxcache &&
PRIVATE(this)->bboxcache->isValid(state)) {
const SbBox3f & bbox = PRIVATE(this)->bboxcache->getProjectedBox();
if (!bbox.isEmpty()) {
outside = SoCullElement::cullTest(state, bbox);
}
}
return outside;
}
#undef PRIVATE