1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoBoxHighlightRenderAction SoBoxHighlightRenderAction.h Inventor/actions/SoBoxHighlightRenderAction.h
35   \brief The SoBoxHighlightRenderAction class renders the scene with highlighted boxes around selections.
36 
37   \ingroup actions
38 
39   This action performs the same tasks as its parent class,
40   SoGLRenderAction, with the added ability to render highlighted
41   bounding boxes around geometry in selected nodes. This is a simple
42   but convenient way of giving feedback to the user upon interaction
43   with the scene graph.
44 
45   To have the highlighting actually happen (and to be able to
46   automatically "select" nodes by picking with the mouse cursor), you
47   need to use SoSelection nodes in place of group nodes.
48 
49   \sa SoLineHighlightRenderAction, SoSelection
50 */
51 
52 #include <Inventor/actions/SoBoxHighlightRenderAction.h>
53 #include <Inventor/actions/SoSearchAction.h>
54 #include <Inventor/actions/SoGetBoundingBoxAction.h>
55 #include <Inventor/nodes/SoSelection.h>
56 #include <Inventor/nodes/SoCube.h>
57 #include <Inventor/nodes/SoCamera.h>
58 #include <Inventor/nodes/SoMatrixTransform.h>
59 #include <Inventor/nodes/SoDrawStyle.h>
60 #include <Inventor/nodes/SoComplexity.h>
61 #include <Inventor/nodes/SoLightModel.h>
62 #include <Inventor/nodes/SoBaseColor.h>
63 #include <cassert>
64 
65 #include "actions/SoSubActionP.h"
66 #include "SbBasicP.h"
67 
68 #ifdef HAVE_CONFIG_H
69 #include "config.h"
70 #endif // HAVE_CONFIG_H
71 
72 /*!
73   \var SoBoxHighlightRenderAction::hlVisible
74 
75   Boolean which decides whether or not the highlights for selected
76   nodes should be visible.
77  */
78 
79 #ifndef DOXYGEN_SKIP_THIS
80 
81 class SoBoxHighlightRenderActionP {
82 public:
SoBoxHighlightRenderActionP(void)83   SoBoxHighlightRenderActionP(void) : master(NULL) { }
84 
85   SoBoxHighlightRenderAction * master;
86   SoSearchAction * searchaction;
87   SoSearchAction * camerasearch;
88   SoGetBoundingBoxAction * bboxaction;
89   SoBaseColor * basecolor;
90   SoTempPath * postprocpath;
91   SoSeparator * bboxseparator;
92   SoMatrixTransform * bboxtransform;
93   SoCube * bboxcube;
94   SoDrawStyle * drawstyle;
95 
96   void initBoxGraph();
97   void drawHighlightBox(const SoPath * path);
98 };
99 
100 #define PRIVATE(obj) ((obj)->pimpl)
101 #define PUBLIC(obj) ((obj)->master)
102 
103 // used to initialize the internal storage class with variables
104 void
initBoxGraph()105 SoBoxHighlightRenderActionP::initBoxGraph()
106 {
107   this->bboxseparator = new SoSeparator;
108   this->bboxseparator->ref();
109   this->bboxseparator->renderCaching = SoSeparator::OFF;
110   this->bboxseparator->boundingBoxCaching = SoSeparator::OFF;
111 
112   this->bboxtransform = new SoMatrixTransform;
113   this->bboxcube = new SoCube;
114 
115   this->drawstyle = new SoDrawStyle;
116   this->drawstyle->style = SoDrawStyleElement::LINES;
117   this->basecolor = new SoBaseColor;
118 
119   SoLightModel * lightmodel = new SoLightModel;
120   lightmodel->model = SoLazyElement::BASE_COLOR;
121 
122   SoComplexity * complexity = new SoComplexity;
123   complexity->textureQuality = 0.0f;
124   complexity->type = SoComplexityTypeElement::BOUNDING_BOX;
125 
126   this->bboxseparator->addChild(this->drawstyle);
127   this->bboxseparator->addChild(this->basecolor);
128 
129   this->bboxseparator->addChild(lightmodel);
130   this->bboxseparator->addChild(complexity);
131 
132   this->bboxseparator->addChild(this->bboxtransform);
133   this->bboxseparator->addChild(this->bboxcube);
134 }
135 
136 
137 // used to render shape and non-shape nodes (usually SoGroup or SoSeparator).
138 void
drawHighlightBox(const SoPath * path)139 SoBoxHighlightRenderActionP::drawHighlightBox(const SoPath * path)
140 {
141   if (this->camerasearch == NULL) {
142     this->camerasearch = new SoSearchAction;
143   }
144 
145   // find camera used to render node
146   this->camerasearch->setFind(SoSearchAction::TYPE);
147   this->camerasearch->setInterest(SoSearchAction::FIRST); // find first camera to break out asap
148   this->camerasearch->setType(SoCamera::getClassTypeId());
149   this->camerasearch->apply(const_cast<SoPath*>(path));
150 
151   if (this->camerasearch->getPath()) {
152     this->bboxseparator->insertChild(this->camerasearch->getPath()->getTail(), 0);
153   }
154   this->camerasearch->reset();
155 
156   if (this->bboxaction == NULL) {
157     this->bboxaction = new SoGetBoundingBoxAction(SbViewportRegion(100, 100));
158   }
159   this->bboxaction->setViewportRegion(PUBLIC(this)->getViewportRegion());
160   this->bboxaction->apply(const_cast<SoPath*>(path));
161 
162   SbXfBox3f & box = this->bboxaction->getXfBoundingBox();
163 
164   if (!box.isEmpty()) {
165     // set cube size
166     float x, y, z;
167     box.getSize(x, y, z);
168     this->bboxcube->width  = x;
169     this->bboxcube->height  = y;
170     this->bboxcube->depth = z;
171 
172     SbMatrix transform = box.getTransform();
173 
174     // get center (in the local bbox coordinate system)
175     SbVec3f center = box.SbBox3f::getCenter();
176 
177     // if center != (0,0,0), move the cube
178     if (center != SbVec3f(0.0f, 0.0f, 0.0f)) {
179       SbMatrix t;
180       t.setTranslate(center);
181       transform.multLeft(t);
182     }
183     this->bboxtransform->matrix = transform;
184 
185     PUBLIC(this)->SoGLRenderAction::apply(this->bboxseparator);
186   }
187   // remove camera
188   this->bboxseparator->removeChild(0);
189 }
190 
191 #endif // DOXYGEN_SKIP_THIS
192 
193 SO_ACTION_SOURCE(SoBoxHighlightRenderAction);
194 
195 // Overridden from parent class.
196 void
initClass(void)197 SoBoxHighlightRenderAction::initClass(void)
198 {
199   SO_ACTION_INTERNAL_INIT_CLASS(SoBoxHighlightRenderAction, SoGLRenderAction);
200 }
201 
202 
203 /*!
204   Default constructor. Note: passes a default SbViewportRegion to the
205   parent constructor.
206  */
SoBoxHighlightRenderAction(void)207 SoBoxHighlightRenderAction::SoBoxHighlightRenderAction(void)
208   : inherited(SbViewportRegion())
209 {
210   this->init();
211 }
212 
213 /*!
214   Constructor, taking an explicit \a viewportregion to render.
215 */
SoBoxHighlightRenderAction(const SbViewportRegion & viewportregion)216 SoBoxHighlightRenderAction::SoBoxHighlightRenderAction(const SbViewportRegion & viewportregion)
217   : inherited(viewportregion)
218 {
219   this->init();
220 }
221 
222 //
223 // private. called by both constructors
224 //
225 void
init(void)226 SoBoxHighlightRenderAction::init(void)
227 {
228   SO_ACTION_CONSTRUCTOR(SoBoxHighlightRenderAction);
229 
230   PRIVATE(this)->master = this;
231 
232   // Initialize local variables
233   PRIVATE(this)->initBoxGraph();
234 
235   this->hlVisible = TRUE;
236 
237   PRIVATE(this)->basecolor->rgb.setValue(1.0f, 0.0f, 0.0f);
238   PRIVATE(this)->drawstyle->linePattern = 0xffff;
239   PRIVATE(this)->drawstyle->lineWidth = 3.0f;
240   PRIVATE(this)->searchaction = NULL;
241   PRIVATE(this)->camerasearch = NULL;
242   PRIVATE(this)->bboxaction = NULL;
243 
244   // SoBase-derived objects should be dynamically allocated.
245   PRIVATE(this)->postprocpath = new SoTempPath(32);
246   PRIVATE(this)->postprocpath->ref();
247 }
248 
249 
250 /*!
251   Destructor.
252 */
~SoBoxHighlightRenderAction(void)253 SoBoxHighlightRenderAction::~SoBoxHighlightRenderAction(void)
254 {
255   PRIVATE(this)->postprocpath->unref();
256   PRIVATE(this)->bboxseparator->unref();
257 
258   delete PRIVATE(this)->searchaction;
259   delete PRIVATE(this)->camerasearch;
260   delete PRIVATE(this)->bboxaction;
261 }
262 
263 // Documented in superclass. Overridden to add highlighting after the
264 // "ordinary" rendering.
265 void
apply(SoNode * node)266 SoBoxHighlightRenderAction::apply(SoNode * node)
267 {
268   SoGLRenderAction::apply(node);
269   if (this->hlVisible) {
270     if (PRIVATE(this)->searchaction == NULL) {
271       PRIVATE(this)->searchaction = new SoSearchAction;
272     }
273     const SbBool searchall = FALSE;
274     PRIVATE(this)->searchaction->setType(SoSelection::getClassTypeId());
275     PRIVATE(this)->searchaction->setInterest(searchall ? SoSearchAction::ALL : SoSearchAction::FIRST);
276     PRIVATE(this)->searchaction->apply(node);
277 
278     if (searchall) {
279       const SoPathList & pathlist = PRIVATE(this)->searchaction->getPaths();
280       if (pathlist.getLength() > 0) {
281         int i;
282         for (i = 0; i < pathlist.getLength(); i++) {
283           SoFullPath * path = static_cast<SoFullPath *>(pathlist[i]);
284           assert(path);
285           SoSelection * selection = static_cast<SoSelection *>(path->getTail());
286           if (selection->getNumSelected() > 0)
287             this->drawBoxes(path, selection->getList());
288         }
289       }
290     }
291     else {
292       SoFullPath * path =
293         static_cast<SoFullPath *>(PRIVATE(this)->searchaction->getPath());
294       if (path) {
295         SoSelection * selection = static_cast<SoSelection *>(path->getTail());
296         if (selection->getNumSelected()) {
297           this->drawBoxes(path, selection->getList());
298         }
299       }
300     }
301     PRIVATE(this)->searchaction->reset();
302   }
303 }
304 
305 // Documented in superclass. This method will just call the
306 // SoGLRenderAction::apply() method (so no highlighting will be done).
307 //
308 // It has been overridden to avoid confusing the compiler, which
309 // typically want to see either all or none of the apply() methods
310 // overridden.
311 void
apply(SoPath * path)312 SoBoxHighlightRenderAction::apply(SoPath * path)
313 {
314   SoGLRenderAction::apply(path);
315 }
316 
317 // Documented in superclass.  This method will just call the
318 // SoGLRenderAction::apply() method (so no highlighting will be done).
319 //
320 // It has been overridden to avoid confusing the compiler, which
321 // typically want to see either all or none of the apply() methods
322 // overridden.
323 void
apply(const SoPathList & pathlist,SbBool obeysrules)324 SoBoxHighlightRenderAction::apply(const SoPathList & pathlist,
325                                   SbBool obeysrules)
326 {
327   SoGLRenderAction::apply(pathlist, obeysrules);
328 }
329 
330 /*!
331   Sets if highlighted boxes should be \a visible when
332   rendering. Defaults to \c TRUE.
333 */
334 void
setVisible(const SbBool visible)335 SoBoxHighlightRenderAction::setVisible(const SbBool visible)
336 {
337   this->hlVisible = visible;
338 }
339 
340 /*!
341   Return if highlighted boxes are to be visible.
342 */
343 SbBool
isVisible(void) const344 SoBoxHighlightRenderAction::isVisible(void) const
345 {
346   return this->hlVisible;
347 }
348 
349 /*!
350   Sets the \a color for the highlighted boxes. Defaults to completely
351   red.
352 */
353 void
setColor(const SbColor & color)354 SoBoxHighlightRenderAction::setColor(const SbColor & color)
355 {
356   PRIVATE(this)->basecolor->rgb = color;
357 }
358 
359 /*!
360   Returns rendering color of the highlighted boxes.
361 */
362 const SbColor &
getColor(void)363 SoBoxHighlightRenderAction::getColor(void)
364 {
365   return PRIVATE(this)->basecolor->rgb[0];
366 }
367 
368 /*!
369   Sets the line \a pattern used for the highlighted boxes. Defaults to
370   \c 0xffff (i.e. drawn with no stipples).
371 */
372 void
setLinePattern(unsigned short pattern)373 SoBoxHighlightRenderAction::setLinePattern(unsigned short pattern)
374 {
375   PRIVATE(this)->drawstyle->linePattern = pattern;
376 }
377 
378 /*!
379   Returns line pattern used when drawing boxes.
380 */
381 unsigned short
getLinePattern(void) const382 SoBoxHighlightRenderAction::getLinePattern(void) const
383 {
384   return PRIVATE(this)->drawstyle->linePattern.getValue();
385 }
386 
387 /*!
388   Sets the line \a width used when drawing boxes, in screen pixels (as
389   for all OpenGL rendering). Defaults to 3.
390 */
391 void
setLineWidth(const float width)392 SoBoxHighlightRenderAction::setLineWidth(const float width)
393 {
394   PRIVATE(this)->drawstyle->lineWidth = width;
395 }
396 
397 /*!
398   Returns the line width used when drawing highlight boxes.
399 */
400 float
getLineWidth(void) const401 SoBoxHighlightRenderAction::getLineWidth(void) const
402 {
403   return PRIVATE(this)->drawstyle->lineWidth.getValue();
404 }
405 
406 void
drawBoxes(SoPath * pathtothis,const SoPathList * pathlist)407 SoBoxHighlightRenderAction::drawBoxes(SoPath * pathtothis, const SoPathList * pathlist)
408 {
409   int i;
410   int thispos = reclassify_cast<SoFullPath *>(pathtothis)->getLength()-1;
411   assert(thispos >= 0);
412   PRIVATE(this)->postprocpath->setHead(pathtothis->getHead()); // reset
413 
414   for (i = 1; i < thispos; i++) {
415     PRIVATE(this)->postprocpath->append(pathtothis->getIndex(i));
416   }
417 
418   // we need to disable accumulation buffer antialiasing while
419   // rendering selected objects
420   int oldnumpasses = this->getNumPasses();
421   this->setNumPasses(1);
422 
423   SoState * thestate = this->getState();
424   thestate->push();
425 
426   for (i = 0; i < pathlist->getLength(); i++) {
427     SoFullPath * path = reclassify_cast<SoFullPath *>((*pathlist)[i]);
428     PRIVATE(this)->postprocpath->append(path->getHead());
429     for (int j = 1; j < path->getLength(); j++) {
430       PRIVATE(this)->postprocpath->append(path->getIndex(j));
431     }
432 
433     // Previously SoGLRenderAction was used to draw the bounding boxes
434     // of shapes in selection paths, by overriding renderstyle state
435     // elements to lines drawstyle and simply doing:
436     //
437     //   SoGLRenderAction::apply(PRIVATE(this)->postprocpath); // Bug
438     //
439     // This could have the unwanted side effect of rendering
440     // non-selected shapes, as they could be part of the path (due to
441     // being placed below SoGroup nodes (instead of SoSeparator
442     // nodes)) up to the selected shape.
443     //
444     //
445     // A better approach turned out to be to soup up and draw only the
446     // bounding boxes of the selected shapes:
447     PRIVATE(this)->drawHighlightBox(PRIVATE(this)->postprocpath);
448 
449     // Remove temporary path from path buffer
450     PRIVATE(this)->postprocpath->truncate(thispos);
451   }
452 
453   this->setNumPasses(oldnumpasses);
454   thestate->pop();
455 }
456 
457 
458 #undef PRIVATE
459 #undef PUBLIC
460