Panda3D
Loading...
Searching...
No Matches
collisionCapsule.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 collisionCapsule.cxx
10 * @author drose
11 * @date 2003-09-25
12 */
13
14#include "collisionCapsule.h"
15#include "collisionSphere.h"
16#include "collisionLine.h"
17#include "collisionRay.h"
18#include "collisionSegment.h"
19#include "collisionHandler.h"
20#include "collisionEntry.h"
21#include "collisionParabola.h"
22#include "config_collide.h"
23#include "look_at.h"
24#include "geom.h"
25#include "geomNode.h"
27#include "datagram.h"
28#include "datagramIterator.h"
29#include "bamReader.h"
30#include "bamWriter.h"
31#include "cmath.h"
32#include "transformState.h"
33#include "geom.h"
34#include "geomTristrips.h"
35#include "geomVertexWriter.h"
36#include "boundingSphere.h"
37
38PStatCollector CollisionCapsule::_volume_pcollector("Collision Volumes:CollisionCapsule");
39PStatCollector CollisionCapsule::_test_pcollector("Collision Tests:CollisionCapsule");
40TypeHandle CollisionCapsule::_type_handle;
41
42/**
43 *
44 */
45CollisionSolid *CollisionCapsule::
46make_copy() {
47 return new CollisionCapsule(*this);
48}
49
50/**
51 *
52 */
53PT(CollisionEntry) CollisionCapsule::
54test_intersection(const CollisionEntry &entry) const {
55 return entry.get_into()->test_intersection_from_capsule(entry);
56}
57
58/**
59 * Transforms the solid by the indicated matrix.
60 */
62xform(const LMatrix4 &mat) {
63 _a = _a * mat;
64 _b = _b * mat;
65
66 // This is a little cheesy and fails miserably in the presence of a non-
67 // uniform scale.
68 LVector3 radius_v = LVector3(_radius, 0.0f, 0.0f) * mat;
69 _radius = length(radius_v);
70
71 recalc_internals();
73}
74
75/**
76 * Returns the point in space deemed to be the "origin" of the solid for
77 * collision purposes. The closest intersection point to this origin point is
78 * considered to be the most significant.
79 */
82 return get_point_a();
83}
84
85/**
86 * Returns a PStatCollector that is used to count the number of bounding
87 * volume tests made against a solid of this type in a given frame.
88 */
91 return _volume_pcollector;
92}
93
94/**
95 * Returns a PStatCollector that is used to count the number of intersection
96 * tests made against a solid of this type in a given frame.
97 */
100 return _test_pcollector;
101}
102
103/**
104 *
105 */
106void CollisionCapsule::
107output(std::ostream &out) const {
108 out << "capsule, a (" << _a << "), b (" << _b << "), r " << _radius;
109}
110
111/**
112 *
113 */
114PT(BoundingVolume) CollisionCapsule::
115compute_internal_bounds() const {
116 PT(BoundingVolume) bound = CollisionSolid::compute_internal_bounds();
117
118 if (bound->is_of_type(GeometricBoundingVolume::get_class_type())) {
119 GeometricBoundingVolume *gbound;
120 DCAST_INTO_R(gbound, bound, bound);
121
122 LVector3 vec = (_b - _a);
123 if (vec.normalize()) {
124 // The bounding volume includes both endpoints, plus a little bit more
125 // to include the radius in both directions.
126 LPoint3 points[2];
127 points[0] = _a - vec * _radius;
128 points[1] = _b + vec * _radius;
129
130 gbound->around(points, points + 2);
131
132 } else {
133 // Both endpoints are coincident; therefore, the bounding volume is a
134 // sphere.
135 BoundingSphere sphere(_a, _radius);
136 gbound->extend_by(&sphere);
137 }
138 }
139
140 return bound;
141}
142
143/**
144 *
145 */
146PT(CollisionEntry) CollisionCapsule::
147test_intersection_from_sphere(const CollisionEntry &entry) const {
148 const CollisionSphere *sphere;
149 DCAST_INTO_R(sphere, entry.get_from(), nullptr);
150
151 CPT(TransformState) wrt_space = entry.get_wrt_space();
152 CPT(TransformState) wrt_prev_space = entry.get_wrt_prev_space();
153
154 const LMatrix4 &wrt_mat = wrt_space->get_mat();
155
156 LPoint3 from_a = sphere->get_center() * wrt_mat;
157 LPoint3 from_b = from_a;
158
159 LPoint3 contact_point;
160 PN_stdfloat actual_t = 0.0f;
161
162 if (wrt_prev_space != wrt_space) {
163 // If the sphere is moving relative to the capsule, it becomes a capsule itself.
164 from_a = sphere->get_center() * wrt_prev_space->get_mat();
165 }
166
167 LVector3 from_direction = from_b - from_a;
168
169 LVector3 from_radius_v =
170 LVector3(sphere->get_radius(), 0.0f, 0.0f) * wrt_mat;
171 PN_stdfloat from_radius = length(from_radius_v);
172
173 double t1, t2;
174 if (!intersects_line(t1, t2, from_a, from_direction, from_radius)) {
175 // No intersection.
176 return nullptr;
177 }
178
179 if (t2 < 0.0 || t1 > 1.0) {
180 // Both intersection points are before the start of the segment or after
181 // the end of the segment.
182 return nullptr;
183 }
184
185 // doubles, not floats, to satisfy min and max templates.
186 actual_t = std::min(1.0, std::max(0.0, t1));
187 contact_point = from_a + actual_t * (from_b - from_a);
188
189 if (collide_cat.is_debug()) {
190 collide_cat.debug()
191 << "intersection detected from " << entry.get_from_node_path() << " into "
192 << entry.get_into_node_path() << "\n";
193 }
194 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
195
196 LPoint3 into_intersection_point;
197 if (t2 > 1.0) {
198 // Point b is within the capsule. The first intersection point is point b
199 // itself.
200 into_intersection_point = from_b;
201 } else {
202 // Point b is outside the capsule, and point a is either inside the capsule or
203 // beyond it. The first intersection point is at t2.
204 into_intersection_point = from_a + t2 * from_direction;
205 }
206 set_intersection_point(new_entry, into_intersection_point, from_radius);
207
208 LPoint3 fake_contact_point;
209 LVector3 contact_normal;
210 calculate_surface_point_and_normal(contact_point,
211 from_radius,
212 fake_contact_point,
213 contact_normal);
214 new_entry->set_contact_pos(contact_point);
215 new_entry->set_contact_normal(contact_normal);
216 new_entry->set_t(actual_t);
217
218 return new_entry;
219}
220
221/**
222 *
223 */
224PT(CollisionEntry) CollisionCapsule::
225test_intersection_from_line(const CollisionEntry &entry) const {
226 const CollisionLine *line;
227 DCAST_INTO_R(line, entry.get_from(), nullptr);
228
229 CPT(TransformState) wrt_space = entry.get_wrt_space();
230 const LMatrix4 &wrt_mat = wrt_space->get_mat();
231
232 LPoint3 from_origin = line->get_origin() * wrt_mat;
233 LVector3 from_direction = line->get_direction() * wrt_mat;
234
235 double t1, t2;
236 if (!intersects_line(t1, t2, from_origin, from_direction, 0.0f)) {
237 // No intersection.
238 return nullptr;
239 }
240
241 if (collide_cat.is_debug()) {
242 collide_cat.debug()
243 << "intersection detected from " << entry.get_from_node_path()
244 << " into " << entry.get_into_node_path() << "\n";
245 }
246 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
247
248 LPoint3 into_intersection_point = from_origin + t1 * from_direction;
249 set_intersection_point(new_entry, into_intersection_point, 0.0);
250
252 new_entry->set_surface_normal(get_effective_normal());
253
254 } else {
255 LVector3 normal = into_intersection_point * _inv_mat;
256 if (normal[1] > _length) {
257 // The point is within the top endcap.
258 normal[1] -= _length;
259 } else if (normal[1] > 0.0f) {
260 // The point is within the cylinder body.
261 normal[1] = 0;
262 }
263 normal = normalize(normal * _mat);
264 new_entry->set_surface_normal(normal);
265 }
266
267 return new_entry;
268}
269
270/**
271 *
272 */
273PT(CollisionEntry) CollisionCapsule::
274test_intersection_from_ray(const CollisionEntry &entry) const {
275 const CollisionRay *ray;
276 DCAST_INTO_R(ray, entry.get_from(), nullptr);
277
278 CPT(TransformState) wrt_space = entry.get_wrt_space();
279 const LMatrix4 &wrt_mat = wrt_space->get_mat();
280
281 LPoint3 from_origin = ray->get_origin() * wrt_mat;
282 LVector3 from_direction = ray->get_direction() * wrt_mat;
283
284 double t1, t2;
285 if (!intersects_line(t1, t2, from_origin, from_direction, 0.0f)) {
286 // No intersection.
287 return nullptr;
288 }
289
290 if (t2 < 0.0) {
291 // Both intersection points are before the start of the ray.
292 return nullptr;
293 }
294
295 if (collide_cat.is_debug()) {
296 collide_cat.debug()
297 << "intersection detected from " << entry.get_from_node_path()
298 << " into " << entry.get_into_node_path() << "\n";
299 }
300 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
301
302 LPoint3 into_intersection_point;
303 if (t1 < 0.0) {
304 // Point a is within the capsule. The first intersection point is point a
305 // itself.
306 into_intersection_point = from_origin;
307 } else {
308 // Point a is outside the capsule. The first intersection point is at t1.
309 into_intersection_point = from_origin + t1 * from_direction;
310 }
311 set_intersection_point(new_entry, into_intersection_point, 0.0);
312
314 new_entry->set_surface_normal(get_effective_normal());
315
316 } else {
317 LVector3 normal = into_intersection_point * _inv_mat;
318 if (normal[1] > _length) {
319 // The point is within the top endcap.
320 normal[1] -= _length;
321 } else if (normal[1] > 0.0f) {
322 // The point is within the cylinder body.
323 normal[1] = 0;
324 }
325 normal = normalize(normal * _mat);
326 new_entry->set_surface_normal(normal);
327 }
328
329 return new_entry;
330}
331
332/**
333 *
334 */
335PT(CollisionEntry) CollisionCapsule::
336test_intersection_from_segment(const CollisionEntry &entry) const {
337 const CollisionSegment *segment;
338 DCAST_INTO_R(segment, entry.get_from(), nullptr);
339
340 CPT(TransformState) wrt_space = entry.get_wrt_space();
341 const LMatrix4 &wrt_mat = wrt_space->get_mat();
342
343 LPoint3 from_a = segment->get_point_a() * wrt_mat;
344 LPoint3 from_b = segment->get_point_b() * wrt_mat;
345 LVector3 from_direction = from_b - from_a;
346
347 double t1, t2;
348 if (!intersects_line(t1, t2, from_a, from_direction, 0.0f)) {
349 // No intersection.
350 return nullptr;
351 }
352
353 if (t2 < 0.0 || t1 > 1.0) {
354 // Both intersection points are before the start of the segment or after
355 // the end of the segment.
356 return nullptr;
357 }
358
359 if (collide_cat.is_debug()) {
360 collide_cat.debug()
361 << "intersection detected from " << entry.get_from_node_path()
362 << " into " << entry.get_into_node_path() << "\n";
363 }
364 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
365
366 LPoint3 into_intersection_point;
367 if (t1 < 0.0) {
368 // Point a is within the capsule. The first intersection point is point a
369 // itself.
370 into_intersection_point = from_a;
371 } else {
372 // Point a is outside the capsule, and point b is either inside the capsule or
373 // beyond it. The first intersection point is at t1.
374 into_intersection_point = from_a + t1 * from_direction;
375 }
376 set_intersection_point(new_entry, into_intersection_point, 0.0);
377
379 new_entry->set_surface_normal(get_effective_normal());
380
381 } else {
382 LVector3 normal = into_intersection_point * _inv_mat;
383 if (normal[1] > _length) {
384 // The point is within the top endcap.
385 normal[1] -= _length;
386 } else if (normal[1] > 0.0f) {
387 // The point is within the cylinder body.
388 normal[1] = 0;
389 }
390 normal = normalize(normal * _mat);
391 new_entry->set_surface_normal(normal);
392 }
393
394 return new_entry;
395}
396
397/**
398 *
399 */
400PT(CollisionEntry) CollisionCapsule::
401test_intersection_from_capsule(const CollisionEntry &entry) const {
402 const CollisionCapsule *capsule;
403 DCAST_INTO_R(capsule, entry.get_from(), nullptr);
404
405 LPoint3 into_a = _a;
406 LVector3 into_direction = _b - into_a;
407
408 CPT(TransformState) wrt_space = entry.get_wrt_space();
409 const LMatrix4 &wrt_mat = wrt_space->get_mat();
410
411 LPoint3 from_a = capsule->get_point_a() * wrt_mat;
412 LPoint3 from_b = capsule->get_point_b() * wrt_mat;
413 LVector3 from_direction = from_b - from_a;
414
415 LVector3 from_radius_v =
416 LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat;
417 PN_stdfloat from_radius = length(from_radius_v);
418
419 // Determine the points on each segment with the smallest distance between.
420 double into_t, from_t;
421 calc_closest_segment_points(into_t, from_t,
422 into_a, into_direction,
423 from_a, from_direction);
424 LPoint3 into_closest = into_a + into_direction * into_t;
425 LPoint3 from_closest = from_a + from_direction * from_t;
426
427 // If the distance is greater than the sum of capsule radii, the test fails.
428 LVector3 closest_vec = from_closest - into_closest;
429 PN_stdfloat distance = closest_vec.length();
430 if (distance > _radius + from_radius) {
431 return nullptr;
432 }
433
434 if (collide_cat.is_debug()) {
435 collide_cat.debug()
436 << "intersection detected from " << entry.get_from_node_path()
437 << " into " << entry.get_into_node_path() << "\n";
438 }
439 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
440
441 if (distance != 0) {
442 // This is the most common case, where the line segments don't touch
443 // exactly. We point the normal along the vector of the closest distance.
444 LVector3 surface_normal = closest_vec * (1.0 / distance);
445
446 new_entry->set_surface_point(into_closest + surface_normal * _radius);
447 new_entry->set_interior_point(from_closest - surface_normal * from_radius);
448
450 new_entry->set_surface_normal(get_effective_normal());
451 } else if (distance != 0) {
452 new_entry->set_surface_normal(surface_normal);
453 }
454 } else {
455 // The rare case of the line segments touching exactly.
456 set_intersection_point(new_entry, into_closest, 0);
457 }
458
459 return new_entry;
460}
461
462/**
463 *
464 */
465PT(CollisionEntry) CollisionCapsule::
466test_intersection_from_parabola(const CollisionEntry &entry) const {
467 const CollisionParabola *parabola;
468 DCAST_INTO_R(parabola, entry.get_from(), nullptr);
469
470 CPT(TransformState) wrt_space = entry.get_wrt_space();
471 const LMatrix4 &wrt_mat = wrt_space->get_mat();
472
473 // Convert the parabola into local coordinate space.
474 LParabola local_p(parabola->get_parabola());
475 local_p.xform(wrt_mat);
476
477 double t;
478 if (!intersects_parabola(t, local_p, parabola->get_t1(), parabola->get_t2(),
479 local_p.calc_point(parabola->get_t1()),
480 local_p.calc_point(parabola->get_t2()))) {
481 // No intersection.
482 return nullptr;
483 }
484
485 if (collide_cat.is_debug()) {
486 collide_cat.debug()
487 << "intersection detected from " << entry.get_from_node_path()
488 << " into " << entry.get_into_node_path() << "\n";
489 }
490 PT(CollisionEntry) new_entry = new CollisionEntry(entry);
491
492 LPoint3 into_intersection_point = local_p.calc_point(t);
493 set_intersection_point(new_entry, into_intersection_point, 0.0);
494
496 new_entry->set_surface_normal(get_effective_normal());
497
498 } else {
499 LVector3 normal = into_intersection_point * _inv_mat;
500 if (normal[1] > _length) {
501 // The point is within the top endcap.
502 normal[1] -= _length;
503 } else if (normal[1] > 0.0f) {
504 // The point is within the cylinder body.
505 normal[1] = 0;
506 }
507 normal = normalize(normal * _mat);
508 new_entry->set_surface_normal(normal);
509 }
510
511 return new_entry;
512}
513
514/**
515 * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this
516 * solid.
517 */
518void CollisionCapsule::
519fill_viz_geom() {
520 if (collide_cat.is_debug()) {
521 collide_cat.debug()
522 << "Recomputing viz for " << *this << "\n";
523 }
524
525 // Generate the vertices such that we draw a capsule with one endpoint at (0,
526 // 0, 0), and another at (0, length, 0). Then we'll rotate and translate it
527 // into place with the appropriate look_at matrix.
528 LVector3 direction = (_b - _a);
529 PN_stdfloat length = direction.length();
530
531 PT(GeomVertexData) vdata = new GeomVertexData
532 ("collision", GeomVertexFormat::get_v3(),
533 Geom::UH_static);
534 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
535
536 PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_static);
537 // Generate the first endcap.
538 static const int num_slices = 8;
539 static const int num_rings = 4;
540 int ri, si;
541 for (ri = 0; ri < num_rings; ri++) {
542 for (si = 0; si <= num_slices; si++) {
543 vertex.add_data3(calc_sphere1_vertex(ri, si, num_rings, num_slices));
544 vertex.add_data3(calc_sphere1_vertex(ri + 1, si, num_rings, num_slices));
545 }
546 strip->add_next_vertices((num_slices + 1) * 2);
547 strip->close_primitive();
548 }
549
550 // Now the cylinder sides.
551 for (si = 0; si <= num_slices; si++) {
552 vertex.add_data3(calc_sphere1_vertex(num_rings, si, num_rings, num_slices));
553 vertex.add_data3(calc_sphere2_vertex(num_rings, si, num_rings, num_slices,
554 length));
555 }
556 strip->add_next_vertices((num_slices + 1) * 2);
557 strip->close_primitive();
558
559 // And the second endcap.
560 for (ri = num_rings - 1; ri >= 0; ri--) {
561 for (si = 0; si <= num_slices; si++) {
562 vertex.add_data3(calc_sphere2_vertex(ri + 1, si, num_rings, num_slices, length));
563 vertex.add_data3(calc_sphere2_vertex(ri, si, num_rings, num_slices, length));
564 }
565 strip->add_next_vertices((num_slices + 1) * 2);
566 strip->close_primitive();
567 }
568
569 PT(Geom) geom = new Geom(vdata);
570 geom->add_primitive(strip);
571
572 // Now transform the vertices to their actual location.
573 LMatrix4 mat;
574 look_at(mat, direction, LVector3(0.0f, 0.0f, 1.0f), CS_zup_right);
575 mat.set_row(3, _a);
576 geom->transform_vertices(mat);
577
578 _viz_geom->add_geom(geom, get_solid_viz_state());
579 _bounds_viz_geom->add_geom(geom, get_solid_bounds_viz_state());
580}
581
582/**
583 * Should be called internally to recompute the matrix and length when the
584 * properties of the capsule have changed.
585 */
586void CollisionCapsule::
587recalc_internals() {
588 LVector3 direction = (_b - _a);
589 _length = direction.length();
590
591 look_at(_mat, direction, LVector3(0.0f, 0.0f, 1.0f), CS_zup_right);
592 _mat.set_row(3, _a);
593 _inv_mat.invert_from(_mat);
594
595 mark_viz_stale();
596 mark_internal_bounds_stale();
597}
598
599/**
600 * Calculates a particular vertex on the surface of the first endcap
601 * hemisphere, for use in generating the viz geometry.
602 */
603LVertex CollisionCapsule::
604calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices) {
605 PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
606 PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
607
608 // Find the point on the rim, based on the slice.
609 PN_stdfloat theta = s * 2.0f * MathNumbers::pi;
610 PN_stdfloat x_rim = ccos(theta);
611 PN_stdfloat z_rim = csin(theta);
612
613 // Now pull that point in towards the pole, based on the ring.
614 PN_stdfloat phi = r * 0.5f * MathNumbers::pi;
615 PN_stdfloat to_pole = csin(phi);
616
617 PN_stdfloat x = _radius * x_rim * to_pole;
618 PN_stdfloat y = -_radius * ccos(phi);
619 PN_stdfloat z = _radius * z_rim * to_pole;
620
621 return LVertex(x, y, z);
622}
623
624/**
625 * Calculates a particular vertex on the surface of the second endcap
626 * hemisphere, for use in generating the viz geometry.
627 */
628LVertex CollisionCapsule::
629calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices,
630 PN_stdfloat length) {
631 PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
632 PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
633
634 // Find the point on the rim, based on the slice.
635 PN_stdfloat theta = s * 2.0f * MathNumbers::pi;
636 PN_stdfloat x_rim = ccos(theta);
637 PN_stdfloat z_rim = csin(theta);
638
639 // Now pull that point in towards the pole, based on the ring.
640 PN_stdfloat phi = r * 0.5f * MathNumbers::pi;
641 PN_stdfloat to_pole = csin(phi);
642
643 PN_stdfloat x = _radius * x_rim * to_pole;
644 PN_stdfloat y = length + _radius * ccos(phi);
645 PN_stdfloat z = _radius * z_rim * to_pole;
646
647 return LVertex(x, y, z);
648}
649
650/**
651 * Given line segments s1 and s2 defined by two points each, computes the
652 * point on each segment with the closest distance between them.
653 */
654void CollisionCapsule::
655calc_closest_segment_points(double &t1, double &t2,
656 const LPoint3 &from1, const LVector3 &delta1,
657 const LPoint3 &from2, const LVector3 &delta2) {
658 // Copyright 2001 softSurfer, 2012 Dan Sunday
659 // This code may be freely used, distributed and modified for any purpose
660 // providing that this copyright notice is included with it.
661 // SoftSurfer makes no warranty for this code, and cannot be held
662 // liable for any real or imagined damage resulting from its use.
663 // Users of this code must verify correctness for their application.
664 LVector3 w = from1 - from2;
665 PN_stdfloat a = delta1.dot(delta1); // always >= 0
666 PN_stdfloat b = delta1.dot(delta2);
667 PN_stdfloat c = delta2.dot(delta2); // always >= 0
668 PN_stdfloat d = delta1.dot(w);
669 PN_stdfloat e = delta2.dot(w);
670 PN_stdfloat D = a * c - b * b; // always >= 0
671 PN_stdfloat sN, sD = D;
672 PN_stdfloat tN, tD = D;
673
674 // compute the line parameters of the two closest points
675 if (IS_NEARLY_ZERO(D)) { // the lines are almost parallel
676 sN = 0.0; // force using point P0 on segment S1
677 sD = 1.0; // to prevent possible division by 0.0 later
678 tN = e;
679 tD = c;
680 } else {
681 // get the closest points on the infinite lines
682 sN = (b*e - c*d);
683 tN = (a*e - b*d);
684 if (sN < 0.0) { // sc < 0 => the s=0 edge is visible
685 sN = 0.0;
686 tN = e;
687 tD = c;
688 } else if (sN > sD) { // sc > 1 => the s=1 edge is visible
689 sN = sD;
690 tN = e + b;
691 tD = c;
692 }
693 }
694
695 if (tN < 0.0) { // tc < 0 => the t=0 edge is visible
696 tN = 0.0;
697 // recompute sc for this edge
698 if (-d < 0.0) {
699 sN = 0.0;
700 } else if (-d > a) {
701 sN = sD;
702 } else {
703 sN = -d;
704 sD = a;
705 }
706 } else if (tN > tD) { // tc > 1 => the t=1 edge is visible
707 tN = tD;
708 // recompute sc for this edge
709 if ((-d + b) < 0.0) {
710 sN = 0;
711 } else if ((-d + b) > a) {
712 sN = sD;
713 } else {
714 sN = (-d + b);
715 sD = a;
716 }
717 }
718
719 // finally do the division to get sc and tc
720 t1 = (IS_NEARLY_ZERO(sN) ? 0.0 : sN / sD);
721 t2 = (IS_NEARLY_ZERO(tN) ? 0.0 : tN / tD);
722}
723
724/**
725 * Determine the point(s) of intersection of a parametric line with the capsule.
726 * The line is infinite in both directions, and passes through "from" and
727 * from+delta. If the line does not intersect the capsule, the function returns
728 * false, and t1 and t2 are undefined. If it does intersect the capsule, it
729 * returns true, and t1 and t2 are set to the points along the equation
730 * from+t*delta that correspond to the two points of intersection.
731 */
732bool CollisionCapsule::
733intersects_line(double &t1, double &t2,
734 const LPoint3 &from0, const LVector3 &delta0,
735 PN_stdfloat inflate_radius) const {
736 // Convert the line into our canonical coordinate space: the capsule is aligned
737 // with the y axis.
738 LPoint3 from = from0 * _inv_mat;
739 LVector3 delta = delta0 * _inv_mat;
740
741 PN_stdfloat radius = _radius + inflate_radius;
742
743 // Now project the line into the X-Z plane to test for intersection with a
744 // 2-d circle around the origin. The equation for this is very similar to
745 // the formula for the intersection of a line with a sphere; see
746 // CollisionSphere::intersects_line() for the complete derivation. It's a
747 // little bit simpler because the circle is centered on the origin.
748 LVector2 from2(from[0], from[2]);
749 LVector2 delta2(delta[0], delta[2]);
750
751 double A = dot(delta2, delta2);
752
753 if (IS_NEARLY_ZERO(A)) {
754 // If the delta2 is 0, the line is perpendicular to the X-Z plane. The
755 // whole line intersects with the infinite cylinder if the point is within
756 // the circle.
757 if (from2.dot(from2) > radius * radius) {
758 // Nope, the 2-d point is outside the circle, so no intersection.
759 return false;
760 }
761
762 if (IS_NEARLY_ZERO(delta[1])) {
763 // Actually, the whole delta vector is 0, so the line is just a point.
764 // In this case, (since we have already shown the point is within the
765 // infinite cylinder), we intersect if and only if the three-dimensional
766 // point is between the endcaps.
767 if (from[1] < -radius || from[1] > _length + radius) {
768 // Way out.
769 return false;
770 }
771 if (from[1] < 0.0f) {
772 // Possibly within the first endcap.
773 if (from.dot(from) > radius * radius) {
774 return false;
775 }
776 } else if (from[1] > _length) {
777 // Possibly within the second endcap.
778 from[1] -= _length;
779 if (from.dot(from) > radius * radius) {
780 return false;
781 }
782 }
783
784 // The point is within the capsule!
785 t1 = t2 = 0.0;
786 return true;
787 }
788
789 // The 2-d point is within the circle, so compute our intersection points
790 // to include the entire vertical slice of the cylinder.
791 t1 = (-radius - from[1]) / delta[1];
792 t2 = (_length + radius - from[1]) / delta[1];
793
794 } else {
795 // The line is not perpendicular to the X-Z plane, so its projection into
796 // the plane is 2-d line. Test that 2-d line for intersection with the
797 // circular projection of the cylinder.
798
799 double B = 2.0f * dot(delta2, from2);
800 double fc_d2 = dot(from2, from2);
801 double C = fc_d2 - radius * radius;
802
803 double radical = B*B - 4.0*A*C;
804
805 if (IS_NEARLY_ZERO(radical)) {
806 // Tangent.
807 t1 = t2 = -B / (2.0*A);
808
809 } else if (radical < 0.0) {
810 // No real roots: no intersection with the line.
811 return false;
812
813 } else {
814 double reciprocal_2A = 1.0 / (2.0 * A);
815 double sqrt_radical = sqrtf(radical);
816 t1 = ( -B - sqrt_radical ) * reciprocal_2A;
817 t2 = ( -B + sqrt_radical ) * reciprocal_2A;
818 }
819 }
820
821 // Now we need to verify that the intersection points fall within the length
822 // of the cylinder.
823 PN_stdfloat t1_y = from[1] + t1 * delta[1];
824 PN_stdfloat t2_y = from[1] + t2 * delta[1];
825
826 if (t1_y < -radius && t2_y < -radius) {
827 // Both points are way off the bottom of the capsule; no intersection.
828 return false;
829 } else if (t1_y > _length + radius && t2_y > _length + radius) {
830 // Both points are way off the top of the capsule; no intersection.
831 return false;
832 }
833
834 if (t1_y < 0.0f) {
835 // The starting point is off the bottom of the capsule. Test the line
836 // against the first endcap.
837 double t1a, t2a;
838 if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
839 // If there's no intersection with the endcap, there can't be an
840 // intersection with the cylinder.
841 return false;
842 }
843 t1 = t1a;
844
845 } else if (t1_y > _length) {
846 // The starting point is off the top of the capsule. Test the line against
847 // the second endcap.
848 double t1b, t2b;
849 if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
850 // If there's no intersection with the endcap, there can't be an
851 // intersection with the cylinder.
852 return false;
853 }
854 t1 = t1b;
855 }
856
857 if (t2_y < 0.0f) {
858 // The ending point is off the bottom of the capsule. Test the line against
859 // the first endcap.
860 double t1a, t2a;
861 if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) {
862 // If there's no intersection with the endcap, there can't be an
863 // intersection with the cylinder.
864 return false;
865 }
866 t2 = t2a;
867
868 } else if (t2_y > _length) {
869 // The ending point is off the top of the capsule. Test the line against the
870 // second endcap.
871 double t1b, t2b;
872 if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) {
873 // If there's no intersection with the endcap, there can't be an
874 // intersection with the cylinder.
875 return false;
876 }
877 t2 = t2b;
878 }
879
880 return true;
881}
882
883/**
884 * After confirming that the line intersects an infinite cylinder, test
885 * whether it intersects one or the other endcaps. The y parameter specifies
886 * the center of the sphere (and hence the particular endcap).
887 */
888bool CollisionCapsule::
889sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y,
890 const LPoint3 &from, const LVector3 &delta,
891 PN_stdfloat radius) {
892 // See CollisionSphere::intersects_line() for a derivation of the formula
893 // here.
894 double A = dot(delta, delta);
895
896 nassertr(A != 0.0, false);
897
898 LVector3 fc = from;
899 fc[1] -= center_y;
900 double B = 2.0f* dot(delta, fc);
901 double fc_d2 = dot(fc, fc);
902 double C = fc_d2 - radius * radius;
903
904 double radical = B*B - 4.0*A*C;
905
906 if (IS_NEARLY_ZERO(radical)) {
907 // Tangent.
908 t1 = t2 = -B / (2.0 * A);
909 return true;
910
911 } else if (radical < 0.0) {
912 // No real roots: no intersection with the line.
913 return false;
914 }
915
916 double reciprocal_2A = 1.0 / (2.0 * A);
917 double sqrt_radical = sqrtf(radical);
918 t1 = ( -B - sqrt_radical ) * reciprocal_2A;
919 t2 = ( -B + sqrt_radical ) * reciprocal_2A;
920
921 return true;
922}
923
924/**
925 * Determine a point of intersection of a parametric parabola with the capsule.
926 *
927 * We only consider the segment of the parabola between t1 and t2, which has
928 * already been computed as corresponding to points p1 and p2. If there is an
929 * intersection, t is set to the parametric point of intersection, and true is
930 * returned; otherwise, false is returned.
931 */
932bool CollisionCapsule::
933intersects_parabola(double &t, const LParabola &parabola,
934 double t1, double t2,
935 const LPoint3 &p1, const LPoint3 &p2) const {
936 // I don't even want to think about the math to do this calculation directly
937 // --it's even worse than sphere-parabola. So I'll use the recursive
938 // subdivision solution again, just like I did for sphere-parabola.
939
940 // First, see if the line segment (p1 - p2) comes sufficiently close to the
941 // parabola. Do this by computing the parametric intervening point and
942 // comparing its distance from the linear intervening point.
943 double tmid = (t1 + t2) * 0.5;
944
945 if (tmid != t1 && tmid != t2) {
946 LPoint3 pmid = parabola.calc_point(tmid);
947 LPoint3 pmid2 = (p1 + p2) * 0.5f;
948
949 if ((pmid - pmid2).length_squared() > 0.001f) {
950 // Subdivide.
951 if (intersects_parabola(t, parabola, t1, tmid, p1, pmid)) {
952 return true;
953 }
954 return intersects_parabola(t, parabola, tmid, t2, pmid, p2);
955 }
956 }
957
958 // The line segment is sufficiently close; compare the segment itself.
959 double t1a, t2a;
960 if (!intersects_line(t1a, t2a, p1, p2 - p1, 0.0f)) {
961 return false;
962 }
963
964 if (t2a < 0.0 || t1a > 1.0) {
965 return false;
966 }
967
968 t = std::max(t1a, 0.0);
969 return true;
970}
971
972/**
973 * Calculates a point that is exactly on the surface of the capsule and its
974 * corresponding normal, given a point that is supposedly on the surface of
975 * the capsule.
976 */
977void CollisionCapsule::
978calculate_surface_point_and_normal(const LPoint3 &surface_point,
979 double extra_radius,
980 LPoint3 &result_point,
981 LVector3 &result_normal) const {
982 // Convert the point into our canonical space for analysis.
983 LPoint3 point = surface_point * _inv_mat;
984 LVector3 normal;
985
986 if (point[1] <= 0.0) {
987 // The point is on the first endcap.
988 normal = point;
989 if (!normal.normalize()) {
990 normal.set(0.0, -1.0, 0.0);
991 }
992 point = normal * _radius;
993
994 } else if (point[1] >= _length) {
995 // The point is on the second endcap.
996 normal.set(point[0], point[1] - _length, point[2]);
997 if (!normal.normalize()) {
998 normal.set(0.0, 1.0, 0.0);
999 }
1000 point = normal * _radius;
1001 point[1] += _length;
1002
1003 } else {
1004 // The point is within the cylinder part.
1005 LVector2d normal2d(point[0], point[2]);
1006 if (!normal2d.normalize()) {
1007 normal2d.set(0.0, 1.0);
1008 }
1009 normal.set(normal2d[0], 0.0, normal2d[1]);
1010 point.set(normal[0] * _radius, point[1], normal[2] * _radius);
1011 }
1012
1013 // Now convert the point and normal back into real space.
1014 result_point = point * _mat;
1015 result_normal = normal * _mat;
1016}
1017
1018/**
1019 * After an intersection has been detected, record the computed intersection
1020 * point in the CollisionEntry, and also compute the relevant normal based on
1021 * that point.
1022 */
1023void CollisionCapsule::
1024set_intersection_point(CollisionEntry *new_entry,
1025 const LPoint3 &into_intersection_point,
1026 double extra_radius) const {
1027 LPoint3 point;
1028 LVector3 normal;
1029
1030 calculate_surface_point_and_normal(into_intersection_point,
1031 extra_radius,
1032 point,
1033 normal);
1034
1035 if (has_effective_normal() && new_entry->get_from()->get_respect_effective_normal()) {
1036 normal = get_effective_normal();
1037 }
1038
1039 new_entry->set_surface_normal(normal);
1040 new_entry->set_surface_point(point);
1041 // Also adjust the original point into the capsule by the amount of
1042 // extra_radius, which should put it on the surface of the capsule if our
1043 // collision was tangential.
1044 new_entry->set_interior_point(into_intersection_point - normal * extra_radius);
1045}
1046
1047/**
1048 * Tells the BamReader how to create objects of type CollisionCapsule.
1049 */
1052 BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
1053}
1054
1055/**
1056 * Writes the contents of this object to the datagram for shipping out to a
1057 * Bam file.
1058 */
1060write_datagram(BamWriter *manager, Datagram &dg) {
1061 CollisionSolid::write_datagram(manager, dg);
1062 _a.write_datagram(dg);
1063 _b.write_datagram(dg);
1064 dg.add_stdfloat(_radius);
1065}
1066
1067/**
1068 * This function is called by the BamReader's factory when a new object of
1069 * type CollisionCapsule is encountered in the Bam file. It should create the
1070 * CollisionCapsule and extract its information from the file.
1071 */
1072TypedWritable *CollisionCapsule::
1073make_from_bam(const FactoryParams &params) {
1074 CollisionCapsule *node = new CollisionCapsule();
1075 DatagramIterator scan;
1076 BamReader *manager;
1077
1078 parse_params(params, scan, manager);
1079 node->fillin(scan, manager);
1080
1081 return node;
1082}
1083
1084/**
1085 * This internal function is called by make_from_bam to read in all of the
1086 * relevant data from the BamFile for the new CollisionCapsule.
1087 */
1088void CollisionCapsule::
1089fillin(DatagramIterator &scan, BamReader *manager) {
1090 CollisionSolid::fillin(scan, manager);
1091 _a.read_datagram(scan);
1092 _b.read_datagram(scan);
1093 _radius = scan.get_stdfloat();
1094 recalc_internals();
1095}
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
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 implements a solid consisting of a cylinder with hemispherical endcaps, also known as a capsule ...
virtual PStatCollector & get_test_pcollector()
Returns a PStatCollector that is used to count the number of intersection tests made against a solid ...
virtual void xform(const LMatrix4 &mat)
Transforms the solid by the indicated matrix.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
static void register_with_read_factory()
Tells the BamReader how to create objects of type CollisionCapsule.
virtual LPoint3 get_collision_origin() const
Returns the point in space deemed to be the "origin" of the solid for collision purposes.
virtual PStatCollector & get_volume_pcollector()
Returns a PStatCollector that is used to count the number of bounding volume tests made against a sol...
Defines a single collision event.
get_into
Returns the CollisionSolid pointer for the particular solid was collided into.
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...
void set_surface_normal(const LVector3 &normal)
Stores the surface normal of the "into" object at the point of the intersection.
void set_interior_point(const LPoint3 &point)
Stores the point, within the interior of the "into" object, which represents the depth to which the "...
get_from
Returns the CollisionSolid pointer for the particular solid that triggered this collision.
void set_surface_point(const LPoint3 &point)
Stores the point, on the surface of the "into" object, at which a collision is detected.
get_t1
Returns the starting point on the parabola.
get_t2
Returns the ending point on the parabola.
get_parabola
Returns the parabola specified by this solid.
The abstract base class for all things that can collide with other things in the world,...
virtual void write_datagram(BamWriter *manager, Datagram &me)
Function to write the important information in the particular object to a Datagram.
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().
A class to retrieve the individual data elements previously stored in a Datagram.
PN_stdfloat get_stdfloat()
Extracts either a 32-bit or a 64-bit floating-point number, according to Datagram::set_stdfloat_doubl...
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
void add_stdfloat(PN_stdfloat value)
Adds either a 32-bit or a 64-bit floating-point number, according to set_stdfloat_double().
Definition datagram.I:133
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
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
bool extend_by(const GeometricBoundingVolume *vol)
Increases the size of the volume to include the given volume.
bool around(const GeometricBoundingVolume **first, const GeometricBoundingVolume **last)
Resets the volume to enclose only the volumes indicated.
A lightweight class that represents a single element that may be timed and/or counted via stats.
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.
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.