Skip to content

Commit 6ea89b0

Browse files
committed
Implement approximate_circular_arc_by_line_segments
1 parent efd59bd commit 6ea89b0

File tree

3 files changed

+242
-11
lines changed

3 files changed

+242
-11
lines changed

include/packingsolver/irregular/shape.hpp

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ struct Point
5050
*/
5151

5252
std::string to_string() const;
53-
54-
/*
55-
* Others
56-
*/
57-
58-
bool operator==(const Point& point) const { return x == point.x && y == point.y; }
5953
};
6054

6155
Point operator+(
@@ -153,6 +147,19 @@ struct ShapeElement
153147
std::string to_string() const;
154148
};
155149

150+
/**
151+
* Convert a shape element of type CircularArc into multiple shape elements
152+
* of type LineSegment.
153+
*/
154+
std::vector<ShapeElement> approximate_circular_arc_by_line_segments(
155+
const ShapeElement& circular_arc,
156+
ElementPos number_of_line_segments,
157+
bool outer);
158+
159+
////////////////////////////////////////////////////////////////////////////////
160+
//////////////////////////////////// Shape /////////////////////////////////////
161+
////////////////////////////////////////////////////////////////////////////////
162+
156163
enum class ShapeType
157164
{
158165
Circle,
@@ -260,7 +267,9 @@ struct BuildShapeElement
260267
* Build a right triangle where the hypotenuse is a drilled circular arc
261268
* build_shape({{0, 0}, {1, 0}, {0, 0, -1}, {1, 1}})
262269
*/
263-
Shape build_shape(const std::vector<BuildShapeElement>& points);
270+
Shape build_shape(
271+
const std::vector<BuildShapeElement>& points,
272+
bool path = false);
264273

265274
double compute_svg_factor(double width);
266275

@@ -287,13 +296,35 @@ std::pair<bool, Shape> equalize_close_y(
287296
Shape clean_shape(
288297
const Shape& shape);
289298

299+
inline bool operator==(
300+
const Point& point_1,
301+
const Point& point_2)
302+
{
303+
return (point_1.x == point_2.x) && (point_1.y == point_2.y);
304+
}
305+
306+
inline bool near(
307+
const Point& point_1,
308+
const Point& point_2)
309+
{
310+
return equal(point_1.x, point_2.x) && equal(point_1.y, point_2.y);
311+
}
312+
290313
bool operator==(
291314
const ShapeElement& element_1,
292315
const ShapeElement& element_2);
293316

317+
bool near(
318+
const ShapeElement& element_1,
319+
const ShapeElement& element_2);
320+
294321
bool operator==(
295322
const Shape& shape_1,
296323
const Shape& shape_2);
297324

325+
bool near(
326+
const Shape& shape_1,
327+
const Shape& shape_2);
328+
298329
}
299330
}

src/irregular/shape.cpp

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,95 @@ char irregular::element2char(ShapeElementType type)
249249
return ' ';
250250
}
251251

