Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions data/tests/clean/clean_extreme_slopes_inner/0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"shape": {
"elements": [
{
"end": {
"x": 29.641303008447174,
"y": 48.08931284909132
},
"start": {
"x": 29.773738644876477,
"y": 12.823728609920455
},
"type": "LineSegment"
},
{
"end": {
"x": 29.640719144732877,
"y": 48.089310656461755
},
"start": {
"x": 29.641303008447174,
"y": 48.08931284909132
},
"type": "LineSegment"
},
{
"end": {
"x": 29.773738644876477,
"y": 12.823728609920455
},
"start": {
"x": 29.640719144732877,
"y": 48.089310656461755
},
"type": "LineSegment"
}
],
"is_path": false,
"type": "general"
},
"expected_output": [
]
}
94 changes: 80 additions & 14 deletions src/clean.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#include "shape/elements_intersections.hpp"
#include "shape/equalize.hpp"
#include "shape/boolean_operations.hpp"
#include "shape/shapes_intersections.hpp"
#ifdef CLEAN_ENABLE_DEBUG
#include "shape/writer.hpp"
#include <fstream>
#endif

//#include <iostream>
Expand Down Expand Up @@ -282,7 +284,8 @@ namespace
{

Shape clean_extreme_slopes_outer_1(
Shape shape_orig)
Shape shape_orig,
std::vector<Shape>& triangles_added)
{
Shape shape = shape_orig;

Expand Down Expand Up @@ -344,9 +347,8 @@ Shape clean_extreme_slopes_outer_1(
shape_new.elements.push_back(element_cur);
start = element_cur.end;
} else {
//std::cout << "remove" << std::endl;
//std::cout << "element_cur: " << element_cur.to_string() << std::endl;
//std::cout << "element_next: " << element_next.to_string() << std::endl;
triangles_added.push_back(build_triangle(
start, element_cur.end, element_next.end));
}
}
shape_new.elements[0].start = start;
Expand Down Expand Up @@ -414,9 +416,8 @@ Shape clean_extreme_slopes_outer_1(
shape_new.elements.push_back(element_cur);
end = element_cur.start;
} else {
//std::cout << "remove" << std::endl;
//std::cout << "element_cur: " << element_cur.to_string() << std::endl;
//std::cout << "element_prev: " << element_prev.to_string() << std::endl;
triangles_added.push_back(build_triangle(
element_prev.start, element_cur.start, end));
}
}
shape_new.elements[0].end = end;
Expand All @@ -428,7 +429,8 @@ Shape clean_extreme_slopes_outer_1(
}

