PrusaSlicer-NonPlainar/tests/libslic3r/test_geometry.cpp
Vojtech Bubnik cc44089440 New BuildVolume class was created, which detects build volume type (rectangular,
circular, convex, concave) and performs efficient collision detection agains these build
volumes. As of now, collision detection is performed against a convex
hull of a concave build volume for efficency.

GCodeProcessor::Result renamed out of GCodeProcessor to GCodeProcessorResult,
so it could be forward declared.

Plater newly exports BuildVolume, not Bed3D. Bed3D is a rendering class,
while BuildVolume is a purely geometric class.

Reduced usage of global wxGetApp, the Bed3D is passed as a parameter
to View3D/Preview/GLCanvas.

Convex hull code was extracted from Geometry.cpp/hpp to Geometry/ConvexHulll.cpp,hpp.
New test inside_convex_polygon().
New efficent point inside polygon test: Decompose convex hull
to bottom / top parts and use the decomposition to detect point inside
a convex polygon in O(log n). decompose_convex_polygon_top_bottom(),
inside_convex_polygon().

New Circle constructing functions: circle_ransac() and circle_taubin_newton().

New polygon_is_convex() test with unit tests.
2021-11-16 10:15:51 +01:00

711 lines
26 KiB
C++

#include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ShortestPath.hpp"
//#include <random>
//#include "libnest2d/tools/benchmark.h"
#include "libslic3r/SVG.hpp"
#include "../libnest2d/printer_parts.hpp"
#include <unordered_set>
using namespace Slic3r;
TEST_CASE("Line::parallel_to", "[Geometry]"){
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 200000, 0 }, { 0, 0 } };
REQUIRE(l.parallel_to(l));
REQUIRE(l.parallel_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.parallel_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 20000, 0 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.parallel_to(l3));
REQUIRE(!l.parallel_to(l4));
}
TEST_CASE("Line::perpendicular_to", "[Geometry]") {
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 0, 200000 }, { 0, 0 } };
REQUIRE(! l.perpendicular_to(l));
REQUIRE(l.perpendicular_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.perpendicular_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 0, 20000 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.perpendicular_to(l3));
REQUIRE(! l.perpendicular_to(l4));
}
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(std::vector<Point>({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
}));
Point point(95706562, -57294774);
REQUIRE(polygon.contains(point));
}
SCENARIO("Intersections of line segments", "[Geometry]"){
GIVEN("Integer coordinates"){
Line line1(Point(5,15),Point(30,15));
Line line2(Point(10,20), Point(10,10));
THEN("The intersection is valid"){
Point point;
line1.intersection(line2,&point);
REQUIRE(Point(10,15) == point);
}
}
GIVEN("Scaled coordinates"){
Line line1(Point(73.6310778185108 / 0.00001, 371.74239268924 / 0.00001), Point(73.6310778185108 / 0.00001, 501.74239268924 / 0.00001));
Line line2(Point(75/0.00001, 437.9853/0.00001), Point(62.7484/0.00001, 440.4223/0.00001));
THEN("There is still an intersection"){
Point point;
REQUIRE(line1.intersection(line2,&point));
}
}
}
SCENARIO("polygon_is_convex works") {
GIVEN("A square of dimension 10") {
WHEN("Polygon is convex clockwise") {
Polygon cw_square { { {0, 0}, {0,10}, {10,10}, {10,0} } };
THEN("it is not convex") {
REQUIRE(! polygon_is_convex(cw_square));
}
}
WHEN("Polygon is convex counter-clockwise") {
Polygon ccw_square { { {0, 0}, {10,0}, {10,10}, {0,10} } };
THEN("it is convex") {
REQUIRE(polygon_is_convex(ccw_square));
}
}
}
GIVEN("A concave polygon") {
Polygon concave = { {0,0}, {10,0}, {10,10}, {0,10}, {0,6}, {4,6}, {4,4}, {0,4} };
THEN("It is not convex") {
REQUIRE(! polygon_is_convex(concave));
}
}
}
TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
Slic3r::Polyline polyline;
polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
REQUIRE(polyline.lines().at(0).a == Point(0,0));
REQUIRE(polyline.lines().at(0).b == Point(10,0));
REQUIRE(polyline.lines().at(1).a == Point(10,0));
REQUIRE(polyline.lines().at(1).b == Point(20,0));
}
TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
Slic3r::Polygon polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
Slic3r::Polyline split = polygon.split_at_index(1);
REQUIRE(split.points[0]==Point(10,0));
REQUIRE(split.points[1]==Point(5,5));
REQUIRE(split.points[2]==Point(0,0));
REQUIRE(split.points[3]==Point(10,0));
}
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
bb.scale(2);
REQUIRE(bb.min == Point(0,2));
REQUIRE(bb.max == Point(40,4));
}
TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
Slic3r::Polygon area = offset(tmp,5).at(0);
REQUIRE(area.area() == Slic3r::Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
}
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-6, 0);
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-3, 9);
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524),
Vec2d(0, 6.0),
Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
Point expected_center { Point::new_scale(-3, 9)};
Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
Point::new_scale(0, 6.0),
Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Point result_center(0,0);
result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center));
}
}
}
}
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
// Some random points in plane.
Points pts {
{ 89243, 4359 }, { 763465, 59687 }, { 3245, 734987 }, { 2459867, 987634 }, { 759866, 67843982 }, { 9754687, 9834658 }, { 87235089, 743984373 },
{ 65874456, 2987546 }, { 98234524, 657654873 }, { 786243598, 287934765 }, { 824356, 734265 }, { 82576449, 7864534 }, { 7826345, 3984765 }
};
const auto c = Slic3r::Geometry::smallest_enclosing_circle_welzl(pts);
// The radius returned is inflated by SCALED_EPSILON, thus all points should be inside.
bool all_inside = std::all_of(pts.begin(), pts.end(), [c](const Point &pt){ return c.contains(pt.cast<double>()); });
auto c2(c);
c2.radius -= SCALED_EPSILON * 2.1;
auto num_on_boundary = std::count_if(pts.begin(), pts.end(), [c2](const Point& pt) { return ! c2.contains(pt.cast<double>(), SCALED_EPSILON); });
REQUIRE(all_inside);
REQUIRE(num_on_boundary == 3);
}
SCENARIO("Path chaining", "[Geometry]") {
GIVEN("A path") {
std::vector<Point> points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
THEN("Chained with no diagonals (thus 26 units long)") {
std::vector<Points::size_type> indices = chain_points(points);
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
REQUIRE(std::abs(dist-26) <= EPSILON);
}
}
}
GIVEN("Gyroid infill end points") {
Polylines polylines = {
{ {28122608, 3221037}, {27919139, 56036027} },
{ {33642863, 3400772}, {30875220, 56450360} },
{ {34579315, 3599827}, {35049758, 55971572} },
{ {26483070, 3374004}, {23971830, 55763598} },
{ {38931405, 4678879}, {38740053, 55077714} },
{ {20311895, 5015778}, {20079051, 54551952} },
{ {16463068, 6773342}, {18823514, 53992958} },
{ {44433771, 7424951}, {42629462, 53346059} },
{ {15697614, 7329492}, {15350896, 52089991} },
{ {48085792, 10147132}, {46435427, 50792118} },
{ {48828819, 10972330}, {49126582, 48368374} },
{ {9654526, 12656711}, {10264020, 47691584} },
{ {5726905, 18648632}, {8070762, 45082416} },
{ {54818187, 39579970}, {52974912, 43271272} },
{ {4464342, 37371742}, {5027890, 39106220} },
{ {54139746, 18417661}, {55177987, 38472580} },
{ {56527590, 32058461}, {56316456, 34067185} },
{ {3303988, 29215290}, {3569863, 32985633} },
{ {56255666, 25025857}, {56478310, 27144087} },
{ {4300034, 22805361}, {3667946, 25752601} },
{ {8266122, 14250611}, {6244813, 17751595} },
{ {12177955, 9886741}, {10703348, 11491900} }
};
Polylines chained = chain_polylines(polylines);
THEN("Chained taking the shortest path") {
double connection_length = 0.;
for (size_t i = 1; i < chained.size(); ++i) {
const Polyline &pl1 = chained[i - 1];
const Polyline &pl2 = chained[i];
connection_length += (pl2.first_point() - pl1.last_point()).cast<double>().norm();
}
REQUIRE(connection_length < 85206000.);
}
}
GIVEN("Loop pieces") {
Point a { 2185796, 19058485 };
Point b { 3957902, 18149382 };
Point c { 2912841, 18790564 };
Point d { 2831848, 18832390 };
Point e { 3179601, 18627769 };
Point f { 3137952, 18653370 };
Polylines polylines = { { a, b },
{ c, d },
{ e, f },
{ d, a },
{ f, c },
{ b, e } };
Polylines chained = chain_polylines(polylines, &a);
THEN("Connected without a gap") {
for (size_t i = 0; i < chained.size(); ++i) {
const Polyline &pl1 = (i == 0) ? chained.back() : chained[i - 1];
const Polyline &pl2 = chained[i];
REQUIRE(pl1.points.back() == pl2.points.front());
}
}
}
}
SCENARIO("Line distances", "[Geometry]"){
GIVEN("A line"){
Line line(Point(0, 0), Point(20, 0));
THEN("Points on the line segment have 0 distance"){
REQUIRE(line.distance_to(Point(0, 0)) == 0);
REQUIRE(line.distance_to(Point(20, 0)) == 0);
REQUIRE(line.distance_to(Point(10, 0)) == 0);
}
THEN("Points off the line have the appropriate distance"){
REQUIRE(line.distance_to(Point(10, 10)) == 10);
REQUIRE(line.distance_to(Point(50, 0)) == 30);
}
}
}
SCENARIO("Polygon convex/concave detection", "[Geometry]"){
GIVEN(("A Square with dimension 100")){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
Point(100,100),
Point(200,100),
Point(200,200),
Point(100,200)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
THEN("It has 4 concave points clockwise"){
square.make_clockwise();
REQUIRE(square.concave_points(PI*4/3).size() == 4);
REQUIRE(square.convex_points(PI*2/3).size() == 0);
}
}
GIVEN("A Square with an extra colinearvertex"){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
Point(150,100),
Point(200,100),
Point(200,200),
Point(100,200),
Point(100,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A Square with an extra collinear vertex in different order"){
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
Point(200,200),
Point(100,200),
Point(100,100),
Point(150,100),
Point(200,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A triangle"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(31286371,461008)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A triangle with an extra collinear point"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(20000000,461012),
Point(31286371,461012)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
// if epsilon is not used.
auto polygon = Slic3r::Polygon(std::vector<Point>({
Point(60246458,14802768),Point(64477191,12360001),
Point(63727343,11060995),Point(64086449,10853608),
Point(66393722,14850069),Point(66034704,15057334),
Point(65284646,13758387),Point(61053864,16200839),
Point(69200258,30310849),Point(62172547,42483120),
Point(61137680,41850279),Point(67799985,30310848),
Point(51399866,1905506),Point(38092663,1905506),
Point(38092663,692699),Point(52100125,692699)
}));
THEN("the correct number of points are detected"){
REQUIRE(polygon.concave_points(PI*4/3).size() == 6);
REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
}
}
}
TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
auto triangle = Slic3r::Polygon(std::vector<Point>({
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
}));
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
}
SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
GIVEN(("square")){
Slic3r::Points points { { 100, 100 }, {100, 200 }, { 200, 200 }, { 200, 100 }, { 150, 150 } };
Slic3r::Polygon hull = Slic3r::Geometry::convex_hull(points);
SECTION("convex hull returns the correct number of points") { REQUIRE(hull.points.size() == 4); }
}
SECTION("arrange returns expected number of positions") {
Pointfs positions;
Slic3r::Geometry::arrange(4, Vec2d(20, 20), 5, nullptr, positions);
REQUIRE(positions.size() == 4);
}
SECTION("directions_parallel") {
REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, 0));
REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, 0));
REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, M_PI / 180));
REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, M_PI / 180));
REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, M_PI, 0));
REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
}
}
TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(20 / SCALING_FACTOR, 0);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 0);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 10 / SCALING_FACTOR);
SVG svg{std::string("one_vertex_touch") + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
//// Only for benchmarking
//static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
//{
// std::uniform_int_distribution<coord_t> dist(0, 100);
// Polygon out;
// out.points.reserve(point_cnt);
// coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
// for (size_t i = 0; i < point_cnt; ++i)
// out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
// tr + dist(rg) / SCALING_FACTOR);
// return Geometry::convex_hull(out.points);
//}
//TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
// constexpr size_t TEST_CNT = 1000;
// constexpr size_t POINT_CNT = 1000;
// auto seed = std::random_device{}();
//// unsigned long seed = 2525634386;
// std::mt19937_64 rg{seed};
// Benchmark bench;
// auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
// auto results = reserve_vector<bool>(TEST_CNT);
// auto expects = reserve_vector<bool>(TEST_CNT);
// for (size_t i = 0; i < TEST_CNT; ++i) {
// tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
// }
// bench.start();
// for (const auto &test : tests)
// results.emplace_back(Geometry::convex_polygons_intersect(test.first, test.second));
// bench.stop();
// std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
// bench.start();
// for (const auto &test : tests)
// expects.emplace_back(!intersection(test.first, test.second).empty());
// bench.stop();
// std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
// REQUIRE(results.size() == expects.size());
// auto seedstr = std::to_string(seed);
// for (size_t i = 0; i < results.size(); ++i) {
// // std::cout << expects[i] << " ";
// if (results[i] != expects[i]) {
// SVG svg{std::string("fail_seed") + seedstr + "_" + std::to_string(i) + ".svg"};
// svg.draw(tests[i].first, "blue");
// svg.draw(tests[i].second, "green");
// svg.Close();
// // std::cout << std::endl;
// }
// REQUIRE(results[i] == expects[i]);
// }
// std::cout << std::endl;
//}
struct Pair
{
size_t first, second;
bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
};
template<> struct std::hash<Pair> {
size_t operator()(const Pair &c) const
{
return c.first * PRINTER_PART_POLYGONS.size() + c.second;
}
};
TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") {
// Overlap of the same polygon should always be an intersection
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
Polygon P = PRINTER_PART_POLYGONS[i];
P = Geometry::convex_hull(P.points);
bool res = Geometry::convex_polygons_intersect(P, P);
if (!res) {
SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"};
svg.draw(P, "green");
svg.Close();
}
REQUIRE(res == true);
}
std::unordered_set<Pair> combos;
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) {
if (i != j) {
size_t a = std::min(i, j), b = std::max(i, j);
combos.insert(Pair{a, b});
}
}
}
// All disjoint
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
B.translate(bba.size() + bbb.size());
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
// All intersecting
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
}