252+
std::vector<ShapeElement> irregular::approximate_circular_arc_by_line_segments(
253+
const ShapeElement& circular_arc,
254+
ElementPos number_of_line_segments,
255+
bool outer)
256+
{
257+
if (circular_arc.type != ShapeElementType::CircularArc) {
258+
throw std::runtime_error(
259+
"packingsolver::irregular::circular_arc_to_line_segments: "
260+
"input element must be of type CircularArc; "
261+
"circular_arc.type: " + element2str(circular_arc.type) + ".");
262+
}
263+
if (!outer && number_of_line_segments < 1) {
264+
throw std::runtime_error(
265+
"packingsolver::irregular::circular_arc_to_line_segments: "
266+
"at least 1 line segment is needed to inner approximate a circular arc; "
267+
"outer: " + std::to_string(outer) + "; "
268+
"number_of_line_segments: " + std::to_string(number_of_line_segments) + ".");
269+
}
270+
271+
Angle angle = angle_radian(
272+
circular_arc.start - circular_arc.center,
273+
circular_arc.end - circular_arc.center);
274+
if (outer) {
275+
if (angle < M_PI && number_of_line_segments < 2) {
276+
throw std::runtime_error(
277+
"packingsolver::irregular::circular_arc_to_line_segments: "
278+
"at least 2 line segments are needed to outer approximate a circular arc with an angle <= PI; "
279+
"outer: " + std::to_string(outer) + "; "
280+
"angle: " + std::to_string(angle) + "; "
281+
"number_of_line_segments: " + std::to_string(number_of_line_segments) + ".");
282+
} else if (angle >= M_PI && number_of_line_segments < 3) {
283+
throw std::runtime_error(
284+
"packingsolver::irregular::circular_arc_to_line_segments: "
285+
"at least 3 line segments are needed to outer approximate a circular arc with an angle >= PI; "
286+
"outer: " + std::to_string(outer) + "; "
287+
"angle: " + std::to_string(angle) + "; "
288+
"number_of_line_segments: " + std::to_string(number_of_line_segments) + ".");
289+
}
290+
}
291+
292+
std::vector<ShapeElement> line_segments;
293+
Length radius = distance(circular_arc.center, circular_arc.start);
294+
Point point_prev = circular_arc.start;
295+
Point point_circle_prev = circular_arc.start;
296+
for (ElementPos line_segment_id = 0;
297+
line_segment_id < number_of_line_segments - 1;
298+
++line_segment_id) {
299+
Angle angle_cur = (angle * (line_segment_id + 1)) / (number_of_line_segments - 1);
300+
Point point_circle;
301+
point_circle.x = circular_arc.center.x + radius * std::cos(angle_cur);
302+
point_circle.y = circular_arc.center.y + radius * std::sin(angle_cur);
303+
Point point_cur;
304+
if (!outer) {
305+
point_cur = point_circle;
306+
} else {
307+
// https://en.wikipedia.org/wiki/Tangent_lines_to_circles#Cartesian_equation
308+
// https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_lines
309+
// The tangent line of the circle at (x1, y1) has Cartesian equation
310+
// (x - x1)(x1 - xc) + (y - y1)(y1 - yc) = 0
311+
// (x1 - xc) * x + (y1 - yc) * y - x1 * (x1 - xc) - y1 * (y1 - yc) = 0
312+
// At (x2, y2)
313+
// (x2 - xc) * x + (y2 - yc) * y - x2 * (x2 - xc) - y2 * (y1 - yc) = 0
314+
LengthDbl a1 = (point_circle_prev.x - circular_arc.center.x);
315+
LengthDbl b1 = (point_circle_prev.y - circular_arc.center.y);
316+
LengthDbl c1 = point_circle_prev.x * (point_circle_prev.x - circular_arc.center.x)
317+
+ point_circle_prev.y * (point_circle_prev.y - circular_arc.center.y);
318+
LengthDbl a2 = (point_circle.x - circular_arc.center.x);
319+
LengthDbl b2 = (point_circle.y - circular_arc.center.y);
320+
LengthDbl c2 = point_circle.x * (point_circle.x - circular_arc.center.x)
321+
+ point_circle.y * (point_circle.y - circular_arc.center.y);
322+
point_cur.x = (c1 * b2 - c2 * b1) / (a1 * b2 - a2 * b1);
323+
point_cur.y = (a1 * c2 - a2 * c1) / (a1 * b2 - a2 * b1);
324+
}
325+
ShapeElement line_segment;
326+
line_segment.start = point_prev;
327+
line_segment.end = point_cur;
328+
line_segment.type = ShapeElementType::LineSegment;
329+
line_segments.push_back(line_segment);
330+
point_prev = point_cur;
331+
point_circle_prev = point_circle;
332+
}
333+
ShapeElement line_segment;
334+
line_segment.start = point_prev;
335+
line_segment.end = circular_arc.end;
336+
line_segment.type = ShapeElementType::LineSegment;
337+
line_segments.push_back(line_segment);
338+
return line_segments;
339+
}
340+
252341
////////////////////////////////////////////////////////////////////////////////
253342
//////////////////////////////////// Shape /////////////////////////////////////
254343
////////////////////////////////////////////////////////////////////////////////
@@ -592,24 +681,29 @@ void Shape::write_svg(
592681
}
593682

