diff --git a/panda/src/collide/collisionBox.cxx b/panda/src/collide/collisionBox.cxx index 2f5385b9bf..84a8e39817 100644 --- a/panda/src/collide/collisionBox.cxx +++ b/panda/src/collide/collisionBox.cxx @@ -16,6 +16,7 @@ #include "collisionRay.h" #include "collisionSphere.h" #include "collisionSegment.h" +#include "collisionParabola.h" #include "collisionCapsule.h" #include "collisionHandler.h" #include "collisionEntry.h" @@ -493,6 +494,82 @@ test_intersection_from_ray(const CollisionEntry &entry) const { return new_entry; } +PT(CollisionEntry) CollisionBox:: +test_intersection_from_parabola(const CollisionEntry &entry) const { + const CollisionParabola *parabola; + DCAST_INTO_R(parabola, entry.get_from(), nullptr); + + const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + + // Convert the parabola into local coordinate space. + LParabola local_p(parabola->get_parabola()); + local_p.xform(wrt_mat); + + PN_stdfloat t = INT_MAX; + PN_stdfloat t1, t2; + int intersecting_face = -1; + for (int i = 0; i < get_num_planes(); i++) { + LPlane face = get_plane(i); + if (!face.intersects_parabola(t1, t2, local_p)) { + // the parabola does not intersect this face, skip to the next one + continue; + } + PN_stdfloat ts[2] = {t1, t2}; + // iterate through the t values to see if each of them are within our + // parabola and the intersection point is behind all other faces + for (int j = 0; j < 2; j++) { + PN_stdfloat cur_t = ts[j]; + if (cur_t > t) { + // we are looking for the earliest t value + // if this t value is greater, don't bother checking it + continue; + } + if (cur_t >= parabola->get_t1() && cur_t <= parabola->get_t2()) { + // the parabola does intersect this plane, now we check + // if the intersection point is behind all other planes + bool behind = true; + for (int k = 0; k < get_num_planes(); k++) { + if (k == i) { + // no need to check the intersecting face + continue; + } + if (get_plane(k).dist_to_plane(local_p.calc_point(cur_t)) > 0.0f) { + // our point is in front of this face, turns out the parabola + // does not collide with the box at this point + behind = false; + break; + } + } + if (behind) { + // the parabola does indeed collide with the box at this point + t = cur_t; + intersecting_face = i; + } + } + } + } + + if (intersecting_face != -1) { + if (collide_cat.is_debug()) { + collide_cat.debug() + << "intersection detected from " << entry.get_from_node_path() + << " into " << entry.get_into_node_path() << "\n"; + } + PT(CollisionEntry) new_entry = new CollisionEntry(entry); + + LPlane face = get_plane(intersecting_face); + + LPoint3 into_intersection_point = local_p.calc_point(t); + LVector3 normal = (has_effective_normal() && parabola->get_respect_effective_normal()) ? get_effective_normal() : face.get_normal(); + + new_entry->set_surface_point(into_intersection_point); + new_entry->set_surface_normal(normal); + return new_entry; + } else { + return nullptr; + } +} + /** * Double dispatch point for segment as a FROM object */ diff --git a/panda/src/collide/collisionBox.h b/panda/src/collide/collisionBox.h index 3baa77d152..4bf96f0212 100644 --- a/panda/src/collide/collisionBox.h +++ b/panda/src/collide/collisionBox.h @@ -81,6 +81,8 @@ protected: test_intersection_from_ray(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; + virtual PT(CollisionEntry) + test_intersection_from_parabola(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) diff --git a/tests/collide/test_into_box.py b/tests/collide/test_into_box.py index 5cda4c51a2..0259e1d4a2 100644 --- a/tests/collide/test_into_box.py +++ b/tests/collide/test_into_box.py @@ -43,3 +43,74 @@ def test_ray_into_box(): # No collision entry = make_collision(CollisionRay(0, 0, 100, 1, 0, 0), box)[0] assert entry is None + + +def test_parabola_into_box(): + # Set up Parabola and Box + parabola = CollisionParabola() + parabola.set_t1(0) + parabola.set_t2(2) + box = CollisionBox((0,0,0), 3, 3, 3) + + # Parabola is inside the Box and not colliding + parabola.set_parabola( + LParabola((-1, 0, -1), (1, 0, 1), (1, 1, 1))) + entry = make_collision(parabola, box)[0] + assert entry is None + + # Parabola is inside the Box and colliding on its projectile + parabola.set_parabola( + LParabola((0, 0, 1), (0, 0, 1), (1, 1, 1))) + # Parabola collides with Box when t == 1 at point (1, 1, 3) + assert parabola.get_parabola().calc_point(1) == (1, 1, 3) + entry, np_from, into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == (1, 1, 3) + assert entry.get_from() == parabola + assert entry.get_into() == box + + # Parabola is inside the Box and colliding on one of the endpoints + parabola.set_parabola( + LParabola((0, 0, 0), (0, 0, 1), (-3, 0, -3))) + entry, np_from, np_into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == (-3, 0, -3) + + # Parabola is outside the Box and not colliding + parabola.set_parabola( + LParabola((0, 0, 0), (0, 0, 1), (-5, 0, 0))) + entry = make_collision(parabola, box)[0] + assert entry is None + + # Parabola is outside the Box and colliding on its projecticle + parabola.set_parabola( + LParabola((-2, -2, -2), (1, 1, 1), (4, 4, 4))) + # Parabola collides with Box when t == 1 at point (3, 3, 3) + assert parabola.get_parabola().calc_point(1) == (3, 3, 3) + entry, np_from, into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == (3, 3, 3) + + # Parabola is outside the Box and colliding on the first endpoint + parabola.set_parabola( + LParabola((1, 1, 1), (1, 1, 1), (3, 3, 3))) + entry, np_from, np_into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == (3, 3, 3) + + # Parabola is outside the Box and colliding on the second endpoint + parabola.set_parabola( + LParabola((1, 0, 1), (-1, 0, -1), (-5, -3, -5))) + assert parabola.get_parabola().calc_point(2) == (-3, -3, -3) + entry, np_from, np_into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == (-3, -3, -3) + + # Parabola intersects the Box at two points, + # t == 0 and t == 2 the earliest one should be chosen. + parabola.set_parabola( + LParabola((-1, -1, -1), (-1, -1, -1), (3, 3, 3))) + entry, np_from, np_into = make_collision(parabola, box) + assert entry.get_surface_point(np_from) == parabola.get_parabola().calc_point(0) + + # First point no longer intersecting + parabola.set_t1(1) + entry, np_from, np_into = make_collision(parabola, box) + assert parabola.get_parabola().calc_point(2) == (-3, -3, -3) + assert entry.get_surface_point(np_from) == parabola.get_parabola().calc_point(2) + assert entry.get_surface_normal(np_from) is not None