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