#pragma once #include "Helpers/MathExtras.hpp" #include #include #include #include #include #include using namespace Eigen; namespace SCollision { template class AABB { public: typedef T UnderlyingType; AABB() = default; // NOTE: Should not use the min and max numeric limits to initialize, since that wont work for WorldScalar AABB(const Vector& initMin, const Vector& initMax): min(initMin), max(initMax) {} Vector min; Vector max; Vector center() const { return (min + max) / T(2); } Vector bottom_left() const { return Vector{min.x(), max.y()}; } Vector top_right() const { return Vector{max.x(), min.y()}; } Vector dim() const { return max - min; } T width() const { return max.x() - min.x(); } T height() const { return max.y() - min.y(); } // Do not try to get intersection between AABBs unless you know they collide AABB get_intersection_between_aabbs(const AABB& other) { return { cwise_vec_max(other.min, min), cwise_vec_min(other.max, max) }; } void include_aabb_in_bounds(const AABB& other) { min = cwise_vec_min(other.min, min); max = cwise_vec_max(other.max, max); } void include_point_in_bounds(const Vector& p) { min = cwise_vec_min(p, min); max = cwise_vec_max(p, max); } bool operator==(const AABB& other) const { return min == other.min && max == other.max; } bool fully_contains_aabb(const AABB& other) const { return other.min.x() >= min.x() && other.min.y() >= min.y() && other.max.x() <= max.x() && other.max.y() <= max.y(); } template AABB transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) const { Vector aabb1 = transformFunc(min); Vector aabb2 = transformFunc(max); Vector aabb3 = transformFunc(top_right()); Vector aabb4 = transformFunc(bottom_left()); AABB toRet{aabb1, aabb1}; toRet.include_point_in_bounds(aabb1); toRet.include_point_in_bounds(aabb2); toRet.include_point_in_bounds(aabb3); toRet.include_point_in_bounds(aabb4); return toRet; } template AABB cast() { AABB toRet{min.template cast(), max.template cast()}; return toRet; } }; template class Circle { public: typedef T UnderlyingType; Circle() = default; Circle(const Vector& initPos, T initRadius): pos(initPos), radius(initRadius), bounds(pos - Vector{radius, radius}, pos + Vector{radius, radius}) {} Vector pos; T radius; AABB bounds; template Circle transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) const { Circle newCirc = Circle(); newCirc.pos = transformFunc(pos); newCirc.radius = transformLinearFunc(radius); newCirc.bounds = AABB(newCirc.pos - Vector{newCirc.radius, newCirc.radius}, newCirc.pos + Vector{newCirc.radius, newCirc.radius}); return newCirc; } template Circle cast() { Circle toRet; toRet.pos = pos.template cast(); toRet.radius = static_cast(radius); toRet.bounds = bounds.template cast(); return toRet; } }; template class Triangle { public: typedef T UnderlyingType; Triangle() = default; Triangle(const std::array, 3>& initP): p(initP), bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2])) {} Triangle(const Vector& p1, const Vector& p2, const Vector& p3): p({p1, p2, p3}), bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2])) {} template Triangle transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) const { Triangle toRet = Triangle(); toRet.p[0] = transformFunc(p[0]); toRet.p[1] = transformFunc(p[1]); toRet.p[2] = transformFunc(p[2]); toRet.bounds = AABB(cwise_vec_min(cwise_vec_min(toRet.p[0], toRet.p[1]), toRet.p[2]), cwise_vec_max(cwise_vec_max(toRet.p[0], toRet.p[1]), toRet.p[2])); return toRet; } template Triangle cast() { Triangle toRet; toRet.p[0] = p[0].template cast(); toRet.p[1] = p[1].template cast(); toRet.p[2] = p[2].template cast(); toRet.bounds = bounds.template cast(); return toRet; } std::array, 3> p; AABB bounds; }; template struct ColliderCollection { typedef T UnderlyingType; AABB bounds; std::vector> aabb; std::vector> circle; std::vector> triangle; void clear() { aabb.clear(); circle.clear(); triangle.clear(); bounds = AABB({0, 0}, {0, 0}); } size_t size() const { return aabb.size() + circle.size() + triangle.size(); } bool empty() const { return aabb.empty() && circle.empty() && triangle.empty(); } template void bound_limit(const ColliderList& list) { for(auto& o : list) bounds.include_aabb_in_bounds(get_bounds(o)); } void recalculate_bounds() { bounds = AABB(); if(!aabb.empty()) bounds = get_bounds(aabb.front()); else if(!circle.empty()) bounds = get_bounds(circle.front()); else if(!triangle.empty()) bounds = get_bounds(triangle.front()); bound_limit(aabb); bound_limit(circle); bound_limit(triangle); } template ColliderCollection transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) const { ColliderCollection toRet; for(auto& o : aabb) toRet.aabb.emplace_back(o.transform(transformFunc, transformLinearFunc)); for(auto& o : circle) toRet.circle.emplace_back(o.transform(transformFunc, transformLinearFunc)); for(auto& o : triangle) toRet.triangle.emplace_back(o.transform(transformFunc, transformLinearFunc)); toRet.recalculate_bounds(); return toRet; } }; template std::ostream& operator<<(std::ostream& os, const AABB& a) { return os << "min: [" << a.min.x() << " " << a.min.y() << "]\n" << "max: [" << a.max.x() << " " << a.max.y() << "]\n"; } template const AABB& get_bounds(const Circle& c) { return c.bounds; } template const AABB& get_bounds(const Triangle& c) { return c.bounds; } template const AABB& get_bounds(const AABB& a) { return a; } template const AABB& get_bounds(const ColliderCollection& c) { return c.bounds; } template void generate_wide_line(ColliderCollection& collection, const Vector& p1, const Vector& p2, T width, bool circleCaps) { if(p1 != p2) { Vector dir = p1 - p2; Vector perpVec = perpendicular_vec2(dir); perpVec.normalize(); Vector perpWidth = (perpVec * width * T(0.5)).eval(); collection.triangle.emplace_back(Triangle{p1 + perpWidth, p1 - perpWidth, p2 + perpWidth}); collection.triangle.emplace_back(Triangle{p2 + perpWidth, p2 - perpWidth, p1 - perpWidth}); } if(circleCaps) { collection.circle.emplace_back(Circle{p1, width * T(0.5)}); if(p1 != p2) collection.circle.emplace_back(Circle{p2, width * T(0.5)}); } collection.recalculate_bounds(); } template Vector two_dim_vec_slerp(const Vector& v1, const Vector& v2) { Vector v1Norm = v1.normalized(); Vector v2Norm = v2.normalized(); Vector avg = (v1Norm + v2Norm) * 0.5; if(std::abs(avg.x()) < 0.00001 && std::abs(avg.y()) < 0.00001) return perpendicular_vec2(v1Norm).normalized(); else return avg.normalized(); } template void generate_polyline(ColliderCollection& collection, const std::vector>& points, T width, bool closePath) { if(points.size() < 2) { return; } Vector perpPrev = perpendicular_vec2((points[1] - points.front()).eval()); perpPrev.normalize(); std::array, 3> vertArray; unsigned newIndex = 0; if(!closePath || points.size() == 2) { vertArray[0] = points.front() + perpPrev * width * 0.5; vertArray[1] = points.front() - perpPrev * width * 0.5; newIndex = 2; } if(points.size() == 2) { vertArray[newIndex] = points.back() + perpPrev * width * 0.5; collection.triangle.emplace_back(Triangle{vertArray}); newIndex = (newIndex + 1) % 3; vertArray[newIndex] = points.back() - perpPrev * width * 0.5; collection.triangle.emplace_back(Triangle{vertArray}); return; } for(size_t x = 0; x <= points.size(); x++) { size_t i = x; size_t iP1 = x + 1; size_t iM1 = x - 1; if(x == 0) { if(!closePath) continue; else iM1 = points.size() - 1; } else if(x == points.size() - 1) { if(!closePath) continue; else { iP1 = 0; } } else if(x == points.size()) { if(!closePath) continue; else { i = 0; iM1 = points.size() - 1; iP1 = 1; } } Vector v1Norm = (points[iM1] - points[i]).normalized(); Vector v2Norm = (points[iP1] - points[i]).normalized(); Vector perp = two_dim_vec_slerp(v1Norm, v2Norm); if(perp.dot(perpPrev) <= 0.0) perp = -perp; T angle1 = std::acos((perp.dot(v1Norm) < 0 ? -perp : perp).dot(v1Norm)); T angle2 = std::acos((perp.dot(v2Norm) < 0 ? -perp : perp).dot(v2Norm)); T sinAngle1 = std::sin(angle1 + angle2); T alteredWidth = std::lerp(0.5, 0.707, sinAngle1); vertArray[newIndex] = points[i] + perp * alteredWidth * width; newIndex = (newIndex + 1) % 3; if(x != 0) collection.triangle.emplace_back(Triangle{vertArray}); vertArray[newIndex] = points[i] - perp * alteredWidth * width; newIndex = (newIndex + 1) % 3; if(x != 0) collection.triangle.emplace_back(Triangle{vertArray}); perpPrev = perp; } if(!closePath) { Vector perp = perpendicular_vec2((points[points.size() - 2] - points.back()).eval()); perp.normalize(); if(perp.dot(perpPrev) <= 0.0) perp = -perp; vertArray[newIndex] = points.back() + perp * width * 0.5; newIndex = (newIndex + 1) % 3; collection.triangle.emplace_back(Triangle{vertArray}); vertArray[newIndex] = points.back() - perp * width * 0.5; newIndex = (newIndex + 1) % 3; collection.triangle.emplace_back(Triangle{vertArray}); } } // https://blackpawn.com/texts/pointinpoly/default.html template bool collide(const Vector& p, const Triangle& t) { Vector v0 = t.p[2] - t.p[0]; Vector v1 = t.p[1] - t.p[0]; Vector v2 = p - t.p[0]; T dot00 = v0.dot(v0); T dot01 = v0.dot(v1); T dot02 = v0.dot(v2); T dot11 = v1.dot(v1); T dot12 = v1.dot(v2); T invDenom = T(1) / (dot00 * dot11 - dot01 * dot01); T u = (dot11 * dot02 - dot01 * dot12) * invDenom; T v = (dot00 * dot12 - dot01 * dot02) * invDenom; return (u >= T(0)) && (v >= T(0)) && (u + v < T(1)); } template bool collide(const Triangle& t, const Vector& p) { return collide(p, t); } template bool collide(const Vector& p, const AABB& a) { return (p.x() > a.min.x() && p.x() < a.max.x() && p.y() > a.min.y() && p.y() < a.max.y()); } template bool collide(const AABB& a, const Vector& p) { return collide(p, a); } template bool collide(const Vector& p, const Circle& a) { return vec_distance(p, a.pos) <= a.radius; } template bool collide(const Circle& a, const Vector& p) { return collide(p, a); } template bool collide(const Circle& c1, const Circle& c2) { return vec_distance(c1.pos, c2.pos) <= (c1.radius + c2.radius); } // https://gamedev.stackexchange.com/a/178154 template bool collide(const Circle& c, const AABB& a) { if(!collide(get_bounds(c), a)) return false; Vector aabbCenter = a.center(); Vector aabbBounds = aabbCenter - a.min; Vector distance = c.pos - aabbCenter; Vector clampDst = cwise_vec_clamp(distance, (-aabbBounds).eval(), aabbBounds); Vector closestPoint = aabbCenter + clampDst; return vec_distance(closestPoint, c.pos) <= c.radius; } template bool collide(const AABB& a, const Circle& c) { return collide(c, a); } template bool collide(const AABB& a1, const AABB& a2) { return a1.min.x() < a2.max.x() && a1.max.x() > a2.min.x() && a1.min.y() < a2.max.y() && a1.max.y() > a2.min.y(); } template bool collide(const Circle& c, const Triangle& t) { if(!collide(get_bounds(c), get_bounds(t))) return false; return collide(t.p[0], c) || collide(t.p[1], c) || collide(t.p[2], c) || collide(c.pos, t) || collision_circle_line_segment(c.pos, c.radius, t.p[0], t.p[1]) || collision_circle_line_segment(c.pos, c.radius, t.p[1], t.p[2]) || collision_circle_line_segment(c.pos, c.radius, t.p[2], t.p[0]); } template bool collide(const Triangle& t, const Circle& c) { return collide(c, t); } template bool collide(const AABB& a, const Triangle& t) { if(!collide(a, get_bounds(t))) return false; return collide(t.p[0], a) || collide(t.p[1], a) || collide(t.p[2], a) || collide(a.min, t) || collide(a.max, t) || collide(a.bottom_left(), t) || collide(a.top_right(), t) || is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.top_right()) || is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.bottom_left()) || is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.top_right()) || is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.bottom_left()) || is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.top_right()) || is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.bottom_left()) || is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.top_right()) || is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.bottom_left()) || is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.top_right()) || is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.bottom_left()) || is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.top_right()) || is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.bottom_left()); } template bool collide(const Triangle& t, const AABB& a) { return collide(a, t); } template bool collide(const Triangle& t1, const Triangle& t2) { //return collide(get_bounds(t1), get_bounds(t2)); if(!collide(get_bounds(t1), get_bounds(t2))) return false; return collide(t1.p[0], t2) || collide(t1.p[1], t2) || collide(t1.p[2], t2) || collide(t2.p[0], t1) || collide(t2.p[1], t1) || collide(t2.p[2], t1) || is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[0], t2.p[1]) || is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[1], t2.p[2]) || is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[2], t2.p[0]) || is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[0], t2.p[1]) || is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[1], t2.p[2]) || is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[2], t2.p[0]) || is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[0], t2.p[1]) || is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[1], t2.p[2]) || is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[2], t2.p[0]); } template bool collide(const Collection& collection, const Collider& c) { for(auto& o : collection.circle) if(collide(c, o)) return true; for(auto& o : collection.aabb) if(collide(c, o)) return true; for(auto& o : collection.triangle) if(collide(c, o)) return true; return false; } template class BVHContainer { public: typedef T UnderlyingType; std::vector> children; ColliderCollection objects; // NOTE: BVH TRANSFORMATION LEADS TO A BROKEN BVH //void transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) { // objects.transform(transformFunc, transformLinearFunc); // for(auto& a : children) // a.transform(transformFunc, transformLinearFunc); //} void clear() { children.clear(); objects.clear(); } bool empty() const { return children.empty() && objects.empty(); } unsigned assign_quad_to_point(const Vector& point, const Vector& origin) const { bool negativeX = point.x() <= origin.x(); bool negativeY = point.y() <= origin.y(); if(negativeX && negativeY) return 0; else if(negativeX && !negativeY) return 1; else if(!negativeX && !negativeY) return 2; else return 3; } void calculate_bvh_recursive(ColliderCollection& bvhObjects, int maxDepth = -1) { bvhObjects.recalculate_bounds(); objects.bounds = bvhObjects.bounds; if(bvhObjects.empty()) { return; } if(bvhObjects.size() <= 2 || maxDepth == 0) { objects = bvhObjects; return; } ColliderCollection parts[4]; Vector halfwayPoint = bvhObjects.bounds.center(); for(auto& o : bvhObjects.aabb) parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].aabb.emplace_back(o); for(auto& o : bvhObjects.circle) parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].circle.emplace_back(o); for(auto& o : bvhObjects.triangle) parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].triangle.emplace_back(o); for(size_t i = 0; i < 4; i++) { if(parts[i].empty()) continue; else if(parts[i].size() == bvhObjects.size()) { objects = bvhObjects; break; } else { children.emplace_back(); children.back().calculate_bvh_recursive(parts[i], maxDepth - 1); } } } template bool is_collide(const Collider& collider) const { if(!collide(get_bounds(collider), get_bounds(objects))) return false; if(collide(objects, collider)) return true; for(const auto& c : children) if(c.is_collide(collider)) return true; return false; } template bool is_collide_other_bvh(const BVH& bvh) const { if(!collide(get_bounds(objects), get_bounds(bvh.objects))) return false; if(is_collide(bvh.objects)) return true; if(bvh.is_collide(objects)) return true; for(const auto& c : children) { for(const auto& d : bvh.children) { if(c.is_collide_other_bvh(d)) return true; } } return false; } }; };