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 SoCullElement SoCullElement.h Inventor/elements/SoCullElement.h
35   \brief The SoCullElement class is used internally for render and pick culling.
36 
37   \ingroup elements
38 
39   The element holds all planes the geometry should be inside, and
40   keeps a bitflag to signal which planes need to be tested.
41 
42   This element is an extension for Coin, and is not available in the
43   original Open Inventor.
44 
45   The maximum number of planes in this element is 32, which should be
46   more than enough, since the view frustum is represented by 6 planes,
47   and the maximum number of OpenGL clipping planes is typically 6 or
48   8.
49 
50   This element is designed for fast culling, and will not do optimal
51   view frustum culling; a box might not be culled even though it is
52   outside the view frustum. The assumption is that the view frustum is
53   small compared to the world model. The element simply records all
54   planes to be culled against, and the graph is not culled until it is
55   completely outside one of the planes.
56 
57   SoCullElement is not active for other actions than SoGLRenderAction.
58   It's possible to enable it for SoCallbackAction by updating it in
59   a post camera callback though. Do something like this:
60 
61   \verbatim
62 
63   static SoCallbackAction::Response
64   camera_cb(void * data, SoCallbackAction * action, const SoNode * node)
65   {
66     SoState * state = action->getState();
67     SoCullElement::setViewVolume(state, SoViewVolumeElement::get(state));
68     return SoCallbackAction::CONTINUE;
69   }
70 
71   [...]
72   SoCallbackAction cba(myviewport);
73   cba.addPostCallback(SoCamera::getClassTypeId(), camera_cb, NULL);
74 
75   \endverbatim
76 
77   When the view volume is set in SoCullElement in the post camera
78   callback, SoCallbackAction will perform culling on Separators and
79   other nodes in the same way as SoGLRenderAction.
80 
81 */
82 
83 #include <Inventor/elements/SoCullElement.h>
84 #include <Inventor/elements/SoModelMatrixElement.h>
85 #include <Inventor/misc/SoState.h>
86 #include <Inventor/SbBox3f.h>
87 #include <Inventor/SbViewVolume.h>
88 #include <cstring>
89 #include <cassert>
90 
91 #include "coindefs.h"
92 #include "SbBasicP.h"
93 
94 #if COIN_DEBUG
95 #include <Inventor/errors/SoDebugError.h>
96 #endif // COIN_DEBUG
97 
98 SO_ELEMENT_SOURCE(SoCullElement);
99 
100 // doc from parent
101 void
initClass(void)102 SoCullElement::initClass(void)
103 {
104   SO_ELEMENT_INIT_CLASS(SoCullElement, inherited);
105 }
106 
107 /*!
108   The destructor.
109 */
~SoCullElement()110 SoCullElement::~SoCullElement()
111 {
112 }
113 
114 // doc from parent
115 void
init(SoState * COIN_UNUSED_ARG (state))116 SoCullElement::init(SoState * COIN_UNUSED_ARG(state))
117 {
118   this->numplanes = 0;
119   this->flags = 0;
120   this->vvindex = -1;
121 }
122 
123 // doc from parent
124 void
push(SoState * COIN_UNUSED_ARG (state))125 SoCullElement::push(SoState * COIN_UNUSED_ARG(state))
126 {
127   const SoCullElement * prev = coin_assert_cast<const SoCullElement *>
128     (
129      this->getNextInStack()
130      );
131 
132   this->flags = prev->flags;
133   this->numplanes = prev->numplanes;
134   this->vvindex = prev->vvindex;
135   for (int i = 0; i < prev->numplanes; i++) this->plane[i] = prev->plane[i];
136 }
137 
138 /*!
139   Sets the current view volume. In effect, this adds six planes to
140   the list of culling planes.  If a view volume has already been
141   set, the old view volume planes are overwritten by the new ones.
142   The view volume must be in the world coordinate systems.
143 */
144 void
setViewVolume(SoState * state,const SbViewVolume & vv)145 SoCullElement::setViewVolume(SoState * state, const SbViewVolume & vv)
146 {
147   SoCullElement * elem = coin_safe_cast<SoCullElement *>
148     (
149      SoElement::getElement(state, classStackIndex)
150      );
151   if (elem) {
152     if (elem->numplanes + 6 > SoCullElement::MAXPLANES) { // _very_ unlikely
153 #if COIN_DEBUG
154       SoDebugError::postWarning("SoCullElement::setViewVolume",  "too many planes");
155 #endif // COIN_DEBUG
156       return;
157     }
158     int i;
159     SbPlane vvplane[6];
160     vv.getViewVolumePlanes(vvplane);
161     if (elem->vvindex >= 0) { // overwrite old view volume
162       for (i = 0; i < 6; i++) {
163         elem->plane[elem->vvindex+i] = vvplane[i];
164         elem->flags &= ~(1<<(elem->vvindex+i));
165       }
166     }
167     else {
168       elem->vvindex = elem->numplanes;
169       for (i = 0; i < 6; i++) elem->plane[elem->numplanes++] = vvplane[i];
170     }
171   }
172 }
173 
174 /*!
175   Add plane geometry must be inside. The plane must be in the world
176   coordinate system.
177 */
178 void
addPlane(SoState * state,const SbPlane & newplane)179 SoCullElement::addPlane(SoState * state, const SbPlane &newplane)
180 {
181   SoCullElement * elem =
182     coin_safe_cast<SoCullElement *>
183     (
184      SoElement::getElement(state, classStackIndex)
185      );
186   if (elem) {
187     if (elem->numplanes >= SoCullElement::MAXPLANES) {  // _very_ unlikely
188 #if COIN_DEBUG
189       SoDebugError::postWarning("SoCullElement::addPlane",  "too many planes");
190 #endif // COIN_DEBUG
191       return;
192     }
193     elem->plane[elem->numplanes++] = newplane;
194   }
195 }
196 
197 /*!
198   Cull against \a box. If \a transform is \c TRUE, the box is assumed
199   to be in object space, and will be transformed into world space
200   using the model matrix.  Returns \c TRUE if box is outside one of
201   the planes, and updates the element to detect when geometry is
202   completely inside all planes.
203 */
204 SbBool
cullBox(SoState * state,const SbBox3f & box,const SbBool transform)205 SoCullElement::cullBox(SoState * state, const SbBox3f & box, const SbBool transform)
206 {
207   return SoCullElement::docull(state, box, transform, TRUE);
208 }
209 
210 /*!
211   Cull against \a box. If \a transform is \c TRUE, the box is
212   assumed to be in object space, and will be transformed into world
213   space using the model matrix.  Returns \c TRUE if box is outside one
214   of the planes. This method will not update the element state, just
215   perform a cull test against active planes.
216 */
217 SbBool
cullTest(SoState * state,const SbBox3f & box,const SbBool transform)218 SoCullElement::cullTest(SoState * state, const SbBox3f & box, const SbBool transform)
219 {
220   return SoCullElement::docull(state, box, transform, FALSE);
221 }
222 
223 /*!
224   Returns \c TRUE if the current geometry is completely inside all
225   planes. There is no need to do a cull test if this is the case.
226 */
227 SbBool
completelyInside(SoState * state)228 SoCullElement::completelyInside(SoState * state)
229 {
230   // use SoState::getConstElement() to avoid cache dependency on this element
231   const SoCullElement * elem = coin_assert_cast<const SoCullElement *>
232     (
233      state->getConstElement(classStackIndex)
234      );
235   unsigned int mask = 0x0001 << elem->numplanes;
236   return elem->flags == (mask-1);
237 }
238 
239 // Documented in superclass. Overridden to assert that this method is
240 // not called for this element.
241 SbBool
matches(const SoElement *) const242 SoCullElement::matches(const SoElement *) const
243 {
244   assert(0 && "should not get here");
245   return FALSE;
246 }
247 
248 // Documented in superclass. Overridden to assert that this method is
249 // not called for this element.
250 SoElement *
copyMatchInfo(void) const251 SoCullElement::copyMatchInfo(void) const
252 {
253   assert(0 && "should not get here");
254   return NULL;
255 }
256 
257 //
258 // private method which does the actual culling
259 //
260 SbBool
docull(SoState * state,const SbBox3f & box,const SbBool transform,const SbBool updateelem)261 SoCullElement::docull(SoState * state, const SbBox3f & box, const SbBool transform,
262                       const SbBool updateelem)
263 {
264   // try to avoid a push if possible
265   const SoCullElement * elem = coin_safe_cast<const SoCullElement *>
266     (
267      state->getElementNoPush(classStackIndex)
268     );
269 
270   if (!elem) return FALSE;
271 
272   int i, j;
273   SbVec3f min, max;
274   min = box.getMin();
275   max = box.getMax();
276   SbVec3f pts[8];
277 
278   SbMatrix mm;
279   SbBool identity = ! transform;
280   if (transform) {
281     SbBool wasopen = state->isCacheOpen();
282     // close the cache, since we don't create a cache dependency on
283     // the model matrix element
284     state->setCacheOpen(FALSE);
285     mm = SoModelMatrixElement::get(state);
286     state->setCacheOpen(wasopen);
287   }
288 
289   // create the 8 box corner points
290   for (i = 0; i < 8; i++) {
291     pts[i][0] = i & 1 ? min[0] : max[0];
292     pts[i][1] = i & 2 ? min[1] : max[1];
293     pts[i][2] = i & 4 ? min[2] : max[2];
294     if (!identity) mm.multVecMatrix(pts[i], pts[i]);
295   }
296 
297   const int n = elem->numplanes;
298   unsigned int flags = elem->flags;
299   const SbPlane * planes = elem->plane;
300   unsigned int mask = 0x0001;
301 
302   for (i = 0; i < n; i++, mask<<=1) {
303     if (!(flags & mask)) {
304       int in = 0;
305       int out = 0;
306       for (j = 0; j < 8; j++) {
307         if (planes[i].isInHalfSpace(pts[j])) in++;
308         else out++;
309       }
310       if (in == 8) {
311         flags |= mask;
312       }
313       else if (out == 8) {
314         return TRUE;
315       }
316     }
317   }
318   if (updateelem && (flags != elem->flags)) {
319     // force a push if necessary
320     SoCullElement * elem = coin_assert_cast<SoCullElement *>
321       (
322        SoElement::getElement(state, classStackIndex)
323        );
324     elem->flags = flags;
325   }
326   return FALSE;
327 }
328