Shape clean_extreme_slopes_inner_1(
const Shape& shape_orig)
const Shape& shape_orig,
std::vector<Shape>& triangles_removed)
{
Shape shape = shape_orig;

Expand Down Expand Up @@ -489,6 +491,9 @@ Shape clean_extreme_slopes_inner_1(
if (!remove) {
shape_new.elements.push_back(element_cur);
start = element_cur.end;
} else {
triangles_removed.push_back(build_triangle(
start, element_cur.end, element_next.end));
}
}
shape_new.elements[0].start = start;
Expand Down Expand Up @@ -552,6 +557,9 @@ Shape clean_extreme_slopes_inner_1(
if (!remove) {
shape_new.elements.push_back(element_cur);
end = element_cur.start;
} else {
triangles_removed.push_back(build_triangle(
element_prev.start, element_cur.start, end));
}
}
shape_new.elements[0].end = end;
Expand All @@ -563,7 +571,8 @@ Shape clean_extreme_slopes_inner_1(
}

Shape clean_extreme_slopes_outer_2(
const Shape& shape_orig)
const Shape& shape_orig,
std::vector<Shape>& triangles_added)
{
if (!shape_orig.check()) {
throw std::invalid_argument(
Expand Down Expand Up @@ -594,6 +603,8 @@ Shape clean_extreme_slopes_outer_2(
element_prev.start, element_prev.end,
{element_cur.end.x, element_cur.start.y}, element_cur.end);
if (p.first) {
triangles_added.push_back(build_triangle(
element_prev.start, element_cur.start, element_cur.end));
element_prev.end = p.second;
element_cur.start = p.second;
}
Expand All @@ -608,6 +619,8 @@ Shape clean_extreme_slopes_outer_2(
element_prev.start, element_prev.end,
{element_cur.start.x, element_cur.end.y}, element_cur.end);
if (p.first) {
triangles_added.push_back(build_triangle(
element_prev.start, element_cur.start, element_cur.end));
element_prev.end = p.second;
element_cur.start = p.second;
}
Expand Down Expand Up @@ -637,6 +650,8 @@ Shape clean_extreme_slopes_outer_2(
element_cur.start, {element_cur.start.x, element_cur.end.y},
element_next.start, element_next.end);
if (p.first) {
triangles_added.push_back(build_triangle(
element_cur.start, element_cur.end, element_next.end));
element_cur.end = p.second;
element_next.start = p.second;
}
Expand All @@ -651,6 +666,8 @@ Shape clean_extreme_slopes_outer_2(
element_cur.start, {element_cur.end.x, element_cur.start.y},
element_next.start, element_next.end);
if (p.first) {
triangles_added.push_back(build_triangle(
element_cur.start, element_cur.end, element_next.end));
element_cur.end = p.second;
element_next.start = p.second;
}
Expand All @@ -664,7 +681,8 @@ Shape clean_extreme_slopes_outer_2(
}

Shape clean_extreme_slopes_inner_2(
const Shape& shape_orig)
const Shape& shape_orig,
std::vector<Shape>& triangles_removed)
{
Shape shape = shape_orig;
ElementPos element_prev_pos = shape.elements.size() - 1;
Expand All @@ -691,6 +709,8 @@ Shape clean_extreme_slopes_inner_2(
element_prev.start, element_prev.end,
{element_cur.end.x, element_cur.start.y}, element_cur.end);
if (p.first) {
triangles_removed.push_back(build_triangle(
element_prev.start, element_cur.start, element_cur.end));
element_prev.end = p.second;
element_cur.start = p.second;
}
Expand All @@ -705,6 +725,8 @@ Shape clean_extreme_slopes_inner_2(
element_prev.start, element_prev.end,
{element_cur.start.x, element_cur.end.y}, element_cur.end);
if (p.first) {
triangles_removed.push_back(build_triangle(
element_prev.start, element_cur.start, element_cur.end));
element_prev.end = p.second;
element_cur.start = p.second;
}
Expand Down Expand Up @@ -737,6 +759,8 @@ Shape clean_extreme_slopes_inner_2(
element_cur.start, {element_cur.start.x, element_cur.end.y},
element_next.start, element_next.end);
if (p.first) {
triangles_removed.push_back(build_triangle(
element_cur.start, element_cur.end, element_next.end));
element_cur.end = p.second;
element_next.start = p.second;
}
Expand All @@ -751,6 +775,8 @@ Shape clean_extreme_slopes_inner_2(
element_cur.start, {element_cur.end.x, element_cur.start.y},
element_next.start, element_next.end);
if (p.first) {
triangles_removed.push_back(build_triangle(
element_cur.start, element_cur.end, element_next.end));
element_cur.end = p.second;
element_next.start = p.second;
}
Expand All @@ -775,23 +801,38 @@ ShapeWithHoles shape::clean_extreme_slopes_outer(
}

Shape shape = shape_orig;
std::vector<Shape> triangles_added;

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

shape = clean_extreme_slopes_outer_1(shape);
shape = clean_extreme_slopes_outer_1(shape, triangles_added);

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

shape = clean_extreme_slopes_outer_2(shape);
shape = clean_extreme_slopes_outer_2(shape, triangles_added);

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

if (!triangles_added.empty() && intersect(shape)) {
// The naive in-place mutations performed by clean_extreme_slopes_outer_1
// and clean_extreme_slopes_outer_2 can produce a self-intersecting
// shape. When that happens, fall back to a robust reconstruction:
// union the corner triangles identified by those two passes with the
// original shape, the same approach used by simplify() to recover
// from invalid in-place simplifications.
std::vector<ShapeWithHoles> shapes_to_union = {{shape_orig}};
for (const Shape& triangle: triangles_added)
shapes_to_union.push_back({triangle});
std::vector<ShapeWithHoles> u = compute_union(shapes_to_union);
return u.front();
}

if (!shape.check()) {
throw std::invalid_argument(
FUNC_SIGNATURE + ": "
Expand All @@ -810,26 +851,51 @@ std::vector<Shape> shape::clean_extreme_slopes_inner(
}

Shape shape = shape_orig;
std::vector<Shape> triangles_removed;

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

shape = clean_extreme_slopes_inner_1(shape);
shape = clean_extreme_slopes_inner_1(shape, triangles_removed);

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

shape = clean_extreme_slopes_inner_2(shape);
shape = clean_extreme_slopes_inner_2(shape, triangles_removed);

shape = equalize_shape(shape);
shape = remove_redundant_vertices(shape).second;
shape = remove_aligned_vertices(shape).second;

if (!triangles_removed.empty() && intersect(shape)) {
// The naive in-place mutations performed by clean_extreme_slopes_inner_1
// and clean_extreme_slopes_inner_2 can produce a self-intersecting
// shape. When that happens, fall back to a robust reconstruction: cut
// the corner triangles identified by those two passes directly out of
// the original shape using a boolean difference, the same approach
// used by simplify() to recover from invalid in-place simplifications.
std::vector<ShapeWithHoles> triangles_removed_swh;
for (const Shape& triangle: triangles_removed)
triangles_removed_swh.push_back({triangle});
std::vector<ShapeWithHoles> difference = compute_difference(
{{shape_orig}},
triangles_removed_swh);

std::vector<Shape> output;
for (const ShapeWithHoles& shape_with_holes: difference)
output.push_back(shape_with_holes.shape);
return output;
}

if (!shape.check()) {
#ifdef CLEAN_ENABLE_DEBUG
Writer().add_shape(shape_orig).add_shape(shape).write_json("tmp.json");
std::ofstream file{"clean_extreme_slopes_inner_inputs.json"};
nlohmann::json json;
json["shape"] = shape_orig.to_json();
file << std::setw(4) << json << std::endl;
#endif
throw std::invalid_argument(
FUNC_SIGNATURE + ": "
Expand Down
79 changes: 79 additions & 0 deletions test/clean_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,85 @@ INSTANTIATE_TEST_SUITE_P(
});


struct CleanExtremeSlopesInnerTestParams
{
std::string name;
Shape shape;
std::vector<Shape> expected_output;


static CleanExtremeSlopesInnerTestParams read_json(
const std::string& file_path)
{
std::ifstream file(file_path);
if (!file.good()) {
throw std::runtime_error(
FUNC_SIGNATURE + ": "
"unable to open file \"" + file_path + "\".");
}

nlohmann::json json;
file >> json;
CleanExtremeSlopesInnerTestParams test_params;
test_params.name = file_path;
test_params.shape = Shape::from_json(json["shape"]);
for (auto& json_shape: json["expected_output"].items())
test_params.expected_output.emplace_back(Shape::from_json(json_shape.value()));
return test_params;
}
};

void PrintTo(const CleanExtremeSlopesInnerTestParams& params, std::ostream* os)
{
*os << "shape " << params.shape.to_string(2) << "\n";
*os << "expected_output\n";
for (const Shape& shape: params.expected_output)
*os << shape.to_string(2) << "\n";
}

class CleanExtremeSlopesInnerTest: public testing::TestWithParam<CleanExtremeSlopesInnerTestParams> { };

TEST_P(CleanExtremeSlopesInnerTest, CleanExtremeSlopesInner)
{
CleanExtremeSlopesInnerTestParams test_params = GetParam();
PrintTo(test_params, &std::cout);

std::vector<Shape> output = clean_extreme_slopes_inner(test_params.shape);
std::cout << "output" << std::endl;
for (const Shape& shape: output)
std::cout << shape.to_string(2) << std::endl;

ASSERT_EQ(output.size(), test_params.expected_output.size());
for (const Shape& expected_shape: test_params.expected_output) {
EXPECT_NE(std::find_if(
output.begin(),
output.end(),
[&expected_shape](const Shape& shape) { return equal(shape, expected_shape); }),
output.end());
}
}

INSTANTIATE_TEST_SUITE_P(
Shape,
CleanExtremeSlopesInnerTest,
testing::ValuesIn(std::vector<CleanExtremeSlopesInnerTestParams>{
{
"Square",
build_shape({{0, 0}, {1, 0}, {1, 1}, {0, 1}}),
{build_shape({{0, 0}, {1, 0}, {1, 1}, {0, 1}})},
}, {
"NearVerticalSpike",
build_shape({{0, 0}, {10, -10}, {10.1, -110}, {20, 0}}),
{build_shape({{0, 0}, {10.1, -110}, {20, 0}})},
},
CleanExtremeSlopesInnerTestParams::read_json(
(fs::path("data") / "tests" / "clean" / "clean_extreme_slopes_inner" / "0.json").string()),
}),
[](const testing::TestParamInfo<CleanExtremeSlopesInnerTest::ParamType>& info) {
return fs::path(info.param.name).stem().string();
});


struct FixSelfIntersectionsTestParams
{
std::string name;
Expand Down
Loading