@@ -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
594683Shape 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+
837950bool 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+ }
0 commit comments