Panda3D
collisionPolygon.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 collisionPolygon.cxx
10  * @author drose
11  * @date 2000-04-25
12  */
13 
14 #include "collisionPolygon.h"
15 #include "collisionHandler.h"
16 #include "collisionEntry.h"
17 #include "collisionSphere.h"
18 #include "collisionLine.h"
19 #include "collisionRay.h"
20 #include "collisionSegment.h"
21 #include "collisionParabola.h"
22 #include "config_collide.h"
23 #include "cullTraverserData.h"
24 #include "boundingBox.h"
25 #include "pointerToArray.h"
26 #include "geomNode.h"
27 #include "geom.h"
28 #include "datagram.h"
29 #include "datagramIterator.h"
30 #include "bamReader.h"
31 #include "bamWriter.h"
32 #include "transformState.h"
33 #include "clipPlaneAttrib.h"
34 #include "nearly_zero.h"
35 #include "geom.h"
36 #include "geomTrifans.h"
37 #include "geomLinestrips.h"
38 #include "geomVertexWriter.h"
39 #include "renderState.h"
40 #include "epvector.h"
41 
42 #include <algorithm>
43 
44 using std::max;
45 using std::min;
46 
47 PStatCollector CollisionPolygon::_volume_pcollector("Collision Volumes:CollisionPolygon");
48 PStatCollector CollisionPolygon::_test_pcollector("Collision Tests:CollisionPolygon");
49 TypeHandle CollisionPolygon::_type_handle;
50 
51 /**
52  *
53  */
54 CollisionPolygon::
55 CollisionPolygon(const CollisionPolygon &copy) :
56  CollisionPlane(copy),
57  _points(copy._points),
58  _to_2d_mat(copy._to_2d_mat)
59 {
60 }
61 
62 /**
63  *
64  */
65 CollisionSolid *CollisionPolygon::
66 make_copy() {
67  return new CollisionPolygon(*this);
68 }
69 
70 /**
71  * Verifies that the indicated set of points will define a valid
72  * CollisionPolygon: that is, at least three non-collinear points, with no
73  * points repeated.
74  *
75  * This does not check that the polygon defined is convex; that check is made
76  * later, once we have projected the points to 2-d space where the decision is
77  * easier.
78  */
80 verify_points(const LPoint3 *begin, const LPoint3 *end) {
81  int num_points = end - begin;
82  if (num_points < 3) {
83  return false;
84  }
85 
86  bool all_ok = true;
87 
88  // First, check for repeated or invalid points.
89  const LPoint3 *pi;
90  for (pi = begin; pi != end && all_ok; ++pi) {
91  if ((*pi).is_nan()) {
92  all_ok = false;
93  } else {
94  // Make sure no points are repeated.
95  const LPoint3 *pj;
96  for (pj = begin; pj != pi && all_ok; ++pj) {
97  if ((*pj) == (*pi)) {
98  all_ok = false;
99  }
100  }
101  }
102  }
103 
104  if (all_ok) {
105  // Create a plane to determine the planarity of the first three points (or
106  // the first two points and the nth point thereafter, in case the first
107  // three points happen to be collinear).
108  bool got_normal = false;
109  for (int i = 2; i < num_points && !got_normal; i++) {
110  LPlane plane(begin[0], begin[1], begin[i]);
111  LVector3 normal = plane.get_normal();
112  PN_stdfloat normal_length = normal.length();
113  got_normal = IS_THRESHOLD_EQUAL(normal_length, 1.0f, 0.001f);
114  }
115 
116  if (!got_normal) {
117  all_ok = false;
118  }
119  }
120 
121  return all_ok;
122 }
123 
124 /**
125  * Returns true if the CollisionPolygon is valid (that is, it has at least
126  * three vertices), or false otherwise.
127  */
128 bool CollisionPolygon::
129 is_valid() const {
130  return (_points.size() >= 3);
131 }
132 
133 /**
134  * Returns true if the CollisionPolygon appears to be concave, or false if it
135  * is safely convex.
136  */
137 bool CollisionPolygon::
138 is_concave() const {
139  if (_points.size() < 3) {
140  // It's not even a valid polygon.
141  return true;
142  }
143 
144  LPoint2 p0 = _points[0]._p;
145  LPoint2 p1 = _points[1]._p;
146  PN_stdfloat dx1 = p1[0] - p0[0];
147  PN_stdfloat dy1 = p1[1] - p0[1];
148  p0 = p1;
149  p1 = _points[2]._p;
150 
151  PN_stdfloat dx2 = p1[0] - p0[0];
152  PN_stdfloat dy2 = p1[1] - p0[1];
153  int asum = ((dx1 * dy2 - dx2 * dy1 >= 0.0f) ? 1 : 0);
154 
155  for (size_t i = 0; i < _points.size() - 1; i++) {
156  p0 = p1;
157  p1 = _points[(i+3) % _points.size()]._p;
158 
159  dx1 = dx2;
160  dy1 = dy2;
161  dx2 = p1[0] - p0[0];
162  dy2 = p1[1] - p0[1];
163  int csum = ((dx1 * dy2 - dx2 * dy1 >= 0.0f) ? 1 : 0);
164 
165  if (csum ^ asum) {
166  // Oops, the polygon is concave.
167  return true;
168  }
169  }
170 
171  // The polygon is safely convex.
172  return false;
173 }
174 
175 /**
176  * Transforms the solid by the indicated matrix.
177  */
179 xform(const LMatrix4 &mat) {
180  // We need to convert all the vertices to 3-d for this operation, and then
181  // convert them back. Hopefully we won't lose too much precision during all
182  // of this.
183 
184  if (collide_cat.is_spam()) {
185  collide_cat.spam()
186  << "CollisionPolygon transformed by:\n";
187  mat.write(collide_cat.spam(false), 2);
188  if (_points.empty()) {
189  collide_cat.spam(false)
190  << " (no points)\n";
191  }
192  }
193 
194  if (!_points.empty()) {
195  LMatrix4 to_3d_mat;
196  rederive_to_3d_mat(to_3d_mat);
197 
198  epvector<LPoint3> verts;
199  verts.reserve(_points.size());
200  Points::const_iterator pi;
201  for (pi = _points.begin(); pi != _points.end(); ++pi) {
202  verts.push_back(to_3d((*pi)._p, to_3d_mat) * mat);
203  }
204 
205  const LPoint3 *verts_begin = &verts[0];
206  const LPoint3 *verts_end = verts_begin + verts.size();
207  setup_points(verts_begin, verts_end);
208  }
209 
210  CollisionSolid::xform(mat);
211 }
212 
213 /**
214  * Returns the point in space deemed to be the "origin" of the solid for
215  * collision purposes. The closest intersection point to this origin point is
216  * considered to be the most significant.
217  */
218 LPoint3 CollisionPolygon::
220  LMatrix4 to_3d_mat;
221  rederive_to_3d_mat(to_3d_mat);
222 
223  LPoint2 median = _points[0]._p;
224  for (int n = 1; n < (int)_points.size(); n++) {
225  median += _points[n]._p;
226  }
227  median /= _points.size();
228 
229  return to_3d(median, to_3d_mat);
230 }
231 
232 /**
233  * Returns a GeomNode that may be rendered to visualize the CollisionSolid.
234  * This is used during the cull traversal to render the CollisionNodes that
235  * have been made visible.
236  */
237 PT(PandaNode) CollisionPolygon::
238 get_viz(const CullTraverser *trav, const CullTraverserData &data,
239  bool bounds_only) const {
240  const ClipPlaneAttrib *cpa = DCAST(ClipPlaneAttrib, data._state->get_attrib(ClipPlaneAttrib::get_class_slot()));
241  if (cpa == nullptr) {
242  // Fortunately, the polygon is not clipped. This is the normal, easy
243  // case.
244  return CollisionSolid::get_viz(trav, data, bounds_only);
245  }
246 
247  if (collide_cat.is_debug()) {
248  collide_cat.debug()
249  << "drawing polygon with clip plane " << *cpa << "\n";
250  }
251 
252  // The polygon is clipped. We need to render it clipped. We could just
253  // turn on the ClipPlaneAttrib state and render the full polygon, letting
254  // the hardware do the clipping, but we get fancy and clip it by hand
255  // instead, just to prove that our clipping algorithm works properly. This
256  // does require some more dynamic work.
257  Points new_points;
258  if (apply_clip_plane(new_points, cpa, data.get_net_transform(trav))) {
259  // All points are behind the clip plane; just draw the original polygon.
260  return CollisionSolid::get_viz(trav, data, bounds_only);
261  }
262 
263  if (new_points.empty()) {
264  // All points are in front of the clip plane; draw nothing.
265  return nullptr;
266  }
267 
268  // Draw the clipped polygon.
269  PT(GeomNode) viz_geom_node = new GeomNode("viz");
270  PT(GeomNode) bounds_viz_geom_node = new GeomNode("bounds_viz");
271  draw_polygon(viz_geom_node, bounds_viz_geom_node, new_points);
272 
273  if (bounds_only) {
274  return bounds_viz_geom_node;
275  } else {
276  return viz_geom_node;
277  }
278 }
279 
280 /**
281  * Returns a PStatCollector that is used to count the number of bounding
282  * volume tests made against a solid of this type in a given frame.
283  */
286  return _volume_pcollector;
287 }
288 
289 /**
290  * Returns a PStatCollector that is used to count the number of intersection
291  * tests made against a solid of this type in a given frame.
292  */
295  return _test_pcollector;
296 }
297 
298 /**
299  *
300  */
301 void CollisionPolygon::
302 output(std::ostream &out) const {
303  out << "cpolygon, (" << get_plane()
304  << "), " << _points.size() << " vertices";
305 }
306 
307 /**
308  *
309  */
310 void CollisionPolygon::
311 write(std::ostream &out, int indent_level) const {
312  indent(out, indent_level) << (*this) << "\n";
313  Points::const_iterator pi;
314  for (pi = _points.begin(); pi != _points.end(); ++pi) {
315  indent(out, indent_level + 2) << (*pi)._p << "\n";
316  }
317 
318  LMatrix4 to_3d_mat;
319  rederive_to_3d_mat(to_3d_mat);
320  out << "In 3-d space:\n";
321  for (pi = _points.begin(); pi != _points.end(); ++pi) {
322  LVertex vert = to_3d((*pi)._p, to_3d_mat);
323  indent(out, indent_level + 2) << vert << "\n";
324  }
325 }
326 
327 /**
328  *
329  */
330 PT(BoundingVolume) CollisionPolygon::
331 compute_internal_bounds() const {
332  if (_points.empty()) {
333  return new BoundingBox;
334  }
335 
336  LMatrix4 to_3d_mat;
337  rederive_to_3d_mat(to_3d_mat);
338 
339  Points::const_iterator pi = _points.begin();
340  LPoint3 p = to_3d((*pi)._p, to_3d_mat);
341 
342  LPoint3 x = p;
343  LPoint3 n = p;
344 
345  for (++pi; pi != _points.end(); ++pi) {
346  p = to_3d((*pi)._p, to_3d_mat);
347 
348  n.set(min(n[0], p[0]),
349  min(n[1], p[1]),
350  min(n[2], p[2]));
351  x.set(max(x[0], p[0]),
352  max(x[1], p[1]),
353  max(x[2], p[2]));
354  }
355 
356  return new BoundingBox(n, x);
357 }
358 
359 /**
360  * This is part of the double-dispatch implementation of test_intersection().
361  * It is called when the "from" object is a sphere.
362  */
363 PT(CollisionEntry) CollisionPolygon::
364 test_intersection_from_sphere(const CollisionEntry &entry) const {
365  if (_points.size() < 3) {
366  return nullptr;
367  }
368 
369  const CollisionSphere *sphere;
370  DCAST_INTO_R(sphere, entry.get_from(), nullptr);
371 
372  CPT(TransformState) wrt_space = entry.get_wrt_space();
373  CPT(TransformState) wrt_prev_space = entry.get_wrt_prev_space();
374 
375  const LMatrix4 &wrt_mat = wrt_space->get_mat();
376 
377  LPoint3 orig_center = sphere->get_center() * wrt_mat;
378  LPoint3 from_center = orig_center;
379  bool moved_from_center = false;
380  PN_stdfloat t = 1.0f;
381  LPoint3 contact_point(from_center);
382  PN_stdfloat actual_t = 1.0f;
383 
384  LVector3 from_radius_v =
385  LVector3(sphere->get_radius(), 0.0f, 0.0f) * wrt_mat;
386  PN_stdfloat from_radius_2 = from_radius_v.length_squared();
387  PN_stdfloat from_radius = csqrt(from_radius_2);
388 
389  if (wrt_prev_space != wrt_space) {
390  // If we have a delta between the previous position and the current
391  // position, we use that to determine some more properties of the
392  // collision.
393  LPoint3 b = from_center;
394  LPoint3 a = sphere->get_center() * wrt_prev_space->get_mat();
395  LVector3 delta = b - a;
396 
397  // First, there is no collision if the "from" object is definitely moving
398  // in the same direction as the plane's normal.
399  PN_stdfloat dot = delta.dot(get_normal());
400  if (dot > 0.1f) {
401  return nullptr;
402  }
403 
404  if (IS_NEARLY_ZERO(dot)) {
405  // If we're moving parallel to the plane, the sphere is tested at its
406  // final point. Leave it as it is.
407 
408  } else {
409 /*
410  * Otherwise, we're moving into the plane; the sphere is tested at the point
411  * along its path that is closest to intersecting the plane. This may be the
412  * actual intersection point, or it may be the starting point or the final
413  * point. dot is equal to the (negative) magnitude of 'delta' along the
414  * direction of the plane normal t = ratio of (distance from start pos to
415  * plane) to (distance from start pos to end pos), along axis of plane normal
416  */
417  PN_stdfloat dist_to_p = dist_to_plane(a);
418  t = (dist_to_p / -dot);
419 
420  // also compute the actual contact point and time of contact for
421  // handlers that need it
422  actual_t = ((dist_to_p - from_radius) / -dot);
423  actual_t = min((PN_stdfloat)1.0, max((PN_stdfloat)0.0, actual_t));
424  contact_point = a + (actual_t * delta);
425 
426  if (t >= 1.0f) {
427  // Leave it where it is.
428 
429  } else if (t < 0.0f) {
430  from_center = a;
431  moved_from_center = true;
432 
433  } else {
434  from_center = a + t * delta;
435  moved_from_center = true;
436  }
437  }
438  }
439 
440  LVector3 normal = (has_effective_normal() && sphere->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
441 #ifndef NDEBUG
442  if (!IS_THRESHOLD_EQUAL(normal.length_squared(), 1.0f, 0.001)) {
443  collide_cat.info()
444  << "polygon within " << entry.get_into_node_path()
445  << " has normal " << normal << " of length " << normal.length()
446  << "\n";
447  normal.normalize();
448  }
449 #endif
450 
451  // The nearest point within the plane to our center is the intersection of
452  // the line (center, center - normal) with the plane.
453  PN_stdfloat dist;
454  if (!get_plane().intersects_line(dist, from_center, -get_normal())) {
455  // No intersection with plane? This means the plane's effective normal
456  // was within the plane itself. A useless polygon.
457  return nullptr;
458  }
459 
460  if (dist > from_radius || dist < -from_radius) {
461  // No intersection with the plane.
462  return nullptr;
463  }
464 
465  LPoint2 p = to_2d(from_center - dist * get_normal());
466  PN_stdfloat edge_dist = 0.0f;
467 
468  const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
469  if (cpa != nullptr) {
470  // We have a clip plane; apply it.
471  Points new_points;
472  if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
473  // All points are behind the clip plane; just do the default test.
474  edge_dist = dist_to_polygon(p, _points);
475 
476  } else if (new_points.empty()) {
477  // The polygon is completely clipped.
478  return nullptr;
479 
480  } else {
481  // Test against the clipped polygon.
482  edge_dist = dist_to_polygon(p, new_points);
483  }
484 
485  } else {
486  // No clip plane is in effect. Do the default test.
487  edge_dist = dist_to_polygon(p, _points);
488  }
489 
490  // Now we have edge_dist, which is the distance from the sphere center to
491  // the nearest edge of the polygon, within the polygon's plane.
492 
493  if (edge_dist > from_radius) {
494  // No intersection; the circle is outside the polygon.
495  return nullptr;
496  }
497 
498  // The sphere appears to intersect the polygon. If the edge is less than
499  // from_radius away, the sphere may be resting on an edge of the polygon.
500  // Determine how far the center of the sphere must remain from the plane,
501  // based on its distance from the nearest edge.
502 
503  PN_stdfloat max_dist = from_radius;
504  if (edge_dist >= 0.0f) {
505  PN_stdfloat max_dist_2 = max(from_radius_2 - edge_dist * edge_dist, (PN_stdfloat)0.0);
506  max_dist = csqrt(max_dist_2);
507  }
508 
509  if (dist > max_dist) {
510  // There's no intersection: the sphere is hanging off the edge.
511  return nullptr;
512  }
513 
514  if (collide_cat.is_debug()) {
515  collide_cat.debug()
516  << "intersection detected from " << entry.get_from_node_path()
517  << " into " << entry.get_into_node_path() << "\n";
518  }
519  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
520 
521  PN_stdfloat into_depth = max_dist - dist;
522  if (moved_from_center) {
523  // We have to base the depth of intersection on the sphere's final resting
524  // point, not the point from which we tested the intersection.
525  PN_stdfloat orig_dist;
526  get_plane().intersects_line(orig_dist, orig_center, -normal);
527  into_depth = max_dist - orig_dist;
528  }
529 
530  new_entry->set_surface_normal(normal);
531  new_entry->set_surface_point(from_center - normal * dist);
532  new_entry->set_interior_point(from_center - normal * (dist + into_depth));
533  new_entry->set_contact_pos(contact_point);
534  new_entry->set_contact_normal(get_normal());
535  new_entry->set_t(actual_t);
536 
537  return new_entry;
538 }
539 
540 /**
541  * This is part of the double-dispatch implementation of test_intersection().
542  * It is called when the "from" object is a line.
543  */
544 PT(CollisionEntry) CollisionPolygon::
545 test_intersection_from_line(const CollisionEntry &entry) const {
546  if (_points.size() < 3) {
547  return nullptr;
548  }
549 
550  const CollisionLine *line;
551  DCAST_INTO_R(line, entry.get_from(), nullptr);
552 
553  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
554 
555  LPoint3 from_origin = line->get_origin() * wrt_mat;
556  LVector3 from_direction = line->get_direction() * wrt_mat;
557 
558  PN_stdfloat t;
559  if (!get_plane().intersects_line(t, from_origin, from_direction)) {
560  // No intersection.
561  return nullptr;
562  }
563 
564  LPoint3 plane_point = from_origin + t * from_direction;
565  LPoint2 p = to_2d(plane_point);
566 
567  const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
568  if (cpa != nullptr) {
569  // We have a clip plane; apply it.
570  Points new_points;
571  if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
572  // All points are behind the clip plane.
573  if (!point_is_inside(p, _points)) {
574  return nullptr;
575  }
576 
577  } else {
578  if (new_points.size() < 3) {
579  return nullptr;
580  }
581  if (!point_is_inside(p, new_points)) {
582  return nullptr;
583  }
584  }
585 
586  } else {
587  // No clip plane is in effect. Do the default test.
588  if (!point_is_inside(p, _points)) {
589  return nullptr;
590  }
591  }
592 
593  if (collide_cat.is_debug()) {
594  collide_cat.debug()
595  << "intersection detected from " << entry.get_from_node_path()
596  << " into " << entry.get_into_node_path() << "\n";
597  }
598  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
599 
600  LVector3 normal = (has_effective_normal() && line->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
601 
602  new_entry->set_surface_normal(normal);
603  new_entry->set_surface_point(plane_point);
604 
605  return new_entry;
606 }
607 
608 /**
609  * This is part of the double-dispatch implementation of test_intersection().
610  * It is called when the "from" object is a ray.
611  */
612 PT(CollisionEntry) CollisionPolygon::
613 test_intersection_from_ray(const CollisionEntry &entry) const {
614  if (_points.size() < 3) {
615  return nullptr;
616  }
617 
618  const CollisionRay *ray;
619  DCAST_INTO_R(ray, entry.get_from(), nullptr);
620 
621  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
622 
623  LPoint3 from_origin = ray->get_origin() * wrt_mat;
624  LVector3 from_direction = ray->get_direction() * wrt_mat;
625 
626  PN_stdfloat t;
627  if (!get_plane().intersects_line(t, from_origin, from_direction)) {
628  // No intersection.
629  return nullptr;
630  }
631 
632  if (t < 0.0f) {
633  // The intersection point is before the start of the ray.
634  return nullptr;
635  }
636 
637  LPoint3 plane_point = from_origin + t * from_direction;
638  LPoint2 p = to_2d(plane_point);
639 
640  const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
641  if (cpa != nullptr) {
642  // We have a clip plane; apply it.
643  Points new_points;
644  if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
645  // All points are behind the clip plane.
646  if (!point_is_inside(p, _points)) {
647  return nullptr;
648  }
649 
650  } else {
651  if (new_points.size() < 3) {
652  return nullptr;
653  }
654  if (!point_is_inside(p, new_points)) {
655  return nullptr;
656  }
657  }
658 
659  } else {
660  // No clip plane is in effect. Do the default test.
661  if (!point_is_inside(p, _points)) {
662  return nullptr;
663  }
664  }
665 
666  if (collide_cat.is_debug()) {
667  collide_cat.debug()
668  << "intersection detected from " << entry.get_from_node_path()
669  << " into " << entry.get_into_node_path() << "\n";
670  }
671  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
672 
673  LVector3 normal = (has_effective_normal() && ray->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
674 
675  new_entry->set_surface_normal(normal);
676  new_entry->set_surface_point(plane_point);
677 
678  return new_entry;
679 }
680 
681 /**
682  * This is part of the double-dispatch implementation of test_intersection().
683  * It is called when the "from" object is a segment.
684  */
685 PT(CollisionEntry) CollisionPolygon::
686 test_intersection_from_segment(const CollisionEntry &entry) const {
687  if (_points.size() < 3) {
688  return nullptr;
689  }
690 
691  const CollisionSegment *segment;
692  DCAST_INTO_R(segment, entry.get_from(), nullptr);
693 
694  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
695 
696  LPoint3 from_a = segment->get_point_a() * wrt_mat;
697  LPoint3 from_b = segment->get_point_b() * wrt_mat;
698  LPoint3 from_direction = from_b - from_a;
699 
700  PN_stdfloat t;
701  if (!get_plane().intersects_line(t, from_a, from_direction)) {
702  // No intersection.
703  return nullptr;
704  }
705 
706  if (t < 0.0f || t > 1.0f) {
707  // The intersection point is before the start of the segment or after the
708  // end of the segment.
709  return nullptr;
710  }
711 
712  LPoint3 plane_point = from_a + t * from_direction;
713  LPoint2 p = to_2d(plane_point);
714 
715  const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
716  if (cpa != nullptr) {
717  // We have a clip plane; apply it.
718  Points new_points;
719  if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
720  // All points are behind the clip plane.
721  if (!point_is_inside(p, _points)) {
722  return nullptr;
723  }
724 
725  } else {
726  if (new_points.size() < 3) {
727  return nullptr;
728  }
729  if (!point_is_inside(p, new_points)) {
730  return nullptr;
731  }
732  }
733 
734  } else {
735  // No clip plane is in effect. Do the default test.
736  if (!point_is_inside(p, _points)) {
737  return nullptr;
738  }
739  }
740 
741  if (collide_cat.is_debug()) {
742  collide_cat.debug()
743  << "intersection detected from " << entry.get_from_node_path()
744  << " into " << entry.get_into_node_path() << "\n";
745  }
746  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
747 
748  LVector3 normal = (has_effective_normal() && segment->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
749 
750  new_entry->set_surface_normal(normal);
751  new_entry->set_surface_point(plane_point);
752 
753  return new_entry;
754 }
755 
756 /**
757  * This is part of the double-dispatch implementation of test_intersection().
758  * It is called when the "from" object is a parabola.
759  */
760 PT(CollisionEntry) CollisionPolygon::
761 test_intersection_from_parabola(const CollisionEntry &entry) const {
762  if (_points.size() < 3) {
763  return nullptr;
764  }
765 
766  const CollisionParabola *parabola;
767  DCAST_INTO_R(parabola, entry.get_from(), nullptr);
768 
769  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
770 
771  // Convert the parabola into local coordinate space.
772  LParabola local_p(parabola->get_parabola());
773  local_p.xform(wrt_mat);
774 
775  PN_stdfloat t1, t2;
776  if (!get_plane().intersects_parabola(t1, t2, local_p)) {
777  // No intersection.
778  return nullptr;
779  }
780 
781  PN_stdfloat t;
782  if (t1 >= parabola->get_t1() && t1 <= parabola->get_t2()) {
783  if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
784  // Both intersection points are within our segment of the parabola.
785  // Choose the first of the two.
786  t = min(t1, t2);
787  } else {
788  // Only t1 is within our segment.
789  t = t1;
790  }
791 
792  } else if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
793  // Only t2 is within our segment.
794  t = t2;
795 
796  } else {
797  // Neither intersection point is within our segment.
798  return nullptr;
799  }
800 
801  LPoint3 plane_point = local_p.calc_point(t);
802  LPoint2 p = to_2d(plane_point);
803 
804  const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
805  if (cpa != nullptr) {
806  // We have a clip plane; apply it.
807  Points new_points;
808  if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
809  // All points are behind the clip plane.
810  if (!point_is_inside(p, _points)) {
811  return nullptr;
812  }
813 
814  } else {
815  if (new_points.size() < 3) {
816  return nullptr;
817  }
818  if (!point_is_inside(p, new_points)) {
819  return nullptr;
820  }
821  }
822 
823  } else {
824  // No clip plane is in effect. Do the default test.
825  if (!point_is_inside(p, _points)) {
826  return nullptr;
827  }
828  }
829 
830  if (collide_cat.is_debug()) {
831  collide_cat.debug()
832  << "intersection detected from " << entry.get_from_node_path()
833  << " into " << entry.get_into_node_path() << "\n";
834  }
835  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
836 
837  LVector3 normal = (has_effective_normal() && parabola->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
838 
839  new_entry->set_surface_normal(normal);
840  new_entry->set_surface_point(plane_point);
841 
842  return new_entry;
843 }
844 
845 /**
846  * This is part of the double-dispatch implementation of test_intersection().
847  * It is called when the "from" object is a box.
848  */
849 PT(CollisionEntry) CollisionPolygon::
850 test_intersection_from_box(const CollisionEntry &entry) const {
851  const CollisionBox *box;
852  DCAST_INTO_R(box, entry.get_from(), nullptr);
853 
854  // To make things easier, transform the box into the coordinate space of the
855  // plane.
856  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
857  LMatrix4 plane_mat = wrt_mat * _to_2d_mat;
858 
859  LPoint3 from_center = box->get_center() * plane_mat;
860  LVector3 from_extents = box->get_dimensions() * 0.5f;
861 
862  // Determine the basis vectors describing the box.
863  LVecBase3 box_x = plane_mat.get_row3(0) * from_extents[0];
864  LVecBase3 box_y = plane_mat.get_row3(1) * from_extents[1];
865  LVecBase3 box_z = plane_mat.get_row3(2) * from_extents[2];
866 
867  // Is there a separating axis between the plane and the box?
868  if (cabs(from_center[1]) > cabs(box_x[1]) + cabs(box_y[1]) + cabs(box_z[1])) {
869  // There is one. No collision.
870  return nullptr;
871  }
872 
873  // Now do the same for each of the box' primary axes.
874  PN_stdfloat r1, center, r2;
875 
876  r1 = cabs(box_x.dot(box_x)) + cabs(box_y.dot(box_x)) + cabs(box_z.dot(box_x));
877  project(box_x, center, r2);
878  if (cabs(from_center.dot(box_x) - center) > r1 + r2) {
879  return nullptr;
880  }
881 
882  r1 = cabs(box_x.dot(box_y)) + cabs(box_y.dot(box_y)) + cabs(box_z.dot(box_y));
883  project(box_y, center, r2);
884  if (cabs(from_center.dot(box_y) - center) > r1 + r2) {
885  return nullptr;
886  }
887 
888  r1 = cabs(box_x.dot(box_z)) + cabs(box_y.dot(box_z)) + cabs(box_z.dot(box_z));
889  project(box_z, center, r2);
890  if (cabs(from_center.dot(box_z) - center) > r1 + r2) {
891  return nullptr;
892  }
893 
894  // Now do the same check for the cross products between the box axes and the
895  // polygon edges.
896  Points::const_iterator pi;
897  for (pi = _points.begin(); pi != _points.end(); ++pi) {
898  const PointDef &pd = *pi;
899  LVector3 axis;
900 
901  axis.set(-box_x[1] * pd._v[1],
902  box_x[0] * pd._v[1] - box_x[2] * pd._v[0],
903  box_x[1] * pd._v[0]);
904  r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
905  project(axis, center, r2);
906  if (cabs(from_center.dot(axis) - center) > r1 + r2) {
907  return nullptr;
908  }
909 
910  axis.set(-box_y[1] * pd._v[1],
911  box_y[0] * pd._v[1] - box_y[2] * pd._v[0],
912  box_y[1] * pd._v[0]);
913  r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
914  project(axis, center, r2);
915  if (cabs(from_center.dot(axis) - center) > r1 + r2) {
916  return nullptr;
917  }
918 
919  axis.set(-box_z[1] * pd._v[1],
920  box_z[0] * pd._v[1] - box_z[2] * pd._v[0],
921  box_z[1] * pd._v[0]);
922  r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
923  project(axis, center, r2);
924  if (cabs(from_center.dot(axis) - center) > r1 + r2) {
925  return nullptr;
926  }
927  }
928 
929  if (collide_cat.is_debug()) {
930  collide_cat.debug()
931  << "intersection detected from " << entry.get_from_node_path()
932  << " into " << entry.get_into_node_path() << "\n";
933  }
934  PT(CollisionEntry) new_entry = new CollisionEntry(entry);
935 
936  LVector3 normal = (has_effective_normal() && box->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
937  new_entry->set_surface_normal(normal);
938 
939  // Determine which point on the cube will be the interior point. This is
940  // the calculation that is also used for the plane, which is not perfectly
941  // applicable, but I suppose it's better than nothing.
942  LPoint3 interior_point = box->get_center() * wrt_mat +
943  wrt_mat.get_row3(0) * from_extents[0] * ((box_x[1] > 0) - (box_x[1] < 0)) +
944  wrt_mat.get_row3(1) * from_extents[1] * ((box_y[1] > 0) - (box_y[1] < 0)) +
945  wrt_mat.get_row3(2) * from_extents[2] * ((box_z[1] > 0) - (box_z[1] < 0));
946 
947  // The surface point is the interior point projected onto the plane.
948  new_entry->set_surface_point(get_plane().project(interior_point));
949  new_entry->set_interior_point(interior_point);
950 
951  return new_entry;
952 }
953 
954 /**
955  * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this
956  * solid.
957  */
958 void CollisionPolygon::
959 fill_viz_geom() {
960  if (collide_cat.is_debug()) {
961  collide_cat.debug()
962  << "Recomputing viz for " << *this << "\n";
963  }
964  nassertv(_viz_geom != nullptr && _bounds_viz_geom != nullptr);
965  draw_polygon(_viz_geom, _bounds_viz_geom, _points);
966 }
967 
968 /**
969  * Returns the linear distance of p to the line segment defined by f and t,
970  * where v = (t - f).normalize(). The result is negative if p is left of the
971  * line, positive if it is right of the line. If the result is positive, it
972  * is constrained by endpoints of the line segment (i.e. the result might be
973  * larger than it would be for a straight distance-to-line test). If the
974  * result is negative, we don't bother.
975  */
976 PN_stdfloat CollisionPolygon::
977 dist_to_line_segment(const LPoint2 &p,
978  const LPoint2 &f, const LPoint2 &t,
979  const LVector2 &v) {
980  LVector2 v1 = (p - f);
981  PN_stdfloat d = (v1[0] * v[1] - v1[1] * v[0]);
982  if (d < 0.0f) {
983  return d;
984  }
985 
986  // Compute the nearest point on the line.
987  LPoint2 q = p + LVector2(-v[1], v[0]) * d;
988 
989  // Now constrain that point to the line segment.
990  if (v[0] > 0.0f) {
991  // X+
992  if (v[1] > 0.0f) {
993  // Y+
994  if (v[0] > v[1]) {
995  // X-dominant.
996  if (q[0] < f[0]) {
997  return (p - f).length();
998  } if (q[0] > t[0]) {
999  return (p - t).length();
1000  } else {
1001  return d;
1002  }
1003  } else {
1004  // Y-dominant.
1005  if (q[1] < f[1]) {
1006  return (p - f).length();
1007  } if (q[1] > t[1]) {
1008  return (p - t).length();
1009  } else {
1010  return d;
1011  }
1012  }
1013  } else {
1014  // Y-
1015  if (v[0] > -v[1]) {
1016  // X-dominant.
1017  if (q[0] < f[0]) {
1018  return (p - f).length();
1019  } if (q[0] > t[0]) {
1020  return (p - t).length();
1021  } else {
1022  return d;
1023  }
1024  } else {
1025  // Y-dominant.
1026  if (q[1] > f[1]) {
1027  return (p - f).length();
1028  } if (q[1] < t[1]) {
1029  return (p - t).length();
1030  } else {
1031  return d;
1032  }
1033  }
1034  }
1035  } else {
1036  // X-
1037  if (v[1] > 0.0f) {
1038  // Y+
1039  if (-v[0] > v[1]) {
1040  // X-dominant.
1041  if (q[0] > f[0]) {
1042  return (p - f).length();
1043  } if (q[0] < t[0]) {
1044  return (p - t).length();
1045  } else {
1046  return d;
1047  }
1048  } else {
1049  // Y-dominant.
1050  if (q[1] < f[1]) {
1051  return (p - f).length();
1052  } if (q[1] > t[1]) {
1053  return (p - t).length();
1054  } else {
1055  return d;
1056  }
1057  }
1058  } else {
1059  // Y-
1060  if (-v[0] > -v[1]) {
1061  // X-dominant.
1062  if (q[0] > f[0]) {
1063  return (p - f).length();
1064  } if (q[0] < t[0]) {
1065  return (p - t).length();
1066  } else {
1067  return d;
1068  }
1069  } else {
1070  // Y-dominant.
1071  if (q[1] > f[1]) {
1072  return (p - f).length();
1073  } if (q[1] < t[1]) {
1074  return (p - t).length();
1075  } else {
1076  return d;
1077  }
1078  }
1079  }
1080  }
1081 }
1082 
1083 
1084 /**
1085  * Now that the _p members of the given points array have been computed, go
1086  * back and compute all of the _v members.
1087  */
1088 void CollisionPolygon::
1089 compute_vectors(Points &points) {
1090  size_t num_points = points.size();
1091  for (size_t i = 0; i < num_points; i++) {
1092  points[i]._v = points[(i + 1) % num_points]._p - points[i]._p;
1093  points[i]._v.normalize();
1094  }
1095 }
1096 
1097 /**
1098  * Fills up the indicated GeomNode with the Geoms to draw the polygon
1099  * indicated with the given set of 2-d points.
1100  */
1101 void CollisionPolygon::
1102 draw_polygon(GeomNode *viz_geom_node, GeomNode *bounds_viz_geom_node,
1103  const CollisionPolygon::Points &points) const {
1104  if (points.size() < 3) {
1105  if (collide_cat.is_debug()) {
1106  collide_cat.debug()
1107  << "(Degenerate poly, ignoring.)\n";
1108  }
1109  return;
1110  }
1111 
1112  LMatrix4 to_3d_mat;
1113  rederive_to_3d_mat(to_3d_mat);
1114 
1115  PT(GeomVertexData) vdata = new GeomVertexData
1116  ("collision", GeomVertexFormat::get_v3(),
1117  Geom::UH_static);
1118  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1119 
1120  Points::const_iterator pi;
1121  for (pi = points.begin(); pi != points.end(); ++pi) {
1122  vertex.add_data3(to_3d((*pi)._p, to_3d_mat));
1123  }
1124 
1125  PT(GeomTrifans) body = new GeomTrifans(Geom::UH_static);
1126  body->add_consecutive_vertices(0, points.size());
1127  body->close_primitive();
1128 
1129  PT(GeomLinestrips) border = new GeomLinestrips(Geom::UH_static);
1130  border->add_consecutive_vertices(0, points.size());
1131  border->add_vertex(0);
1132  border->close_primitive();
1133 
1134  PT(Geom) geom1 = new Geom(vdata);
1135  geom1->add_primitive(body);
1136 
1137  PT(Geom) geom2 = new Geom(vdata);
1138  geom2->add_primitive(border);
1139 
1140  viz_geom_node->add_geom(geom1, ((CollisionPolygon *)this)->get_solid_viz_state());
1141  viz_geom_node->add_geom(geom2, ((CollisionPolygon *)this)->get_wireframe_viz_state());
1142 
1143  bounds_viz_geom_node->add_geom(geom1, ((CollisionPolygon *)this)->get_solid_bounds_viz_state());
1144  bounds_viz_geom_node->add_geom(geom2, ((CollisionPolygon *)this)->get_wireframe_bounds_viz_state());
1145 }
1146 
1147 
1148 /**
1149  * Returns true if the indicated point is within the polygon's 2-d space,
1150  * false otherwise.
1151  */
1152 bool CollisionPolygon::
1153 point_is_inside(const LPoint2 &p, const CollisionPolygon::Points &points) const {
1154  // We insist that the polygon be convex. This makes things a bit simpler.
1155 
1156  // In the case of a convex polygon, defined with points in counterclockwise
1157  // order, a point is interior to the polygon iff the point is not right of
1158  // each of the edges.
1159  for (int i = 0; i < (int)points.size() - 1; i++) {
1160  if (is_right(p - points[i]._p, points[i+1]._p - points[i]._p)) {
1161  return false;
1162  }
1163  }
1164  if (is_right(p - points[points.size() - 1]._p,
1165  points[0]._p - points[points.size() - 1]._p)) {
1166  return false;
1167  }
1168 
1169  return true;
1170 }
1171 
1172 /**
1173  * Returns the linear distance from the 2-d point to the nearest part of the
1174  * polygon defined by the points vector. The result is negative if the point
1175  * is within the polygon.
1176  */
1177 PN_stdfloat CollisionPolygon::
1178 dist_to_polygon(const LPoint2 &p, const CollisionPolygon::Points &points) const {
1179 
1180  // We know that that the polygon is convex and is defined with the points in
1181  // counterclockwise order. Therefore, we simply compare the signed distance
1182  // to each line segment; we ignore any negative values, and take the minimum
1183  // of all the positive values.
1184 
1185  // If all values are negative, the point is within the polygon; we therefore
1186  // return an arbitrary negative result.
1187 
1188  bool got_dist = false;
1189  PN_stdfloat best_dist = -1.0f;
1190 
1191  size_t num_points = points.size();
1192  for (size_t i = 0; i < num_points - 1; ++i) {
1193  PN_stdfloat d = dist_to_line_segment(p, points[i]._p, points[i + 1]._p,
1194  points[i]._v);
1195  if (d >= 0.0f) {
1196  if (!got_dist || d < best_dist) {
1197  best_dist = d;
1198  got_dist = true;
1199  }
1200  }
1201  }
1202 
1203  PN_stdfloat d = dist_to_line_segment(p, points[num_points - 1]._p, points[0]._p,
1204  points[num_points - 1]._v);
1205  if (d >= 0.0f) {
1206  if (!got_dist || d < best_dist) {
1207  best_dist = d;
1208  got_dist = true;
1209  }
1210  }
1211 
1212  return best_dist;
1213 }
1214 
1215 /**
1216  * Projects the polygon onto the given axis, returning the center on the line
1217  * and the half extent.
1218  */
1219 void CollisionPolygon::
1220 project(const LVector3 &axis, PN_stdfloat &center, PN_stdfloat &extent) const {
1221  PN_stdfloat begin, end;
1222 
1223  Points::const_iterator pi;
1224  pi = _points.begin();
1225 
1226  const LPoint2 &point = (*pi)._p;
1227  begin = point[0] * axis[0] + point[1] * axis[2];
1228  end = begin;
1229 
1230  for (; pi != _points.end(); ++pi) {
1231  const LPoint2 &point = (*pi)._p;
1232 
1233  PN_stdfloat t = point[0] * axis[0] + point[1] * axis[2];
1234  begin = min(begin, t);
1235  end = max(end, t);
1236  }
1237 
1238  center = (end + begin) * 0.5f;
1239  extent = cabs((end - begin) * 0.5f);
1240 }
1241 
1242 /**
1243  *
1244  */
1245 void CollisionPolygon::
1246 setup_points(const LPoint3 *begin, const LPoint3 *end) {
1247  int num_points = end - begin;
1248  nassertv(num_points >= 3);
1249 
1250  _points.clear();
1251 
1252  // Tell the base CollisionPlane class what its plane will be. To do this,
1253  // we must first compute the polygon normal.
1254  LVector3 normal = LVector3::zero();
1255 
1256  // Project the polygon into each of the three major planes and calculate the
1257  // area of each 2-d projection. This becomes the polygon normal. This
1258  // works because the ratio between these different areas corresponds to the
1259  // angle at which the polygon is tilted toward each plane.
1260  for (int i = 0; i < num_points; i++) {
1261  const LPoint3 &p0 = begin[i];
1262  const LPoint3 &p1 = begin[(i + 1) % num_points];
1263  normal[0] += p0[1] * p1[2] - p0[2] * p1[1];
1264  normal[1] += p0[2] * p1[0] - p0[0] * p1[2];
1265  normal[2] += p0[0] * p1[1] - p0[1] * p1[0];
1266  }
1267 
1268  if (normal.length_squared() == 0.0f) {
1269  // The polygon has no area.
1270  return;
1271  }
1272 
1273 #ifndef NDEBUG
1274  // Make sure all the source points are good.
1275  {
1276  if (!verify_points(begin, end)) {
1277  collide_cat.error() << "Invalid points in CollisionPolygon:\n";
1278  const LPoint3 *pi;
1279  for (pi = begin; pi != end; ++pi) {
1280  collide_cat.error(false) << " " << (*pi) << "\n";
1281  }
1282  collide_cat.error(false)
1283  << " normal " << normal << " with length " << normal.length() << "\n";
1284 
1285  return;
1286  }
1287  }
1288 
1289  if (collide_cat.is_spam()) {
1290  collide_cat.spam()
1291  << "CollisionPolygon defined with " << num_points << " vertices:\n";
1292  const LPoint3 *pi;
1293  for (pi = begin; pi != end; ++pi) {
1294  collide_cat.spam(false) << " " << (*pi) << "\n";
1295  }
1296  }
1297 #endif
1298 
1299  set_plane(LPlane(normal, begin[0]));
1300 
1301  // Construct a matrix that rotates the points from the (X,0,Z) plane into
1302  // the 3-d plane.
1303  LMatrix4 to_3d_mat;
1304  calc_to_3d_mat(to_3d_mat);
1305 
1306  // And the inverse matrix rotates points from 3-d space into the 2-d plane.
1307  _to_2d_mat.invert_from(to_3d_mat);
1308 
1309  // Now project all of the points onto the 2-d plane.
1310 
1311  const LPoint3 *pi;
1312  for (pi = begin; pi != end; ++pi) {
1313  LPoint3 point = (*pi) * _to_2d_mat;
1314  _points.push_back(PointDef(point[0], point[2]));
1315  }
1316 
1317  nassertv(_points.size() >= 3);
1318 
1319 #ifndef NDEBUG
1320  /*
1321  // Now make sure the points define a convex polygon.
1322  if (is_concave()) {
1323  collide_cat.error() << "Invalid concave CollisionPolygon defined:\n";
1324  const LPoint3 *pi;
1325  for (pi = begin; pi != end; ++pi) {
1326  collide_cat.error(false) << " " << (*pi) << "\n";
1327  }
1328  collide_cat.error(false)
1329  << " normal " << normal << " with length " << normal.length() << "\n";
1330  _points.clear();
1331  }
1332  */
1333 #endif
1334 
1335  compute_vectors(_points);
1336 }
1337 
1338 /**
1339  * Converts the indicated point to 3-d space according to the way
1340  * CollisionPolygons used to be stored in bam files prior to 4.9.
1341  */
1342 LPoint3 CollisionPolygon::
1343 legacy_to_3d(const LVecBase2 &point2d, int axis) const {
1344  nassertr(!point2d.is_nan(), LPoint3(0.0f, 0.0f, 0.0f));
1345 
1346  LVector3 normal = get_normal();
1347  PN_stdfloat D = get_plane()[3];
1348 
1349  nassertr(!normal.is_nan(), LPoint3(0.0f, 0.0f, 0.0f));
1350  nassertr(!cnan(D), LPoint3(0.0f, 0.0f, 0.0f));
1351 
1352  switch (axis) {
1353  case 0: // AT_x:
1354  return LPoint3(-(normal[1]*point2d[0] + normal[2]*point2d[1] + D)/normal[0], point2d[0], point2d[1]);
1355 
1356  case 1: // AT_y:
1357  return LPoint3(point2d[0],
1358  -(normal[0]*point2d[0] + normal[2]*point2d[1] + D)/normal[1], point2d[1]);
1359 
1360  case 2: // AT_z:
1361  return LPoint3(point2d[0], point2d[1],
1362  -(normal[0]*point2d[0] + normal[1]*point2d[1] + D)/normal[2]);
1363  }
1364 
1365  nassertr(false, LPoint3(0.0f, 0.0f, 0.0f));
1366  return LPoint3(0.0f, 0.0f, 0.0f);
1367 }
1368 
1369 /**
1370  * Clips the source_points of the polygon by the indicated clipping plane, and
1371  * modifies new_points to reflect the new set of clipped points (but does not
1372  * compute the vectors in new_points).
1373  *
1374  * The return value is true if the set of points is unmodified (all points are
1375  * behind the clip plane), or false otherwise.
1376  */
1377 bool CollisionPolygon::
1378 clip_polygon(CollisionPolygon::Points &new_points,
1379  const CollisionPolygon::Points &source_points,
1380  const LPlane &plane) const {
1381  new_points.clear();
1382  if (source_points.empty()) {
1383  return true;
1384  }
1385 
1386  LPoint3 from3d;
1387  LVector3 delta3d;
1388  if (!plane.intersects_plane(from3d, delta3d, get_plane())) {
1389  // The clipping plane is parallel to the polygon. The polygon is either
1390  // all in or all out.
1391  if (plane.dist_to_plane(get_plane().get_point()) < 0.0) {
1392  // A point within the polygon is behind the clipping plane: the polygon
1393  // is all in.
1394  new_points = source_points;
1395  return true;
1396  }
1397  return false;
1398  }
1399 
1400  // Project the line of intersection into the 2-d plane. Now we have a 2-d
1401  // clipping line.
1402  LPoint2 from2d = to_2d(from3d);
1403  LVector2 delta2d = to_2d(delta3d);
1404 
1405  PN_stdfloat a = -delta2d[1];
1406  PN_stdfloat b = delta2d[0];
1407  PN_stdfloat c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0];
1408 
1409  // Now walk through the points. Any point on the left of our line gets
1410  // removed, and the line segment clipped at the point of intersection.
1411 
1412  // We might increase the number of vertices by as many as 1, if the plane
1413  // clips off exactly one corner. (We might also decrease the number of
1414  // vertices, or keep them the same number.)
1415  new_points.reserve(source_points.size() + 1);
1416 
1417  LPoint2 last_point = source_points.back()._p;
1418  bool last_is_in = !is_right(last_point - from2d, delta2d);
1419  bool all_in = last_is_in;
1420  Points::const_iterator pi;
1421  for (pi = source_points.begin(); pi != source_points.end(); ++pi) {
1422  const LPoint2 &this_point = (*pi)._p;
1423  bool this_is_in = !is_right(this_point - from2d, delta2d);
1424 
1425  // There appears to be a compiler bug in gcc 4.0: we need to extract this
1426  // comparison outside of the if statement.
1427  bool crossed_over = (this_is_in != last_is_in);
1428  if (crossed_over) {
1429  // We have just crossed over the clipping line. Find the point of
1430  // intersection.
1431  LVector2 d = this_point - last_point;
1432  PN_stdfloat denom = (a * d[0] + b * d[1]);
1433  if (denom != 0.0) {
1434  PN_stdfloat t = -(a * last_point[0] + b * last_point[1] + c) / denom;
1435  LPoint2 p = last_point + t * d;
1436 
1437  new_points.push_back(PointDef(p[0], p[1]));
1438  last_is_in = this_is_in;
1439  }
1440  }
1441 
1442  if (this_is_in) {
1443  // We are behind the clipping line. Keep the point.
1444  new_points.push_back(PointDef(this_point[0], this_point[1]));
1445  } else {
1446  all_in = false;
1447  }
1448 
1449  last_point = this_point;
1450  }
1451 
1452  return all_in;
1453 }
1454 
1455 /**
1456  * Clips the polygon by all of the clip planes named in the clip plane
1457  * attribute and fills new_points up with the resulting points.
1458  *
1459  * The return value is true if the set of points is unmodified (all points are
1460  * behind all the clip planes), or false otherwise.
1461  */
1462 bool CollisionPolygon::
1463 apply_clip_plane(CollisionPolygon::Points &new_points,
1464  const ClipPlaneAttrib *cpa,
1465  const TransformState *net_transform) const {
1466  bool all_in = true;
1467 
1468  int num_planes = cpa->get_num_on_planes();
1469  bool first_plane = true;
1470 
1471  for (int i = 0; i < num_planes; i++) {
1472  NodePath plane_path = cpa->get_on_plane(i);
1473  PlaneNode *plane_node = DCAST(PlaneNode, plane_path.node());
1474  if ((plane_node->get_clip_effect() & PlaneNode::CE_collision) != 0) {
1475  CPT(TransformState) new_transform =
1476  net_transform->invert_compose(plane_path.get_net_transform());
1477 
1478  LPlane plane = plane_node->get_plane() * new_transform->get_mat();
1479  if (first_plane) {
1480  first_plane = false;
1481  if (!clip_polygon(new_points, _points, plane)) {
1482  all_in = false;
1483  }
1484  } else {
1485  Points last_points;
1486  last_points.swap(new_points);
1487  if (!clip_polygon(new_points, last_points, plane)) {
1488  all_in = false;
1489  }
1490  }
1491  }
1492  }
1493 
1494  if (!all_in) {
1495  compute_vectors(new_points);
1496  }
1497 
1498  return all_in;
1499 }
1500 
1501 /**
1502  * Function to write the important information in the particular object to a
1503  * Datagram
1504  */
1505 void CollisionPolygon::
1507  CollisionPlane::write_datagram(manager, me);
1508  me.add_uint16(_points.size());
1509  for (size_t i = 0; i < _points.size(); i++) {
1510  _points[i]._p.write_datagram(me);
1511  _points[i]._v.write_datagram(me);
1512  }
1513  _to_2d_mat.write_datagram(me);
1514 }
1515 
1516 /**
1517  * Function that reads out of the datagram (or asks manager to read) all of
1518  * the data that is needed to re-create this object and stores it in the
1519  * appropiate place
1520  */
1521 void CollisionPolygon::
1522 fillin(DatagramIterator &scan, BamReader *manager) {
1523  CollisionPlane::fillin(scan, manager);
1524 
1525  size_t size = scan.get_uint16();
1526  for (size_t i = 0; i < size; i++) {
1527  LPoint2 p;
1528  LVector2 v;
1529  p.read_datagram(scan);
1530  v.read_datagram(scan);
1531  _points.push_back(PointDef(p, v));
1532  }
1533  _to_2d_mat.read_datagram(scan);
1534 
1535  if (manager->get_file_minor_ver() < 13) {
1536  // Before bam version 6.13, we were inadvertently storing CollisionPolygon
1537  // vertices clockwise, instead of counter-clockwise. Correct that by re-
1538  // projecting.
1539  if (_points.size() >= 3) {
1540  LMatrix4 to_3d_mat;
1541  rederive_to_3d_mat(to_3d_mat);
1542 
1543  epvector<LPoint3> verts;
1544  verts.reserve(_points.size());
1545  Points::const_iterator pi;
1546  for (pi = _points.begin(); pi != _points.end(); ++pi) {
1547  verts.push_back(to_3d((*pi)._p, to_3d_mat));
1548  }
1549 
1550  const LPoint3 *verts_begin = &verts[0];
1551  const LPoint3 *verts_end = verts_begin + verts.size();
1552  setup_points(verts_begin, verts_end);
1553  }
1554  }
1555 }
1556 
1557 
1558 /**
1559  * Factory method to generate a CollisionPolygon object
1560  */
1564  DatagramIterator scan;
1565  BamReader *manager;
1566 
1567  parse_params(params, scan, manager);
1568  me->fillin(scan, manager);
1569  return me;
1570 }
1571 
1572 /**
1573  * Factory method to generate a CollisionPolygon object
1574  */
1575 void CollisionPolygon::
1578 }
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
get_t2
Returns the ending point on the parabola.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
const LVector3 & get_effective_normal() const
Returns the normal that was set by set_effective_normal().
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
get_from_node_path
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
Definition: boundingBox.h:29
An infinite ray, with a specific origin and direction.
Definition: collisionRay.h:27
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static void register_with_read_factory()
Factory method to generate a CollisionPolygon object.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Indicates a coordinate-system transform on vertices.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual LPoint3 get_collision_origin() const
Returns the point in space deemed to be the "origin" of the solid for collision purposes.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
PT(PandaNode) CollisionPolygon
Returns a GeomNode that may be rendered to visualize the CollisionSolid.
The abstract base class for all things that can collide with other things in the world,...
virtual PStatCollector & get_test_pcollector()
Returns a PStatCollector that is used to count the number of intersection tests made against a solid ...
A cuboid collision volume or object.
Definition: collisionBox.h:27
Defines a series of triangle fans.
Definition: geomTrifans.h:23
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
get_parabola
Returns the parabola specified by this solid.
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
This collects together the pieces of data that are accumulated for each node while walking the scene ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This functions similarly to a LightAttrib.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
A spherical collision volume or object.
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_into_node_path
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
int get_file_minor_ver() const
Returns the minor version number of the Bam file currently being read.
Definition: bamReader.I:83
get_respect_effective_normal
See set_respect_effective_normal().
static TypedWritable * make_CollisionPolygon(const FactoryParams &params)
Factory method to generate a CollisionPolygon object.
get_t1
Returns the starting point on the parabola.
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A lightweight class that represents a single element that may be timed and/or counted via stats.
void parse_params(const FactoryParams &params, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
Definition: bamReader.I:275
void add_uint16(uint16_t value)
Adds an unsigned 16-bit integer to the datagram.
Definition: datagram.I:85
A finite line segment, with two specific endpoints but no thickness.
static bool verify_points(const LPoint3 &a, const LPoint3 &b, const LPoint3 &c)
Verifies that the indicated set of points will define a valid CollisionPolygon: that is,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const LPlane & get_plane() const
Returns the plane represented by the PlaneNode.
Definition: planeNode.I:54
get_num_on_planes
Returns the number of planes that are enabled by the attribute.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
Defines a single collision event.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
Definition: geom.h:54
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void register_factory(TypeHandle handle, CreateFunc *func, void *user_data=nullptr)
Registers a new kind of thing the Factory will be able to create.
Definition: factory.I:73
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An infinite line, similar to a CollisionRay, except that it extends in both directions.
Definition: collisionLine.h:25
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
get_on_plane
Returns the nth plane enabled by the attribute, sorted in render order.
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
This defines a parabolic arc, or subset of an arc, similar to the path of a projectile or falling obj...
Defines a series of line strips.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_point
Returns the nth vertex of the CollisionPolygon, expressed in 3-D space.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_effective_normal() const
Returns true if a special normal was set by set_effective_normal(), false otherwise.
A class to retrieve the individual data elements previously stored in a Datagram.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual PStatCollector & get_volume_pcollector()
Returns a PStatCollector that is used to count the number of bounding volume tests made against a sol...
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node that contains a plane.
Definition: planeNode.h:36
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:584
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_clip_effect() const
Returns the clip_effect bits for this clip plane.
Definition: planeNode.I:128