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