Преглед на файлове

add collisionHeightfield class

hecris преди 6 години
родител
ревизия
d9d7141368

+ 23 - 0
panda/src/collide/collisionHeightfield.I

@@ -0,0 +1,23 @@
+INLINE CollisionHeightfield::
+CollisionHeightfield() {
+}
+
+INLINE CollisionHeightfield::
+CollisionHeightfield(const CollisionHeightfield &copy) {
+}
+
+INLINE void CollisionHeightfield::
+flush_level() {
+  _volume_pcollector.flush_level();
+  _test_pcollector.flush_level();
+}
+
+INLINE PNMImage &CollisionHeightfield::
+heightfield() {
+  return _heightfield;
+}
+
+INLINE double CollisionHeightfield::
+get_height(int x, int y) const {
+  return _heightfield.get_gray(x, y) * _max_height;
+}

+ 759 - 0
panda/src/collide/collisionHeightfield.cxx

@@ -0,0 +1,759 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file CollisionHeightfield.cxx
+ * @author hecris
+ * @date 2019-07-01
+ */
+
+#include "collisionHeightfield.h"
+#include "collisionHandler.h"
+#include "collisionEntry.h"
+#include "cmath.h"
+#include "config_collide.h"
+#include "boundingBox.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include <queue>
+#include <algorithm>
+
+using std::min;
+using std::max;
+using std::queue;
+using std::vector;
+using std::sort;
+
+/**
+ *
+ */
+CollisionHeightfield::
+CollisionHeightfield(PNMImage &heightfield,
+                     double max_height, int subdivisions) {
+  _heightfield = heightfield;
+  _max_height = max_height;
+  setup_quadtree(subdivisions);
+}
+
+/**
+ *
+ */
+PT(CollisionEntry) CollisionHeightfield::
+test_intersection_from_ray(const CollisionEntry &entry) const {
+  const CollisionRay *ray;
+  DCAST_INTO_R(ray, entry.get_from(), nullptr);
+  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
+
+  LPoint3 from_origin = ray->get_origin() * wrt_mat;
+  LPoint3 from_direction = ray->get_direction() * wrt_mat;
+
+  IntersectionParams params;
+  params.from_origin = from_origin;
+  params.from_direction = from_direction;
+  vector<QuadTreeIntersection> intersections;
+  intersections = find_intersections(line_intersects_box, params);
+
+  if (intersections.size() == 0) {
+    return nullptr;
+  }
+  // Sort intersections by their t1 values so we can return
+  // the first intersection found.
+  sort(intersections.begin(), intersections.end());
+
+  double t1, t2;
+  for (unsigned i = 0; i < intersections.size(); i++) {
+    t1 = intersections[i].tmin;
+    t2 = intersections[i].tmax;
+
+    if (t1 < 0.0 && t2 < 0.0) continue;
+    t1 = max(t1, 0.0);
+
+    LPoint3 p1 = from_origin + from_direction * t1;
+    LPoint3 p2 = from_origin + from_direction * t2;
+    // We use Bresenhaum's Line Algorithm to directly get the heightfield
+    // coordinates that the line passes through.
+    int err = cabs(2 * (p2[1] - p1[1]));
+    int deltaerr = cabs(err - (p2[0] - p1[0]));
+
+    int x = p1[0], y = p1[1];
+    bool x_increasing = x < p2[0];
+    bool y_increasing = y < p2[1];
+    while ((x_increasing && x <= p2[0]) || (!x_increasing && x >= p2[0])) {
+      bool intersected = false;
+      Triangle int_tri;
+      vector<Triangle> triangles = get_triangles(x, y);
+      double min_t = DBL_MAX;
+      for (Triangle tri : triangles) {
+        double t;
+        if (line_intersects_triangle(t, from_origin, from_direction, tri)) {
+          if (t < 0) continue;
+          if (t < min_t) {
+            intersected = true;
+            min_t = t;
+            int_tri = tri;
+          }
+        }
+      }
+      // If the ray intersects this heightfield element, we can
+      // stop here and return the intersection.
+      if (intersected) {
+        PT(CollisionEntry) new_entry = new CollisionEntry(entry);
+        new_entry->set_surface_point(from_origin + from_direction * min_t);
+        LPlane p = LPlane(int_tri.p1, int_tri.p2, int_tri.p3);
+        LVector3 normal = p.get_normal();
+        if (p.dist_to_plane(from_origin) < 0.0f) normal *= -1;
+        new_entry->set_surface_normal(normal);
+        return new_entry;
+      }
+      // Otherwise, go to the next heightfield element
+      deltaerr += err;
+      if (deltaerr >= 0) {
+        if (y_increasing) y++;
+        else y--;
+        deltaerr -= 2 * (p2[0] - p1[0]);
+      }
+      if (x_increasing) x++;
+      else x--;
+    }
+  }
+
+  return nullptr;
+}
+
+/**
+ *
+ */
+PT(CollisionEntry) CollisionHeightfield::
+test_intersection_from_sphere(const CollisionEntry &entry) const {
+  const CollisionSphere *sphere;
+  DCAST_INTO_R(sphere, entry.get_from(), nullptr);
+
+  CPT(TransformState) wrt_space = entry.get_wrt_space();
+  const LMatrix4 &wrt_mat = wrt_space->get_mat();
+
+  LPoint3 center = sphere->get_center() * wrt_mat;
+  LVector3 radius_v = LVector3(sphere->get_radius(), 0.0f, 0.0f) * wrt_mat;
+  PN_stdfloat radius_2 = radius_v.length_squared();
+  PN_stdfloat radius = csqrt(radius_2);
+
+  IntersectionParams params;
+  params.center = center;
+  params.radius = radius;
+
+  vector<QuadTreeIntersection> intersections;
+  intersections= find_intersections(sphere_intersects_box, params);
+
+  if (intersections.size() == 0) return nullptr;
+
+  LPoint3 point;
+  LPoint3 closest_point;
+  PN_stdfloat dist;
+  PN_stdfloat dist_min = FLT_MAX;
+
+  bool intersected = false;
+  #define in_box(x, y, node) (x >= node.area.min[0] && y >= node.area.min[1] \
+                           && x <= node.area.max[0] && y <= node.area.max[1])
+  for (unsigned i = 0; i < intersections.size(); i++) {
+    QuadTreeNode node = _nodes[intersections[i].node_index];
+    // Iterate through the circle's area and find triangle intersections,
+    // find the one closest to the center of the sphere.
+    for (int dx = -radius; dx <= radius; dx++) {
+      for (int dy = -radius; dy <= radius; dy++) {
+        int x = dx + center[0];
+        int y = dy + center[1];
+        if (!in_box(x, y, node)) continue;
+        if (dx * dx + dy * dy > radius_2) continue;
+
+        vector<Triangle> triangles = get_triangles(x, y);
+        for (Triangle tri : triangles) {
+          point = closest_point_on_triangle(center, tri);
+          dist = (point - center).length_squared();
+          if (dist > radius_2) continue;
+          if (dist < dist_min) {
+            intersected = true;
+            dist_min = dist;
+            closest_point = point;
+          }
+        }
+      }
+    }
+  }
+  #undef in_box
+  if (intersected) {
+    PT(CollisionEntry) new_entry = new CollisionEntry(entry);
+    LVector3 v(center - closest_point);
+    PN_stdfloat v_length = v.length();
+    if (IS_NEARLY_ZERO(v_length)) {
+      v.set(1, 0, 0);
+    } else {
+      v /= v_length;
+    }
+    new_entry->set_surface_point(closest_point);
+    new_entry->set_surface_normal(v);
+    new_entry->set_interior_point(center - radius * v);
+    return new_entry;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ *
+ */
+PT(CollisionEntry) CollisionHeightfield::
+test_intersection_from_box(const CollisionEntry &entry) const {
+  const CollisionBox *box;
+  DCAST_INTO_R(box, entry.get_from(), nullptr);
+
+  const LMatrix4 &wrt_mat = entry.get_wrt_mat();
+  LPoint3 box_min = box->get_min() * wrt_mat;
+  LPoint3 box_max = box->get_max() * wrt_mat;
+
+  IntersectionParams params;
+  params.box_min = box_min;
+  params.box_max = box_max;
+
+  vector<QuadTreeIntersection> intersections;
+  intersections = find_intersections(box_intersects_box, params);
+
+  if (intersections.size() == 0) {
+    return nullptr;
+  }
+
+  bool intersected = false;
+  Triangle intersected_tri;
+  for (unsigned i = 0; i < intersections.size(); i++) {
+    QuadTreeNode node = _nodes[intersections[i].node_index];
+    // Find the overlapping rectangle between the two boxes and
+    // test the heightfield elements in that area.
+    LVecBase2 overlap_min = LVecBase2(max(box_min[0], node.area.min[0]),
+                                      max(box_min[1], node.area.min[1]));
+    LVecBase2 overlap_max = LVecBase2(min(box_max[0], node.area.max[0]),
+                                      min(box_max[1], node.area.max[1]));
+    for (int x = overlap_min[0]; x < overlap_max[0]; x++) {
+      for (int y = overlap_min[1]; y < overlap_max[1]; y++) {
+        vector<Triangle> triangles = get_triangles(x, y);
+        for (Triangle tri : triangles) {
+          if (box_intersects_triangle(box_min, box_max, tri)) {
+            intersected = true;
+            intersected_tri = tri;
+          }
+        }
+      }
+    }
+  }
+
+  if (intersected) {
+    PT(CollisionEntry) new_entry = new CollisionEntry(entry);
+    LPlane p = LPlane(intersected_tri.p1, intersected_tri.p2, intersected_tri.p3);
+    LVector3 normal = p.get_normal();
+    if (p.dist_to_plane(box->get_center() * wrt_mat) < 0.0f) {
+      normal *= -1;
+      p.flip();
+    }
+    new_entry->set_surface_normal(normal);
+    PN_stdfloat min_dist = 0;
+    LPoint3 interior_point;
+    // Find the deepest vertex and set it as the interior point
+    for (int i = 0; i < 8; i++) {
+      LPoint3 vertex = box->get_point(i) * wrt_mat;
+      PN_stdfloat dist = p.dist_to_plane(vertex);
+      if (dist <= min_dist) {
+        min_dist = dist;
+        interior_point = vertex;
+      }
+    }
+    new_entry->set_interior_point(interior_point);
+    new_entry->set_surface_point(
+        closest_point_on_triangle(interior_point, intersected_tri));
+    return new_entry;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ *
+ */
+bool CollisionHeightfield::
+line_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                    IntersectionParams &params) {
+  LPoint3 from = params.from_origin;
+  LPoint3 delta = params.from_direction;
+
+  double tmin = -DBL_MAX;
+  double tmax = DBL_MAX;
+  for (int i = 0; i < 3; ++i) {
+    PN_stdfloat d = delta[i];
+    if (!IS_NEARLY_ZERO(d)) {
+      double tmin2 = (box_min[i] - from[i]) / d;
+      double tmax2 = (box_max[i] - from[i]) / d;
+      if (tmin2 > tmax2) {
+        std::swap(tmin2, tmax2);
+      }
+      tmin = max(tmin, tmin2);
+      tmax = min(tmax, tmax2);
+      if (tmin > tmax) {
+        return false;
+      }
+    } else if (from[i] < box_min[i] || from[i] > box_max[i]) {
+      // The line is parallel
+      return false;
+    }
+  }
+  params.t1 = tmin;
+  params.t2 = tmax;
+  return true;
+}
+
+/**
+ *
+ */
+bool CollisionHeightfield::
+line_intersects_triangle(double &t, const LPoint3 &from,
+                         const LPoint3 &delta,
+                         const Triangle &triangle) {
+  // Implementation of Möller-Trumbore algorithm
+  const PN_stdfloat EPSILON = 1.0e-7;
+  PN_stdfloat a,f,u,v;
+  LVector3 edge1, edge2, h, s, q;
+  edge1 = triangle.p2 - triangle.p1;
+  edge2 = triangle.p3 - triangle.p1;
+  h = delta.cross(edge2);
+  a = dot(edge1, h);
+  if (a > -EPSILON && a < EPSILON) {
+    // line parallel to triangle
+    return false;
+  }
+  f = 1.0 / a;
+  s = from - triangle.p1;
+  u = f * dot(s, h);
+  if (u < 0.0 || u > 1.0) {
+    return false;
+  }
+  q = s.cross(edge1);
+  v = f * dot(delta, q);
+  if (v < 0.0 || u + v > 1.0) {
+    return false;
+  }
+  t = f * dot(edge2, q);
+  return true;
+}
+
+/**
+ *
+ */
+bool CollisionHeightfield::
+sphere_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                      IntersectionParams &params) {
+  LPoint3 center = params.center;
+  double radius = params.radius;
+  LPoint3 p = center.fmin(box_max).fmax(box_min);
+  return (center - p).length_squared() <= radius * radius;
+}
+
+/**
+ *
+ */
+bool CollisionHeightfield::
+box_intersects_triangle(const LPoint3 &box_min, const LPoint3 &box_max,
+                        const Triangle &triangle) {
+  // Using the Seperating Axis Theorem, we have a maximum of 13 SAT tests
+  // Refer to Christer Ericson's Collision Detection book for full explanation
+  LPoint3 v0 = triangle.p1, v1 = triangle.p2, v2 = triangle.p3;
+
+  PN_stdfloat p0, p1, p2, r;
+  LVector3 c = (box_min + box_max) * 0.5f;
+  PN_stdfloat e0 = (box_max[0] - box_min[0]) * 0.5f;
+  PN_stdfloat e1 = (box_max[1] - box_min[1]) * 0.5f;
+  PN_stdfloat e2 = (box_max[2] - box_min[2]) * 0.5f;
+
+  v0 = v0 - c;
+  v1 = v1 - c;
+  v2 = v2 - c;
+
+  LVector3 f0 = v1 - v0, f1 = v2 - v1, f2 = v0 - v2;
+
+  r = e1 * cabs(f0[2]) + e2 * cabs(f0[1]);
+  p0 =  v0[1]*(-v1[2] + v0[2]) + v0[2]*(v1[1] - v0[1]);
+  p2 =  v2[1]*(-v1[2] + v0[2]) + v2[2]*(v1[1] - v0[1]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e1 * cabs(f1[2]) + e2 * cabs(f1[1]);
+  p0 =  v0[1]*(-v2[2] + v1[2]) + v0[2]*(v2[1] - v1[1]);
+  p2 =  v2[1]*(-v2[2] + v1[2]) + v2[2]*(v2[1] - v1[1]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e1 * cabs(f2[2]) + e2 * cabs(f2[1]);
+  p0 =  v0[1]*(-v0[2] + v2[2]) + v0[2]*(v0[1] - v2[1]);
+  p1 =  v1[1]*(-v0[2] + v2[2]) + v1[2]*(v0[1] - v2[1]);
+  if (max(-1 * max(p0, p1), min(p0, p1)) > r) return false;
+
+  r = e0 * cabs(f0[2]) + e2 * cabs(f0[0]);
+  p0 =  v0[0]*(v1[2] - v0[2]) + v0[2]*(-v1[0] + v0[0]);
+  p2 =  v2[0]*(v1[2] - v0[2]) + v2[2]*(-v1[0] + v0[0]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e0 * cabs(f1[2]) + e2 * cabs(f1[0]);
+  p0 =  v0[0]*(v2[2] - v1[2]) + v0[2]*(-v2[0] + v1[0]);
+  p2 =  v2[0]*(v2[2] - v1[2]) + v2[2]*(-v2[0] + v1[0]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e0 * cabs(f2[2]) + e2 * cabs(f2[0]);
+  p0 =  v0[0]*(v0[2] - v2[2]) + v0[2]*(-v0[0] + v2[0]);
+  p1 =  v1[0]*(v0[2] - v2[2]) + v1[2]*(-v0[0] + v2[0]);
+  if (max(-1 * max(p0, p1), min(p0, p1)) > r) return false;
+
+  r = e0 * cabs(f0[1]) + e1 * cabs(f0[0]);
+  p0 =  v0[0]*(-v1[1] + v0[1]) + v0[1]*(v1[0] - v0[0]);
+  p2 =  v2[0]*(-v1[1] + v0[1]) + v2[1]*(v1[0] - v0[0]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e0 * cabs(f1[1]) + e1 * cabs(f1[0]);
+  p0 =  v0[0]*(-v2[1] + v1[1]) + v0[1]*(v2[0] - v1[0]);
+  p2 =  v2[0]*(-v2[1] + v1[1]) + v2[1]*(v2[0] - v1[0]);
+  if (max(-1 * max(p0, p2), min(p0, p2)) > r) return false;
+
+  r = e0 * cabs(f2[1]) + e1 * cabs(f2[0]);
+  p0 =  v0[0]*(-v0[1] + v2[1]) + v0[1]*(v0[0] - v2[0]);
+  p1 =  v1[0]*(-v0[1] + v2[1]) + v1[1]*(v0[0] - v2[0]);
+  if (max(-1 * max(p0, p1), min(p0, p1)) > r) return false;
+
+  if (max(max(v0[0], v1[0]), v2[0]) < -e0 ||
+      min(min(v0[0], v1[0]), v2[0]) > e0) return false;
+  if (max(max(v0[1], v1[1]), v2[1]) < -e1 ||
+      min(min(v0[1], v1[1]), v2[1]) > e1) return false;
+  if (max(max(v0[2], v1[2]), v2[2]) < -e2 ||
+      min(min(v0[2], v1[2]), v2[2]) > e2) return false;
+
+  LVector3 n = f0.cross(f1);
+  PN_stdfloat d = dot(n, triangle.p1);
+
+  LPoint3 e = box_max - c;
+
+  PN_stdfloat r2 = e[0] * cabs(n[0]) + e[1] * cabs(n[1]) + e[2] * cabs(n[2]);
+  PN_stdfloat s = dot(n, c) - d;
+  return cabs(s) <= r2;
+}
+
+/**
+ *
+ */
+bool CollisionHeightfield::
+box_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                   IntersectionParams &params) {
+
+  return (box_min[0] <= params.box_max[0] && box_max[0] >= params.box_min[0]) &&
+         (box_min[1] <= params.box_max[1] && box_max[1] >= params.box_min[1]) &&
+         (box_min[2] <= params.box_max[2] && box_max[2] >= params.box_min[2]);
+}
+
+/**
+ * Returns the closest point on a triangle to another given point.
+ */
+LPoint3 CollisionHeightfield::
+closest_point_on_triangle(const LPoint3 &p, const Triangle &triangle) {
+  LVector3 ab = triangle.p2 - triangle.p1;
+  LVector3 ac = triangle.p3 - triangle.p1;
+  LVector3 ap = p - triangle.p1;
+  PN_stdfloat d1 = dot(ab, ap);
+  PN_stdfloat d2 = dot(ac, ap);
+  if (d1 <= 0.0f && d2 <= 0.0f) return triangle.p1;
+
+  LVector3 bp = p - triangle.p2;
+  PN_stdfloat d3 = dot(ab, bp);
+  PN_stdfloat d4 = dot(ac, bp);
+  if (d3 >= 0.0f && d4 <= d3) return triangle.p2;
+
+  PN_stdfloat vc = d1 * d4 - d3 * d2;
+  if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
+    PN_stdfloat v = d1 / (d1 - d3);
+    return triangle.p1 + v * ab;
+  }
+
+  LVector3 cp = p - triangle.p3;
+  PN_stdfloat d5 = dot(ab, cp);
+  PN_stdfloat d6 = dot(ac, cp);
+  if (d6 >= 0.0f && d5 <= d6) return triangle.p3;
+
+  PN_stdfloat vb = d5 * d2 - d1 * d6;
+  if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
+    PN_stdfloat w = d2 / (d2 - d6);
+    return triangle.p1 + w * ac;
+  }
+
+  PN_stdfloat va = d3 * d6 - d5 * d4;
+  if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
+    PN_stdfloat w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
+    return triangle.p2 + w * (triangle.p3 - triangle.p2);
+  }
+
+  PN_stdfloat denom = 1.0f / (va + vb + vc);
+  PN_stdfloat v = vb * denom;
+  PN_stdfloat w = vc * denom;
+  return triangle.p1 + ab * v + ac * w;
+}
+
+/**
+ * Given a pointer to a function that tests for intersection between a solid
+ * and a box, return the quad tree nodes that are intersected by that solid.
+ */
+vector<CollisionHeightfield::QuadTreeIntersection> CollisionHeightfield::
+find_intersections(BoxIntersection intersects_box, IntersectionParams params) const {
+  queue<QuadTreeNode> q;
+  QuadTreeNode node = _nodes[0];
+  q.push(node);
+
+  LPoint3 box_min, box_max;
+  vector<QuadTreeIntersection> intersections;
+
+  while (!q.empty()) {
+    node = q.front();
+    q.pop();
+    box_min = {node.area.min[0], node.area.min[1], node.height_min};
+    box_max = {node.area.max[0], node.area.max[1], node.height_max};
+    if (!intersects_box(box_min, box_max, params)) {
+      continue;
+    }
+
+    if (node.index >= _leaf_first_index) {
+      QuadTreeIntersection intersection = {node.index, params.t1, params.t2};
+      intersections.push_back(intersection);
+    } else {
+      int child_first_index = 4 * node.index + 1;
+      q.push(_nodes[child_first_index]);
+      q.push(_nodes[child_first_index + 1]);
+      q.push(_nodes[child_first_index + 2]);
+      q.push(_nodes[child_first_index + 3]);
+    }
+  }
+
+  return intersections;
+}
+
+/**
+ * Return the triangles that are defined at the
+ * given 2D coordinate.
+ */
+vector<CollisionHeightfield::Triangle> CollisionHeightfield::
+get_triangles(int x, int y) const {
+  int rows = _heightfield.get_read_x_size();
+  int cols = _heightfield.get_read_y_size();
+  vector<Triangle> triangles;
+  if (x < 0 || y < 0 || x >= rows || y >= cols)
+    return triangles;
+
+  Triangle t;
+  int y2 = cols - 1 - y;
+  bool odd = (x + y2) % 2;
+  #define get_point(dx, dy) LPoint3(x + dx, y - dy, get_height(x + dx, y2 + dy));
+  t.p1 = get_point(0, 0);
+  if (x - 1 >= 0 && y2 - 1 >= 0) {
+    if (odd) {
+      t.p2 = get_point(-1, 0);
+      t.p3 = get_point(0, -1);
+      triangles.push_back(t);
+    } else {
+      t.p2 = get_point(-1, -1);
+      t.p3 = get_point(-1, 0);
+      triangles.push_back(t);
+      t.p3 = get_point(0, -1);
+      triangles.push_back(t);
+    }
+  }
+
+  if (x + 1 < rows && y2 + 1 < cols) {
+    if (odd) {
+      t.p2 = get_point(1, 0);
+      t.p3 = get_point(0, 1);
+      triangles.push_back(t);
+    } else {
+      t.p2 = get_point(1, 1);
+      t.p3 = get_point(0, 1);
+      triangles.push_back(t);
+      t.p3 = get_point(1, 0);
+      triangles.push_back(t);
+    }
+  }
+
+  if (x - 1 >= 0 && y2 + 1 < cols) {
+    if (odd) {
+      t.p2 = get_point(0, 1);
+      t.p3 = get_point(0, 1);
+      triangles.push_back(t);
+    } else {
+      t.p2 = get_point(-1, 1);
+      t.p3 = get_point(-1, 0);
+      triangles.push_back(t);
+      t.p3 = get_point(0, 1);
+      triangles.push_back(t);
+    }
+  }
+
+  if (x + 1 < rows && y2 - 1 >= 0) {
+    if (odd) {
+      t.p2 = get_point(0, -1);
+      t.p3 = get_point(1, 0);
+      triangles.push_back(t);
+    } else {
+      t.p2 = get_point(1, -1);
+      t.p3 = get_point(0, -1);
+      triangles.push_back(t);
+      t.p3 = get_point(1, 0);
+      triangles.push_back(t);
+    }
+  }
+  #undef get_point
+
+  return triangles;
+}
+
+/**
+ * Processes the given heightfield image and creates a
+ * quad tree where each node represents a smaller
+ * sub-rectangle of the heightfield. Each quad tree node
+ * has a height_min and height_max value, thus representing
+ * a box in 3D space.
+ */
+void CollisionHeightfield::
+setup_quadtree(int subdivisions) {
+  int nodes_count = 0;
+  for (int i = 0; i <= subdivisions; i++) {
+    nodes_count += pow(4, i);
+  }
+  QuadTreeNode *nodes = new QuadTreeNode[nodes_count];
+  nodes[0].area.min = {0, 0};
+  nodes[0].area.max = {(float)_heightfield.get_read_x_size(),
+                       (float)_heightfield.get_read_y_size()};
+  nodes[0].index = 0;
+  QuadTreeNode parent;
+  for (int i = 1; i < nodes_count; i += 4) {
+    parent = nodes[(i-1) / 4];
+    LVector2 sub_area = (parent.area.max - parent.area.min) / 2;
+    // SE Quadrant
+    nodes[i].area.min = parent.area.min;
+    nodes[i].area.max = parent.area.min + sub_area;
+    nodes[i].index = i;
+    // SW Quadrant
+    nodes[i + 1].area.min = {parent.area.min[0] + sub_area[0],
+                             parent.area.min[1]};
+    nodes[i + 1].area.max = nodes[i + 1].area.min + sub_area;
+    nodes[i + 1].index = i + 1;
+    // NE Quadrant
+    nodes[i + 2].area.min = {parent.area.min[0],
+                             parent.area.min[1] + sub_area[1]};
+    nodes[i + 2].area.max = nodes[i + 2].area.min + sub_area;
+    nodes[i + 2].index = i + 2;
+    // NW Quadrant
+    nodes[i + 3].area.min = parent.area.min + sub_area;
+    nodes[i + 3].area.max = parent.area.max;
+    nodes[i + 3].index = i + 3;
+  }
+
+  // We now process the heightfield image, setting the
+  // height_min and height_max values of each quad tree node
+  // starting from the leaves.
+  int leaf_first_index = 0;
+  for (int i = 1; i <= subdivisions - 1; i++) {
+    leaf_first_index += pow(4, i);
+  }
+
+  QuadTreeNode node;
+  QuadTreeNode child;
+  for (int i = nodes_count - 1; i >= 0; i--) {
+    node = nodes[i];
+    PN_stdfloat height_min = INT_MAX;
+    PN_stdfloat height_max = INT_MIN;
+    if (i >= leaf_first_index) {
+      for (int x = node.area.min[0]; x < node.area.max[0]; x++) {
+        for (int y = node.area.min[1]; y < node.area.max[1]; y++) {
+          PN_stdfloat value = _heightfield.get_gray(x,
+            _heightfield.get_read_y_size() - 1 - y) * _max_height;
+
+          height_min = min(value, height_min);
+          height_max = max(value, height_max);
+        }
+      }
+    } else {
+      for (int c = (i * 4) + 1, cmax = c + 4; c < cmax; c++) {
+        child = nodes[c];
+        height_min = min(child.height_min, height_min);
+        height_max = max(child.height_max, height_max);
+      }
+    }
+    nodes[i].height_min = height_min;
+    nodes[i].height_max = height_max;
+  }
+  _nodes = nodes;
+  _nodes_count = nodes_count;
+  _leaf_first_index = leaf_first_index;
+}
+
+/**
+ * Generic CollisionSolid member functions
+ */
+void CollisionHeightfield::
+fill_viz_geom() {
+}
+
+CollisionSolid *CollisionHeightfield::
+make_copy() {
+  return new CollisionHeightfield(*this);
+}
+
+LPoint3 CollisionHeightfield::
+get_collision_origin() const {
+  return LPoint3(0, 0, 0);
+}
+
+PT(BoundingVolume) CollisionHeightfield::
+compute_internal_bounds() const {
+  QuadTreeNode node = _nodes[0];
+  LPoint3 box_min = {node.area.min[0], node.area.min[1],
+                     node.height_min};
+  LPoint3 box_max = {node.area.max[0], node.area.max[1],
+                     node.height_max};
+  return new BoundingBox(box_min, box_max);
+}
+
+PStatCollector &CollisionHeightfield::
+get_volume_pcollector() {
+  return _volume_pcollector;
+}
+
+PStatCollector &CollisionHeightfield::
+get_test_pcollector() {
+  return _test_pcollector;
+}
+
+TypedWritable *CollisionHeightfield::
+make_CollisionHeightfield(const FactoryParams &params) {
+  CollisionHeightfield *me = new CollisionHeightfield;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  me->fillin(scan, manager);
+  return me;
+}
+
+void CollisionHeightfield::
+fillin(DatagramIterator &scan, BamReader *manager) {
+}
+
+void CollisionHeightfield::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_CollisionHeightfield);
+}
+
+PStatCollector CollisionHeightfield::_volume_pcollector(
+      "Collision Volumes:CollisionHeightfield");
+PStatCollector CollisionHeightfield::_test_pcollector(
+      "Collision Tests:CollisionHeightfield");
+TypeHandle CollisionHeightfield::_type_handle;

