/**************************************************************************\ * * 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 SoSurroundScale SoSurroundScale.h Inventor/nodes/SoSurroundScale.h \brief The SoSurroundScale class is used to automatically scale geometry to surround other geometry. \ingroup nodes This node calculates a transformation (a translation and a scale) which will, when the node is traversed, be appended to the current model matrix, making a default cube placed directly to the right of this node in the graph surround geometry to the right of the container branch this node is on. The container is specified by the field SoSurroundScale::numNodesUpToContainer. When calculating the bounding box to be surrounded, the bounding box action will be applied to the container node, and the bounding box calculations will be reset after traversing the node specified by the field SoSurroundScale::numNodesUpToReset. A way of thinking of the container node is that there exists a path from the root-node to the SoSurroundScale node. This path has a number of nodes in it. numNodesUpToContainer is the number of nodes in the path from the SoSurroundScale node towards the root node. When having counted numNodesUpToContainer from the SoSurroundScale node, you will have reached the container node. The container node is the seed-node that the bounding box is calculated from. All nodes lower than the seed-node will be a part of the bounding box unless they are closer to the SoSurroundScale node than numNodesUpToReset. To make sure the node(s) that are to be scaled by the SoSurroundScale node aren't a part of the bounding box, the numNodesUpToReset is used. It is also the number of nodes up from the SoSurroundScale node towards the root node. The meaning of numNodesUpToReset is that nodes that are closer to the SoSurroundNode than the node at position numNodesUpToReset are not to be used for bounding box calculations. Here's a complete, stand-alone example which sets up an SoTrackballDragger (connected to a cube), and uses an SoSurroundScale node to make it auto-scale to the size of the high cube and translates it an offset to one side of the cube: \code #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { QWidget * window = SoQt::init(argv[0]); if (window == NULL) exit(1); SoSeparator * root = new SoSeparator; root->ref(); SoSeparator * surroundsep = new SoSeparator; root->addChild(surroundsep); SoTranslation * translation = new SoTranslation; translation->translation = SbVec3f(12, 0, 0); surroundsep->addChild(translation); SoSurroundScale * ss = new SoSurroundScale; ss->numNodesUpToReset = 1; ss->numNodesUpToContainer = 2; surroundsep->addChild(ss); SoAntiSquish * antisquish = new SoAntiSquish; antisquish->sizing = SoAntiSquish::BIGGEST_DIMENSION; surroundsep->addChild(antisquish); SoTrackballDragger * dragger = new SoTrackballDragger; surroundsep->addChild(dragger); SoRotation * rotation = new SoRotation; rotation->rotation.connectFrom(& dragger->rotation); root->addChild(rotation); SoCube * cube = new SoCube; cube->height = 10.0f; root->addChild(cube); SoQtExaminerViewer * viewer = new SoQtExaminerViewer(window); viewer->setSceneGraph(root); viewer->setViewing(FALSE); viewer->setDecoration(FALSE); viewer->show(); SoQt::show(window); SoQt::mainLoop(); delete viewer; root->unref(); return 0; } \endcode It might be easier to see how the SoSurroundScale node works in the above example by looking at the actual scenegraph: \code #Inventor V2.1 ascii Separator { Separator { Translation { translation 12 0 0 } SurroundScale { numNodesUpToContainer 2 numNodesUpToReset 1 } AntiSquish { sizing BIGGEST_DIMENSION } DEF mydragger TrackballDragger { } } Rotation { rotation 0 0 1 0 = USE mydragger . rotation } Cube { height 10 } } \endcode This node is internally used by manipulators to make it possible for them to scale their dragger's geometry to match the scenegraph geometry it is modifying, as is demonstrated above (but outside of the context of a manipulator). It is also generally useful for application programmers who want any particular piece of geometry surround other geometry of unknown or changing extent. SoSurroundScale nodes in the scenegraph are often paired up with SoAntiSquish nodes to get uniform scaling along all three principal axes, as has also been done in the above example. FILE FORMAT/DEFAULTS: \code SurroundScale { numNodesUpToContainer 0 numNodesUpToReset 0 } \endcode \sa SoAntiSquish */ // ************************************************************************* #include #include #include #include #include #include #include // ************************************************************************* /*! \var SoSFInt32 SoSurroundScale::numNodesUpToContainer Number of nodes in the path counting from this and "upwards" to the container node. */ /*! \var SoSFInt32 SoSurroundScale::numNodesUpToReset Number of nodes in the path counting from this and "upwards" to the node where we will reset the bounding box value. */ /*! \var SoSurroundScale::cachedScale \COININTERNAL */ /*! \var SoSurroundScale::cachedInvScale \COININTERNAL */ /*! \var SoSurroundScale::cachedTranslation \COININTERNAL */ /*! \var SoSurroundScale::cacheOK \COININTERNAL */ /*! \var SoSurroundScale::doTranslations \COININTERNAL */ // ************************************************************************* SO_NODE_SOURCE(SoSurroundScale); /*! Constructor. */ SoSurroundScale::SoSurroundScale(void) { SO_NODE_INTERNAL_CONSTRUCTOR(SoSurroundScale); SO_NODE_ADD_FIELD(numNodesUpToContainer, (0)); SO_NODE_ADD_FIELD(numNodesUpToReset, (0)); this->cacheOK = FALSE; this->ignoreinbbox = FALSE; this->doTranslations = TRUE; } /*! Destructor. */ SoSurroundScale::~SoSurroundScale() { } // Doc in superclass. void SoSurroundScale::initClass(void) { SO_NODE_INTERNAL_INIT_CLASS(SoSurroundScale, SO_FROM_INVENTOR_1); } /*! Invalidates the cached transformation, forcing a recalculation to be done the next time this node is traversed. */ void SoSurroundScale::invalidate(void) { this->cacheOK = FALSE; } // Doc in superclass. void SoSurroundScale::doAction(SoAction * action) { SoState * state = action->getState(); if (!this->cacheOK) { SbMatrix dummy; this->updateMySurroundParams(action, dummy); } if (this->doTranslations && this->cachedTranslation != SbVec3f(0.0f, 0.0f, 0.0f)) { SoModelMatrixElement::translateBy(state, this, this->cachedTranslation); } if (this->cachedScale != SbVec3f(1.0f, 1.0f, 1.0f)) SoModelMatrixElement::scaleBy(state, this, this->cachedScale); } /*! Sets whether the translation part of the transformation should be ignored or not. Default behavior is to translate. */ void SoSurroundScale::setDoingTranslations(const SbBool val) { this->doTranslations = val; } /*! Returns whether the translation part of the transformation should be ignored or not. \sa setDoingTranslations() */ SbBool SoSurroundScale::isDoingTranslations(void) { return this->doTranslations; } // Doc in superclass. void SoSurroundScale::callback(SoCallbackAction * action) { SoSurroundScale::doAction((SoAction *)action); } // Doc in superclass. void SoSurroundScale::GLRender(SoGLRenderAction * action) { SoSurroundScale::doAction((SoAction *)action); } // Doc in superclass. void SoSurroundScale::getBoundingBox(SoGetBoundingBoxAction * action) { if (!this->isIgnoreInBbox()) SoSurroundScale::doAction((SoAction *)action); } // Doc in superclass. void SoSurroundScale::getMatrix(SoGetMatrixAction * action) { if (!this->cacheOK) { this->updateMySurroundParams(action, action->getInverse()); } if (this->doTranslations && this->cachedTranslation != SbVec3f(0.0f, 0.0f, 0.0f)) { SbMatrix m; m.setTranslate(this->cachedTranslation); action->getMatrix().multLeft(m); m.setTranslate(- this->cachedTranslation); action->getInverse().multRight(m); } if (this->cachedScale != SbVec3f(1.0f, 1.0f, 1.0f)) { SbMatrix m; m.setScale(this->cachedScale); action->getMatrix().multLeft(m); m.setScale(SbVec3f(1.0f / this->cachedScale[0], 1.0f / this->cachedScale[1], 1.0f / this->cachedScale[2])); action->getInverse().multRight(m); } } // Doc in superclass. void SoSurroundScale::pick(SoPickAction * action) { SoSurroundScale::doAction((SoAction *)action); } /*! Calculates the translation and scale needed to make a default cube surround geometry to the right of the branch this node is on. */ void SoSurroundScale::updateMySurroundParams(SoAction * action, const SbMatrix & /*inv*/) { // I haven't found any use for the inv argument. The function // should be kept as is to make this node OIV compatible though. // pederb, 20000220 int numtocontainer = this->numNodesUpToContainer.getValue(); int numtoreset = this->numNodesUpToReset.getValue(); const SoFullPath * curpath = (const SoFullPath *) action->getCurPath(); const int curpathlen = curpath->getLength(); if ((numtocontainer <= 0) || (numtocontainer >= curpathlen)) { #if COIN_DEBUG SoDebugError::postWarning("SoSurroundScale::updateMySurroundParams", "illegal field value, numNodesUpToContainer (==%d)" "should always be > 0 and < path length\n", numtocontainer); #endif // debug this->cachedScale.setValue(1.0f, 1.0f, 1.0f); this->cachedInvScale.setValue(1.0f, 1.0f, 1.0f); this->cachedTranslation.setValue(0.0f, 0.0f, 0.0f); this->cacheOK = FALSE; return; } // make sure we don't get here when calculating the bbox SbBool storedignore = this->isIgnoreInBbox(); this->setIgnoreInBbox(TRUE); SoPath * applypath = curpath->copy(0, curpathlen - numtocontainer); applypath->ref(); SoPath * resetpath = NULL; // if numtoreset is out of range, just ignore it and don't use a // reset path if ((numtoreset >= 0) && (numtoreset < numtocontainer)) { resetpath = curpath->copy(0, curpathlen - numtoreset); resetpath->ref(); } SbViewportRegion vp(100, 100); // need to test if SoViewportRegionElement is enabled since this // element is not enabled for SoAudioRenderAction. if (action->getState()->isElementEnabled(SoViewportRegionElement::getClassStackIndex())) { vp = SoViewportRegionElement::get(action->getState()); } SoGetBoundingBoxAction bboxaction(vp); // reset bbox when returning from surroundscale branch, // meaning we'll calculate the bbox of only the geometry // to the right of this branch, getting the wanted result. if (resetpath) { bboxaction.setResetPath(resetpath, FALSE, SoGetBoundingBoxAction::ALL); } bboxaction.apply(applypath); applypath->unref(); if (resetpath) { resetpath->unref(); } SbBox3f box = bboxaction.getBoundingBox(); if (box.isEmpty()) { this->cachedScale.setValue(1.0f, 1.0f, 1.0f); this->cachedInvScale.setValue(1.0f, 1.0f, 1.0f); this->cachedTranslation.setValue(0.0f, 0.0f, 0.0f); } else { box.getSize(this->cachedScale[0], this->cachedScale[1], this->cachedScale[2]); if (this->cachedScale[0] <= 0.0f || this->cachedScale[1] <= 0.0f || this->cachedScale[2] <= 0.0f) { // find the smallest scale not zero SbVec3f s = this->cachedScale; float min = SbMax(SbMax(s[0], s[1]), s[2]); if (s[0] > 0.0f && s[0] < min) min = s[0]; if (s[1] > 0.0f && s[1] < min) min = s[1]; if (s[2] > 0.0f && s[2] < min) min = s[2]; min *= 0.05f; // set empty dimensions to some value if (min <= 0.0f) min = 1.0f; if (s[0] <= 0.0f) this->cachedScale[0] = min; if (s[1] <= 0.0f) this->cachedScale[1] = min; if (s[2] <= 0.0f) this->cachedScale[2] = min; } this->cachedScale *= 0.5f; this->cachedInvScale[0] = 1.0f / this->cachedScale[0]; this->cachedInvScale[1] = 1.0f / this->cachedScale[1]; this->cachedInvScale[2] = 1.0f / this->cachedScale[2]; this->cachedTranslation = box.getCenter(); } this->setIgnoreInBbox(storedignore); this->cacheOK = TRUE; } /*! Sets whether bounding box calculations in SoGetBoundingBoxAction should be affected by this node. Default is to ignore our bounding box calculations. */ void SoSurroundScale::setIgnoreInBbox(const SbBool val) { this->ignoreinbbox = val; } /*! Returns the value of the flag that decides whether bounding box calculations in SoGetBoundingBoxAction should be affected by this node. \sa setIgnoreInBbox() */ SbBool SoSurroundScale::isIgnoreInBbox(void) { return this->ignoreinbbox; }