Panda3D
boundingSphere.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file boundingSphere.cxx
10  * @author drose
11  * @date 1999-10-01
12  */
13 
14 #include "boundingSphere.h"
15 #include "boundingBox.h"
16 #include "boundingHexahedron.h"
17 #include "boundingLine.h"
18 #include "boundingPlane.h"
19 #include "config_mathutil.h"
20 #include "dcast.h"
21 #include "cmath.h"
22 
23 #include <algorithm>
24 
25 using std::max;
26 using std::min;
27 
28 TypeHandle BoundingSphere::_type_handle;
29 
30 /**
31  *
32  */
33 BoundingVolume *BoundingSphere::
34 make_copy() const {
35  return new BoundingSphere(*this);
36 }
37 
38 /**
39  *
40  */
41 LPoint3 BoundingSphere::
42 get_min() const {
43  nassertr(!is_empty(), LPoint3::zero());
44  nassertr(!is_infinite(), LPoint3::zero());
45  return LPoint3(_center[0] - _radius,
46  _center[1] - _radius,
47  _center[2] - _radius);
48 }
49 
50 /**
51  *
52  */
53 LPoint3 BoundingSphere::
54 get_max() const {
55  nassertr(!is_empty(), LPoint3::zero());
56  nassertr(!is_infinite(), LPoint3::zero());
57  return LPoint3(_center[0] + _radius,
58  _center[1] + _radius,
59  _center[2] + _radius);
60 }
61 
62 /**
63  *
64  */
65 PN_stdfloat BoundingSphere::
66 get_volume() const {
67  nassertr(!is_infinite(), 0.0f);
68  if (is_empty()) {
69  return 0.0f;
70  }
71 
72  // Volume of a sphere: four-thirds pi r cubed.
73  return 4.0f / 3.0f * MathNumbers::pi_f * _radius * _radius * _radius;
74 }
75 
76 /**
77  *
78  */
79 LPoint3 BoundingSphere::
80 get_approx_center() const {
81  nassertr(!is_empty(), LPoint3::zero());
82  nassertr(!is_infinite(), LPoint3::zero());
83  return get_center();
84 }
85 
86 /**
87  *
88  */
89 void BoundingSphere::
90 xform(const LMatrix4 &mat) {
91  nassertv(!mat.is_nan());
92 
93  if (!is_empty() && !is_infinite()) {
94  // First, determine the longest axis of the matrix, in case it contains a
95  // non-uniform scale.
96 
97  LVecBase3 x, y, z;
98  mat.get_row3(x, 0);
99  mat.get_row3(y, 1);
100  mat.get_row3(z, 2);
101 
102  PN_stdfloat xd = dot(x, x);
103  PN_stdfloat yd = dot(y, y);
104  PN_stdfloat zd = dot(z, z);
105 
106  PN_stdfloat scale = max(xd, yd);
107  scale = max(scale, zd);
108  scale = csqrt(scale);
109 
110  // Transform the radius
111  _radius *= scale;
112 
113  // Transform the center
114  _center = _center * mat;
115  }
116 }
117 
118 /**
119  *
120  */
121 void BoundingSphere::
122 output(std::ostream &out) const {
123  if (is_empty()) {
124  out << "bsphere, empty";
125  } else if (is_infinite()) {
126  out << "bsphere, infinite";
127  } else {
128  out << "bsphere, c (" << _center << "), r " << _radius;
129  }
130 }
131 
132 /**
133  * Virtual downcast method. Returns this object as a pointer of the indicated
134  * type, if it is in fact that type. Returns NULL if it is not that type.
135  */
138  return this;
139 }
140 
141 /**
142  *
143  */
144 bool BoundingSphere::
145 extend_other(BoundingVolume *other) const {
146  return other->extend_by_sphere(this);
147 }
148 
149 /**
150  *
151  */
152 bool BoundingSphere::
153 around_other(BoundingVolume *other,
154  const BoundingVolume **first,
155  const BoundingVolume **last) const {
156  return other->around_spheres(first, last);
157 }
158 
159 /**
160  *
161  */
162 int BoundingSphere::
163 contains_other(const BoundingVolume *other) const {
164  return other->contains_sphere(this);
165 }
166 
167 
168 /**
169  *
170  */
171 bool BoundingSphere::
172 extend_by_point(const LPoint3 &point) {
173  nassertr(!point.is_nan(), false);
174 
175  if (is_empty()) {
176  _center = point;
177  _radius = 0.0f;
178  _flags = 0;
179  } else if (!is_infinite()) {
180  LVector3 v = point - _center;
181  PN_stdfloat dist2 = dot(v, v);
182  if (dist2 > _radius * _radius) {
183  _radius = csqrt(dist2);
184  }
185  }
186  return true;
187 }
188 
189 /**
190  *
191  */
192 bool BoundingSphere::
193 extend_by_sphere(const BoundingSphere *sphere) {
194  nassertr(!sphere->is_empty() && !sphere->is_infinite(), false);
195  nassertr(!is_infinite(), false);
196 
197  if (is_empty()) {
198  _center = sphere->_center;
199  _radius = sphere->_radius;
200  _flags = 0;
201  } else {
202  PN_stdfloat dist = length(sphere->_center - _center);
203 
204  _radius = max(_radius, dist + sphere->_radius);
205  }
206  return true;
207 }
208 
209 /**
210  *
211  */
212 bool BoundingSphere::
213 extend_by_box(const BoundingBox *box) {
214  const LVector3 &min1 = box->get_minq();
215  const LVector3 &max1 = box->get_maxq();
216 
217  if (is_empty()) {
218  _center = (min1 + max1) * 0.5f;
219  _radius = length(LVector3(max1 - _center));
220  _flags = 0;
221 
222  } else {
223  // Find the minimum radius necessary to reach the corner.
224  PN_stdfloat max_dist2 = -1.0;
225  for (int i = 0; i < 8; ++i) {
226  PN_stdfloat dist2 = (box->get_point(i) - _center).length_squared();
227  if (dist2 > max_dist2) {
228  max_dist2 = dist2;
229  }
230  }
231  if (max_dist2 > _radius * _radius) {
232  _radius = csqrt(max_dist2);
233  }
234  }
235 
236  return true;
237 }
238 
239 /**
240  *
241  */
242 bool BoundingSphere::
243 extend_by_hexahedron(const BoundingHexahedron *hexahedron) {
244  nassertr(!hexahedron->is_empty(), false);
245 
246  BoundingBox box(hexahedron->get_min(), hexahedron->get_max());
247  box.local_object();
248  return extend_by_box(&box);
249 }
250 
251 /**
252  *
253  */
254 bool BoundingSphere::
255 extend_by_finite(const FiniteBoundingVolume *volume) {
256  nassertr(!volume->is_empty(), false);
257 
258  BoundingBox box(volume->get_min(), volume->get_max());
259  box.local_object();
260  return extend_by_box(&box);
261 }
262 
263 /**
264  *
265  */
266 bool BoundingSphere::
267 around_points(const LPoint3 *first, const LPoint3 *last) {
268  nassertr(first != last, false);
269 
270  // First, get the box of all the points to construct a bounding box.
271  const LPoint3 *p = first;
272 
273 #ifndef NDEBUG
274  // Skip any NaN points.
275  int skipped_nan = 0;
276  while (p != last && (*p).is_nan()) {
277  ++p;
278  ++skipped_nan;
279  }
280  if (p == last) {
281  mathutil_cat.warning()
282  << "BoundingSphere around NaN\n";
283  return false;
284  }
285 #endif
286 
287  LPoint3 min_box(*p);
288  LPoint3 max_box(*p);
289  ++p;
290 
291 #ifndef NDEBUG
292  // Skip more NaN points.
293  while (p != last && (*p).is_nan()) {
294  ++p;
295  ++skipped_nan;
296  }
297 #endif
298 
299  if (p == last) {
300  // Only one point; we have a radius of zero. This is not the same thing
301  // as an empty sphere, because our volume contains one point; an empty
302  // sphere contains no points.
303  _center = min_box;
304  _radius = 0.0f;
305 
306  } else {
307  // More than one point; we have a nonzero radius.
308  while (p != last) {
309 #ifndef NDEBUG
310  // Skip more NaN points.
311  if ((*p).is_nan()) {
312  ++skipped_nan;
313  } else
314 #endif
315  {
316  min_box.set(min(min_box[0], (*p)[0]),
317  min(min_box[1], (*p)[1]),
318  min(min_box[2], (*p)[2]));
319  max_box.set(max(max_box[0], (*p)[0]),
320  max(max_box[1], (*p)[1]),
321  max(max_box[2], (*p)[2]));
322  }
323  ++p;
324  }
325 
326  // Now take the center of the bounding box as the center of the sphere.
327  _center = (min_box + max_box) * 0.5f;
328 
329  // Now walk back through to get the max distance from center.
330  PN_stdfloat max_dist2 = 0.0f;
331  for (p = first; p != last; ++p) {
332  LVector3 v = (*p) - _center;
333  PN_stdfloat dist2 = dot(v, v);
334  max_dist2 = max(max_dist2, dist2);
335  }
336 
337  _radius = csqrt(max_dist2);
338  }
339 
340 #ifndef NDEBUG
341  if (skipped_nan != 0) {
342  mathutil_cat.warning()
343  << "BoundingSphere ignored " << skipped_nan << " NaN points of "
344  << (last - first) << " total.\n";
345  }
346 #endif
347 
348  _flags = 0;
349 
350  return true;
351 }
352 
353 /**
354  *
355  */
356 bool BoundingSphere::
357 around_finite(const BoundingVolume **first,
358  const BoundingVolume **last) {
359  nassertr(first != last, false);
360 
361  // We're given a set of bounding volumes, all of which are finite, and at
362  // least the first one of which is guaranteed to be nonempty. Some others
363  // may not be.
364 
365  // First, get the box of all the points to construct a bounding box.
366  const BoundingVolume **p = first;
367  nassertr(!(*p)->is_empty() && !(*p)->is_infinite(), false);
368  const FiniteBoundingVolume *vol = (*p)->as_finite_bounding_volume();
369  nassertr(vol != nullptr, false);
370  LPoint3 min_box = vol->get_min();
371  LPoint3 max_box = vol->get_max();
372 
373  bool any_spheres = (vol->as_bounding_sphere() != nullptr);
374 
375  for (++p; p != last; ++p) {
376  nassertr(!(*p)->is_infinite(), false);
377  if (!(*p)->is_empty()) {
378  vol = (*p)->as_finite_bounding_volume();
379  if (vol == nullptr) {
380  set_infinite();
381  return true;
382  }
383  LPoint3 min1 = vol->get_min();
384  LPoint3 max1 = vol->get_max();
385  min_box.set(min(min_box[0], min1[0]),
386  min(min_box[1], min1[1]),
387  min(min_box[2], min1[2]));
388  max_box.set(max(max_box[0], max1[0]),
389  max(max_box[1], max1[1]),
390  max(max_box[2], max1[2]));
391 
392  if (vol->as_bounding_sphere() != nullptr) {
393  any_spheres = true;
394  }
395  }
396  }
397 
398  // Now take the center of the bounding box as the center of the sphere.
399  _center = (min_box + max_box) * 0.5f;
400 
401  if (!any_spheres) {
402  // Since there are no spheres in the list, we have to make this sphere
403  // fully enclose all of the bounding boxes.
404  _radius = length(max_box - _center);
405 
406  } else {
407  // We might be able to go tighter, by lopping off the corners of the
408  // spheres.
409  _radius = 0.0f;
410  for (p = first; p != last; ++p) {
411  if (!(*p)->is_empty()) {
412  const BoundingSphere *sphere = (*p)->as_bounding_sphere();
413  if (sphere != nullptr) {
414  // This is a sphere; consider its corner.
415  PN_stdfloat dist = length(sphere->_center - _center);
416  _radius = max(_radius, dist + sphere->_radius);
417 
418  } else {
419  // This is a nonsphere. We fit around it.
420  const FiniteBoundingVolume *vol = (*p)->as_finite_bounding_volume();
421  nassertr(vol != nullptr, false);
422 
423  BoundingBox box(vol->get_min(), vol->get_max());
424  box.local_object();
425 
426  // Find the minimum radius necessary to reach the corner.
427  PN_stdfloat max_dist2 = -1.0;
428  for (int i = 0; i < 8; ++i) {
429  PN_stdfloat dist2 = (box.get_point(i) - _center).length_squared();
430  if (dist2 > max_dist2) {
431  max_dist2 = dist2;
432  }
433  }
434  _radius = max(_radius, csqrt(max_dist2));
435  }
436  }
437  }
438  }
439 
440  _flags = 0;
441  return true;
442 }
443 
444 /**
445  *
446  */
447 int BoundingSphere::
448 contains_point(const LPoint3 &point) const {
449  nassertr(!point.is_nan(), IF_no_intersection);
450 
451  if (is_empty()) {
452  return IF_no_intersection;
453 
454  } else if (is_infinite()) {
455  return IF_possible | IF_some | IF_all;
456 
457  } else {
458  LVector3 v = point - _center;
459  PN_stdfloat dist2 = dot(v, v);
460  return (dist2 <= _radius * _radius) ?
461  IF_possible | IF_some | IF_all : IF_no_intersection;
462  }
463 }
464 
465 /**
466  *
467  */
468 int BoundingSphere::
469 contains_lineseg(const LPoint3 &a, const LPoint3 &b) const {
470  nassertr(!a.is_nan() && !b.is_nan(), IF_no_intersection);
471 
472  if (a == b) {
473  return contains_point(a);
474  }
475  if (is_empty()) {
476  return IF_no_intersection;
477 
478  } else if (is_infinite()) {
479  return IF_possible | IF_some | IF_all;
480 
481  } else {
482  LPoint3 from = a;
483  LVector3 delta = b - a;
484  PN_stdfloat t1, t2;
485 
486  // Solve the equation for the intersection of a line with a sphere using
487  // the quadratic equation.
488  PN_stdfloat A = dot(delta, delta);
489 
490  nassertr(A != 0.0f, 0); // Trivial line segment.
491 
492  LVector3 fc = from - _center;
493  PN_stdfloat B = 2.0f * dot(delta, fc);
494  PN_stdfloat C = dot(fc, fc) - _radius * _radius;
495 
496  PN_stdfloat radical = B*B - 4.0f*A*C;
497 
498  if (IS_NEARLY_ZERO(radical)) {
499  // Tangent.
500  t1 = t2 = -B / (2.0f*A);
501  return (t1 >= 0.0f && t1 <= 1.0f) ?
502  IF_possible | IF_some : IF_no_intersection;
503  }
504 
505  if (radical < 0.0f) {
506  // No real roots: no intersection with the line.
507  return IF_no_intersection;
508  }
509 
510  PN_stdfloat reciprocal_2A = 1.0f/(2.0f*A);
511  PN_stdfloat sqrt_radical = csqrt(radical);
512 
513  t1 = ( -B - sqrt_radical ) * reciprocal_2A;
514  t2 = ( -B + sqrt_radical ) * reciprocal_2A;
515 
516  if (t1 >= 0.0f && t2 <= 1.0f) {
517  return IF_possible | IF_some | IF_all;
518  } else if (t1 <= 1.0f && t2 >= 0.0f) {
519  return IF_possible | IF_some;
520  } else {
521  return IF_no_intersection;
522  }
523  }
524 }
525 
526 /**
527  * Double-dispatch support: called by contains_other() when the type we're
528  * testing for intersection is known to be a sphere.
529  */
530 int BoundingSphere::
531 contains_sphere(const BoundingSphere *sphere) const {
532  nassertr(!is_empty() && !is_infinite(), 0);
533  nassertr(!sphere->is_empty() && !sphere->is_infinite(), 0);
534 
535  LVector3 v = sphere->_center - _center;
536  PN_stdfloat dist2 = dot(v, v);
537 
538  if (_radius >= sphere->_radius &&
539  dist2 <= (_radius - sphere->_radius) * (_radius - sphere->_radius)) {
540  // The other sphere is completely within this sphere.
541  return IF_possible | IF_some | IF_all;
542 
543  } else if (dist2 > (_radius + sphere->_radius) * (_radius + sphere->_radius)) {
544  // The other sphere is completely outside this sphere.
545  return IF_no_intersection;
546 
547  } else {
548  // The other sphere is partially within this sphere.
549  return IF_possible | IF_some;
550  }
551 }
552 
553 /**
554  * Double-dispatch support: called by contains_other() when the type we're
555  * testing for intersection is known to be a box.
556  */
557 int BoundingSphere::
558 contains_box(const BoundingBox *box) const {
559  return box->contains_sphere(this) & ~IF_all;
560 }
561 
562 /**
563  * Double-dispatch support: called by contains_other() when the type we're
564  * testing for intersection is known to be a hexahedron.
565  */
566 int BoundingSphere::
567 contains_hexahedron(const BoundingHexahedron *hexahedron) const {
568  return hexahedron->contains_sphere(this) & ~IF_all;
569 }
570 
571 /**
572  * Double-dispatch support: called by contains_other() when the type we're
573  * testing for intersection is known to be a line.
574  */
575 int BoundingSphere::
576 contains_line(const BoundingLine *line) const {
577  return line->contains_sphere(this) & ~IF_all;
578 }
579 
580 /**
581  * Double-dispatch support: called by contains_other() when the type we're
582  * testing for intersection is known to be a plane.
583  */
584 int BoundingSphere::
585 contains_plane(const BoundingPlane *plane) const {
586  return plane->contains_sphere(this) & ~IF_all;
587 }
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
Definition: boundingBox.h:29
get_point
Returns the nth vertex of the rectangular solid.
Definition: boundingBox.h:51
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual const BoundingSphere * as_bounding_sphere() const
Virtual downcast method.
bool is_empty() const
Any kind of volume might be empty.
This defines a bounding sphere, consisting of a center and a radius.
bool is_infinite() const
The other side of the empty coin is an infinite volume.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This funny bounding volume is an infinite plane that divides space into two regions: the part behind ...
Definition: boundingPlane.h:28
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
void set_infinite()
Marks the volume as infinite, even if it is normally finite.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const LPoint3 & get_maxq() const
An inline accessor for the maximum value.
Definition: boundingBox.I:52
void local_object()
This function should be called, once, immediately after creating a new instance of some ReferenceCoun...
A special kind of GeometricBoundingVolume that is known to be finite.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BoundingSphere()
Constructs an empty sphere.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
virtual const FiniteBoundingVolume * as_finite_bounding_volume() const
Virtual downcast method.
This defines a bounding convex hexahedron.
This funny bounding volume is an infinite line with no thickness and extending to infinity in both di...
Definition: boundingLine.h:29
const LPoint3 & get_minq() const
An inline accessor for the minimum value.
Definition: boundingBox.I:41
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.