+ 159 - 0
panda/src/collide/collisionHeightfield.h

@@ -0,0 +1,159 @@
+#ifndef COLLISIONHEIGHTFIELD_H
+#define COLLISIONHEIGHTFIELD_H
+
+#include "pandabase.h"
+#include "collisionSolid.h"
+#include "pnmImage.h"
+
+using std::vector;
+
+/*
+ * CollisionHeightfield efficiently deals with collisions on uneven
+ * terrain given a heightfield image. A quad tree is implemented to
+ * significantly reduce the amount of triangles tested. Each quad
+ * tree node represents a sub-rectangle of the heightfield image
+ * and thus a box in 3D space.
+ * */
+class EXPCL_PANDA_COLLIDE CollisionHeightfield : public CollisionSolid {
+PUBLISHED:
+  CollisionHeightfield(PNMImage &heightfield,
+                       double max_height, int subdivisions);
+  ~CollisionHeightfield() { delete[] _nodes; }
+  virtual LPoint3 get_collision_origin() const;
+  INLINE PNMImage &heightfield();
+
+protected:
+  struct Rect {
+    LVector2 min;
+    LVector2 max;
+  };
+
+  struct QuadTreeNode {
+    int index;
+    Rect area;
+    PN_stdfloat height_min;
+    PN_stdfloat height_max;
+  };
+
+  struct QuadTreeIntersection {
+    int node_index;
+    double tmin;
+    double tmax;
+    bool operator < (const QuadTreeIntersection& intersection) const {
+      return tmin < intersection.tmin;
+    }
+  };
+
+  struct IntersectionParams {
+    // From Line
+    double t1;
+    double t2;
+    LPoint3 from_origin;
+    LVector3 from_direction;
+    // From Sphere
+    LPoint3 center;
+    double radius;
+    // From Box
+    LPoint3 box_min;
+    LPoint3 box_max;
+  };
+
+  struct Triangle {
+    LPoint3 p1;
+    LPoint3 p2;
+    LPoint3 p3;
+  };
+
+private:
+  PNMImage _heightfield;
+  double _max_height;
+  // Todo: PT(QuadTreeNode) _nodes;
+  QuadTreeNode *_nodes;
+  int _nodes_count;
+  int _leaf_first_index;
+  void setup_quadtree(int subdivisions);
+  INLINE double get_height(int x, int y) const;
+  vector<Triangle> get_triangles(int x, int y) const;
+
+  // A pointer to a function that tests for intersection between a box and a
+  // solid defined by the given IntersectionParams.
+  typedef bool (*BoxIntersection)(const LPoint3 &box_min, const LPoint3 &box_max,
+                                  IntersectionParams &params);
+
+  vector<QuadTreeIntersection> find_intersections(BoxIntersection intersects_box,
+                                                  IntersectionParams params) const;
+
+protected:
+  static LPoint3 closest_point_on_triangle(const LPoint3 &p, const Triangle &triangle);
+
+  static bool line_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                                  IntersectionParams &params);
+
+  static bool line_intersects_triangle(double &t, const LPoint3 &from,
+                                const LPoint3 &delta,
+                                const Triangle &triangle);
+
+  static bool sphere_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                                    IntersectionParams &params);
+
+  static bool box_intersects_box(const LPoint3 &box_min, const LPoint3 &box_max,
+                                 IntersectionParams &params);
+
+  static bool box_intersects_triangle(const LPoint3 &box_min, const LPoint3 &box_max,
+                               const Triangle &triangle);
+
+protected:
+  virtual PT(CollisionEntry)
+  test_intersection_from_ray(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_sphere(const CollisionEntry &entry) const;
+  virtual PT(CollisionEntry)
+  test_intersection_from_box(const CollisionEntry &entry) const;
+
+  virtual void fill_viz_geom();
+
+public:
+  INLINE CollisionHeightfield(const CollisionHeightfield &copy);
+  virtual CollisionSolid *make_copy();
+
+  virtual PStatCollector &get_volume_pcollector();
+  virtual PStatCollector &get_test_pcollector();
+
+  INLINE static void flush_level();
+
+protected:
+  virtual PT(BoundingVolume) compute_internal_bounds() const;
+
+private:
+  INLINE CollisionHeightfield();
+  static PStatCollector _volume_pcollector;
+  static PStatCollector _test_pcollector;
+
+protected:
+  static TypedWritable *make_CollisionHeightfield(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static void register_with_read_factory();
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    CollisionSolid::init_type();
+    register_type(_type_handle, "CollisionHeightfield",
+                  CollisionSolid::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "collisionHeightfield.I"
+
+#endif

+ 1 - 0
panda/src/collide/collisionSolid.h

@@ -181,6 +181,7 @@ private:
   friend class CollisionParabola;
   friend class CollisionParabola;
   friend class CollisionHandlerFluidPusher;
   friend class CollisionHandlerFluidPusher;
   friend class CollisionBox;
   friend class CollisionBox;
+  friend class CollisionHeightfield;
 };
 };
 
 
 INLINE std::ostream &operator << (std::ostream &out, const CollisionSolid &cs) {
 INLINE std::ostream &operator << (std::ostream &out, const CollisionSolid &cs) {

+ 2 - 0
panda/src/collide/collisionTraverser.cxx

@@ -19,6 +19,7 @@
 #include "collisionRecorder.h"
 #include "collisionRecorder.h"
 #include "collisionVisualizer.h"
 #include "collisionVisualizer.h"
 #include "collisionSphere.h"
 #include "collisionSphere.h"
+#include "collisionHeightfield.h"
 #include "collisionBox.h"
 #include "collisionBox.h"
 #include "collisionCapsule.h"
 #include "collisionCapsule.h"
 #include "collisionPolygon.h"
 #include "collisionPolygon.h"
@@ -349,6 +350,7 @@ traverse(const NodePath &root) {
   CollisionPolygon::flush_level();
   CollisionPolygon::flush_level();
   CollisionPlane::flush_level();
   CollisionPlane::flush_level();
   CollisionBox::flush_level();
   CollisionBox::flush_level();
+  CollisionHeightfield::flush_level();
 }
 }
 
 
 #ifdef DO_COLLISION_RECORDING
 #ifdef DO_COLLISION_RECORDING

+ 3 - 0
panda/src/collide/config_collide.cxx

@@ -25,6 +25,7 @@
 #include "collisionHandlerFluidPusher.h"
 #include "collisionHandlerFluidPusher.h"
 #include "collisionHandlerQueue.h"
 #include "collisionHandlerQueue.h"
 #include "collisionInvSphere.h"
 #include "collisionInvSphere.h"
+#include "collisionHeightfield.h"
 #include "collisionLine.h"
 #include "collisionLine.h"
 #include "collisionLevelStateBase.h"
 #include "collisionLevelStateBase.h"
 #include "collisionGeom.h"
 #include "collisionGeom.h"
@@ -138,6 +139,7 @@ init_libcollide() {
   CollisionHandlerFluidPusher::init_type();
   CollisionHandlerFluidPusher::init_type();
   CollisionHandlerQueue::init_type();
   CollisionHandlerQueue::init_type();
   CollisionInvSphere::init_type();
   CollisionInvSphere::init_type();
+  CollisionHeightfield::init_type();
   CollisionLine::init_type();
   CollisionLine::init_type();
   CollisionLevelStateBase::init_type();
   CollisionLevelStateBase::init_type();
   CollisionGeom::init_type();
   CollisionGeom::init_type();
@@ -164,6 +166,7 @@ init_libcollide() {
   CollisionBox::register_with_read_factory();
   CollisionBox::register_with_read_factory();
   CollisionCapsule::register_with_read_factory();
   CollisionCapsule::register_with_read_factory();
   CollisionInvSphere::register_with_read_factory();
   CollisionInvSphere::register_with_read_factory();
+  CollisionHeightfield::register_with_read_factory();
   CollisionLine::register_with_read_factory();
   CollisionLine::register_with_read_factory();
   CollisionNode::register_with_read_factory();
   CollisionNode::register_with_read_factory();
   CollisionParabola::register_with_read_factory();
   CollisionParabola::register_with_read_factory();

+ 1 - 0
panda/src/collide/p3collide_composite1.cxx

@@ -13,3 +13,4 @@
 #include "collisionHandlerFluidPusher.cxx"
 #include "collisionHandlerFluidPusher.cxx"
 #include "collisionHandlerQueue.cxx"
 #include "collisionHandlerQueue.cxx"
 #include "collisionInvSphere.cxx"
 #include "collisionInvSphere.cxx"
+#include "collisionHeightfield.cxx"

+ 1 - 1
tests/collide/collisions.py

@@ -2,7 +2,7 @@ from panda3d.core import CollisionNode, NodePath
 from panda3d.core import CollisionTraverser, CollisionHandlerQueue
 from panda3d.core import CollisionTraverser, CollisionHandlerQueue
 from panda3d.core import CollisionSphere, CollisionBox, CollisionPolygon, CollisionCapsule
 from panda3d.core import CollisionSphere, CollisionBox, CollisionPolygon, CollisionCapsule
 from panda3d.core import CollisionLine, CollisionRay, CollisionSegment, CollisionParabola
 from panda3d.core import CollisionLine, CollisionRay, CollisionSegment, CollisionParabola
-from panda3d.core import CollisionPlane
+from panda3d.core import CollisionPlane, CollisionHeightfield
 from panda3d.core import Point3, Vec3, Plane, LParabola
 from panda3d.core import Point3, Vec3, Plane, LParabola
 
 
 
 

+ 5 - 0
tests/collide/test_into_heightfield.py

@@ -0,0 +1,5 @@
+from collisions import *
+from panda3d.core import PNMImage
+
+def test_ray_into_heightfield():
+    pass