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 SbPlane SbPlane.h Inventor/SbLinear.h
35   \brief The SbPlane class represents a plane in 3D space.
36 
37   \ingroup base
38 
39   SbPlane is used by many other classes in Coin.  It provides a way of
40   representing a plane, specified by a plane normal vector and a
41   distance from the origin of the coordinate system.
42 */
43 
44 #include <cassert>
45 #include <cstdio>
46 #include <Inventor/SbPlane.h>
47 #include <Inventor/SbLine.h>
48 #include <Inventor/SbVec3d.h>
49 #include <Inventor/SbMatrix.h>
50 #include <cfloat>
51 
52 #if COIN_DEBUG
53 #include <Inventor/errors/SoDebugError.h>
54 #endif // COIN_DEBUG
55 
56 
57 /*!
58   An SbPlane instantiated with the default constructor will be
59   uninitialized.
60 */
SbPlane(void)61 SbPlane::SbPlane(void)
62 {
63 }
64 
65 /*!
66   Construct an SbPlane instance with a normal pointing in the given
67   direction and the given shortest distance from the origin of the
68   coordinate system to a point in the plane.
69 
70   \a normal must not be a null vector.
71 */
SbPlane(const SbVec3f & normalref,const float D)72 SbPlane::SbPlane(const SbVec3f& normalref, const float D)
73 {
74 #if COIN_DEBUG
75   if (normalref.sqrLength() == 0.0f) {
76     SoDebugError::postWarning("SbPlane::SbPlane",
77                               "Plane normal vector is a null vector.");
78   }
79 #endif // COIN_DEBUG
80 
81   this->normal = normalref;
82   // we test for a null vector above, just normalize
83   (void) this->normal.normalize();
84   this->distance = D;
85 }
86 
87 /*!
88   Construct an SbPlane with three points laying in the plane.  Make
89   sure \a p0, \a p1 and \a p2 are actually three distinct points,
90   not on a line, when using this constructor.
91 */
SbPlane(const SbVec3f & p0,const SbVec3f & p1,const SbVec3f & p2)92 SbPlane::SbPlane(const SbVec3f& p0, const SbVec3f& p1, const SbVec3f& p2)
93 {
94 #if COIN_DEBUG
95   if(!(p0 != p1 && p1 != p2 && p0 != p2))
96     SoDebugError::postWarning("SbPlane::SbPlane",
97                               "The three points defining the plane cannot "
98                               "be coincident.");
99 #endif // COIN_DEBUG
100 
101   this->normal = (p1 - p0).cross(p2 - p0);
102 #if COIN_DEBUG
103   if (this->normal.sqrLength() == 0.0f) {
104     SoDebugError::postWarning("SbPlane::SbPlane",
105                               "The three points defining the plane cannot "
106                               "be on line.");
107   }
108 #endif // COIN_DEBUG
109 
110   // we test and warn about a null vector above
111   (void) this->normal.normalize();
112   //     N�point
113   // d = -------, |N| == 1
114   //       |N|�
115 
116   this->distance = this->normal.dot(p0);
117 }
118 
119 /*!
120   Construct an SbPlane from a normal and a point laying in the plane.
121 
122   \a normal must not be a null vector.
123 */
SbPlane(const SbVec3f & normalref,const SbVec3f & point)124 SbPlane::SbPlane(const SbVec3f& normalref, const SbVec3f& point)
125 {
126 #if COIN_DEBUG
127   if(normalref.sqrLength() == 0.0f)
128     SoDebugError::postWarning("SbPlane::SbPlane",
129                               "Plane normal vector is a null vector.");
130 #endif // COIN_DEBUG
131 
132   this->normal = normalref;
133 
134   // we test and warn about a null vector above
135   (void) this->normal.normalize();
136 
137   //     N�point
138   // d = -------, |N| == 1
139   //       |N|�
140 
141   this->distance = this->normal.dot(point);
142 }
143 
144 
145 /*!
146   Add the given offset \a d to the plane distance from the origin.
147 */
148 void
offset(const float d)149 SbPlane::offset(const float d)
150 {
151   this->distance += d;
152 }
153 
154 /*!
155   Find the point on given line \a l intersecting the plane and return
156   it in \a intersection. If the line is parallel to the plane,
157   we return \c FALSE, otherwise \c TRUE.
158 
159   Do not pass an invalid line for the \a l parameter (i.e. with a
160   null direction vector).
161 */
162 SbBool
intersect(const SbLine & l,SbVec3f & intersection) const163 SbPlane::intersect(const SbLine& l, SbVec3f& intersection) const
164 {
165 #if COIN_DEBUG
166   if (this->normal.sqrLength() == 0.0f) {
167     SoDebugError::postWarning("SbPlane::intersect",
168                               "Normal vector for plane is null vector");
169 
170   }
171   if (l.getDirection().sqrLength() == 0.0f) {
172     SoDebugError::postWarning("SbPlane::intersect",
173                               "Intersecting line doesn't have a direction.");
174   }
175 #endif // COIN_DEBUG
176 
177   // Check if the line is parallel to the plane.
178   if(fabs(l.getDirection().dot(this->normal)) < FLT_EPSILON) return FALSE;
179 
180   // From the discussion on SbLine::getClosestPoint() we know that
181   // any point on the line can be expressed as:
182   //                    Q = P + t*D    (1)
183   //
184   // We can also easily see that a point must satisfy this equation to lie
185   // in the plane:
186   //                    N�(Q - d*N) = 0, where N is the normal vector,
187   //                                     Q is the point and d the offset
188   //                                     from the origin.
189   //
190   // Combining these two equations and simplifying we get:
191   //
192   //                          d*|N|� - N�P
193   //                    t = ----------------, |N| == 1
194   //                               N�D
195   //
196   // Substituting t back in (1), we've solved the problem.
197   //                                                         19980816 mortene.
198 
199   float t =
200     (this->distance - this->normal.dot(l.getPosition()))
201     / this->normal.dot(l.getDirection());
202 
203   intersection = l.getPosition() + t * l.getDirection();
204 
205   return TRUE;
206 }
207 
208 /*!
209   Transform the plane by \a matrix.
210 
211   \sa offset()
212 */
213 void
transform(const SbMatrix & matrix)214 SbPlane::transform(const SbMatrix& matrix)
215 {
216   SbVec3f ptInPlane = this->normal * this->distance;
217 
218   // according to discussions on comp.graphics.algorithms, the inverse
219   // transpose matrix should be used to rotate the plane normal.
220   SbMatrix invtransp = matrix.inverse().transpose();
221   invtransp.multDirMatrix(this->normal, this->normal);
222 
223   // the point should be transformed using the original matrix
224   matrix.multVecMatrix(ptInPlane, ptInPlane);
225 
226   if (this->normal.normalize() == 0.0f) {
227 #if COIN_DEBUG
228     SoDebugError::postWarning("SbPlane::transform",
229                               "The transformation invalidated the plane.");
230 #endif // COIN_DEBUG
231   }
232   this->distance = this->normal.dot(ptInPlane);
233 }
234 
235 /*!
236   Check if the given point lies in the halfspace of the plane which the
237   plane normal vector is pointing.
238 */
239 SbBool
isInHalfSpace(const SbVec3f & point) const240 SbPlane::isInHalfSpace(const SbVec3f& point) const
241 {
242   return this->getDistance(point) >= 0.0f;
243 }
244 
245 /*!
246   Return the distance from \a point to plane. Positive distance means
247   the point is in the plane's half space.
248 
249   This method is an extension specific to Coin versus the original SGI
250   Inventor API.
251 */
252 float
getDistance(const SbVec3f & point) const253 SbPlane::getDistance(const SbVec3f &point) const
254 {
255   // convert to double before doing the dot product to increase precision of the result
256   SbVec3d dp(point);
257   SbVec3d dn(this->normal);
258   return static_cast<float> (dp.dot(dn)) - this->distance;
259 }
260 
261 /*!
262   Return the plane's normal vector, which indicates which direction the plane
263   is oriented.
264 
265   \sa getDistanceFromOrigin().
266 */
267 const SbVec3f&
getNormal(void) const268 SbPlane::getNormal(void) const
269 {
270   return this->normal;
271 }
272 
273 /*!
274   Return distance from origin of coordinate system to the point in the plane
275   which is closest to the origin.
276 
277   \sa getNormal().
278 */
279 float
getDistanceFromOrigin(void) const280 SbPlane::getDistanceFromOrigin(void) const
281 {
282   return this->distance;
283 }
284 
285 /*!
286   Intersect this plane with \a pl, and return the resulting line in \a
287   line. Returns \c TRUE if an intersection line can be found, and \c
288   FALSE if the planes are parallel.
289 
290   Please note that the resulting SbLine must be considered as a
291   \e line intersecting the SbLine's origin, extending infinitely in both
292   directions.
293 
294   \COIN_FUNCTION_EXTENSION
295 
296   \since Coin 2.0
297 */
298 SbBool
intersect(const SbPlane & pl,SbLine & line) const299 SbPlane::intersect(const SbPlane & pl, SbLine & line) const
300 {
301   // Based on code from Graphics Gems III, Plane-to-Plane Intersection
302   // by Priamos Georgiades
303 
304   float invdet;  // inverse of 2x2 matrix determinant
305   SbVec3f dir2;  // holds the squares of the coordinates of xdir
306 
307   SbVec3f xpt;
308   SbVec3f xdir;
309   xdir = this->normal.cross(pl.normal);
310 
311   dir2[0] = xdir[0] * xdir[0];
312   dir2[1] = xdir[1] * xdir[1];
313   dir2[2] = xdir[2] * xdir[2];
314 
315   const SbVec3f & pl1n = this->normal;
316   const SbVec3f & pl2n = pl.normal;
317   const float pl1w = -this->distance;
318   const float pl2w = -pl.distance;
319 
320   if ((dir2[2] > dir2[1]) && (dir2[2] > dir2[0]) && (dir2[2] > FLT_EPSILON)) {
321     // then get a point on the XY plane
322     invdet = 1.0f / xdir[2];
323     xpt = SbVec3f(pl1n[1] * pl2w - pl2n[1] * pl1w,
324                   pl2n[0] * pl1w - pl1n[0] * pl2w, 0.0f);
325   }
326   else if ((dir2[1] > dir2[0]) && (dir2[1] > FLT_EPSILON)) {
327     // then get a point on the XZ plane
328     invdet = - 1.0f / xdir[1];
329     xpt = SbVec3f(pl1n[2] * pl2w - pl2n[2] * pl1w, 0.0f,
330                   pl2n[0] * pl1w - pl1n[0] * pl2w);
331   }
332   else if (dir2[0] > FLT_EPSILON) {
333     // then get a point on the YZ plane
334     invdet = 1.0f / xdir[0];
335     xpt = SbVec3f(0.0f, pl1n[2] * pl2w - pl2n[2] * pl1w,
336                   pl2n[1] * pl1w - pl1n[1] * pl2w);
337   }
338   else // xdir is zero, then no point of intersection exists
339     return FALSE;
340 
341   xpt *= invdet;
342   invdet = 1.0f / static_cast<float>(sqrt(dir2[0] + dir2[1] + dir2[2]));
343 
344   xdir *= invdet;
345   line.setPosDir(xpt, xdir);
346   return TRUE;
347 }
348 
349 /*!
350   \relates SbPlane
351 
352   Check the two given planes for equality.
353 */
354 int
operator ==(const SbPlane & p1,const SbPlane & p2)355 operator ==(const SbPlane& p1, const SbPlane& p2)
356 {
357   if(p1.getDistanceFromOrigin() == p2.getDistanceFromOrigin() &&
358      p1.getNormal() == p2.getNormal()) return TRUE;
359   return FALSE;
360 }
361 
362 /*!
363   \relates SbPlane
364 
365   Check the two given planes for unequality.
366 */
367 int
operator !=(const SbPlane & p1,const SbPlane & p2)368 operator !=(const SbPlane& p1, const SbPlane& p2)
369 {
370   return !(p1 == p2);
371 }
372 
373 /*!
374   Dump the state of this object to the \a file stream. Only works in
375   debug version of library, method does nothing in an optimized build.
376 */
377 void
print(FILE * fp) const378 SbPlane::print(FILE * fp) const
379 {
380 #if COIN_DEBUG
381   this->getNormal().print(fp);
382   (void)fprintf(fp, "  %f", this->getDistanceFromOrigin());
383 #endif // COIN_DEBUG
384 }
385 
386 #ifdef COIN_TEST_SUITE
387 #include <Inventor/SbPlane.h>
388 #include <Inventor/SbLine.h>
389 
390 using namespace SIM::Coin::TestSuite;
391 
BOOST_AUTO_TEST_CASE(signCorrect)392 BOOST_AUTO_TEST_CASE(signCorrect)
393 {
394   SbPlane plane1(SbVec3f(0.0, 0.0, 1.0), 3.0);
395   SbPlane plane2(SbVec3f(1.0, 0.0, 0.0), 21.0);
396   SbLine line;
397   plane1.intersect(plane2, line);
398 
399   SbVec3f intersect = line.getPosition();
400   SbVec3f vec(21, 0, 3);
401 
402   check_compare(intersect,vec, "SbPlane SignCorrect", .1f);
403 
404 }
405 
406 #endif //COIN_TEST_SUITE
407