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 SoRayPickAction SoRayPickAction.h Inventor/actions/SoRayPickAction.h
35 \brief The SoRayPickAction class does ray intersection with scene graphs.
36
37 \ingroup actions
38
39 For interaction with the scene graph geometry, it is necessary to be
40 able to do intersection testing for rays. This functionality is
41 provided by the SoRayPickAction class.
42
43 SoRayPickAction can be used to pass arbitrary rays through the scene
44 for intersection detections, by using the setRay() method.
45
46 Because a very common operation is to check for intersections along
47 the ray from the mousecursor upon mouseclicks, it also contains
48 convenience methods for setting up a ray from the near plane to the
49 far plane from the 2D mousecursor coordinates. See the setPoint()
50 and setNormalizedPoint() methods. A simple usage example for this
51 case is presented below.
52
53
54 Note that one common mistake when using a raypick action to
55 intersect from a point under the mousecursor after a mouseclick is
56 that one tries to apply it to a scenegraph that does not contain a
57 camera \e explicitly set up by the application programmer. Without a
58 camera as part of the traversal, the raypick action does not know
59 which view volume to send the ray through.
60
61 In this regard, be aware that the getSceneGraph() call in the
62 So*-libraries' viewer classes will return the root of the
63 user-supplied scenegraph, not the "real" internal scenegraph root
64 used by the viewer (which should always contain a camera node). So
65 raypicks done from the application code will fail when doing this:
66
67 \code
68 // initializing viewer scenegraph
69 SoSeparator * root = new SoSeparator;
70 root->ref();
71
72 SoEventCallback * ecb = new SoEventCallback;
73 ecb->addEventCallback(SoMouseButtonEvent::getClassTypeId(), event_cb, viewer);
74 root->addChild(ecb);
75
76 root->addChild(new SoCone);
77
78 viewer->setSceneGraph( root );
79 // -- [snip] -------------------------
80
81 // attempting raypick in the event_cb() callback method
82 SoRayPickAction rp( viewer->getViewportRegion() );
83 rp.setPoint(mouseevent->getPosition());
84 rp.apply(viewer->getSceneGraph());
85 // BUG: results will not be what you expected, as no camera was
86 // part of the "user's scenegraph"
87 \endcode
88
89 While this is the correct way to do it:
90
91 \code
92 // initializing viewer scenegraph
93 SoSeparator * root = new SoSeparator;
94 root->ref();
95
96 // Need to set up our own camera in the "user scenegraph", or else
97 // the raypick action will fail because the camera is hidden in the
98 // viewer-specific root of the scenegraph.
99 SoPerspectiveCamera * pcam = new SoPerspectiveCamera;
100 root->addChild(pcam);
101
102 SoEventCallback * ecb = new SoEventCallback;
103 ecb->addEventCallback(SoMouseButtonEvent::getClassTypeId(), event_cb, viewer);
104 root->addChild(ecb);
105
106 root->addChild(new SoCone);
107
108 viewer->setSceneGraph( root );
109 pcam->viewAll( root, viewer->getViewportRegion() );
110 // -- [snip] -------------------------
111
112 // attempting raypick in the event_cb() callback method
113 SoRayPickAction rp( viewer->getViewportRegion() );
114 rp.setPoint(mouseevent->getPosition());
115 rp.apply(viewer->getSceneGraph());
116 \endcode
117
118 Or if you do want the convenience of having the viewer set up a
119 camera for you implicitly, you can get hold of the root-node of the
120 "complete" scenegraph by simply calling:
121
122 \code
123 SoNode * realroot = viewer->getSceneManager()->getSceneGraph();
124 \endcode
125 */
126 // FIXME: in the class doc, also mention how one can use
127 // SoRayPickAction from within an SoHandleEventAction callback with
128 // the getNodeAppliedTo() method etc. Include a usage example code
129 // snippet. 20010920 mortene.
130
131 #include <Inventor/actions/SoRayPickAction.h>
132
133 #include <cfloat>
134
135 #include <Inventor/SbLine.h>
136 #include <Inventor/SoPickedPoint.h>
137 #include <Inventor/elements/SoClipPlaneElement.h>
138 #include <Inventor/elements/SoModelMatrixElement.h>
139 #include <Inventor/elements/SoOverrideElement.h>
140 #include <Inventor/elements/SoTextureOverrideElement.h>
141 #include <Inventor/elements/SoPickRayElement.h>
142 #include <Inventor/elements/SoPickStyleElement.h>
143 #include <Inventor/elements/SoShapeHintsElement.h>
144 #include <Inventor/elements/SoViewVolumeElement.h>
145 #include <Inventor/elements/SoViewportRegionElement.h>
146 #include <Inventor/lists/SoEnabledElementsList.h>
147 #include <Inventor/lists/SoPickedPointList.h>
148 #include <Inventor/misc/SoState.h>
149 #include <Inventor/nodes/SoCamera.h>
150 #include <Inventor/nodes/SoLOD.h>
151 #include <Inventor/nodes/SoLevelOfDetail.h>
152 #include <Inventor/nodes/SoSeparator.h>
153 #include <Inventor/nodes/SoShape.h>
154 #include <Inventor/SbVec3d.h>
155 #include <Inventor/SbVec2d.h>
156 #include <Inventor/SbDPLine.h>
157 #include <Inventor/SbDPPlane.h>
158 #include <Inventor/SbDPMatrix.h>
159 #if COIN_DEBUG
160 #include <Inventor/errors/SoDebugError.h>
161 #endif // COIN_DEBUG
162
163 #include "actions/SoSubActionP.h"
164
165
166
167 // *************************************************************************
168
169 // The private data for the SoRayPickAction.
170
171 class SoRayPickActionP {
172 public:
SoRayPickActionP(void)173 SoRayPickActionP(void) : owner(NULL) { }
174
175 // Hidden private methods.
176
177 SbBool isBetweenPlanesWS(const SbVec3d & intersection,
178 const SoClipPlaneElement * planes) const;
179 void cleanupPickedPoints(void);
180 void setFlag(const unsigned int flag);
181 void clearFlag(const unsigned int flag);
182 SbBool isFlagSet(const unsigned int flag) const;
183 void calcObjectSpaceData(SoState * ownerstate);
184 void calcMatrices(SoState * ownerstate);
185 void setPickStyleFlags(SoState * ownerstate);
186
187 // Hidden private variables.
188
189 SbViewVolume osvolume;
190 SbViewVolume wsvolume;
191 SbLine osline_sp;
192
193 // use double precision types to increase picking precision
194 SbDPLine osline;
195 SbDPPlane nearplane;
196 SbVec2s vppoint;
197 SbVec2f normvppoint;
198 SbVec3d raystart;
199 SbVec3d raydirection;
200 double rayradiusstart;
201 double rayradiusdelta;
202 double raynear;
203 double rayfar;
204 float radiusinpixels;
205
206 SbDPLine wsline;
207 SbDPMatrix obj2world;
208 SbDPMatrix world2obj;
209 SbDPMatrix extramatrix;
210
211 SoPickedPointList pickedpointlist;
212 SbList <double> ppdistance;
213
214 unsigned int flags;
215 SbBool objectspacevalid; // FIXME: why not a flag?
216
217 enum {
218 WS_RAY_SET = 0x0001, // ray set by setRay()
219 WS_RAY_COMPUTED = 0x0002, // ray computed in computeWorldSpaceRay()
220 PICK_ALL = 0x0004, // return all picked objects, or just closest
221 NORM_POINT = 0x0008, // is normalized vppoint calculated
222 CLIP_NEAR = 0x0010, // clip ray at near plane?
223 CLIP_FAR = 0x0020, // clip ray at far plane?
224 EXTRA_MATRIX = 0x0040, // is extra matrix supplied in setObjectSpace()
225 PPLIST_IS_SORTED = 0x0080, // did we sort pickedpointslist ?
226 OSVOLUME_DIRTY = 0x0100, // did we calculate osvolume?
227 PUSH_PICK_TO_FRONT = 0x0200, // should pick go in front?
228 CULL_BACKFACES = 0x0400 // should backface picks be ignored?
229 };
230
231 SoRayPickAction * owner;
232 };
233
234 #define PRIVATE(obj) ((obj)->pimpl)
235
236 // *************************************************************************
237
238 SO_ACTION_SOURCE(SoRayPickAction);
239
240
241 // Override from parent class.
242 void
initClass(void)243 SoRayPickAction::initClass(void)
244 {
245 SO_ACTION_INTERNAL_INIT_CLASS(SoRayPickAction, SoPickAction);
246
247 SO_ENABLE(SoRayPickAction, SoPickRayElement);
248 SO_ENABLE(SoRayPickAction, SoViewportRegionElement);
249 SO_ENABLE(SoRayPickAction, SoOverrideElement);
250 SO_ENABLE(SoRayPickAction, SoTextureOverrideElement);
251 }
252
253
254 /*!
255 Constructor.
256
257 Some node types need a \a viewportregion to know exactly how they
258 are positioned within the scene. For an in-depth explanation of why
259 the \a viewportregion argument is needed, see the documentation of
260 SoGetBoundingBox::SoGetBoundingBox(const SbViewportRegion &).
261 */
SoRayPickAction(const SbViewportRegion & viewportregion)262 SoRayPickAction::SoRayPickAction(const SbViewportRegion & viewportregion)
263 : inherited(viewportregion)
264 {
265 PRIVATE(this)->owner = this;
266 PRIVATE(this)->radiusinpixels = 5.0f;
267 PRIVATE(this)->flags = 0;
268 PRIVATE(this)->objectspacevalid = TRUE;
269
270 SO_ACTION_CONSTRUCTOR(SoRayPickAction);
271 }
272
273 /*!
274 Destructor, free temporary resources used by action.
275 */
~SoRayPickAction(void)276 SoRayPickAction::~SoRayPickAction(void)
277 {
278 PRIVATE(this)->cleanupPickedPoints();
279 }
280
281 /*!
282 Sets the viewport-space point. This point is calculated into a line
283 from the near clipping plane to the far clipping plane, and the
284 intersection ray follows the line.
285
286 This is a convenient way to detect object intersection below the
287 cursor.
288 */
289 void
setPoint(const SbVec2s & viewportpoint)290 SoRayPickAction::setPoint(const SbVec2s & viewportpoint)
291 {
292 PRIVATE(this)->vppoint = viewportpoint;
293 PRIVATE(this)->clearFlag(SoRayPickActionP::NORM_POINT |
294 SoRayPickActionP::WS_RAY_SET |
295 SoRayPickActionP::WS_RAY_COMPUTED);
296 PRIVATE(this)->setFlag(SoRayPickActionP::CLIP_NEAR |
297 SoRayPickActionP::CLIP_FAR);
298 }
299
300 /*!
301 Sets the viewport-space point which the ray is sent through.
302 The coordinate is normalized, ranging from (0, 0) to (1, 1).
303
304 \sa setPoint()
305 */
306 void
setNormalizedPoint(const SbVec2f & normpoint)307 SoRayPickAction::setNormalizedPoint(const SbVec2f & normpoint)
308 {
309 PRIVATE(this)->normvppoint = normpoint;
310 PRIVATE(this)->clearFlag(SoRayPickActionP::WS_RAY_SET |
311 SoRayPickActionP::WS_RAY_COMPUTED);
312 PRIVATE(this)->setFlag(SoRayPickActionP::NORM_POINT |
313 SoRayPickActionP::CLIP_NEAR |
314 SoRayPickActionP::CLIP_FAR);
315 }
316
317 /*!
318 Sets the radius of the picking ray, in screen pixels. Default value
319 is 5.0.
320
321 The radius of the intersection ray will only influence the pick
322 operation's behavior versus lines and points, and has no effect on
323 picking of shapes / polygons.
324 */
325 void
setRadius(const float radiusinpixels)326 SoRayPickAction::setRadius(const float radiusinpixels)
327 {
328 PRIVATE(this)->radiusinpixels = radiusinpixels;
329 }
330
331
332 /*!
333 Gets the radius of the picking ray, in screen pixels.
334 */
335 float
getRadius(void) const336 SoRayPickAction::getRadius(void) const
337 {
338 return PRIVATE(this)->radiusinpixels;
339 }
340
341 /*!
342 Sets the intersection ray in world-space coordinates.
343
344 Use this method if you want to send any ray through the scene to
345 detect intersections, independently of mouse cursor position upon
346 clicks and scene graph camera settings.
347 */
348 void
setRay(const SbVec3f & start,const SbVec3f & direction,float neardistance,float fardistance)349 SoRayPickAction::setRay(const SbVec3f & start, const SbVec3f & direction,
350 float neardistance, float fardistance)
351 {
352 #if COIN_DEBUG
353 if (direction == SbVec3f(0.0f, 0.0f, 0.0f)) {
354 SoDebugError::postWarning("SoRayPickAction::setRay",
355 "Ray has no direction");
356
357 }
358 #endif // COIN_DEBUG
359 if (neardistance >= 0.0f) PRIVATE(this)->setFlag(SoRayPickActionP::CLIP_NEAR);
360 else {
361 PRIVATE(this)->clearFlag(SoRayPickActionP::CLIP_NEAR);
362 neardistance = 1.0f;
363 // make sure neardistance is smaller than fardistance
364 if (fardistance > 0.0f && neardistance >= fardistance) {
365 neardistance = fardistance * 0.01f;
366 }
367 }
368
369 if (fardistance >= 0.0f) PRIVATE(this)->setFlag(SoRayPickActionP::CLIP_FAR);
370 else {
371 PRIVATE(this)->clearFlag(SoRayPickActionP::CLIP_FAR);
372 // just set to some value bigger than neardistance.
373 fardistance = neardistance + 10.0f;
374 }
375
376 // set these to some values. They will be set to better values
377 // in computeWorldSpaceRay() (when we know the view volume).
378 PRIVATE(this)->rayradiusstart = 0.01;
379 PRIVATE(this)->rayradiusdelta = 0.0;
380
381 PRIVATE(this)->raystart.setValue(start);
382 PRIVATE(this)->raydirection.setValue(direction);
383 (void) PRIVATE(this)->raydirection.normalize();
384 PRIVATE(this)->raynear = neardistance;
385 PRIVATE(this)->rayfar = fardistance;
386 PRIVATE(this)->wsline = SbDPLine(PRIVATE(this)->raystart,
387 PRIVATE(this)->raystart + PRIVATE(this)->raydirection);
388
389 // D = shortest distance from origin to plane
390 const double D = PRIVATE(this)->raydirection.dot(PRIVATE(this)->raystart);
391 PRIVATE(this)->nearplane = SbDPPlane(PRIVATE(this)->raydirection, D + PRIVATE(this)->raynear);
392
393 PRIVATE(this)->setFlag(SoRayPickActionP::WS_RAY_SET);
394
395 // We use a real cone for picking, but keep pick view volume in sync to be
396 // compatible with OIV
397 PRIVATE(this)->wsvolume.perspective(0.0, 1.0, neardistance, fardistance);
398 PRIVATE(this)->wsvolume.translateCamera(start);
399 PRIVATE(this)->wsvolume.rotateCamera(SbRotation(SbVec3f(0.0f, 0.0f, -1.0f), direction));
400 PRIVATE(this)->setFlag(SoRayPickActionP::OSVOLUME_DIRTY);
401 }
402
403 /*!
404 Lets you decide whether or not all the objects the ray intersects
405 with should be picked. If not, only the intersection point of the
406 object closest to the camera will be picked.
407
408 Default value of the "pick all" flag is \c FALSE.
409 */
410 void
setPickAll(const SbBool flag)411 SoRayPickAction::setPickAll(const SbBool flag)
412 {
413 if (flag) PRIVATE(this)->setFlag(SoRayPickActionP::PICK_ALL);
414 else PRIVATE(this)->clearFlag(SoRayPickActionP::PICK_ALL);
415 }
416
417 /*!
418 Returns whether only the closest object or all the objects the ray
419 intersects with is picked.
420
421 \sa setPickAll()
422 */
423 SbBool
isPickAll(void) const424 SoRayPickAction::isPickAll(void) const
425 {
426 return PRIVATE(this)->isFlagSet(SoRayPickActionP::PICK_ALL);
427 }
428
429 /*!
430 Returns a list of the picked points.
431 */
432 const SoPickedPointList &
getPickedPointList(void) const433 SoRayPickAction::getPickedPointList(void) const
434 {
435 int n = PRIVATE(this)->pickedpointlist.getLength();
436 if (!PRIVATE(this)->isFlagSet(SoRayPickActionP::PPLIST_IS_SORTED) && n > 1) {
437 SoPickedPoint ** pparray = reinterpret_cast<SoPickedPoint **>(PRIVATE(this)->pickedpointlist.getArrayPtr());
438 double * darray = const_cast<double*>(PRIVATE(this)->ppdistance.getArrayPtr());
439
440 int i, j, distance;
441 SoPickedPoint * pptmp;
442 double dtmp;
443
444 // shell sort algorithm (O(nlog(n))
445 for (distance = 1; distance <= n/9; distance = 3*distance + 1) ;
446 for (; distance > 0; distance /= 3) {
447 for (i = distance; i < n; i++) {
448 dtmp = darray[i];
449 pptmp = pparray[i];
450 j = i;
451 while (j >= distance && darray[j-distance] > dtmp) {
452 darray[j] = darray[j-distance];
453 pparray[j] = pparray[j-distance];
454 j -= distance;
455 }
456 darray[j] = dtmp;
457 pparray[j] = pptmp;
458 }
459 }
460 SoRayPickActionP * thisp =
461 const_cast<SoRayPickActionP *>(&PRIVATE(this).get());
462 thisp->setFlag(SoRayPickActionP::PPLIST_IS_SORTED);
463 }
464
465 return PRIVATE(this)->pickedpointlist;
466 }
467
468 /*!
469 Returns the picked point with \a index in the list of picked points.
470
471 Returns \c NULL if less than \a index + 1 points where picked during
472 the last raypick action.
473 */
474 SoPickedPoint *
getPickedPoint(const int index) const475 SoRayPickAction::getPickedPoint(const int index) const
476 {
477 assert(index >= 0);
478 if (index < PRIVATE(this)->pickedpointlist.getLength()) {
479 return this->getPickedPointList()[index];
480 }
481 return NULL;
482 }
483
484 /*!
485 \COININTERNAL
486 */
487 void
computeWorldSpaceRay(void)488 SoRayPickAction::computeWorldSpaceRay(void)
489 {
490 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::WS_RAY_SET)) {
491 // set the ray radius to some very small value, since
492 // the user set the ray manually using setRay().
493 //
494 // FIXME: Wouldn't it be a nice new feature to be able to
495 // set the radius of the ray in setRay()? pederb, 2001-01-05
496 const SbViewVolume & vv = SoViewVolumeElement::get(this->state);
497 PRIVATE(this)->rayradiusstart = SbMin(vv.getWidth(), vv.getHeight()) * FLT_EPSILON;
498 PRIVATE(this)->rayradiusdelta = 0.0f;
499 }
500 else {
501 const SbViewVolume & vv = SoViewVolumeElement::get(this->state);
502 const SbViewportRegion & vp = SoViewportRegionElement::get(this->state);
503
504 if (!PRIVATE(this)->isFlagSet(SoRayPickActionP::NORM_POINT)) {
505 SbVec2s pt = PRIVATE(this)->vppoint - vp.getViewportOriginPixels();
506 SbVec2s size = vp.getViewportSizePixels();
507 PRIVATE(this)->normvppoint.setValue(float(pt[0]) / float(size[0]),
508 float(pt[1]) / float(size[1]));
509 }
510
511 #if COIN_DEBUG
512 if (vv.getDepth() == 0.0f || vv.getWidth() == 0.0f || vv.getHeight() == 0.0f) {
513 SoDebugError::postWarning("SoRayPickAction::computeWorldSpaceRay",
514 "invalid frustum: <%f, %f, %f>",
515 vv.getWidth(), vv.getHeight(), vv.getDepth());
516 return;
517 }
518 #endif // COIN_DEBUG
519
520 SbDPLine templine;
521 SbVec2d tmppt;
522 tmppt.setValue(PRIVATE(this)->normvppoint);
523 vv.getDPViewVolume().projectPointToLine(tmppt, templine);
524 PRIVATE(this)->raystart = templine.getPosition();
525 PRIVATE(this)->raydirection = templine.getDirection();
526
527 PRIVATE(this)->raynear = 0.0;
528 PRIVATE(this)->rayfar = vv.getDPViewVolume().getDepth();
529
530 SbVec2s vpsize = vp.getViewportSizePixels();
531 PRIVATE(this)->rayradiusstart = (double(vv.getHeight()) / double(vpsize[1]))*
532 double(PRIVATE(this)->radiusinpixels);
533 PRIVATE(this)->rayradiusdelta = 0.0;
534 if (vv.getProjectionType() == SbViewVolume::PERSPECTIVE) {
535 SbVec3d dir(0.0f, vv.getHeight()*0.5f, vv.getNearDist());
536 // no need to test here, we know vv isn't empty
537 (void) dir.normalize();
538 SbVec3d upperfar = dir * (vv.getNearDist()+vv.getDepth()) /
539 dir.dot(SbVec3d(0.0f, 0.0f, 1.0f));
540
541 double farheight = double(upperfar[1])*2.0;
542 double farsize = (farheight / double(vpsize[1])) * double(PRIVATE(this)->radiusinpixels);
543 PRIVATE(this)->rayradiusdelta = (farsize - PRIVATE(this)->rayradiusstart) / double(vv.getDepth());
544 }
545 PRIVATE(this)->wsline = SbDPLine(PRIVATE(this)->raystart,
546 PRIVATE(this)->raystart + PRIVATE(this)->raydirection);
547
548 PRIVATE(this)->nearplane = SbDPPlane(vv.getDPViewVolume().getProjectionDirection(),
549 PRIVATE(this)->raystart);
550 PRIVATE(this)->setFlag(SoRayPickActionP::WS_RAY_COMPUTED);
551
552 // we pick on a real cone, but keep pick view volume in sync to be
553 // compatible with OIV.
554 double normradius = double(PRIVATE(this)->radiusinpixels) /
555 double(SbMin(vp.getViewportSizePixels()[0], vp.getViewportSizePixels()[1]));
556
557 PRIVATE(this)->wsvolume = vv.narrow(float(PRIVATE(this)->normvppoint[0] - normradius),
558 float(PRIVATE(this)->normvppoint[1] - normradius),
559 float(PRIVATE(this)->normvppoint[0] + normradius),
560 float(PRIVATE(this)->normvppoint[1] + normradius));
561 SoPickRayElement::set(state, PRIVATE(this)->wsvolume);
562 PRIVATE(this)->setFlag(SoRayPickActionP::OSVOLUME_DIRTY);
563 }
564 }
565
566 /*!
567 \COININTERNAL
568 */
569 SbBool
hasWorldSpaceRay(void) const570 SoRayPickAction::hasWorldSpaceRay(void) const
571 {
572 return PRIVATE(this)->isFlagSet(SoRayPickActionP::WS_RAY_SET|SoRayPickActionP::WS_RAY_COMPUTED);
573 }
574
575 /*!
576 \COININTERNAL
577 */
578 void
setObjectSpace(void)579 SoRayPickAction::setObjectSpace(void)
580 {
581 PRIVATE(this)->clearFlag(SoRayPickActionP::EXTRA_MATRIX);
582 PRIVATE(this)->calcObjectSpaceData(this->state);
583 PRIVATE(this)->setPickStyleFlags(this->state);
584 }
585
586 /*!
587 \COININTERNAL
588 */
589 void
setObjectSpace(const SbMatrix & matrix)590 SoRayPickAction::setObjectSpace(const SbMatrix & matrix)
591 {
592 PRIVATE(this)->setFlag(SoRayPickActionP::EXTRA_MATRIX);
593 PRIVATE(this)->extramatrix = SbDPMatrix(matrix);
594 PRIVATE(this)->calcObjectSpaceData(this->state);
595 PRIVATE(this)->setPickStyleFlags(this->state);
596 }
597
598 /*!
599 \COININTERNAL
600 */
601 SbBool
intersect(const SbVec3f & v0_in,const SbVec3f & v1_in,const SbVec3f & v2_in,SbVec3f & intersection,SbVec3f & barycentric,SbBool & front) const602 SoRayPickAction::intersect(const SbVec3f & v0_in,
603 const SbVec3f & v1_in,
604 const SbVec3f & v2_in,
605 SbVec3f & intersection, SbVec3f & barycentric,
606 SbBool & front) const
607 {
608 // Calculating intersections when we have a degenerate transform
609 // makes no sense. We could do the intersection calculations in
610 // world space, but it is impossible to calculate the object space
611 // intersection point, so we just return FALSE.
612 if (!PRIVATE(this)->objectspacevalid) return FALSE;
613
614 SbVec3d v0,v1,v2;
615 v0.setValue(v0_in);
616 v1.setValue(v1_in);
617 v2.setValue(v2_in);
618
619 const SbVec3d & orig = PRIVATE(this)->osline.getPosition();
620 const SbVec3d & dir = PRIVATE(this)->osline.getDirection();
621
622 SbVec3d edge1 = v1 - v0;
623 SbVec3d edge2 = v2 - v0;
624
625 SbVec3d pvec = dir.cross(edge2);
626
627 // if determinant is near zero, ray lies in plane of triangle
628 double det = edge1.dot(pvec);
629 if (fabs(det) < DBL_EPSILON) return FALSE;
630
631 // does ray hit front or back of triangle
632 if (det > 0.0) front = TRUE;
633 else front = FALSE;
634
635 // create some more intuitive barycentric coordinate names
636 double u, v, w;
637 double inv_det = 1.0 / det;
638
639 // calculate distance from v0 to ray origin
640 SbVec3d tvec = orig - v0;
641
642 // calculate U parameter and test bounds
643 u = tvec.dot(pvec) * inv_det;
644 if (u < 0.0 || u > 1.0)
645 return FALSE;
646
647 // prepare to test V parameter
648 SbVec3d qvec = tvec.cross(edge1);
649
650 // calculate V parameter and test bounds
651 v = dir.dot(qvec) * inv_det;
652 if (v < 0.0 || u + v > 1.0)
653 return FALSE;
654
655 // third barycentric coordinate
656 w = 1.0 - u - v;
657
658 // calculate t and intersection point
659 double t = edge2.dot(qvec) * inv_det;
660
661 SbVec3d itmp = orig + t * dir;
662 intersection.setValue(itmp);
663
664 // set the barycentric coordinates before returning
665 barycentric[0] = static_cast<float>(w);
666 barycentric[1] = static_cast<float>(u);
667 barycentric[2] = static_cast<float>(v);
668
669 return TRUE;
670 }
671
672 /*!
673 \COININTERNAL
674 */
675 SbBool
intersect(const SbVec3f & v0_in,const SbVec3f & v1_in,SbVec3f & intersection) const676 SoRayPickAction::intersect(const SbVec3f & v0_in, const SbVec3f & v1_in,
677 SbVec3f & intersection) const
678 {
679 // Calculating intersections when we have a degenerate transform
680 // makes no sense. We could do the intersection calculations in
681 // world space, but it is impossible to calculate the object space
682 // intersection point, so we just return FALSE.
683 if (!PRIVATE(this)->objectspacevalid) return FALSE;
684
685 SbVec3d v0, v1;
686 v0.setValue(v0_in);
687 v1.setValue(v1_in);
688
689 // test if we have a valid line, and do point intersection testing
690 // if we don't
691 if (v0 == v1) {
692 intersection = v0_in;
693 // this might return TRUE or FALSE. We already set the
694 // intersection point.
695 return this->intersect(v0_in);
696 }
697
698 SbDPLine line(v0, v1);
699 SbVec3d op0, op1; // object space
700 SbVec3d p0, p1; // world space
701
702 if (!PRIVATE(this)->osline.getClosestPoints(line, op0, op1)) return FALSE;
703
704 // clamp op1 between v0 and v1
705 if ((op1-v0).dot(line.getDirection()) < 0.0) op1 = v0;
706 else if ((v1-op1).dot(line.getDirection()) < 0.0) op1 = v1;
707
708 PRIVATE(this)->obj2world.multVecMatrix(op0, p0);
709 PRIVATE(this)->obj2world.multVecMatrix(op1, p1);
710
711 // distance between points
712 double distance = (p1-p0).length();
713
714 double raypos = PRIVATE(this)->nearplane.getDistance(p0);
715
716 double radius = static_cast<float>((PRIVATE(this)->rayradiusstart +
717 PRIVATE(this)->rayradiusdelta * raypos));
718
719 if (radius >= distance) {
720 intersection.setValue(op1);
721 return TRUE;
722 }
723 return FALSE;
724 }
725
726 /*!
727 \COININTERNAL
728 */
729 SbBool
intersect(const SbVec3f & point_in) const730 SoRayPickAction::intersect(const SbVec3f & point_in) const
731 {
732 // Calculating intersections when we have a degenerate transform
733 // makes no sense. We could do the intersection calculations in
734 // world space, but it is impossible to calculate the object space
735 // intersection point, so we just return FALSE.
736 if (!PRIVATE(this)->objectspacevalid) return FALSE;
737
738 SbVec3d point;
739 point.setValue(point_in);
740
741 SbVec3d wpoint;
742 PRIVATE(this)->obj2world.multVecMatrix(point, wpoint);
743 SbVec3d ptonline = PRIVATE(this)->wsline.getClosestPoint(wpoint);
744
745 // distance between points
746 double distance = (wpoint-ptonline).length();
747
748 double raypos = PRIVATE(this)->nearplane.getDistance(ptonline);
749
750 double radius = static_cast<double>((PRIVATE(this)->rayradiusstart +
751 PRIVATE(this)->rayradiusdelta * raypos));
752
753 return (radius >= distance);
754 }
755
756 // calculates the square distance (smallest possible) from a 2D point
757 // to a 2D rectangle
758 static double
dist_to_quad(const double xmin,const double ymin,const double xmax,const double ymax,const double x,const double y,double & cx,double & cy)759 dist_to_quad(const double xmin, const double ymin,
760 const double xmax, const double ymax,
761 const double x, const double y,
762 double & cx, double & cy)
763 {
764 if (x < xmin) {
765 if (y < ymin) {
766 cx = xmin;
767 cy = ymin;
768 return (x-xmin)*(x-xmin) + (y-ymin)*(y-ymin);
769 }
770 else if (y > ymax) {
771 cx = xmin;
772 cy = ymax;
773 return (x-xmin)*(x-xmin) + (y-ymax)*(y-ymax);
774 }
775 else {
776 cx = xmin;
777 cy = y;
778 return (x-xmin)*(x-xmin);
779 }
780 }
781 else if (x > xmax) {
782 if (y < ymin) {
783 cx = xmax;
784 cy = ymin;
785 return (x-xmax)*(x-xmax) + (y-ymin) * (y-ymin);
786 }
787 else if (y > ymax) {
788 cx = xmax;
789 cy = ymax;
790 return (x-xmax)*(x-xmax) + (y-ymax)*(y-ymax);
791 }
792 else {
793 cx = xmax;
794 cy = y;
795 return (x-xmax)*(x-xmax);
796 }
797 }
798 else {
799 if (y < ymin) {
800 cx = x;
801 cy = ymin;
802 return (y-ymin)*(y-ymin);
803 }
804 else if (y > ymax) {
805 cx = x;
806 cy = ymax;
807 return (y-ymax)*(y-ymax);
808 }
809 else {
810 // inside rectangle
811 cx = x;
812 cy = y;
813 return -1.0;
814 }
815 }
816 }
817
818 /*!
819 \COININTERNAL
820 */
821 SbBool
intersect(const SbBox3f & box,SbVec3f & intersection,const SbBool usefullviewvolume)822 SoRayPickAction::intersect(const SbBox3f & box, SbVec3f & intersection,
823 const SbBool usefullviewvolume)
824 {
825 // Calculating intersections when we have a degenerate transform
826 // makes no sense. We could do the intersection calculations in
827 // world space, but it is impossible to calculate the object space
828 // intersection point, so we just return FALSE.
829 if (!PRIVATE(this)->objectspacevalid) return FALSE;
830
831 const SbDPLine & line = PRIVATE(this)->osline;
832 SbVec3d bounds[2];
833 bounds[0].setValue(box.getMin());
834 bounds[1].setValue(box.getMax());
835
836 SbVec3d ptonray, ptonbox;
837 double sqrmindist = DBL_MAX;
838
839 SbBool conepick = usefullviewvolume && !PRIVATE(this)->isFlagSet(SoRayPickActionP::WS_RAY_SET);
840
841 int i;
842
843 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::CLIP_NEAR|SoRayPickActionP::CLIP_FAR)) {
844 // check if all points are in front of the near or behind the far
845 // clipping plane
846 int numnear = 0;
847 int numfar = 0;
848
849 for (i = 0; i < 8; i++) {
850 SbVec3d bp(i&1 ? bounds[0][0] : bounds[1][0],
851 i&2 ? bounds[0][1] : bounds[1][1],
852 i&4 ? bounds[0][2] : bounds[1][2]);
853 PRIVATE(this)->obj2world.multVecMatrix(bp, bp);
854 double dist = PRIVATE(this)->nearplane.getDistance(bp);
855 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::CLIP_NEAR)) {
856 if (dist < 0.0) numnear++;
857 }
858 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::CLIP_FAR)) {
859 if (dist > (PRIVATE(this)->rayfar - PRIVATE(this)->raynear)) numfar++;
860 }
861 if ((numnear < i) && (numfar < i)) break;
862 }
863 if (numnear == 8 || numfar == 8) return FALSE;
864 }
865
866 for (int j = 0; j < 2; j++) {
867 for (i = 0; i < 3; i++) {
868 SbVec3d norm(0.0f, 0.0f, 0.0f);
869 norm[i] = 1.0f;
870 SbVec3d isect;
871
872 SbDPPlane plane(norm, bounds[j][i]);
873 if (plane.intersect(line, isect)) {
874 int i1 = (i+1) % 3;
875 int i2 = (i+2) % 3;
876 double x, y;
877
878 double d = dist_to_quad(bounds[0][i1], bounds[0][i2],
879 bounds[1][i1], bounds[1][i2],
880 isect[i1], isect[i2],
881 x, y);
882 if (d <= 0.0f) {
883 // center of ray hit box directly
884 intersection.setValue(isect);
885 return TRUE;
886 }
887 else if (d < sqrmindist) {
888 sqrmindist = d;
889 ptonray = ptonbox = isect;
890 ptonbox[i1] = x;
891 ptonbox[i2] = y;
892 }
893 }
894 }
895 }
896 if (sqrmindist != DBL_MAX && conepick) {
897 // transform ptonray and ptonbox to world space to test on ray cone
898 SbVec3d wptonray, wptonbox;
899 PRIVATE(this)->obj2world.multVecMatrix(ptonbox, wptonbox);
900 PRIVATE(this)->obj2world.multVecMatrix(ptonray, wptonray);
901
902 double raypos = PRIVATE(this)->nearplane.getDistance(wptonray);
903 double distance = (wptonray-wptonbox).length();
904
905 // find ray radius at wptonray
906 double radius = static_cast<float>((PRIVATE(this)->rayradiusstart +
907 PRIVATE(this)->rayradiusdelta * raypos));
908
909 // test for cone intersection
910 if (radius >= distance) {
911 intersection.setValue(ptonbox); // set intersection to the point on box closest to ray
912 return TRUE;
913 }
914 }
915 return FALSE;
916 }
917
918
919 /*!
920 \COININTERNAL
921 */
922 SbBool
intersect(const SbBox3f & box,const SbBool usefullviewvolume)923 SoRayPickAction::intersect(const SbBox3f & box, const SbBool usefullviewvolume)
924 {
925 SbVec3f dummy;
926 return this->intersect(box, dummy, usefullviewvolume);
927 }
928
929 /*!
930 \COININTERNAL
931 */
932 const SbViewVolume &
getViewVolume(void)933 SoRayPickAction::getViewVolume(void)
934 {
935 if (PRIVATE(this)->objectspacevalid &&
936 PRIVATE(this)->isFlagSet(SoRayPickActionP::OSVOLUME_DIRTY)) {
937 // we pick on a real cone, but calculate pick view volume
938 // to be compatible with OIV.
939 PRIVATE(this)->osvolume = SoPickRayElement::get(this->getState());
940 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::EXTRA_MATRIX)) {
941 SbDPMatrix m = PRIVATE(this)->world2obj * PRIVATE(this)->extramatrix;
942 SbMatrix tmp(
943 static_cast<float>(m[0][0]), static_cast<float>(m[0][1]),
944 static_cast<float>(m[0][2]), static_cast<float>(m[0][3]),
945
946 static_cast<float>(m[1][0]), static_cast<float>(m[1][1]),
947 static_cast<float>(m[1][2]), static_cast<float>(m[1][3]),
948
949 static_cast<float>(m[2][0]), static_cast<float>(m[2][1]),
950 static_cast<float>(m[2][2]), static_cast<float>(m[2][3]),
951
952 static_cast<float>(m[3][0]), static_cast<float>(m[3][1]),
953 static_cast<float>(m[3][2]), static_cast<float>(m[3][3])
954 );
955
956 PRIVATE(this)->osvolume.transform(tmp);
957 }
958 else {
959 const SbDPMatrix & m = PRIVATE(this)->world2obj;
960 SbMatrix tmp(
961 static_cast<float>(m[0][0]), static_cast<float>(m[0][1]),
962 static_cast<float>(m[0][2]), static_cast<float>(m[0][3]),
963
964 static_cast<float>(m[1][0]), static_cast<float>(m[1][1]),
965 static_cast<float>(m[1][2]), static_cast<float>(m[1][3]),
966
967 static_cast<float>(m[2][0]), static_cast<float>(m[2][1]),
968 static_cast<float>(m[2][2]), static_cast<float>(m[2][3]),
969
970 static_cast<float>(m[3][0]), static_cast<float>(m[3][1]),
971 static_cast<float>(m[3][2]), static_cast<float>(m[3][3])
972 );
973
974
975 PRIVATE(this)->osvolume.transform(tmp);
976 }
977 PRIVATE(this)->clearFlag(SoRayPickActionP::OSVOLUME_DIRTY);
978 }
979 return PRIVATE(this)->osvolume;
980 }
981
982 /*!
983 \COININTERNAL
984 */
985 const SbLine &
getLine(void)986 SoRayPickAction::getLine(void)
987 {
988 return PRIVATE(this)->osline_sp;
989 }
990
991 /*!
992 \COININTERNAL
993 */
994 SbBool
isBetweenPlanes(const SbVec3f & intersection_in) const995 SoRayPickAction::isBetweenPlanes(const SbVec3f & intersection_in) const
996 {
997 SbVec3d intersection;
998 intersection.setValue(intersection_in);
999 SbVec3d worldpoint;
1000 PRIVATE(this)->obj2world.multVecMatrix(intersection, worldpoint);
1001 return PRIVATE(this)->isBetweenPlanesWS(worldpoint,
1002 SoClipPlaneElement::getInstance(this->state));
1003 }
1004
1005 /*!
1006 \COININTERNAL
1007 */
1008 SoPickedPoint *
addIntersection(const SbVec3f & objectspacepoint_in,SbBool frontpick)1009 SoRayPickAction::addIntersection(const SbVec3f & objectspacepoint_in, SbBool frontpick)
1010 {
1011 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::CULL_BACKFACES) && !frontpick)
1012 return NULL;
1013
1014 SbVec3d objectspacepoint;
1015 objectspacepoint.setValue(objectspacepoint_in);
1016 SbVec3d worldpoint;
1017 PRIVATE(this)->obj2world.multVecMatrix(objectspacepoint, worldpoint);
1018 double dist = PRIVATE(this)->isFlagSet(SoRayPickActionP::PUSH_PICK_TO_FRONT) ?
1019 0.0 : PRIVATE(this)->nearplane.getDistance(worldpoint);
1020
1021 if (!PRIVATE(this)->isFlagSet(SoRayPickActionP::PICK_ALL) && PRIVATE(this)->pickedpointlist.getLength()) {
1022 // got to test if new candidate is closer than old one
1023 if (dist >= PRIVATE(this)->ppdistance[0]) return NULL; // farther
1024 // remove old point
1025 PRIVATE(this)->pickedpointlist.truncate(0);
1026 PRIVATE(this)->ppdistance.truncate(0);
1027 }
1028
1029 // create the new picked point
1030 SoPickedPoint * pp = new SoPickedPoint(this->getCurPath(),
1031 this->state, objectspacepoint_in);
1032 PRIVATE(this)->pickedpointlist.append(pp);
1033 PRIVATE(this)->ppdistance.append(dist);
1034 PRIVATE(this)->clearFlag(SoRayPickActionP::PPLIST_IS_SORTED);
1035 return pp;
1036 }
1037
1038 /*!
1039 Truncates the internal picked points list.
1040
1041 \since Coin 2.2
1042 */
1043 void
reset(void)1044 SoRayPickAction::reset(void)
1045 {
1046 PRIVATE(this)->cleanupPickedPoints();
1047 }
1048
1049 // Documented in superclass.
1050 void
beginTraversal(SoNode * node)1051 SoRayPickAction::beginTraversal(SoNode * node)
1052 {
1053 PRIVATE(this)->cleanupPickedPoints();
1054 this->getState()->push();
1055 SoViewportRegionElement::set(this->getState(), this->vpRegion);
1056
1057 if (PRIVATE(this)->isFlagSet(SoRayPickActionP::WS_RAY_SET)) {
1058 SoPickRayElement::set(state, PRIVATE(this)->wsvolume);
1059 }
1060 inherited::beginTraversal(node);
1061 this->getState()->pop();
1062 }
1063
1064
1065 //////// Hidden private methods for //////////////////////////////////////
1066 //////// SoRayPickActionP (pimpl) ////////////////////////////////////////
1067
1068 SbBool
isBetweenPlanesWS(const SbVec3d & intersection,const SoClipPlaneElement * planes) const1069 SoRayPickActionP::isBetweenPlanesWS(const SbVec3d & intersection,
1070 const SoClipPlaneElement * planes) const
1071 {
1072 SbVec3f isect_f;
1073 isect_f.setValue(intersection);
1074 double dist = this->nearplane.getDistance(intersection);
1075 if (this->isFlagSet(CLIP_NEAR)) {
1076 if (dist < 0) return FALSE;
1077 }
1078 if (this->isFlagSet(CLIP_FAR)) {
1079 if (dist > (this->rayfar - this->raynear)) return FALSE;
1080 }
1081 int n = planes->getNum();
1082 for (int i = 0; i < n; i++) {
1083 if (!planes->get(i).isInHalfSpace(isect_f)) return FALSE;
1084 }
1085 return TRUE;
1086 }
1087
1088 void
cleanupPickedPoints(void)1089 SoRayPickActionP::cleanupPickedPoints(void)
1090 {
1091 this->pickedpointlist.truncate(0); // this will delete all SoPickedPoint instances in the list
1092 this->ppdistance.truncate(0);
1093 this->clearFlag(PPLIST_IS_SORTED);
1094 }
1095
1096 void
setFlag(const unsigned int flag)1097 SoRayPickActionP::setFlag(const unsigned int flag)
1098 {
1099 this->flags |= flag;
1100 }
1101
1102 void
clearFlag(const unsigned int flag)1103 SoRayPickActionP::clearFlag(const unsigned int flag)
1104 {
1105 this->flags &= ~flag;
1106 }
1107
1108 SbBool
isFlagSet(const unsigned int flag) const1109 SoRayPickActionP::isFlagSet(const unsigned int flag) const
1110 {
1111 return (this->flags & flag) != 0;
1112 }
1113
1114 void
calcObjectSpaceData(SoState * ownerstate)1115 SoRayPickActionP::calcObjectSpaceData(SoState * ownerstate)
1116 {
1117 this->calcMatrices(ownerstate);
1118
1119 SbVec3d start, dir;
1120
1121 if (this->objectspacevalid) {
1122 this->world2obj.multVecMatrix(this->raystart, start);
1123 this->world2obj.multDirMatrix(this->raydirection, dir);
1124 this->osline = SbDPLine(start, start + dir);
1125
1126 SbVec3f tmp1, tmp2;
1127 tmp1.setValue(start);
1128
1129 // scale direction with depth to avoid that line gets no direction
1130 // when we convert it to single precision below.
1131 dir *= this->rayfar;
1132 tmp2.setValue(dir);
1133
1134 this->osline_sp = SbLine(tmp1, tmp1 + tmp2);
1135 }
1136 }
1137
1138 void
calcMatrices(SoState * state)1139 SoRayPickActionP::calcMatrices(SoState * state)
1140 {
1141 const SbMatrix & tmp = SoModelMatrixElement::get(state);
1142 this->obj2world = SbDPMatrix(tmp);
1143 if (this->isFlagSet(EXTRA_MATRIX)) {
1144 this->obj2world.multLeft(this->extramatrix);
1145 }
1146 this->world2obj = this->obj2world.inverse();
1147 // FIXME: find a safe way to test if we were able to properly calculate the inverse matrix
1148 this->objectspacevalid = TRUE;
1149 }
1150
1151 void
setPickStyleFlags(SoState * state)1152 SoRayPickActionP::setPickStyleFlags(SoState * state)
1153 {
1154 assert(state->isElementEnabled(SoPickStyleElement::getClassStackIndex()));
1155 assert(state->isElementEnabled(SoShapeHintsElement::getClassStackIndex()));
1156
1157 SoPickStyleElement::Style style = SoPickStyleElement::get(state);
1158 SoShapeHintsElement::ShapeType type = SoShapeHintsElement::getShapeType(state);
1159 SoShapeHintsElement::VertexOrdering ordering = SoShapeHintsElement::getVertexOrdering(state);
1160
1161 // assert(style != SoPickStyleElement::UNPICKABLE); // could we get here?
1162
1163 if (style == SoPickStyleElement::SHAPE_FRONTFACES &&
1164 type == SoShapeHintsElement::SOLID &&
1165 (ordering == SoShapeHintsElement::COUNTERCLOCKWISE ||
1166 ordering == SoShapeHintsElement::CLOCKWISE)) {
1167 this->setFlag(CULL_BACKFACES);
1168 } else {
1169 this->clearFlag(CULL_BACKFACES);
1170 }
1171
1172 if ((style == SoPickStyleElement::SHAPE_ON_TOP) ||
1173 (style == SoPickStyleElement::BOUNDING_BOX_ON_TOP)) {
1174 this->setFlag(PUSH_PICK_TO_FRONT);
1175 } else {
1176 this->clearFlag(PUSH_PICK_TO_FRONT);
1177 }
1178 }
1179
1180 #undef PRIVATE
1181