2023-04-02 21:53:21 +02:00
|
|
|
#include "collisions.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
2023-05-06 14:07:47 +02:00
|
|
|
#include <cassert>
|
2023-04-02 21:53:21 +02:00
|
|
|
#include <iostream>
|
|
|
|
#include <vector>
|
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
struct vertex {
|
|
|
|
vec2d v, p1, p2;
|
|
|
|
};
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
typedef std::pair<vec2d, vec2d> segment;
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
enum Orientation {
|
|
|
|
COLINEAR,
|
|
|
|
CLOCKWISE,
|
|
|
|
COUNTER_CLOCKWISE,
|
2023-04-25 12:09:07 +02:00
|
|
|
};
|
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-04-25 12:09:07 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
// 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);
|
|
|
|
}
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
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;
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
// 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()]});
|
2023-04-02 21:53:21 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
static collision penetration(segment& edge, vertex& vertex, vec2d& d) {
|
|
|
|
collision ret{true};
|
|
|
|
ret.impact_point = vertex.v;
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
vec2d n = (edge.second - edge.first).orthogonal();
|
|
|
|
ret.n = vec2d::normalize(n);
|
2023-04-25 12:09:07 +02:00
|
|
|
|
2023-05-18 11:24:08 +02:00
|
|
|
if (vec2d::dot(n, d) > 0)
|
|
|
|
ret.n *= -1;
|
|
|
|
// std::cout << "-------------- Impact: penetration --------------"
|
|
|
|
// << std::endl;
|
2023-05-06 14:07:47 +02:00
|
|
|
return ret;
|
|
|
|
}
|
2023-04-25 12:09:07 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
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;
|
2023-04-25 12:09:07 +02:00
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
ret.impact_point = (min + max) / 2;
|
|
|
|
ret.n = base.orthogonal();
|
|
|
|
if (vec2d::dot(ret.n, d) > 0)
|
|
|
|
ret.n *= -1;
|
2023-05-18 11:24:08 +02:00
|
|
|
// std::cout << "-------------- Impact: parallel --------------" <<
|
|
|
|
// std::endl;
|
2023-05-06 14:07:47 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-05-17 13:22:34 +02:00
|
|
|
#define SMALLEST_DIST 3
|
|
|
|
|
|
|
|
static bool are_edges_colinear(segment& e1, segment& e2) {
|
|
|
|
vec2d e1_vec = e1.second - e1.first;
|
|
|
|
vec2d e2_vec = e2.second - e2.first;
|
|
|
|
return are_vecs_parallel(e1_vec, e2_vec) &&
|
|
|
|
distance_between_parallel_segments(e1, e2) < SMALLEST_DIST;
|
|
|
|
}
|
2023-05-06 14:07:47 +02:00
|
|
|
|
|
|
|
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);
|
2023-04-25 12:09:07 +02:00
|
|
|
vec2d d = q.centroid() - p.centroid();
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-17 13:22:34 +02:00
|
|
|
segment edge_p1, edge_p2;
|
2023-05-06 14:07:47 +02:00
|
|
|
bool col1, col2;
|
|
|
|
for (auto& vertex : vertices_p)
|
2023-05-17 13:22:34 +02:00
|
|
|
for (auto& edge_q : edges_q) {
|
|
|
|
edge_p1 = {vertex.v, vertex.p1};
|
|
|
|
edge_p2 = {vertex.v, vertex.p2};
|
|
|
|
col1 = do_intersect(edge_q, edge_p1);
|
|
|
|
col2 = do_intersect(edge_q, edge_p2);
|
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
if (col1 || col2) {
|
2023-05-17 13:22:34 +02:00
|
|
|
if (are_edges_colinear(edge_q, edge_p1))
|
|
|
|
return parallel(edge_q, edge_p1, d);
|
|
|
|
|
|
|
|
if (are_edges_colinear(edge_q, edge_p2))
|
|
|
|
return parallel(edge_q, edge_p2, d);
|
|
|
|
|
2023-05-06 14:07:47 +02:00
|
|
|
if (col1 && col2)
|
2023-05-17 13:22:34 +02:00
|
|
|
return penetration(edge_q, vertex, d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {false};
|
|
|
|
}
|
|
|
|
|
|
|
|
static collision vertex_vertex_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();
|
|
|
|
|
|
|
|
for (auto& vertex : vertices_p)
|
|
|
|
for (auto& edge_q : edges_q) {
|
|
|
|
if (do_intersect(edge_q, {vertex.v, vertex.p1})) {
|
|
|
|
vec2d edge_q_vec = edge_q.second - edge_q.first;
|
|
|
|
vec2d n = vec2d::normalize(edge_q_vec.orthogonal());
|
|
|
|
|
|
|
|
if (vec2d::dot(n, d) > 0)
|
|
|
|
n *= -1;
|
|
|
|
|
2023-05-18 11:24:08 +02:00
|
|
|
// std::cout
|
|
|
|
// << "-------------- Impact: angle in angle --------------"
|
|
|
|
// << std::endl;
|
2023-05-17 13:22:34 +02:00
|
|
|
return {true, n, vertex.v};
|
2023-05-06 14:07:47 +02:00
|
|
|
}
|
2023-04-25 12:09:07 +02:00
|
|
|
}
|
2023-05-06 14:07:47 +02:00
|
|
|
return {false};
|
|
|
|
}
|
|
|
|
|
|
|
|
static collision convex_collides(polygon& p, polygon& q) {
|
|
|
|
collision ret;
|
|
|
|
|
|
|
|
if ((ret = vertex_edge_collision(p, q)).collides)
|
|
|
|
return ret;
|
|
|
|
|
2023-05-17 13:22:34 +02:00
|
|
|
if ((ret = vertex_edge_collision(q, p)).collides) {
|
2023-05-06 14:07:47 +02:00
|
|
|
ret.n *= -1;
|
2023-05-17 13:22:34 +02:00
|
|
|
return ret;
|
|
|
|
}
|
2023-04-02 21:53:21 +02:00
|
|
|
|
2023-05-17 13:22:34 +02:00
|
|
|
if ((ret = vertex_vertex_collision(p, q)).collides)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if ((ret = vertex_vertex_collision(q, p)).collides)
|
|
|
|
ret.n *= -1;
|
2023-04-02 21:53:21 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
collision collides(polygon& p, polygon& q) {
|
|
|
|
return convex_collides(p, q);
|
|
|
|
}
|