594683
Shape packingsolver::irregular::build_shape(
595-
const std::vector<BuildShapeElement>& points)
684+
const std::vector<BuildShapeElement>& points,
685+
bool path)
596686
{
597687
Shape shape;
598688
Point point_prev = {points.back().x, points.back().y};
689+
ShapeElementType type = ShapeElementType::LineSegment;
599690
bool anticlockwise = false;
600691
Point center = {0, 0};
601692
for (ElementPos pos = 0; pos < (ElementPos)points.size(); ++pos) {
602-
const BuildShapeElement point = points[pos];
693+
const BuildShapeElement& point = points[pos];
603694
if (point.type == 0) {
604695
ShapeElement element;
605-
element.type = ShapeElementType::LineSegment;
696+
element.type = type;
606697
element.start = point_prev;
607698
element.end = {points[pos].x, points[pos].y};
608-
shape.elements.push_back(element);
699+
if (!path || pos > 0)
700+
shape.elements.push_back(element);
609701
point_prev = element.end;
702+
type = ShapeElementType::LineSegment;
610703
} else {
611704
anticlockwise = (point.type == 1);
612705
center = {points[pos].x, points[pos].y};
706+
type = ShapeElementType::CircularArc;
613707
}
614708
}
615709
return shape;
@@ -834,6 +928,25 @@ bool irregular::operator==(
834928
return true;
835929
}
836930

931+
bool irregular::near(
932+
const ShapeElement& element_1,
933+
const ShapeElement& element_2)
934+
{
935+
if (element_1.type != element_2.type)
936+
return false;
937+
if (!near(element_1.start, element_2.start))
938+
return false;
939+
if (!near(element_1.end, element_2.end))
940+
return false;
941+
if (element_1.type == ShapeElementType::CircularArc) {
942+
if (!near(element_1.center, element_2.center))
943+
return false;
944+
if (element_1.anticlockwise != element_2.anticlockwise)
945+
return false;
946+
}
947+
return true;
948+
}
949+
837950
bool irregular::operator==(
838951
const Shape& shape_1,
839952
const Shape& shape_2)
@@ -865,3 +978,35 @@ bool irregular::operator==(
865978

866979
return true;
867980
}
981+
982+
bool irregular::near(
983+
const Shape& shape_1,
984+
const Shape& shape_2)
985+
{
986+
// First, check if both shapes have the same number of elements.
987+
if (shape_1.elements.size() != shape_2.elements.size())
988+
return false;
989+
990+
ElementPos offset = -1;
991+
for (ElementPos element_pos = 0;
992+
element_pos < (ElementPos)shape_2.elements.size();
993+
++element_pos) {
994+
if (near(shape_2.elements[element_pos], shape_1.elements[0])) {
995+
offset = element_pos;
996+
break;
997+
}
998+
}
999+
if (offset == -1)
1000+
return false;
1001+
1002+
for (ElementPos element_pos = 0;
1003+
element_pos < (ElementPos)shape_2.elements.size();
1004+
++element_pos) {
1005+
ElementPos element_pos_2 = (element_pos + offset) % shape_2.elements.size();
1006+
if (!near(shape_1.elements[element_pos], shape_2.elements[element_pos_2])) {
1007+
return false;
1008+
}
1009+
}
1010+
1011+
return true;
1012+
}

test/irregular/shape_test.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,58 @@ INSTANTIATE_TEST_SUITE_P(
3535
build_shape({{0, 0}, {100, 0}, {100, 100}, {50, 50}}),
3636
build_shape({{0, 0}, {100, 0}, {100, 100}})
3737
}}));
38+
39+
40+
struct ApproximateCircularArcByLineSegmentsTestParams
41+
{
42+
ShapeElement circular_arc;
43+
ElementPos number_of_line_segments;
44+
bool outer;
45+
std::vector<ShapeElement> expected_line_segments;
46+
};
47+
48+
class IrregularApproximateCircularArcByLineSegmentsTest: public testing::TestWithParam<ApproximateCircularArcByLineSegmentsTestParams> { };
49+
50+
TEST_P(IrregularApproximateCircularArcByLineSegmentsTest, ApproximateCircularArcByLineSegments)
51+
{
52+
ApproximateCircularArcByLineSegmentsTestParams test_params = GetParam();
53+
std::cout << "circular_arc" << std::endl;
54+
std::cout << test_params.circular_arc.to_string() << std::endl;
55+
std::cout << "expected_line_segments" << std::endl;
56+
for (const ShapeElement& line_segment: test_params.expected_line_segments)
57+
std::cout << line_segment.to_string() << std::endl;
58+
std::vector<ShapeElement> line_segments = approximate_circular_arc_by_line_segments(
59+
test_params.circular_arc,
60+
test_params.number_of_line_segments,
61+
test_params.outer);
62+
std::cout << "line_segments" << std::endl;
63+
for (const ShapeElement& line_segment: line_segments)
64+
std::cout << line_segment.to_string() << std::endl;
65+
66+
ASSERT_EQ(line_segments.size(), test_params.number_of_line_segments);
67+
for (ElementPos pos = 0; pos < test_params.number_of_line_segments; ++pos) {
68+
//std::cout << std::setprecision (15) << line_segments[pos].start.x << std::endl;
69+
EXPECT_TRUE(near(line_segments[pos], test_params.expected_line_segments[pos]));
70+
}
71+
}
72+
73+
INSTANTIATE_TEST_SUITE_P(
74+
Irregular,
75+
IrregularApproximateCircularArcByLineSegmentsTest,
76+
testing::ValuesIn(std::vector<ApproximateCircularArcByLineSegmentsTestParams>{
77+
{
78+
build_shape({{1, 0}, {0, 0, 1}, {0, 1}}, true).elements.front(),
79+
1,
80+
false,
81+
build_shape({{1, 0}, {0, 1}}, true).elements
82+
}, {
83+
build_shape({{1, 0}, {0, 0, 1}, {0, 1}}, true).elements.front(),
84+
2,
85+
true,
86+
build_shape({{1, 0}, {1, 1}, {0, 1}}, true).elements
87+
}, {
88+
build_shape({{1, 0}, {0, 0, 1}, {0, 1}}, true).elements.front(),
89+
3,
90+
true,
91+
build_shape({{1, 0}, {1, 0.414213562373095}, {0.414213562373095, 1}, {0, 1}}, true).elements
92+
}}));

0 commit comments

Comments
 (0)