Revamped collision detection, missing the vertex inside vertex case
(which is happening a bit too often imho)
This commit is contained in:
parent
2cfa921547
commit
37a76e3e8d
262
collisions.cc
262
collisions.cc
@ -1,108 +1,204 @@
|
|||||||
#include "collisions.h"
|
#include "collisions.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
static std::vector<vec2d> edges_of(polygon& p) {
|
struct vertex {
|
||||||
std::vector<vec2d> edges;
|
vec2d v, p1, p2;
|
||||||
edges.reserve(p.global_points.size());
|
|
||||||
|
|
||||||
for (uint i = 0; i < p.global_points.size(); ++i)
|
|
||||||
edges.push_back(p.global_points[(i + 1) % p.global_points.size()] -
|
|
||||||
p.global_points[i]);
|
|
||||||
return edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct projection_t {
|
|
||||||
double min_proj, max_proj;
|
|
||||||
vec2d min_point, max_point;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static projection_t project(polygon& p, vec2d axis) {
|
typedef std::pair<vec2d, vec2d> segment;
|
||||||
projection_t ret{INFINITY, -INFINITY};
|
|
||||||
|
|
||||||
double proj;
|
enum Orientation {
|
||||||
for (auto& point : p.global_points) {
|
COLINEAR,
|
||||||
proj = vec2d::dot(point, axis);
|
CLOCKWISE,
|
||||||
|
COUNTER_CLOCKWISE,
|
||||||
|
};
|
||||||
|
|
||||||
if (proj < ret.min_proj) {
|
static std::vector<vertex> vertices_of(polygon& p) {
|
||||||
ret.min_proj = proj;
|
std::vector<vertex> vertices;
|
||||||
ret.min_point = point;
|
vertices.reserve(p.global_points.size());
|
||||||
|
|
||||||
|
// i = 1 and <= points.size() cuz { -1 % n = -1 } et c'est chiant
|
||||||
|
// so start from 1, "overflow" with i = points.size() and gg
|
||||||
|
for (uint i = 1; i <= p.global_points.size(); ++i)
|
||||||
|
vertices.push_back({
|
||||||
|
p.global_points[i % p.points.size()],
|
||||||
|
p.global_points[(i + 1) % p.points.size()],
|
||||||
|
p.global_points[(i - 1) % p.points.size()],
|
||||||
|
});
|
||||||
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proj > ret.max_proj) {
|
// Given three collinear points p, q, r, the function checks if
|
||||||
ret.max_proj = proj;
|
// point q lies on line segment 'pr'
|
||||||
ret.max_point = point;
|
static bool on_segment(vec2d& q, segment& pr) {
|
||||||
}
|
return q.x <= std::max(pr.first.x, pr.second.x) &&
|
||||||
|
q.x >= std::min(pr.first.x, pr.second.x) &&
|
||||||
|
q.y <= std::max(pr.first.y, pr.second.y) &&
|
||||||
|
q.y >= std::min(pr.first.y, pr.second.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Orientation orientation(vec2d& p, vec2d& q, vec2d& r) {
|
||||||
|
int v = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
||||||
|
if (v == 0)
|
||||||
|
return COLINEAR;
|
||||||
|
return v > 0 ? CLOCKWISE : COUNTER_CLOCKWISE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool do_intersect(segment s1, segment s2) {
|
||||||
|
// Find the four orientations needed for general and
|
||||||
|
// special cases
|
||||||
|
Orientation o1 = orientation(s1.first, s1.second, s2.first);
|
||||||
|
Orientation o2 = orientation(s1.first, s1.second, s2.second);
|
||||||
|
Orientation o3 = orientation(s2.first, s2.second, s1.first);
|
||||||
|
Orientation o4 = orientation(s2.first, s2.second, s1.second);
|
||||||
|
|
||||||
|
// General case
|
||||||
|
if (o1 != o2 && o3 != o4)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Special Cases
|
||||||
|
// p1, q1 and p2 are collinear and p2 lies on segment p1q1
|
||||||
|
if (o1 == COLINEAR && on_segment(s2.first, s1))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// p1, q1 and q2 are collinear and q2 lies on segment p1q1
|
||||||
|
if (o2 == COLINEAR && on_segment(s2.second, s1))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// p2, q2 and p1 are collinear and p1 lies on segment p2q2
|
||||||
|
if (o3 == COLINEAR && on_segment(s1.first, s2))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// p2, q2 and q1 are collinear and q1 lies on segment p2q2
|
||||||
|
if (o4 == COLINEAR && on_segment(s1.second, s2))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<segment> edges_of(polygon& p) {
|
||||||
|
std::vector<segment> ret;
|
||||||
|
ret.reserve(p.points.size());
|
||||||
|
for (uint i = 0; i < p.points.size(); ++i)
|
||||||
|
ret.push_back(
|
||||||
|
{p.global_points[i], p.global_points[(i + 1) % p.points.size()]});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static collision penetration(segment& edge, vertex& vertex, vec2d& d) {
|
||||||
|
collision ret{true};
|
||||||
|
ret.impact_point = vertex.v;
|
||||||
|
|
||||||
|
vec2d n = (edge.second - edge.first).orthogonal();
|
||||||
|
ret.n = vec2d::normalize(n);
|
||||||
|
|
||||||
|
if (vec2d::dot(n, d) > 0)
|
||||||
|
ret.n *= -1;
|
||||||
|
std::cout << "Double pene omg" << std::endl;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static collision parallel(segment edge_p, segment edge_q, vec2d d) {
|
||||||
|
collision ret{true};
|
||||||
|
|
||||||
|
vec2d line_start = edge_p.first;
|
||||||
|
vec2d base = vec2d::normalize(edge_p.second - line_start);
|
||||||
|
|
||||||
|
std::pair<double, vec2d> proj_p1, proj_p2, proj_q1, proj_q2;
|
||||||
|
proj_p1 = {0, edge_p.first};
|
||||||
|
proj_p2 = {vec2d::dot(edge_p.second - line_start, base), edge_p.second};
|
||||||
|
proj_q1 = {vec2d::dot(edge_q.first - line_start, base), edge_q.first};
|
||||||
|
proj_q2 = {vec2d::dot(edge_q.second - line_start, base), edge_q.second};
|
||||||
|
|
||||||
|
std::pair<double, vec2d>*p_min, *q_min, *p_max, *q_max;
|
||||||
|
if (proj_p1.first < proj_p2.first) {
|
||||||
|
p_min = &proj_p1;
|
||||||
|
p_max = &proj_p2;
|
||||||
|
} else {
|
||||||
|
p_min = &proj_p2;
|
||||||
|
p_max = &proj_p1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proj_q1.first < proj_q2.first) {
|
||||||
|
q_min = &proj_q1;
|
||||||
|
q_max = &proj_q2;
|
||||||
|
} else {
|
||||||
|
q_min = &proj_q2;
|
||||||
|
q_max = &proj_q1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2d min = p_min->first < q_min->first ? q_min->second : p_min->second;
|
||||||
|
vec2d max = p_max->first < q_max->first ? p_max->second : q_max->second;
|
||||||
|
|
||||||
|
ret.impact_point = (min + max) / 2;
|
||||||
|
std::cout << "impact point: " << ret.impact_point << std::endl;
|
||||||
|
ret.n = base.orthogonal();
|
||||||
|
if (vec2d::dot(ret.n, d) > 0)
|
||||||
|
ret.n *= -1;
|
||||||
|
std::cout << "Parallel lol" << std::endl;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool are_vecs_parallel(vec2d s1, vec2d s2) {
|
||||||
|
return std::abs(vec2d::dot(vec2d::normalize(s1), vec2d::normalize(s2))) >
|
||||||
|
.99;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double distance_between_parallel_segments(segment s1, segment s2) {
|
||||||
|
double area = vec2d::cross(s1.first - s2.first, s2.second - s2.first);
|
||||||
|
double base = vec2d::norm(s2.second - s2.first);
|
||||||
|
return std::abs(area / base);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SMALLEST_DIST 5
|
||||||
|
|
||||||
|
static collision vertex_edge_collision(polygon& p, polygon& q) {
|
||||||
|
std::vector<vertex> vertices_p = vertices_of(p);
|
||||||
|
std::vector<segment> edges_q = edges_of(q);
|
||||||
|
vec2d d = q.centroid() - p.centroid();
|
||||||
|
|
||||||
|
bool col1, col2;
|
||||||
|
for (auto& vertex : vertices_p)
|
||||||
|
for (auto& edge : edges_q) {
|
||||||
|
col1 = do_intersect(edge, {vertex.v, vertex.p1});
|
||||||
|
col2 = do_intersect(edge, {vertex.v, vertex.p2});
|
||||||
|
if (col1 || col2) {
|
||||||
|
if (col1 && col2)
|
||||||
|
return penetration(edge, vertex, d);
|
||||||
|
|
||||||
|
vec2d edge_v = edge.second - edge.first;
|
||||||
|
if (are_vecs_parallel(edge_v, vertex.v - vertex.p1) &&
|
||||||
|
distance_between_parallel_segments(
|
||||||
|
edge, {vertex.v, vertex.p1}) < SMALLEST_DIST)
|
||||||
|
return parallel(edge, {vertex.v, vertex.p1}, d);
|
||||||
|
|
||||||
|
double dist = distance_between_parallel_segments(
|
||||||
|
edge, {vertex.v, vertex.p2});
|
||||||
|
std::cout << "dist: " << dist << std::endl;
|
||||||
|
if (are_vecs_parallel(edge_v, vertex.v - vertex.p2) &&
|
||||||
|
distance_between_parallel_segments(
|
||||||
|
edge, {vertex.v, vertex.p2}) < SMALLEST_DIST)
|
||||||
|
return parallel(edge, {vertex.v, vertex.p2}, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {false};
|
||||||
|
}
|
||||||
|
|
||||||
static collision convex_collides(polygon& p, polygon& q) {
|
static collision convex_collides(polygon& p, polygon& q) {
|
||||||
collision ret{false};
|
collision ret;
|
||||||
|
|
||||||
std::vector<vec2d> edges_p = edges_of(p);
|
std::cout << "Checking P is penetrates Q" << std::endl;
|
||||||
std::vector<vec2d> edges_q = edges_of(q);
|
if ((ret = vertex_edge_collision(p, q)).collides)
|
||||||
|
|
||||||
std::vector<vec2d> edges;
|
|
||||||
edges.reserve(edges_p.size() + edges_q.size());
|
|
||||||
edges.insert(edges.end(), edges_p.begin(), edges_p.end());
|
|
||||||
edges.insert(edges.end(), edges_q.begin(), edges_q.end());
|
|
||||||
|
|
||||||
std::vector<vec2d> orthogonals;
|
|
||||||
orthogonals.reserve(edges.size());
|
|
||||||
for (auto& v : edges)
|
|
||||||
orthogonals.push_back(v.orthogonal());
|
|
||||||
|
|
||||||
std::vector<collision> candidate_collisions;
|
|
||||||
candidate_collisions.reserve(orthogonals.size());
|
|
||||||
|
|
||||||
vec2d d = q.centroid() - p.centroid();
|
|
||||||
|
|
||||||
double min_overlap = INFINITY;
|
|
||||||
for (auto& o : orthogonals) {
|
|
||||||
vec2d axis = vec2d::normalize(o);
|
|
||||||
if (vec2d::dot(d, axis) > 0)
|
|
||||||
axis *= -1;
|
|
||||||
|
|
||||||
|
|
||||||
projection_t p_proj = project(p, axis);
|
|
||||||
projection_t q_proj = project(q, axis);
|
|
||||||
if (p_proj.max_proj < q_proj.min_proj ||
|
|
||||||
q_proj.max_proj < p_proj.min_proj)
|
|
||||||
// the axis is separating (the projections don't overlap)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
std::cout << "Checking Q is penetrates P" << std::endl;
|
||||||
|
if ((ret = vertex_edge_collision(q, p)).collides)
|
||||||
|
ret.n *= -1;
|
||||||
|
|
||||||
double overlap = std::min(p_proj.max_proj, q_proj.max_proj) -
|
|
||||||
std::max(p_proj.min_proj, q_proj.min_proj);
|
|
||||||
|
|
||||||
if (overlap < min_overlap) {
|
|
||||||
min_overlap = overlap;
|
|
||||||
ret.n = axis;
|
|
||||||
|
|
||||||
ret.impact_point = p_proj.max_proj > q_proj.min_proj
|
|
||||||
? p_proj.min_point
|
|
||||||
: q_proj.max_point;
|
|
||||||
|
|
||||||
// if (vec2d::dot(axis, d) > 0) {
|
|
||||||
// ret.impact_point = p_proj.max_proj > q_proj.min_proj
|
|
||||||
// ? q_proj.min_point
|
|
||||||
// : q_proj.max_point;
|
|
||||||
// } else {
|
|
||||||
// ret.impact_point = p_proj.max_proj > q_proj.min_proj
|
|
||||||
// ? p_proj.min_point
|
|
||||||
// : p_proj.max_point;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if no axis is sperating, then they must be colliding
|
|
||||||
ret.collides = true;
|
|
||||||
|
|
||||||
// if (vec2d::dot(d, ret.n) > 0)
|
|
||||||
// ret.n *= -1;
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user