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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
static std::vector<vec2d> edges_of(polygon& p) {
|
||||
std::vector<vec2d> edges;
|
||||
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;
|
||||
struct vertex {
|
||||
vec2d v, p1, p2;
|
||||
};
|
||||
|
||||
static projection_t project(polygon& p, vec2d axis) {
|
||||
projection_t ret{INFINITY, -INFINITY};
|
||||
typedef std::pair<vec2d, vec2d> segment;
|
||||
|
||||
double proj;
|
||||
for (auto& point : p.global_points) {
|
||||
proj = vec2d::dot(point, axis);
|
||||
enum Orientation {
|
||||
COLINEAR,
|
||||
CLOCKWISE,
|
||||
COUNTER_CLOCKWISE,
|
||||
};
|
||||
|
||||
if (proj < ret.min_proj) {
|
||||
ret.min_proj = proj;
|
||||
ret.min_point = point;
|
||||
static std::vector<vertex> vertices_of(polygon& p) {
|
||||
std::vector<vertex> vertices;
|
||||
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) {
|
||||
ret.max_proj = proj;
|
||||
ret.max_point = point;
|
||||
}
|
||||
// Given three collinear points p, q, r, the function checks if
|
||||
// point q lies on line segment 'pr'
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
collision ret{false};
|
||||
collision ret;
|
||||
|
||||
std::vector<vec2d> edges_p = edges_of(p);
|
||||
std::vector<vec2d> edges_q = edges_of(q);
|
||||
|
||||
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)
|
||||
std::cout << "Checking P is penetrates Q" << std::endl;
|
||||
if ((ret = vertex_edge_collision(p, q)).collides)
|
||||
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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user