Geometry.pm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. package Slic3r::Geometry;
  2. use strict;
  3. use warnings;
  4. require Exporter;
  5. our @ISA = qw(Exporter);
  6. our @EXPORT_OK = qw(
  7. PI epsilon slope line_atan lines_parallel three_points_aligned
  8. line_point_belongs_to_segment points_coincide distance_between_points
  9. line_length midpoint point_in_polygon point_in_segment segment_in_segment
  10. point_is_on_left_of_segment polyline_lines polygon_lines nearest_point
  11. point_along_segment polygon_segment_having_point polygon_has_subsegment
  12. polygon_has_vertex polyline_length can_connect_points deg2rad rad2deg
  13. rotate_points move_points remove_coinciding_points clip_segment_polygon
  14. sum_vectors multiply_vector subtract_vectors dot perp polygon_points_visibility
  15. line_intersection bounding_box bounding_box_intersect
  16. clip_segment_complex_polygon longest_segment angle3points
  17. );
  18. use Slic3r::Geometry::DouglasPeucker ();
  19. use XXX;
  20. use constant PI => 4 * atan2(1, 1);
  21. use constant A => 0;
  22. use constant B => 1;
  23. use constant X => 0;
  24. use constant Y => 1;
  25. our $parallel_degrees_limit = abs(deg2rad(3));
  26. our $epsilon = 1E-4;
  27. sub epsilon () { $epsilon }
  28. sub slope {
  29. my ($line) = @_;
  30. return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
  31. return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]);
  32. }
  33. sub line_atan {
  34. my ($line) = @_;
  35. return atan2($line->[B][Y] - $line->[A][Y], $line->[B][X] - $line->[A][X]);
  36. }
  37. sub lines_parallel {
  38. my ($line1, $line2) = @_;
  39. return abs(line_atan($line1) - line_atan($line2)) < $parallel_degrees_limit;
  40. }
  41. sub three_points_aligned {
  42. my ($p1, $p2, $p3) = @_;
  43. return lines_parallel([$p1, $p2], [$p2, $p3]);
  44. }
  45. # this subroutine checks whether a given point may belong to a given
  46. # segment given the hypothesis that it belongs to the line containing
  47. # the segment
  48. sub line_point_belongs_to_segment {
  49. my ($point, $segment) = @_;
  50. #printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
  51. # @$point, map @$_, @$segment;
  52. my @segment_extents = (
  53. [ sort { $a <=> $b } map $_->[X], @$segment ],
  54. [ sort { $a <=> $b } map $_->[Y], @$segment ],
  55. );
  56. return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon);
  57. return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon);
  58. return 1;
  59. }
  60. sub points_coincide {
  61. my ($p1, $p2) = @_;
  62. return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
  63. return 0;
  64. }
  65. sub distance_between_points {
  66. my ($p1, $p2) = @_;
  67. return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
  68. }
  69. sub line_length {
  70. my ($line) = @_;
  71. return distance_between_points(@$line[A, B]);
  72. }
  73. sub longest_segment {
  74. my (@lines) = @_;
  75. my ($longest, $maxlength);
  76. foreach my $line (@lines) {
  77. my $line_length = line_length($line);
  78. if (!defined $longest || $line_length > $maxlength) {
  79. $longest = $line;
  80. $maxlength = $line_length;
  81. }
  82. }
  83. return $longest;
  84. }
  85. sub midpoint {
  86. my ($line) = @_;
  87. return [ ($line->[B][X] + $line->[A][X]) / 2, ($line->[B][Y] + $line->[A][Y]) / 2 ];
  88. }
  89. sub point_in_polygon {
  90. my ($point, $polygon) = @_;
  91. my ($x, $y) = @$point;
  92. my @xy = map @$_, @$polygon;
  93. # Derived from the comp.graphics.algorithms FAQ,
  94. # courtesy of Wm. Randolph Franklin
  95. my $n = @xy / 2; # Number of points in polygon
  96. my @i = map { 2*$_ } 0..(@xy/2); # The even indices of @xy
  97. my @x = map { $xy[$_] } @i; # Even indices: x-coordinates
  98. my @y = map { $xy[$_ + 1] } @i; # Odd indices: y-coordinates
  99. my ($i, $j);
  100. my $side = 0; # 0 = outside; 1 = inside
  101. for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
  102. if (
  103. # If the y is between the (y-) borders...
  104. ($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
  105. and
  106. # ...the (x,y) to infinity line crosses the edge
  107. # from the ith point to the jth point...
  108. ($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i])
  109. ) {
  110. $side = not $side; # Jump the fence
  111. }
  112. }
  113. # if point is not in polygon, let's check whether it belongs to the contour
  114. if (!$side && 0) {
  115. return 1 if polygon_segment_having_point($polygon, $point);
  116. }
  117. return $side;
  118. }
  119. sub point_in_segment {
  120. my ($point, $line) = @_;
  121. my ($x, $y) = @$point;
  122. my @line_x = sort { $a <=> $b } $line->[A][X], $line->[B][X];
  123. my @line_y = sort { $a <=> $b } $line->[A][Y], $line->[B][Y];
  124. # check whether the point is in the segment bounding box
  125. return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
  126. && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
  127. # if line is vertical, check whether point's X is the same as the line
  128. if ($line->[A][X] == $line->[B][X]) {
  129. return 1 if abs($x - $line->[A][X]) < epsilon;
  130. }
  131. # calculate the Y in line at X of the point
  132. my $y3 = $line->[A][Y] + ($line->[B][Y] - $line->[A][Y])
  133. * ($x - $line->[A][X]) / ($line->[B][X] - $line->[A][X]);
  134. return abs($y3 - $y) < epsilon ? 1 : 0;
  135. }
  136. sub segment_in_segment {
  137. my ($needle, $haystack) = @_;
  138. # a segment is contained in another segment if its endpoints are contained
  139. return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack);
  140. }
  141. sub point_is_on_left_of_segment {
  142. my ($point, $line) = @_;
  143. return (($line->[B][X] - $line->[A][X])*($point->[Y] - $line->[A][Y])
  144. - ($line->[B][Y] - $line->[A][Y])*($point->[X] - $line->[A][X])) > 0;
  145. }
  146. sub polyline_lines {
  147. my ($polygon) = @_;
  148. my @lines = ();
  149. my $last_point;
  150. foreach my $point (@$polygon) {
  151. push @lines, [ $last_point, $point ] if $last_point;
  152. $last_point = $point;
  153. }
  154. return @lines;
  155. }
  156. sub polygon_lines {
  157. my ($polygon) = @_;
  158. return polyline_lines([ @$polygon, $polygon->[0] ]);
  159. }
  160. sub nearest_point {
  161. my ($point, $points) = @_;
  162. my ($nearest_point, $distance);
  163. foreach my $p (@$points) {
  164. my $d = distance_between_points($point, $p);
  165. if (!defined $distance || $d < $distance) {
  166. $nearest_point = $p;
  167. $distance = $d;
  168. return $p if $distance < epsilon;
  169. }
  170. }
  171. return $nearest_point;
  172. }
  173. # given a segment $p1-$p2, get the point at $distance from $p1 along segment
  174. sub point_along_segment {
  175. my ($p1, $p2, $distance) = @_;
  176. my $point = [ @$p1 ];
  177. my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
  178. for (X, Y) {
  179. if ($p1->[$_] != $p2->[$_]) {
  180. $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
  181. }
  182. }
  183. return $point;
  184. }
  185. # given a $polygon, return the (first) segment having $point
  186. sub polygon_segment_having_point {
  187. my ($polygon, $point) = @_;
  188. foreach my $line (polygon_lines($polygon)) {
  189. return $line if point_in_segment($point, $line);
  190. }
  191. return undef;
  192. }
  193. # return true if the given segment is contained in any edge of the polygon
  194. sub polygon_has_subsegment {
  195. my ($polygon, $segment) = @_;
  196. foreach my $line (polygon_lines($polygon)) {
  197. return 1 if segment_in_segment($segment, $line);
  198. }
  199. return 0;
  200. }
  201. sub polygon_has_vertex {
  202. my ($polygon, $point) = @_;
  203. foreach my $p (@$polygon) {
  204. return 1 if points_coincide($p, $point);
  205. }
  206. return 0;
  207. }
  208. sub polyline_length {
  209. my ($polyline) = @_;
  210. my $length = 0;
  211. $length += line_length($_) for polygon_lines($polyline);
  212. return $length;
  213. }
  214. sub can_connect_points {
  215. my ($p1, $p2, $polygons) = @_;
  216. # check that the two points are visible from each other
  217. return 0 if grep !polygon_points_visibility($_, $p1, $p2), @$polygons;
  218. # get segment where $p1 lies
  219. my $p1_segment;
  220. for (@$polygons) {
  221. $p1_segment = polygon_segment_having_point($_, $p1);
  222. last if $p1_segment;
  223. }
  224. # defensive programming, this shouldn't happen
  225. if (!$p1_segment) {
  226. die sprintf "Point %f,%f wasn't found in polygon contour or holes!", @$p1;
  227. }
  228. # check whether $p2 is internal or external (internal = on the left)
  229. return point_is_on_left_of_segment($p2, $p1_segment)
  230. || point_in_segment($p2, $p1_segment);
  231. }
  232. sub deg2rad {
  233. my ($degrees) = @_;
  234. return PI() * $degrees / 180;
  235. }
  236. sub rad2deg {
  237. my ($rad) = @_;
  238. return $rad / PI() * 180;
  239. }
  240. sub rotate_points {
  241. my ($radians, $center, @points) = @_;
  242. $center ||= [0,0];
  243. return map {
  244. [
  245. $center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]),
  246. $center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]),
  247. ]
  248. } @points;
  249. }
  250. sub move_points {
  251. my ($shift, @points) = @_;
  252. return map [ $shift->[X] + $_->[X], $shift->[Y] + $_->[Y] ], @points;
  253. }
  254. # preserves order
  255. sub remove_coinciding_points {
  256. my ($points) = @_;
  257. my %p = map { sprintf('%f,%f', @$_) => "$_" } @$points;
  258. %p = reverse %p;
  259. @$points = grep $p{"$_"}, @$points;
  260. }
  261. # implementation of Liang-Barsky algorithm
  262. # polygon must be convex and ccw
  263. sub clip_segment_polygon {
  264. my ($line, $polygon) = @_;
  265. if (@$line == 1) {
  266. # the segment is a point, check for inclusion
  267. return point_in_polygon($line, $polygon);
  268. }
  269. my @V = (@$polygon, $polygon->[0]);
  270. my $tE = 0; # the maximum entering segment parameter
  271. my $tL = 1; # the minimum entering segment parameter
  272. my $dS = subtract_vectors($line->[B], $line->[A]); # the segment direction vector
  273. for (my $i = 0; $i < $#V; $i++) { # process polygon edge V[i]V[Vi+1]
  274. my $e = subtract_vectors($V[$i+1], $V[$i]);
  275. my $N = perp($e, subtract_vectors($line->[A], $V[$i]));
  276. my $D = -perp($e, $dS);
  277. if (abs($D) < epsilon) { # $line is nearly parallel to this edge
  278. ($N < 0) ? return : next; # P0 outside this edge ? $line is outside : $line cannot cross edge, thus ignoring
  279. }
  280. my $t = $N / $D;
  281. if ($D < 0) { # $line is entering across this edge
  282. if ($t > $tE) { # new max $tE
  283. $tE = $t;
  284. return if $tE > $tL; # $line enters after leaving polygon?
  285. }
  286. } else { # $line is leaving across this edge
  287. if ($t < $tL) { # new min $tL
  288. $tL = $t;
  289. return if $tL < $tE; # $line leaves before entering polygon?
  290. }
  291. }
  292. }
  293. # $tE <= $tL implies that there is a valid intersection subsegment
  294. return [
  295. sum_vectors($line->[A], multiply_vector($dS, $tE)), # = P(tE) = point where S enters polygon
  296. sum_vectors($line->[A], multiply_vector($dS, $tL)), # = P(tE) = point where S enters polygon
  297. ];
  298. }
  299. sub sum_vectors {
  300. my ($v1, $v2) = @_;
  301. return [ $v1->[X] + $v2->[X], $v1->[Y] + $v2->[Y] ];
  302. }
  303. sub multiply_vector {
  304. my ($line, $scalar) = @_;
  305. return [ $line->[X] * $scalar, $line->[Y] * $scalar ];
  306. }
  307. sub subtract_vectors {
  308. my ($line2, $line1) = @_;
  309. return [ $line2->[X] - $line1->[X], $line2->[Y] - $line1->[Y] ];
  310. }
  311. # 2D dot product
  312. sub dot {
  313. my ($u, $v) = @_;
  314. return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
  315. }
  316. # 2D perp product
  317. sub perp {
  318. my ($u, $v) = @_;
  319. return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
  320. }
  321. sub polygon_points_visibility {
  322. my ($polygon, $p1, $p2) = @_;
  323. my $our_line = [ $p1, $p2 ];
  324. foreach my $line (polygon_lines($polygon)) {
  325. my $intersection = line_intersection($our_line, $line, 1) or next;
  326. next if grep points_coincide($intersection, $_), $p1, $p2;
  327. return 0;
  328. }
  329. return 1;
  330. }
  331. sub line_intersection {
  332. my ($line1, $line2, $require_crossing) = @_;
  333. $require_crossing ||= 0;
  334. my $intersection = _line_intersection(map @$_, @$line1, @$line2);
  335. return (ref $intersection && $intersection->[1] == $require_crossing)
  336. ? $intersection->[0]
  337. : undef;
  338. }
  339. sub _line_intersection {
  340. my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 );
  341. if ( @_ == 8 ) {
  342. ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
  343. # The bounding boxes chop the lines into line segments.
  344. # bounding_box() is defined later in this chapter.
  345. my @box_a = bounding_box([ [$x0, $y0], [$x1, $y1] ]);
  346. my @box_b = bounding_box([ [$x2, $y2], [$x3, $y3] ]);
  347. # Take this test away and the line segments are
  348. # turned into lines going from infinite to another.
  349. # bounding_box_intersect() defined later in this chapter.
  350. return "out of bounding box" unless bounding_box_intersect( 2, @box_a, @box_b );
  351. }
  352. elsif ( @_ == 4 ) { # The parametric form.
  353. $x0 = $x2 = 0;
  354. ( $y0, $y2 ) = @_[ 1, 3 ];
  355. # Need to multiply by 'enough' to get 'far enough'.
  356. my $abs_y0 = abs $y0;
  357. my $abs_y2 = abs $y2;
  358. my $enough = 10 * ( $abs_y0 > $abs_y2 ? $abs_y0 : $abs_y2 );
  359. $x1 = $x3 = $enough;
  360. $y1 = $_[0] * $x1 + $y0;
  361. $y3 = $_[2] * $x2 + $y2;
  362. }
  363. my ($x, $y); # The as-yet-undetermined intersection point.
  364. my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences
  365. my $dx10 = $x1 - $x0; # between the points P and Q.
  366. my $dy32 = $y3 - $y2;
  367. my $dx32 = $x3 - $x2;
  368. my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"?
  369. my $dx10z = abs( $dx10 ) < epsilon;
  370. my $dy32z = abs( $dy32 ) < epsilon;
  371. my $dx32z = abs( $dx32 ) < epsilon;
  372. my $dyx10; # The slopes.
  373. my $dyx32;
  374. $dyx10 = $dy10 / $dx10 unless $dx10z;
  375. $dyx32 = $dy32 / $dx32 unless $dx32z;
  376. # Now we know all differences and the slopes;
  377. # we can detect horizontal/vertical special cases.
  378. # E.g., slope = 0 means a horizontal line.
  379. unless ( defined $dyx10 or defined $dyx32 ) {
  380. return "parallel vertical";
  381. }
  382. elsif ( $dy10z and not $dy32z ) { # First line horizontal.
  383. $y = $y0;
  384. $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
  385. }
  386. elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
  387. $y = $y2;
  388. $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
  389. }
  390. elsif ( $dx10z and not $dx32z ) { # First line vertical.
  391. $x = $x0;
  392. $y = $y2 + $dyx32 * ( $x - $x2 );
  393. }
  394. elsif ( not $dx10z and $dx32z ) { # Second line vertical.
  395. $x = $x2;
  396. $y = $y0 + $dyx10 * ( $x - $x0 );
  397. }
  398. elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) {
  399. # The slopes are suspiciously close to each other.
  400. # Either we have parallel collinear or just parallel lines.
  401. # The bounding box checks have already weeded the cases
  402. # "parallel horizontal" and "parallel vertical" away.
  403. my $ya = $y0 - $dyx10 * $x0;
  404. my $yb = $y2 - $dyx32 * $x2;
  405. return "parallel collinear" if abs( $ya - $yb ) < epsilon;
  406. return "parallel";
  407. }
  408. else {
  409. # None of the special cases matched.
  410. # We have a "honest" line intersection.
  411. $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
  412. $y = $y0 + $dyx10 * ($x - $x0);
  413. }
  414. my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
  415. my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
  416. return [[$x, $y], $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
  417. }
  418. # 2D
  419. sub bounding_box {
  420. my ($points) = @_;
  421. my @x = sort { $a <=> $b } map $_->[X], @$points;
  422. my @y = sort { $a <=> $b } map $_->[Y], @$points;
  423. return ($x[0], $y[0], $x[-1], $y[-1]);
  424. }
  425. # bounding_box_intersect($d, @a, @b)
  426. # Return true if the given bounding boxes @a and @b intersect
  427. # in $d dimensions. Used by line_intersection().
  428. sub bounding_box_intersect {
  429. my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
  430. my @aa = splice( @bb, 0, 2 * $d ); # The first box.
  431. # (@bb is the second one.)
  432. # Must intersect in all dimensions.
  433. for ( my $i_min = 0; $i_min < $d; $i_min++ ) {
  434. my $i_max = $i_min + $d; # The index for the maximum.
  435. return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ];
  436. return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ];
  437. }
  438. return 1;
  439. }
  440. sub clip_segment_complex_polygon {
  441. my ($line, $polygons) = @_;
  442. my @intersections = grep $_, map line_intersection($line, $_, 1),
  443. map polygon_lines($_), @$polygons or return ();
  444. # this is not very elegant, however it works
  445. @intersections = sort { sprintf("%020f,%020f", @$a) cmp sprintf("%020f,%020f", @$b) } @intersections;
  446. shift(@intersections) if !grep(point_in_polygon($intersections[0], $_), @$polygons)
  447. && !grep(polygon_segment_having_point($_, $intersections[0]), @$polygons);
  448. # defensive programming
  449. ###die "Invalid intersections" if @intersections % 2 != 0;
  450. my @lines = ();
  451. while (@intersections) {
  452. # skip tangent points
  453. my @points = map shift @intersections, 1..2;
  454. next if !$points[1];
  455. next if points_coincide(@points);
  456. push @lines, [ @points ];
  457. }
  458. return [@lines];
  459. }
  460. sub angle3points {
  461. my ($p1, $p2, $p3) = @_;
  462. # p1 is the center
  463. my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
  464. - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
  465. # we only want to return only positive angles
  466. return $angle <= 0 ? $angle + 2*PI() : $angle;
  467. }
  468. 1;