Panda3D
Loading...
Searching...
No Matches
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
44using std::max;
45using std::min;
46
47PStatCollector CollisionPolygon::_volume_pcollector("Collision Volumes:CollisionPolygon");
48PStatCollector CollisionPolygon::_test_pcollector("Collision Tests:CollisionPolygon");
49TypeHandle CollisionPolygon::_type_handle;
50
51/**
52 *
53 */
54CollisionPolygon::
55CollisionPolygon(const CollisionPolygon &copy) :
56 CollisionPlane(copy),
57 _points(copy._points),
58 _to_2d_mat(copy._to_2d_mat)
59{
60}
61
62/**
63 *
64 */
65CollisionSolid *CollisionPolygon::
66make_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 */
80verify_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 */
129is_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 */
138is_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 */
179xform(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
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 */
219get_collision_origin() const {
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 */
238get_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 */
301void CollisionPolygon::
302output(std::ostream &out) const {
303 out << "cpolygon, (" << get_plane()
304 << "), " << _points.size() << " vertices";
305}
306
307/**
308 *
309 */
310void CollisionPolygon::
311write(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 */
330PT(BoundingVolume) CollisionPolygon::
331compute_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 */
363PT(CollisionEntry) CollisionPolygon::
364test_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 if (collide_cat.is_info()) {
444 collide_cat.info()
445 << "polygon within " << entry.get_into_node_path()
446 << " has normal " << normal << " of length " << normal.length()
447 << "\n";
448 }
449 normal.normalize();
450 }
451#endif
452
453 // The nearest point within the plane to our center is the intersection of
454 // the line (center, center - normal) with the plane.
455 PN_stdfloat dist;
456 if (!get_plane().intersects_line(dist, from_center, -get_normal())) {
457 // No intersection with plane? This means the plane's effective normal
458 // was within the plane itself. A useless polygon.
459 return nullptr;
460 }
461
462 if (dist > from_radius || dist < -from_radius) {
463 // No intersection with the plane.
464 return nullptr;
465 }
466
467 LPoint2 p = to_2d(from_center - dist * get_normal());
468 PN_stdfloat edge_dist = 0.0f;
469
470 const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
471 if (cpa != nullptr) {
472 // We have a clip plane; apply it.
473 Points new_points;
474 if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
475 // All points are behind the clip plane; just do the default test.
476 edge_dist = dist_to_polygon(p, _points);
477
478 } else if (new_points.empty()) {
479 // The polygon is completely clipped.
480 return nullptr;
481
482 } else {
483 // Test against the clipped polygon.
484 edge_dist = dist_to_polygon(p, new_points);
485 }
486
487 } else {
488 // No clip plane is in effect. Do the default test.
489 edge_dist = dist_to_polygon(p, _points);
490 }
491
492 // Now we have edge_dist, which is the distance from the sphere center to
493 // the nearest edge of the polygon, within the polygon's plane.
494
495 if (edge_dist > from_radius) {
496 // No intersection; the circle is outside the polygon.
497 return nullptr;
498 }
499
500 // The sphere appears to intersect the polygon. If the edge is less than
501 // from_radius away, the sphere may be resting on an edge of the polygon.
502 // Determine how far the center of the sphere must remain from the plane,
503 // based on its distance from the nearest edge.
504
505 PN_stdfloat max_dist = from_radius;
506 if (edge_dist >= 0.0f) {
507 PN_stdfloat max_dist_2 = max(from_radius_2 - edge_dist * edge_dist, (PN_stdfloat)0.0);
508 max_dist = csqrt(max_dist_2);
509 }
510
511 if (dist > max_dist || -dist > max_dist) {
512 // There's no intersection: the sphere is hanging above or under the edge.
513 return nullptr;
514 }
515
516 if (collide_cat.is_debug()) {
517 collide_cat.debug()
518 << "intersection detected from " << entry.get_from_node_path()
519 << " into " << entry.get_into_node_path() << "\n";
520 }
521 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
522
523 PN_stdfloat into_depth = max_dist - dist;
524 if (moved_from_center) {
525 // We have to base the depth of intersection on the sphere's final resting
526 // point, not the point from which we tested the intersection.
527 PN_stdfloat orig_dist;
528 get_plane().intersects_line(orig_dist, orig_center, -normal);
529 into_depth = max_dist - orig_dist;
530 }
531
532 new_entry->set_surface_normal(normal);
533 new_entry->set_surface_point(from_center - normal * dist);
534 new_entry->set_interior_point(from_center - normal * (dist + into_depth));
535 new_entry->set_contact_pos(contact_point);
536 new_entry->set_contact_normal(get_normal());
537 new_entry->set_t(actual_t);
538
539 return new_entry;
540}
541
542/**
543 * This is part of the double-dispatch implementation of test_intersection().
544 * It is called when the "from" object is a line.
545 */
546PT(CollisionEntry) CollisionPolygon::
547test_intersection_from_line(const CollisionEntry &entry) const {
548 if (_points.size() < 3) {
549 return nullptr;
550 }
551
552 const CollisionLine *line;
553 DCAST_INTO_R(line, entry.get_from(), nullptr);
554
555 CPT(TransformState) wrt_space = entry.get_wrt_space();
556 const LMatrix4 &wrt_mat = wrt_space->get_mat();
557
558 LPoint3 from_origin = line->get_origin() * wrt_mat;
559 LVector3 from_direction = line->get_direction() * wrt_mat;
560
561 PN_stdfloat t;
562 if (!get_plane().intersects_line(t, from_origin, from_direction)) {
563 // No intersection.
564 return nullptr;
565 }
566
567 LPoint3 plane_point = from_origin + t * from_direction;
568 LPoint2 p = to_2d(plane_point);
569
570 const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
571 if (cpa != nullptr) {
572 // We have a clip plane; apply it.
573 Points new_points;
574 if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
575 // All points are behind the clip plane.
576 if (!point_is_inside(p, _points)) {
577 return nullptr;
578 }
579
580 } else {
581 if (new_points.size() < 3) {
582 return nullptr;
583 }
584 if (!point_is_inside(p, new_points)) {
585 return nullptr;
586 }
587 }
588
589 } else {
590 // No clip plane is in effect. Do the default test.
591 if (!point_is_inside(p, _points)) {
592 return nullptr;
593 }
594 }
595
596 if (collide_cat.is_debug()) {
597 collide_cat.debug()
598 << "intersection detected from " << entry.get_from_node_path()
599 << " into " << entry.get_into_node_path() << "\n";
600 }
601 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
602
603 LVector3 normal = (has_effective_normal() && line->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
604
605 new_entry->set_surface_normal(normal);
606 new_entry->set_surface_point(plane_point);
607
608 return new_entry;
609}
610
611/**
612 * This is part of the double-dispatch implementation of test_intersection().
613 * It is called when the "from" object is a ray.
614 */
615PT(CollisionEntry) CollisionPolygon::
616test_intersection_from_ray(const CollisionEntry &entry) const {
617 if (_points.size() < 3) {
618 return nullptr;
619 }
620
621 const CollisionRay *ray;
622 DCAST_INTO_R(ray, entry.get_from(), nullptr);
623
624 CPT(TransformState) wrt_space = entry.get_wrt_space();
625 const LMatrix4 &wrt_mat = wrt_space->get_mat();
626
627 LPoint3 from_origin = ray->get_origin() * wrt_mat;
628 LVector3 from_direction = ray->get_direction() * wrt_mat;
629
630 PN_stdfloat t;
631 if (!get_plane().intersects_line(t, from_origin, from_direction)) {
632 // No intersection.
633 return nullptr;
634 }
635
636 if (t < 0.0f) {
637 // The intersection point is before the start of the ray.
638 return nullptr;
639 }
640
641 LPoint3 plane_point = from_origin + t * from_direction;
642 LPoint2 p = to_2d(plane_point);
643
644 const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
645 if (cpa != nullptr) {
646 // We have a clip plane; apply it.
647 Points new_points;
648 if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
649 // All points are behind the clip plane.
650 if (!point_is_inside(p, _points)) {
651 return nullptr;
652 }
653
654 } else {
655 if (new_points.size() < 3) {
656 return nullptr;
657 }
658 if (!point_is_inside(p, new_points)) {
659 return nullptr;
660 }
661 }
662
663 } else {
664 // No clip plane is in effect. Do the default test.
665 if (!point_is_inside(p, _points)) {
666 return nullptr;
667 }
668 }
669
670 if (collide_cat.is_debug()) {
671 collide_cat.debug()
672 << "intersection detected from " << entry.get_from_node_path()
673 << " into " << entry.get_into_node_path() << "\n";
674 }
675 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
676
677 LVector3 normal = (has_effective_normal() && ray->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
678
679 new_entry->set_surface_normal(normal);
680 new_entry->set_surface_point(plane_point);
681
682 return new_entry;
683}
684
685/**
686 * This is part of the double-dispatch implementation of test_intersection().
687 * It is called when the "from" object is a segment.
688 */
689PT(CollisionEntry) CollisionPolygon::
690test_intersection_from_segment(const CollisionEntry &entry) const {
691 if (_points.size() < 3) {
692 return nullptr;
693 }
694
695 const CollisionSegment *segment;
696 DCAST_INTO_R(segment, entry.get_from(), nullptr);
697
698 CPT(TransformState) wrt_space = entry.get_wrt_space();
699 const LMatrix4 &wrt_mat = wrt_space->get_mat();
700
701 LPoint3 from_a = segment->get_point_a() * wrt_mat;
702 LPoint3 from_b = segment->get_point_b() * wrt_mat;
703 LPoint3 from_direction = from_b - from_a;
704
705 PN_stdfloat t;
706 if (!get_plane().intersects_line(t, from_a, from_direction)) {
707 // No intersection.
708 return nullptr;
709 }
710
711 if (t < 0.0f || t > 1.0f) {
712 // The intersection point is before the start of the segment or after the
713 // end of the segment.
714 return nullptr;
715 }
716
717 LPoint3 plane_point = from_a + t * from_direction;
718 LPoint2 p = to_2d(plane_point);
719
720 const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
721 if (cpa != nullptr) {
722 // We have a clip plane; apply it.
723 Points new_points;
724 if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
725 // All points are behind the clip plane.
726 if (!point_is_inside(p, _points)) {
727 return nullptr;
728 }
729
730 } else {
731 if (new_points.size() < 3) {
732 return nullptr;
733 }
734 if (!point_is_inside(p, new_points)) {
735 return nullptr;
736 }
737 }
738
739 } else {
740 // No clip plane is in effect. Do the default test.
741 if (!point_is_inside(p, _points)) {
742 return nullptr;
743 }
744 }
745
746 if (collide_cat.is_debug()) {
747 collide_cat.debug()
748 << "intersection detected from " << entry.get_from_node_path()
749 << " into " << entry.get_into_node_path() << "\n";
750 }
751 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
752
753 LVector3 normal = (has_effective_normal() && segment->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
754
755 new_entry->set_surface_normal(normal);
756 new_entry->set_surface_point(plane_point);
757
758 return new_entry;
759}
760
761/**
762 *
763 */
764PT(CollisionEntry) CollisionPolygon::
765test_intersection_from_capsule(const CollisionEntry &entry) const {
766 const CollisionCapsule *capsule;
767 DCAST_INTO_R(capsule, entry.get_from(), nullptr);
768
769 CPT(TransformState) wrt_space = entry.get_wrt_space();
770 const LMatrix4 &wrt_mat = wrt_space->get_mat();
771
772 LPoint3 from_a = capsule->get_point_a() * wrt_mat;
773 LPoint3 from_b = capsule->get_point_b() * wrt_mat;
774 LVector3 from_radius_v =
775 LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
776 PN_stdfloat from_radius_2 = from_radius_v.length_squared();
777 PN_stdfloat from_radius = csqrt(from_radius_2);
778
779 PN_stdfloat dist_a = get_plane().dist_to_plane(from_a);
780 PN_stdfloat dist_b = get_plane().dist_to_plane(from_b);
781
782 // Some early-outs (optional)
783 if (dist_a >= from_radius && dist_b >= from_radius) {
784 // Entirely in front of the plane means no intersection.
785 return nullptr;
786 }
787
788 if (dist_a <= -from_radius && dist_b <= -from_radius) {
789 // Entirely behind the plane also means no intersection.
790 return nullptr;
791 }
792
793 LMatrix4 to_3d_mat;
794 rederive_to_3d_mat(to_3d_mat);
795
796 // Find the intersection point between the capsule's axis and the plane.
797 LPoint3 intersect_3d;
798 if (!get_plane().intersects_line(intersect_3d, from_a, from_b)) {
799 // Completely parallel. Take an arbitrary point along the capsule axis.
800 intersect_3d = (from_a + from_b) * 0.5f;
801 }
802
803 // Find the closest point on the polygon to this intersection point.
804 LPoint2 intersect_2d = to_2d(intersect_3d);
805 LPoint2 closest_p_2d = intersect_2d;
806 PN_stdfloat best_dist_2 = -1;
807
808 size_t num_points = _points.size();
809 for (size_t i = 0; i < num_points; ++i) {
810 const LPoint2 &p1 = _points[i]._p;
811 const LPoint2 &p2 = _points[(i + 1) % num_points]._p;
812
813 // Is the intersection outside the polygon?
814 LVector2 v = intersect_2d - p1;
815 LVector2 pv = p2 - p1;
816 if (is_right(v, pv)) {
817 PN_stdfloat t = v.dot(pv) / pv.length_squared();
818 t = max(min(t, (PN_stdfloat)1), (PN_stdfloat)0);
819
820 LPoint2 p = p1 + pv * t;
821 PN_stdfloat d = (p - intersect_2d).length_squared();
822 if (best_dist_2 < 0 || d < best_dist_2) {
823 closest_p_2d = p;
824 best_dist_2 = d;
825 }
826 }
827 }
828
829 LPoint3 closest_p_3d = to_3d(closest_p_2d, to_3d_mat);
830
831 // Now find the closest point on the capsule axis to this point.
832 LVector3 from_v = from_b - from_a;
833
834 PN_stdfloat t = (closest_p_3d - from_a).dot(from_v) / from_v.length_squared();
835 LPoint3 ref_point_3d = from_a + from_v * max(min(t, (PN_stdfloat)1), (PN_stdfloat)0);
836
837 // Okay, now we have a point to apply the sphere test on.
838
839 // The nearest point within the plane to our reference is the intersection of
840 // the line (reference, reference - normal) with the plane.
841 PN_stdfloat dist;
842 if (!get_plane().intersects_line(dist, ref_point_3d, -get_normal())) {
843 // No intersection with plane? This means the plane's effective normal
844 // was within the plane itself. A useless polygon.
845 return nullptr;
846 }
847
848 if (dist > from_radius || dist < -from_radius) {
849 // No intersection with the plane.
850 return nullptr;
851 }
852
853 LPoint2 ref_point_2d = to_2d(ref_point_3d);
854 LPoint2 surface_point_2d = ref_point_2d;
855 PN_stdfloat edge_dist_2 = -1;
856
857 for (size_t i = 0; i < num_points; ++i) {
858 const LPoint2 &p1 = _points[i]._p;
859 const LPoint2 &p2 = _points[(i + 1) % num_points]._p;
860
861 // Is the intersection outside the polygon?
862 LVector2 v = ref_point_2d - p1;
863 LVector2 pv = p2 - p1;
864 if (is_right(v, pv)) {
865 PN_stdfloat t = v.dot(pv) / pv.length_squared();
866 t = max(min(t, (PN_stdfloat)1), (PN_stdfloat)0);
867
868 LPoint2 p = p1 + pv * t;
869 PN_stdfloat d = (p - ref_point_2d).length_squared();
870 if (edge_dist_2 < 0 || d < edge_dist_2) {
871 surface_point_2d = p;
872 edge_dist_2 = d;
873 }
874 }
875 }
876
877 // Now we have edge_dist_2, which is the square of the distance from the
878 // reference point to the nearest edge of the polygon, within the polygon's
879 // plane.
880
881 if (edge_dist_2 > from_radius_2) {
882 // No intersection; the circle is outside the polygon.
883 return nullptr;
884 }
885
886 // The sphere appears to intersect the polygon. If the edge is less than
887 // from_radius away, the sphere may be resting on an edge of the polygon.
888 // Determine how far the center of the sphere must remain from the plane,
889 // based on its distance from the nearest edge.
890
891 PN_stdfloat max_dist = from_radius;
892 if (edge_dist_2 >= 0.0f) {
893 PN_stdfloat max_dist_2 = max(from_radius_2 - edge_dist_2, (PN_stdfloat)0.0);
894 max_dist = csqrt(max_dist_2);
895 }
896
897 if (dist > max_dist || -dist > max_dist) {
898 // There's no intersection: the sphere is hanging above or under the edge.
899 return nullptr;
900 }
901
902 if (collide_cat.is_debug()) {
903 collide_cat.debug()
904 << "intersection detected from " << entry.get_from_node_path()
905 << " into " << entry.get_into_node_path() << "\n";
906 }
907 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
908
909 LVector3 normal = (has_effective_normal() && capsule->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
910 new_entry->set_surface_normal(normal);
911
912 if ((dist_a < 0 || dist_b < 0) && !IS_NEARLY_EQUAL(dist_a, dist_b)) {
913 // We need to report the deepest point below the polygon as the interior
914 // point so that the pusher will completely push it out.
915 LPoint3 deepest_3d = (dist_a < dist_b) ? from_a : from_b;
916 LPoint2 deepest_2d = to_2d(deepest_3d);
917 surface_point_2d = deepest_2d;
918 PN_stdfloat best_dist_2 = -1;
919
920 for (size_t i = 0; i < num_points; ++i) {
921 const LPoint2 &p1 = _points[i]._p;
922 const LPoint2 &p2 = _points[(i + 1) % num_points]._p;
923
924 // Is the deepest point outside the polygon?
925 LVector2 v = deepest_2d - p1;
926 LVector2 pv = p2 - p1;
927 if (is_right(v, pv)) {
928 PN_stdfloat t = v.dot(pv) / pv.length_squared();
929 t = max(min(t, (PN_stdfloat)1), (PN_stdfloat)0);
930
931 LPoint2 p = p1 + pv * t;
932 PN_stdfloat d = (p - deepest_2d).length_squared();
933 if (best_dist_2 < 0 || d < best_dist_2) {
934 surface_point_2d = p;
935 best_dist_2 = d;
936 }
937 }
938 }
939
940 if (best_dist_2 < 0) {
941 // Deepest point is completely within the polygon, easy case.
942 new_entry->set_surface_point(deepest_3d - normal * min(dist_a, dist_b));
943 new_entry->set_interior_point(deepest_3d - normal * from_radius);
944 return new_entry;
945 }
946 }
947
948 // Colliding with an edge, use the sphere test results.
949 LPoint3 surface_point = to_3d(surface_point_2d, to_3d_mat);
950 LPoint3 interior_point = ref_point_3d - normal * max_dist;
951 new_entry->set_surface_point(surface_point);
952 new_entry->set_interior_point((interior_point - surface_point).project(normal) + surface_point);
953 return new_entry;
954}
955
956/**
957 * This is part of the double-dispatch implementation of test_intersection().
958 * It is called when the "from" object is a parabola.
959 */
960PT(CollisionEntry) CollisionPolygon::
961test_intersection_from_parabola(const CollisionEntry &entry) const {
962 if (_points.size() < 3) {
963 return nullptr;
964 }
965
966 const CollisionParabola *parabola;
967 DCAST_INTO_R(parabola, entry.get_from(), nullptr);
968
969 CPT(TransformState) wrt_space = entry.get_wrt_space();
970 const LMatrix4 &wrt_mat = wrt_space->get_mat();
971
972 // Convert the parabola into local coordinate space.
973 LParabola local_p(parabola->get_parabola());
974 local_p.xform(wrt_mat);
975
976 PN_stdfloat t1, t2;
977 if (!get_plane().intersects_parabola(t1, t2, local_p)) {
978 // No intersection.
979 return nullptr;
980 }
981
982 PN_stdfloat t;
983 if (t1 >= parabola->get_t1() && t1 <= parabola->get_t2()) {
984 if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
985 // Both intersection points are within our segment of the parabola.
986 // Choose the first of the two.
987 t = min(t1, t2);
988 } else {
989 // Only t1 is within our segment.
990 t = t1;
991 }
992
993 } else if (t2 >= parabola->get_t1() && t2 <= parabola->get_t2()) {
994 // Only t2 is within our segment.
995 t = t2;
996
997 } else {
998 // Neither intersection point is within our segment.
999 return nullptr;
1000 }
1001
1002 LPoint3 plane_point = local_p.calc_point(t);
1003 LPoint2 p = to_2d(plane_point);
1004
1005 const ClipPlaneAttrib *cpa = entry.get_into_clip_planes();
1006 if (cpa != nullptr) {
1007 // We have a clip plane; apply it.
1008 Points new_points;
1009 if (apply_clip_plane(new_points, cpa, entry.get_into_node_path().get_net_transform())) {
1010 // All points are behind the clip plane.
1011 if (!point_is_inside(p, _points)) {
1012 return nullptr;
1013 }
1014
1015 } else {
1016 if (new_points.size() < 3) {
1017 return nullptr;
1018 }
1019 if (!point_is_inside(p, new_points)) {
1020 return nullptr;
1021 }
1022 }
1023
1024 } else {
1025 // No clip plane is in effect. Do the default test.
1026 if (!point_is_inside(p, _points)) {
1027 return nullptr;
1028 }
1029 }
1030
1031 if (collide_cat.is_debug()) {
1032 collide_cat.debug()
1033 << "intersection detected from " << entry.get_from_node_path()
1034 << " into " << entry.get_into_node_path() << "\n";
1035 }
1036 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
1037
1038 LVector3 normal = (has_effective_normal() && parabola->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
1039
1040 new_entry->set_surface_normal(normal);
1041 new_entry->set_surface_point(plane_point);
1042
1043 return new_entry;
1044}
1045
1046/**
1047 * This is part of the double-dispatch implementation of test_intersection().
1048 * It is called when the "from" object is a box.
1049 */
1050PT(CollisionEntry) CollisionPolygon::
1051test_intersection_from_box(const CollisionEntry &entry) const {
1052 const CollisionBox *box;
1053 DCAST_INTO_R(box, entry.get_from(), nullptr);
1054
1055 // To make things easier, transform the box into the coordinate space of the
1056 // plane.
1057 CPT(TransformState) wrt_space = entry.get_wrt_space();
1058 const LMatrix4 &wrt_mat = wrt_space->get_mat();
1059 LMatrix4 plane_mat = wrt_mat * _to_2d_mat;
1060
1061 LPoint3 from_center = box->get_center() * plane_mat;
1062 LVector3 from_extents = box->get_dimensions() * 0.5f;
1063
1064 // Determine the basis vectors describing the box.
1065 LVecBase3 box_x = plane_mat.get_row3(0) * from_extents[0];
1066 LVecBase3 box_y = plane_mat.get_row3(1) * from_extents[1];
1067 LVecBase3 box_z = plane_mat.get_row3(2) * from_extents[2];
1068
1069 // Is there a separating axis between the plane and the box?
1070 if (cabs(from_center[1]) > cabs(box_x[1]) + cabs(box_y[1]) + cabs(box_z[1])) {
1071 // There is one. No collision.
1072 return nullptr;
1073 }
1074
1075 // Now do the same for each of the box' primary axes.
1076 PN_stdfloat r1, center, r2;
1077
1078 r1 = cabs(box_x.dot(box_x)) + cabs(box_y.dot(box_x)) + cabs(box_z.dot(box_x));
1079 project(box_x, center, r2);
1080 if (cabs(from_center.dot(box_x) - center) > r1 + r2) {
1081 return nullptr;
1082 }
1083
1084 r1 = cabs(box_x.dot(box_y)) + cabs(box_y.dot(box_y)) + cabs(box_z.dot(box_y));
1085 project(box_y, center, r2);
1086 if (cabs(from_center.dot(box_y) - center) > r1 + r2) {
1087 return nullptr;
1088 }
1089
1090 r1 = cabs(box_x.dot(box_z)) + cabs(box_y.dot(box_z)) + cabs(box_z.dot(box_z));
1091 project(box_z, center, r2);
1092 if (cabs(from_center.dot(box_z) - center) > r1 + r2) {
1093 return nullptr;
1094 }
1095
1096 // Now do the same check for the cross products between the box axes and the
1097 // polygon edges.
1098 Points::const_iterator pi;
1099 for (pi = _points.begin(); pi != _points.end(); ++pi) {
1100 const PointDef &pd = *pi;
1101 LVector3 axis;
1102
1103 axis.set(-box_x[1] * pd._v[1],
1104 box_x[0] * pd._v[1] - box_x[2] * pd._v[0],
1105 box_x[1] * pd._v[0]);
1106 r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
1107 project(axis, center, r2);
1108 if (cabs(from_center.dot(axis) - center) > r1 + r2) {
1109 return nullptr;
1110 }
1111
1112 axis.set(-box_y[1] * pd._v[1],
1113 box_y[0] * pd._v[1] - box_y[2] * pd._v[0],
1114 box_y[1] * pd._v[0]);
1115 r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
1116 project(axis, center, r2);
1117 if (cabs(from_center.dot(axis) - center) > r1 + r2) {
1118 return nullptr;
1119 }
1120
1121 axis.set(-box_z[1] * pd._v[1],
1122 box_z[0] * pd._v[1] - box_z[2] * pd._v[0],
1123 box_z[1] * pd._v[0]);
1124 r1 = cabs(box_x.dot(axis)) + cabs(box_y.dot(axis)) + cabs(box_z.dot(axis));
1125 project(axis, center, r2);
1126 if (cabs(from_center.dot(axis) - center) > r1 + r2) {
1127 return nullptr;
1128 }
1129 }
1130
1131 if (collide_cat.is_debug()) {
1132 collide_cat.debug()
1133 << "intersection detected from " << entry.get_from_node_path()
1134 << " into " << entry.get_into_node_path() << "\n";
1135 }
1136 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
1137
1138 LVector3 normal = (has_effective_normal() && box->get_respect_effective_normal()) ? get_effective_normal() : get_normal();
1139 new_entry->set_surface_normal(normal);
1140
1141 // Determine which point on the cube will be the interior point. This is
1142 // the calculation that is also used for the plane, which is not perfectly
1143 // applicable, but I suppose it's better than nothing.
1144 LPoint3 interior_point = box->get_center() * wrt_mat +
1145 wrt_mat.get_row3(0) * from_extents[0] * ((box_x[1] > 0) - (box_x[1] < 0)) +
1146 wrt_mat.get_row3(1) * from_extents[1] * ((box_y[1] > 0) - (box_y[1] < 0)) +
1147 wrt_mat.get_row3(2) * from_extents[2] * ((box_z[1] > 0) - (box_z[1] < 0));
1148
1149 // The surface point is the interior point projected onto the plane.
1150 new_entry->set_surface_point(get_plane().project(interior_point));
1151 new_entry->set_interior_point(interior_point);
1152
1153 return new_entry;
1154}
1155
1156/**
1157 * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this
1158 * solid.
1159 */
1160void CollisionPolygon::
1161fill_viz_geom() {
1162 if (collide_cat.is_debug()) {
1163 collide_cat.debug()
1164 << "Recomputing viz for " << *this << "\n";
1165 }
1166 nassertv(_viz_geom != nullptr && _bounds_viz_geom != nullptr);
1167 draw_polygon(_viz_geom, _bounds_viz_geom, _points);
1168}
1169
1170/**
1171 * Returns the linear distance of p to the line segment defined by f and t,
1172 * where v = (t - f).normalize(). The result is negative if p is left of the
1173 * line, positive if it is right of the line. If the result is positive, it
1174 * is constrained by endpoints of the line segment (i.e. the result might be
1175 * larger than it would be for a straight distance-to-line test). If the
1176 * result is negative, we don't bother.
1177 */
1178PN_stdfloat CollisionPolygon::
1179dist_to_line_segment(const LPoint2 &p,
1180 const LPoint2 &f, const LPoint2 &t,
1181 const LVector2 &v) {
1182 LVector2 v1 = (p - f);
1183 PN_stdfloat d = (v1[0] * v[1] - v1[1] * v[0]);
1184 if (d < 0.0f) {
1185 return d;
1186 }
1187
1188 // Compute the nearest point on the line.
1189 LPoint2 q = p + LVector2(-v[1], v[0]) * d;
1190
1191 // Now constrain that point to the line segment.
1192 if (v[0] > 0.0f) {
1193 // X+
1194 if (v[1] > 0.0f) {
1195 // Y+
1196 if (v[0] > v[1]) {
1197 // X-dominant.
1198 if (q[0] < f[0]) {
1199 return (p - f).length();
1200 } if (q[0] > t[0]) {
1201 return (p - t).length();
1202 } else {
1203 return d;
1204 }
1205 } else {
1206 // Y-dominant.
1207 if (q[1] < f[1]) {
1208 return (p - f).length();
1209 } if (q[1] > t[1]) {
1210 return (p - t).length();
1211 } else {
1212 return d;
1213 }
1214 }
1215 } else {
1216 // Y-
1217 if (v[0] > -v[1]) {
1218 // X-dominant.
1219 if (q[0] < f[0]) {
1220 return (p - f).length();
1221 } if (q[0] > t[0]) {
1222 return (p - t).length();
1223 } else {
1224 return d;
1225 }
1226 } else {
1227 // Y-dominant.
1228 if (q[1] > f[1]) {
1229 return (p - f).length();
1230 } if (q[1] < t[1]) {
1231 return (p - t).length();
1232 } else {
1233 return d;
1234 }
1235 }
1236 }
1237 } else {
1238 // X-
1239 if (v[1] > 0.0f) {
1240 // Y+
1241 if (-v[0] > v[1]) {
1242 // X-dominant.
1243 if (q[0] > f[0]) {
1244 return (p - f).length();
1245 } if (q[0] < t[0]) {
1246 return (p - t).length();
1247 } else {
1248 return d;
1249 }
1250 } else {
1251 // Y-dominant.
1252 if (q[1] < f[1]) {
1253 return (p - f).length();
1254 } if (q[1] > t[1]) {
1255 return (p - t).length();
1256 } else {
1257 return d;
1258 }
1259 }
1260 } else {
1261 // Y-
1262 if (-v[0] > -v[1]) {
1263 // X-dominant.
1264 if (q[0] > f[0]) {
1265 return (p - f).length();
1266 } if (q[0] < t[0]) {
1267 return (p - t).length();
1268 } else {
1269 return d;
1270 }
1271 } else {
1272 // Y-dominant.
1273 if (q[1] > f[1]) {
1274 return (p - f).length();
1275 } if (q[1] < t[1]) {
1276 return (p - t).length();
1277 } else {
1278 return d;
1279 }
1280 }
1281 }
1282 }
1283}
1284
1285
1286/**
1287 * Now that the _p members of the given points array have been computed, go
1288 * back and compute all of the _v members.
1289 */
1290void CollisionPolygon::
1291compute_vectors(Points &points) {
1292 size_t num_points = points.size();
1293 for (size_t i = 0; i < num_points; i++) {
1294 points[i]._v = points[(i + 1) % num_points]._p - points[i]._p;
1295 points[i]._v.normalize();
1296 }
1297}
1298
1299/**
1300 * Fills up the indicated GeomNode with the Geoms to draw the polygon
1301 * indicated with the given set of 2-d points.
1302 */
1303void CollisionPolygon::
1304draw_polygon(GeomNode *viz_geom_node, GeomNode *bounds_viz_geom_node,
1305 const CollisionPolygon::Points &points) const {
1306 if (points.size() < 3) {
1307 if (collide_cat.is_debug()) {
1308 collide_cat.debug()
1309 << "(Degenerate poly, ignoring.)\n";
1310 }
1311 return;
1312 }
1313
1314 LMatrix4 to_3d_mat;
1315 rederive_to_3d_mat(to_3d_mat);
1316
1317 PT(GeomVertexData) vdata = new GeomVertexData
1318 ("collision", GeomVertexFormat::get_v3(),
1319 Geom::UH_static);
1320 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1321
1322 Points::const_iterator pi;
1323 for (pi = points.begin(); pi != points.end(); ++pi) {
1324 vertex.add_data3(to_3d((*pi)._p, to_3d_mat));
1325 }
1326
1327 PT(GeomTrifans) body = new GeomTrifans(Geom::UH_static);
1328 body->add_consecutive_vertices(0, points.size());
1329 body->close_primitive();
1330
1331 PT(GeomLinestrips) border = new GeomLinestrips(Geom::UH_static);
1332 border->add_consecutive_vertices(0, points.size());
1333 border->add_vertex(0);
1334 border->close_primitive();
1335
1336 PT(Geom) geom1 = new Geom(vdata);
1337 geom1->add_primitive(body);
1338
1339 PT(Geom) geom2 = new Geom(vdata);
1340 geom2->add_primitive(border);
1341
1342 viz_geom_node->add_geom(geom1, ((CollisionPolygon *)this)->get_solid_viz_state());
1343 viz_geom_node->add_geom(geom2, ((CollisionPolygon *)this)->get_wireframe_viz_state());
1344
1345 bounds_viz_geom_node->add_geom(geom1, ((CollisionPolygon *)this)->get_solid_bounds_viz_state());
1346 bounds_viz_geom_node->add_geom(geom2, ((CollisionPolygon *)this)->get_wireframe_bounds_viz_state());
1347}
1348
1349
1350/**
1351 * Returns true if the indicated point is within the polygon's 2-d space,
1352 * false otherwise.
1353 */
1354bool CollisionPolygon::
1355point_is_inside(const LPoint2 &p, const CollisionPolygon::Points &points) const {
1356 // We insist that the polygon be convex. This makes things a bit simpler.
1357
1358 // In the case of a convex polygon, defined with points in counterclockwise
1359 // order, a point is interior to the polygon iff the point is not right of
1360 // each of the edges.
1361 for (int i = 0; i < (int)points.size() - 1; i++) {
1362 if (is_right(p - points[i]._p, points[i+1]._p - points[i]._p)) {
1363 return false;
1364 }
1365 }
1366 if (is_right(p - points[points.size() - 1]._p,
1367 points[0]._p - points[points.size() - 1]._p)) {
1368 return false;
1369 }
1370
1371 return true;
1372}
1373
1374/**
1375 * Returns the linear distance from the 2-d point to the nearest part of the
1376 * polygon defined by the points vector. The result is negative if the point
1377 * is within the polygon.
1378 */
1379PN_stdfloat CollisionPolygon::
1380dist_to_polygon(const LPoint2 &p, const CollisionPolygon::Points &points) const {
1381
1382 // We know that that the polygon is convex and is defined with the points in
1383 // counterclockwise order. Therefore, we simply compare the signed distance
1384 // to each line segment; we ignore any negative values, and take the minimum
1385 // of all the positive values.
1386
1387 // If all values are negative, the point is within the polygon; we therefore
1388 // return an arbitrary negative result.
1389
1390 bool got_dist = false;
1391 PN_stdfloat best_dist = -1.0f;
1392
1393 size_t num_points = points.size();
1394 for (size_t i = 0; i < num_points - 1; ++i) {
1395 PN_stdfloat d = dist_to_line_segment(p, points[i]._p, points[i + 1]._p,
1396 points[i]._v);
1397 if (d >= 0.0f) {
1398 if (!got_dist || d < best_dist) {
1399 best_dist = d;
1400 got_dist = true;
1401 }
1402 }
1403 }
1404
1405 PN_stdfloat d = dist_to_line_segment(p, points[num_points - 1]._p, points[0]._p,
1406 points[num_points - 1]._v);
1407 if (d >= 0.0f) {
1408 if (!got_dist || d < best_dist) {
1409 best_dist = d;
1410 got_dist = true;
1411 }
1412 }
1413
1414 return best_dist;
1415}
1416
1417/**
1418 * Projects the polygon onto the given axis, returning the center on the line
1419 * and the half extent.
1420 */
1421void CollisionPolygon::
1422project(const LVector3 &axis, PN_stdfloat &center, PN_stdfloat &extent) const {
1423 PN_stdfloat begin, end;
1424
1425 Points::const_iterator pi;
1426 pi = _points.begin();
1427
1428 const LPoint2 &point = (*pi)._p;
1429 begin = point[0] * axis[0] + point[1] * axis[2];
1430 end = begin;
1431
1432 for (; pi != _points.end(); ++pi) {
1433 const LPoint2 &point = (*pi)._p;
1434
1435 PN_stdfloat t = point[0] * axis[0] + point[1] * axis[2];
1436 begin = min(begin, t);
1437 end = max(end, t);
1438 }
1439
1440 center = (end + begin) * 0.5f;
1441 extent = cabs((end - begin) * 0.5f);
1442}
1443
1444/**
1445 *
1446 */
1447void CollisionPolygon::
1448setup_points(const LPoint3 *begin, const LPoint3 *end) {
1449 int num_points = end - begin;
1450 nassertv(num_points >= 3);
1451
1452 _points.clear();
1453
1454 // Tell the base CollisionPlane class what its plane will be. To do this,
1455 // we must first compute the polygon normal.
1456 LVector3 normal = LVector3::zero();
1457
1458 // Project the polygon into each of the three major planes and calculate the
1459 // area of each 2-d projection. This becomes the polygon normal. This
1460 // works because the ratio between these different areas corresponds to the
1461 // angle at which the polygon is tilted toward each plane.
1462 for (int i = 0; i < num_points; i++) {
1463 const LPoint3 &p0 = begin[i];
1464 const LPoint3 &p1 = begin[(i + 1) % num_points];
1465 normal[0] += p0[1] * p1[2] - p0[2] * p1[1];
1466 normal[1] += p0[2] * p1[0] - p0[0] * p1[2];
1467 normal[2] += p0[0] * p1[1] - p0[1] * p1[0];
1468 }
1469
1470 if (normal.length_squared() == 0.0f) {
1471 // The polygon has no area.
1472 return;
1473 }
1474
1475#ifndef NDEBUG
1476 // Make sure all the source points are good.
1477 {
1478 if (!verify_points(begin, end)) {
1479 collide_cat.error() << "Invalid points in CollisionPolygon:\n";
1480 const LPoint3 *pi;
1481 for (pi = begin; pi != end; ++pi) {
1482 collide_cat.error(false) << " " << (*pi) << "\n";
1483 }
1484 collide_cat.error(false)
1485 << " normal " << normal << " with length " << normal.length() << "\n";
1486
1487 return;
1488 }
1489 }
1490
1491 if (collide_cat.is_spam()) {
1492 collide_cat.spam()
1493 << "CollisionPolygon defined with " << num_points << " vertices:\n";
1494 const LPoint3 *pi;
1495 for (pi = begin; pi != end; ++pi) {
1496 collide_cat.spam(false) << " " << (*pi) << "\n";
1497 }
1498 }
1499#endif
1500
1501 set_plane(LPlane(normal, begin[0]));
1502
1503 // Construct a matrix that rotates the points from the (X,0,Z) plane into
1504 // the 3-d plane.
1505 LMatrix4 to_3d_mat;
1506 calc_to_3d_mat(to_3d_mat);
1507
1508 // And the inverse matrix rotates points from 3-d space into the 2-d plane.
1509 _to_2d_mat.invert_from(to_3d_mat);
1510
1511 // Now project all of the points onto the 2-d plane.
1512
1513 const LPoint3 *pi;
1514 for (pi = begin; pi != end; ++pi) {
1515 LPoint3 point = (*pi) * _to_2d_mat;
1516 _points.push_back(PointDef(point[0], point[2]));
1517 }
1518
1519 nassertv(_points.size() >= 3);
1520
1521#ifndef NDEBUG
1522 /*
1523 // Now make sure the points define a convex polygon.
1524 if (is_concave()) {
1525 collide_cat.error() << "Invalid concave CollisionPolygon defined:\n";
1526 const LPoint3 *pi;
1527 for (pi = begin; pi != end; ++pi) {
1528 collide_cat.error(false) << " " << (*pi) << "\n";
1529 }
1530 collide_cat.error(false)
1531 << " normal " << normal << " with length " << normal.length() << "\n";
1532 _points.clear();
1533 }
1534 */
1535#endif
1536
1537 compute_vectors(_points);
1538}
1539
1540/**
1541 * Converts the indicated point to 3-d space according to the way
1542 * CollisionPolygons used to be stored in bam files prior to 4.9.
1543 */
1544LPoint3 CollisionPolygon::
1545legacy_to_3d(const LVecBase2 &point2d, int axis) const {
1546 nassertr(!point2d.is_nan(), LPoint3(0.0f, 0.0f, 0.0f));
1547
1548 LVector3 normal = get_normal();
1549 PN_stdfloat D = get_plane()[3];
1550
1551 nassertr(!normal.is_nan(), LPoint3(0.0f, 0.0f, 0.0f));
1552 nassertr(!cnan(D), LPoint3(0.0f, 0.0f, 0.0f));
1553
1554 switch (axis) {
1555 case 0: // AT_x:
1556 return LPoint3(-(normal[1]*point2d[0] + normal[2]*point2d[1] + D)/normal[0], point2d[0], point2d[1]);
1557
1558 case 1: // AT_y:
1559 return LPoint3(point2d[0],
1560 -(normal[0]*point2d[0] + normal[2]*point2d[1] + D)/normal[1], point2d[1]);
1561
1562 case 2: // AT_z:
1563 return LPoint3(point2d[0], point2d[1],
1564 -(normal[0]*point2d[0] + normal[1]*point2d[1] + D)/normal[2]);
1565 }
1566
1567 nassertr(false, LPoint3(0.0f, 0.0f, 0.0f));
1568 return LPoint3(0.0f, 0.0f, 0.0f);
1569}
1570
1571/**
1572 * Clips the source_points of the polygon by the indicated clipping plane, and
1573 * modifies new_points to reflect the new set of clipped points (but does not
1574 * compute the vectors in new_points).
1575 *
1576 * The return value is true if the set of points is unmodified (all points are
1577 * behind the clip plane), or false otherwise.
1578 */
1579bool CollisionPolygon::
1580clip_polygon(CollisionPolygon::Points &new_points,
1581 const CollisionPolygon::Points &source_points,
1582 const LPlane &plane) const {
1583 new_points.clear();
1584 if (source_points.empty()) {
1585 return true;
1586 }
1587
1588 LPoint3 from3d;
1589 LVector3 delta3d;
1590 if (!plane.intersects_plane(from3d, delta3d, get_plane())) {
1591 // The clipping plane is parallel to the polygon. The polygon is either
1592 // all in or all out.
1593 if (plane.dist_to_plane(get_plane().get_point()) < 0.0) {
1594 // A point within the polygon is behind the clipping plane: the polygon
1595 // is all in.
1596 new_points = source_points;
1597 return true;
1598 }
1599 return false;
1600 }
1601
1602 // Project the line of intersection into the 2-d plane. Now we have a 2-d
1603 // clipping line.
1604 LPoint2 from2d = to_2d(from3d);
1605 LVector2 delta2d = to_2d(delta3d);
1606
1607 PN_stdfloat a = -delta2d[1];
1608 PN_stdfloat b = delta2d[0];
1609 PN_stdfloat c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0];
1610
1611 // Now walk through the points. Any point on the left of our line gets
1612 // removed, and the line segment clipped at the point of intersection.
1613
1614 // We might increase the number of vertices by as many as 1, if the plane
1615 // clips off exactly one corner. (We might also decrease the number of
1616 // vertices, or keep them the same number.)
1617 new_points.reserve(source_points.size() + 1);
1618
1619 LPoint2 last_point = source_points.back()._p;
1620 bool last_is_in = !is_right(last_point - from2d, delta2d);
1621 bool all_in = last_is_in;
1622 Points::const_iterator pi;
1623 for (pi = source_points.begin(); pi != source_points.end(); ++pi) {
1624 const LPoint2 &this_point = (*pi)._p;
1625 bool this_is_in = !is_right(this_point - from2d, delta2d);
1626
1627 // There appears to be a compiler bug in gcc 4.0: we need to extract this
1628 // comparison outside of the if statement.
1629 bool crossed_over = (this_is_in != last_is_in);
1630 if (crossed_over) {
1631 // We have just crossed over the clipping line. Find the point of
1632 // intersection.
1633 LVector2 d = this_point - last_point;
1634 PN_stdfloat denom = (a * d[0] + b * d[1]);
1635 if (denom != 0.0) {
1636 PN_stdfloat t = -(a * last_point[0] + b * last_point[1] + c) / denom;
1637 LPoint2 p = last_point + t * d;
1638
1639 new_points.push_back(PointDef(p[0], p[1]));
1640 last_is_in = this_is_in;
1641 }
1642 }
1643
1644 if (this_is_in) {
1645 // We are behind the clipping line. Keep the point.
1646 new_points.push_back(PointDef(this_point[0], this_point[1]));
1647 } else {
1648 all_in = false;
1649 }
1650
1651 last_point = this_point;
1652 }
1653
1654 return all_in;
1655}
1656
1657/**
1658 * Clips the polygon by all of the clip planes named in the clip plane
1659 * attribute and fills new_points up with the resulting points.
1660 *
1661 * The return value is true if the set of points is unmodified (all points are
1662 * behind all the clip planes), or false otherwise.
1663 */
1664bool CollisionPolygon::
1665apply_clip_plane(CollisionPolygon::Points &new_points,
1666 const ClipPlaneAttrib *cpa,
1667 const TransformState *net_transform) const {
1668 bool all_in = true;
1669
1670 int num_planes = cpa->get_num_on_planes();
1671 bool first_plane = true;
1672
1673 for (int i = 0; i < num_planes; i++) {
1674 NodePath plane_path = cpa->get_on_plane(i);
1675 PlaneNode *plane_node = DCAST(PlaneNode, plane_path.node());
1676 if ((plane_node->get_clip_effect() & PlaneNode::CE_collision) != 0) {
1677 CPT(TransformState) new_transform =
1678 net_transform->invert_compose(plane_path.get_net_transform());
1679
1680 LPlane plane = plane_node->get_plane() * new_transform->get_mat();
1681 if (first_plane) {
1682 first_plane = false;
1683 if (!clip_polygon(new_points, _points, plane)) {
1684 all_in = false;
1685 }
1686 } else {
1687 Points last_points;
1688 last_points.swap(new_points);
1689 if (!clip_polygon(new_points, last_points, plane)) {
1690 all_in = false;
1691 }
1692 }
1693 }
1694 }
1695
1696 if (!all_in) {
1697 compute_vectors(new_points);
1698 }
1699
1700 return all_in;
1701}
1702
1703/**
1704 * Function to write the important information in the particular object to a
1705 * Datagram
1706 */
1708write_datagram(BamWriter *manager, Datagram &me) {
1709 CollisionPlane::write_datagram(manager, me);
1710 me.add_uint16(_points.size());
1711 for (size_t i = 0; i < _points.size(); i++) {
1712 _points[i]._p.write_datagram(me);
1713 _points[i]._v.write_datagram(me);
1714 }
1715 _to_2d_mat.write_datagram(me);
1716}
1717
1718/**
1719 * Function that reads out of the datagram (or asks manager to read) all of
1720 * the data that is needed to re-create this object and stores it in the
1721 * appropiate place
1722 */
1723void CollisionPolygon::
1724fillin(DatagramIterator &scan, BamReader *manager) {
1725 CollisionPlane::fillin(scan, manager);
1726
1727 size_t size = scan.get_uint16();
1728 for (size_t i = 0; i < size; i++) {
1729 LPoint2 p;
1730 LVector2 v;
1731 p.read_datagram(scan);
1732 v.read_datagram(scan);
1733 _points.push_back(PointDef(p, v));
1734 }
1735 _to_2d_mat.read_datagram(scan);
1736
1737 if (manager->get_file_minor_ver() < 13) {
1738 // Before bam version 6.13, we were inadvertently storing CollisionPolygon
1739 // vertices clockwise, instead of counter-clockwise. Correct that by re-
1740 // projecting.
1741 if (_points.size() >= 3) {
1742 LMatrix4 to_3d_mat;
1743 rederive_to_3d_mat(to_3d_mat);
1744
1745 epvector<LPoint3> verts;
1746 verts.reserve(_points.size());
1747 Points::const_iterator pi;
1748 for (pi = _points.begin(); pi != _points.end(); ++pi) {
1749 verts.push_back(to_3d((*pi)._p, to_3d_mat));
1750 }
1751
1752 const LPoint3 *verts_begin = &verts[0];
1753 const LPoint3 *verts_end = verts_begin + verts.size();
1754 setup_points(verts_begin, verts_end);
1755 }
1756 }
1757}
1758
1759
1760/**
1761 * Factory method to generate a CollisionPolygon object
1762 */
1764make_CollisionPolygon(const FactoryParams &params) {
1765 CollisionPolygon *me = new CollisionPolygon;
1766 DatagramIterator scan;
1767 BamReader *manager;
1768
1769 parse_params(params, scan, manager);
1770 me->fillin(scan, manager);
1771 return me;
1772}
1773
1774/**
1775 * Factory method to generate a CollisionPolygon object
1776 */
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
int get_file_minor_ver() const
Returns the minor version number of the Bam file currently being read.
Definition bamReader.I:83
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition bamReader.I:177
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition bamWriter.h:63
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
This functions similarly to a LightAttrib.
get_on_plane
Returns the nth plane enabled by the attribute, sorted in render order.
get_num_on_planes
Returns the number of planes that are enabled by the attribute.
Defines a single collision event.
get_from_node_path
Returns the NodePath that represents the CollisionNode that contains the CollisionSolid that triggere...
get_into_node_path
Returns the NodePath that represents the specific CollisionNode or GeomNode instance that was collide...
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
get_t1
Returns the starting point on the parabola.
get_parabola
Returns the parabola specified by this solid.
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
static bool verify_points(const LPoint3 *begin, const LPoint3 *end)
Verifies that the indicated set of points will define a valid CollisionPolygon: that is,...
get_point
Returns the nth vertex of the CollisionPolygon, expressed in 3-D space.
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
virtual LPoint3 get_collision_origin() const
Returns the point in space deemed to be the "origin" of the solid for collision purposes.
is_concave
Returns true if the CollisionPolygon appears to be concave, or false if it is safely convex.
virtual PStatCollector & get_test_pcollector()
Returns a PStatCollector that is used to count the number of intersection tests made against a solid ...
static void register_with_read_factory()
Factory method to generate a CollisionPolygon object.
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
static TypedWritable * make_CollisionPolygon(const FactoryParams &params)
Factory method to generate a CollisionPolygon object.
virtual PointerTo< PandaNode > get_viz(const CullTraverser *trav, const CullTraverserData &data, bool bounds_only) const
Returns a GeomNode that may be rendered to visualize the CollisionSolid.
is_valid
Returns true if the CollisionPolygon is valid (that is, it has at least three vertices),...
virtual PStatCollector & get_volume_pcollector()
Returns a PStatCollector that is used to count the number of bounding volume tests made against a sol...
The abstract base class for all things that can collide with other things in the world,...
bool has_effective_normal() const
Returns true if a special normal was set by set_effective_normal(), false otherwise.
const LVector3 & get_effective_normal() const
Returns the normal that was set by set_effective_normal().
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
get_respect_effective_normal
See set_respect_effective_normal().
This collects together the pieces of data that are accumulated for each node while walking the scene ...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
A class to retrieve the individual data elements previously stored in a Datagram.
uint16_t get_uint16()
Extracts an unsigned 16-bit integer.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
void add_uint16(uint16_t value)
Adds an unsigned 16-bit integer to the datagram.
Definition datagram.I:85
An instance of this class is passed to the Factory when requesting it to do its business and construc...
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
A node that holds Geom objects, renderable pieces of geometry.
Definition geomNode.h:34
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition geomNode.cxx:612
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
PandaNode * node() const
Returns the referenced node of the path.
Definition nodePath.I:227
A lightweight class that represents a single element that may be timed and/or counted via stats.
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
get_clip_effect
Returns the clip_effect bits for this clip plane.
Definition planeNode.h:71
get_plane
Returns the plane represented by the PlaneNode.
Definition planeNode.h:68
Indicates a coordinate-system transform on vertices.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
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.
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.
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.
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.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
STTransform::operator CPT TransformState() const
This is used internally to convert an STTransform into a TransformState pointer.
Definition stTransform.I:98
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.