Geometry.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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 X Y Z A B X1 Y1 X2 Y2 Z1 Z2 MIN MAX epsilon slope
  8. line_point_belongs_to_segment points_coincide distance_between_points
  9. normalize tan move_points_3D
  10. point_in_polygon point_in_segment segment_in_segment
  11. polyline_lines polygon_lines
  12. point_along_segment polygon_segment_having_point polygon_has_subsegment
  13. deg2rad rad2deg
  14. rotate_points move_points
  15. dot perp
  16. line_intersection bounding_box bounding_box_intersect
  17. angle3points
  18. chained_path chained_path_from collinear scale unscale
  19. rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
  20. polyline_remove_short_segments normal triangle_normal polygon_is_convex
  21. scaled_epsilon bounding_box_3D size_3D size_2D
  22. convex_hull directions_parallel directions_parallel_within
  23. );
  24. use constant PI => 4 * atan2(1, 1);
  25. use constant A => 0;
  26. use constant B => 1;
  27. use constant X => 0;
  28. use constant Y => 1;
  29. use constant Z => 2;
  30. use constant X1 => 0;
  31. use constant Y1 => 1;
  32. use constant X2 => 2;
  33. use constant Y2 => 3;
  34. use constant Z1 => 4;
  35. use constant Z2 => 5;
  36. use constant MIN => 0;
  37. use constant MAX => 1;
  38. our $parallel_degrees_limit = abs(deg2rad(0.1));
  39. sub epsilon () { 1E-4 }
  40. sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
  41. sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
  42. sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
  43. sub tan {
  44. my ($angle) = @_;
  45. return (sin $angle) / (cos $angle);
  46. }
  47. sub slope {
  48. my ($line) = @_;
  49. return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
  50. return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]);
  51. }
  52. # this subroutine checks whether a given point may belong to a given
  53. # segment given the hypothesis that it belongs to the line containing
  54. # the segment
  55. sub line_point_belongs_to_segment {
  56. my ($point, $segment) = @_;
  57. #printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
  58. # @$point, map @$_, @$segment;
  59. my @segment_extents = (
  60. [ sort { $a <=> $b } map $_->[X], @$segment ],
  61. [ sort { $a <=> $b } map $_->[Y], @$segment ],
  62. );
  63. return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon);
  64. return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon);
  65. return 1;
  66. }
  67. sub points_coincide {
  68. my ($p1, $p2) = @_;
  69. return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
  70. return 0;
  71. }
  72. sub distance_between_points {
  73. my ($p1, $p2) = @_;
  74. return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
  75. }
  76. # this will check whether a point is in a polygon regardless of polygon orientation
  77. sub point_in_polygon {
  78. my ($point, $polygon) = @_;
  79. my ($x, $y) = @$point;
  80. my $n = @$polygon;
  81. my @x = map $_->[X], @$polygon;
  82. my @y = map $_->[Y], @$polygon;
  83. # Derived from the comp.graphics.algorithms FAQ,
  84. # courtesy of Wm. Randolph Franklin
  85. my ($i, $j);
  86. my $side = 0; # 0 = outside; 1 = inside
  87. for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
  88. if (
  89. # If the y is between the (y-) borders...
  90. ($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
  91. and
  92. # ...the (x,y) to infinity line crosses the edge
  93. # from the ith point to the jth point...
  94. ($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i])
  95. ) {
  96. $side = not $side; # Jump the fence
  97. }
  98. }
  99. # if point is not in polygon, let's check whether it belongs to the contour
  100. if (!$side && 0) {
  101. return 1 if polygon_segment_having_point($polygon, $point);
  102. }
  103. return $side;
  104. }
  105. sub point_in_segment {
  106. my ($point, $line) = @_;
  107. my ($x, $y) = @$point;
  108. my $line_p = $line->pp;
  109. my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X];
  110. my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y];
  111. # check whether the point is in the segment bounding box
  112. return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
  113. && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
  114. # if line is vertical, check whether point's X is the same as the line
  115. if ($line_p->[A][X] == $line_p->[B][X]) {
  116. return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0;
  117. }
  118. # calculate the Y in line at X of the point
  119. my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y])
  120. * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]);
  121. return abs($y3 - $y) < epsilon ? 1 : 0;
  122. }
  123. sub segment_in_segment {
  124. my ($needle, $haystack) = @_;
  125. # a segment is contained in another segment if its endpoints are contained
  126. return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack);
  127. }
  128. sub polyline_lines {
  129. my ($polyline) = @_;
  130. my @points = @$polyline;
  131. return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
  132. }
  133. sub polygon_lines {
  134. my ($polygon) = @_;
  135. return polyline_lines([ @$polygon, $polygon->[0] ]);
  136. }
  137. # given a segment $p1-$p2, get the point at $distance from $p1 along segment
  138. sub point_along_segment {
  139. my ($p1, $p2, $distance) = @_;
  140. my $point = [ @$p1 ];
  141. my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
  142. for (X, Y) {
  143. if ($p1->[$_] != $p2->[$_]) {
  144. $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
  145. }
  146. }
  147. return Slic3r::Point->new(@$point);
  148. }
  149. # given a $polygon, return the (first) segment having $point
  150. sub polygon_segment_having_point {
  151. my ($polygon, $point) = @_;
  152. foreach my $line (@{ $polygon->lines }) {
  153. return $line if point_in_segment($point, $line);
  154. }
  155. return undef;
  156. }
  157. # return true if the given segment is contained in any edge of the polygon
  158. sub polygon_has_subsegment {
  159. my ($polygon, $segment) = @_;
  160. foreach my $line (polygon_lines($polygon)) {
  161. return 1 if segment_in_segment($segment, $line);
  162. }
  163. return 0;
  164. }
  165. # polygon must be simple (non complex) and ccw
  166. sub polygon_is_convex {
  167. my ($points) = @_;
  168. for (my $i = 0; $i <= $#$points; $i++) {
  169. my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]);
  170. return 0 if $angle < PI;
  171. }
  172. return 1;
  173. }
  174. sub deg2rad {
  175. my ($degrees) = @_;
  176. return PI() * $degrees / 180;
  177. }
  178. sub rad2deg {
  179. my ($rad) = @_;
  180. return $rad / PI() * 180;
  181. }
  182. sub rad2deg_dir {
  183. my ($rad) = @_;
  184. $rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2);
  185. $rad += PI if $rad < 0;
  186. return rad2deg($rad);
  187. }
  188. sub rotate_points {
  189. my ($radians, $center, @points) = @_;
  190. $center //= [0,0];
  191. return map {
  192. [
  193. $center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]),
  194. $center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]),
  195. ]
  196. } @points;
  197. }
  198. sub move_points {
  199. my ($shift, @points) = @_;
  200. return map {
  201. my @p = @$_;
  202. Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]);
  203. } @points;
  204. }
  205. sub move_points_3D {
  206. my ($shift, @points) = @_;
  207. return map [
  208. $shift->[X] + $_->[X],
  209. $shift->[Y] + $_->[Y],
  210. $shift->[Z] + $_->[Z],
  211. ], @points;
  212. }
  213. sub normal {
  214. my ($line1, $line2) = @_;
  215. return [
  216. ($line1->[Y] * $line2->[Z]) - ($line1->[Z] * $line2->[Y]),
  217. -($line2->[Z] * $line1->[X]) + ($line2->[X] * $line1->[Z]),
  218. ($line1->[X] * $line2->[Y]) - ($line1->[Y] * $line2->[X]),
  219. ];
  220. }
  221. sub triangle_normal {
  222. my ($v1, $v2, $v3) = @_;
  223. my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ];
  224. my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ];
  225. return normal($u, $v);
  226. }
  227. sub normalize {
  228. my ($line) = @_;
  229. my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) )
  230. or return [0, 0, 0]; # to avoid illegal division by zero
  231. return [ map $_ / $len, @$line ];
  232. }
  233. # 2D dot product
  234. sub dot {
  235. my ($u, $v) = @_;
  236. return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
  237. }
  238. # 2D perp product
  239. sub perp {
  240. my ($u, $v) = @_;
  241. return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
  242. }
  243. sub line_intersects_any {
  244. my ($line, $lines) = @_;
  245. for (@$lines) {
  246. return 1 if line_intersection($line, $_, 1);
  247. }
  248. return 0;
  249. }
  250. sub line_intersection {
  251. my ($line1, $line2, $require_crossing) = @_;
  252. $require_crossing ||= 0;
  253. my $intersection = _line_intersection(map @$_, @$line1, @$line2);
  254. return (ref $intersection && $intersection->[1] == $require_crossing)
  255. ? $intersection->[0]
  256. : undef;
  257. }
  258. sub collinear {
  259. my ($line1, $line2, $require_overlapping) = @_;
  260. my $intersection = _line_intersection(map @$_, @$line1, @$line2);
  261. return 0 unless !ref($intersection)
  262. && ($intersection eq 'parallel collinear'
  263. || ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon));
  264. if ($require_overlapping) {
  265. my @box_a = bounding_box([ $line1->[0], $line1->[1] ]);
  266. my @box_b = bounding_box([ $line2->[0], $line2->[1] ]);
  267. return 0 unless bounding_box_intersect( 2, @box_a, @box_b );
  268. }
  269. return 1;
  270. }
  271. sub _line_intersection {
  272. my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
  273. my ($x, $y); # The as-yet-undetermined intersection point.
  274. my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences
  275. my $dx10 = $x1 - $x0; # between the points P and Q.
  276. my $dy32 = $y3 - $y2;
  277. my $dx32 = $x3 - $x2;
  278. my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"?
  279. my $dx10z = abs( $dx10 ) < epsilon;
  280. my $dy32z = abs( $dy32 ) < epsilon;
  281. my $dx32z = abs( $dx32 ) < epsilon;
  282. my $dyx10; # The slopes.
  283. my $dyx32;
  284. $dyx10 = $dy10 / $dx10 unless $dx10z;
  285. $dyx32 = $dy32 / $dx32 unless $dx32z;
  286. # Now we know all differences and the slopes;
  287. # we can detect horizontal/vertical special cases.
  288. # E.g., slope = 0 means a horizontal line.
  289. unless ( defined $dyx10 or defined $dyx32 ) {
  290. return "parallel vertical";
  291. }
  292. elsif ( $dy10z and not $dy32z ) { # First line horizontal.
  293. $y = $y0;
  294. $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
  295. }
  296. elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
  297. $y = $y2;
  298. $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
  299. }
  300. elsif ( $dx10z and not $dx32z ) { # First line vertical.
  301. $x = $x0;
  302. $y = $y2 + $dyx32 * ( $x - $x2 );
  303. }
  304. elsif ( not $dx10z and $dx32z ) { # Second line vertical.
  305. $x = $x2;
  306. $y = $y0 + $dyx10 * ( $x - $x0 );
  307. }
  308. elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) {
  309. # The slopes are suspiciously close to each other.
  310. # Either we have parallel collinear or just parallel lines.
  311. # The bounding box checks have already weeded the cases
  312. # "parallel horizontal" and "parallel vertical" away.
  313. my $ya = $y0 - $dyx10 * $x0;
  314. my $yb = $y2 - $dyx32 * $x2;
  315. return "parallel collinear" if abs( $ya - $yb ) < epsilon;
  316. return "parallel";
  317. }
  318. else {
  319. # None of the special cases matched.
  320. # We have a "honest" line intersection.
  321. $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
  322. $y = $y0 + $dyx10 * ($x - $x0);
  323. }
  324. my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
  325. my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
  326. return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
  327. }
  328. # http://paulbourke.net/geometry/lineline2d/
  329. sub _line_intersection2 {
  330. my ($line1, $line2) = @_;
  331. my $denom = ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[B][X] - $line1->[A][X])
  332. - ($line2->[B][X] - $line2->[A][X]) * ($line1->[B][Y] - $line1->[A][Y]);
  333. my $numerA = ($line2->[B][X] - $line2->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
  334. - ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
  335. my $numerB = ($line1->[B][X] - $line1->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
  336. - ($line1->[B][Y] - $line1->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
  337. # are the lines coincident?
  338. if (abs($numerA) < epsilon && abs($numerB) < epsilon && abs($denom) < epsilon) {
  339. return Slic3r::Point->new(
  340. ($line1->[A][X] + $line1->[B][X]) / 2,
  341. ($line1->[A][Y] + $line1->[B][Y]) / 2,
  342. );
  343. }
  344. # are the lines parallel?
  345. if (abs($denom) < epsilon) {
  346. return undef;
  347. }
  348. # is the intersection along the segments?
  349. my $muA = $numerA / $denom;
  350. my $muB = $numerB / $denom;
  351. if ($muA < 0 || $muA > 1 || $muB < 0 || $muB > 1) {
  352. return undef;
  353. }
  354. return Slic3r::Point->new(
  355. $line1->[A][X] + $muA * ($line1->[B][X] - $line1->[A][X]),
  356. $line1->[A][Y] + $muA * ($line1->[B][Y] - $line1->[A][Y]),
  357. );
  358. }
  359. # 2D
  360. sub bounding_box {
  361. my ($points) = @_;
  362. my @x = map $_->x, @$points;
  363. my @y = map $_->y, @$points; #,,
  364. my @bb = (undef, undef, undef, undef);
  365. for (0..$#x) {
  366. $bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1];
  367. $bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2];
  368. $bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1];
  369. $bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2];
  370. }
  371. return @bb[X1,Y1,X2,Y2];
  372. }
  373. sub bounding_box_center {
  374. my ($bounding_box) = @_;
  375. return Slic3r::Point->new(
  376. ($bounding_box->[X2] + $bounding_box->[X1]) / 2,
  377. ($bounding_box->[Y2] + $bounding_box->[Y1]) / 2,
  378. );
  379. }
  380. sub size_2D {
  381. my @bounding_box = bounding_box(@_);
  382. return (
  383. ($bounding_box[X2] - $bounding_box[X1]),
  384. ($bounding_box[Y2] - $bounding_box[Y1]),
  385. );
  386. }
  387. # bounding_box_intersect($d, @a, @b)
  388. # Return true if the given bounding boxes @a and @b intersect
  389. # in $d dimensions. Used by line_intersection().
  390. sub bounding_box_intersect {
  391. my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
  392. my @aa = splice( @bb, 0, 2 * $d ); # The first box.
  393. # (@bb is the second one.)
  394. # Must intersect in all dimensions.
  395. for ( my $i_min = 0; $i_min < $d; $i_min++ ) {
  396. my $i_max = $i_min + $d; # The index for the maximum.
  397. return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ];
  398. return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ];
  399. }
  400. return 1;
  401. }
  402. # 3D
  403. sub bounding_box_3D {
  404. my ($points) = @_;
  405. my @extents = (map [undef, undef], X,Y,Z);
  406. foreach my $point (@$points) {
  407. for (X,Y,Z) {
  408. $extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
  409. $extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
  410. }
  411. }
  412. return @extents;
  413. }
  414. sub size_3D {
  415. my ($points) = @_;
  416. my @extents = bounding_box_3D($points);
  417. return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
  418. }
  419. # this assumes a CCW rotation from $p2 to $p3 around $p1
  420. sub angle3points {
  421. my ($p1, $p2, $p3) = @_;
  422. # p1 is the center
  423. my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
  424. - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
  425. # we only want to return only positive angles
  426. return $angle <= 0 ? $angle + 2*PI() : $angle;
  427. }
  428. sub polyline_remove_short_segments {
  429. my ($points, $min_length, $isPolygon) = @_;
  430. for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) {
  431. if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) {
  432. # we can remove $points->[$i]
  433. splice @$points, $i, 1;
  434. $i--;
  435. }
  436. }
  437. }
  438. sub douglas_peucker {
  439. my ($points, $tolerance) = @_;
  440. no warnings "recursion";
  441. my $results = [];
  442. my $dmax = 0;
  443. my $index = 0;
  444. for my $i (1..$#$points) {
  445. my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1]));
  446. if ($d > $dmax) {
  447. $index = $i;
  448. $dmax = $d;
  449. }
  450. }
  451. if ($dmax >= $tolerance) {
  452. my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
  453. $results = [
  454. @$dp1[0..($#$dp1-1)],
  455. @{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
  456. ];
  457. } else {
  458. $results = [ $points->[0], $points->[-1] ];
  459. }
  460. return $results;
  461. }
  462. sub douglas_peucker2 {
  463. my ($points, $tolerance) = @_;
  464. my $anchor = 0;
  465. my $floater = $#$points;
  466. my @stack = ();
  467. my %keep = ();
  468. push @stack, [$anchor, $floater];
  469. while (@stack) {
  470. ($anchor, $floater) = @{pop @stack};
  471. # initialize line segment
  472. my ($anchor_x, $anchor_y, $seg_len);
  473. if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) {
  474. $anchor_x = $points->[$floater][X] - $points->[$anchor][X];
  475. $anchor_y = $points->[$floater][Y] - $points->[$anchor][Y];
  476. $seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2));
  477. # get the unit vector
  478. $anchor_x /= $seg_len;
  479. $anchor_y /= $seg_len;
  480. } else {
  481. $anchor_x = $anchor_y = $seg_len = 0;
  482. }
  483. # inner loop:
  484. my $max_dist = 0;
  485. my $farthest = $anchor + 1;
  486. for my $i (($anchor + 1) .. $floater) {
  487. my $dist_to_seg = 0;
  488. # compare to anchor
  489. my $vecX = $points->[$i][X] - $points->[$anchor][X];
  490. my $vecY = $points->[$i][Y] - $points->[$anchor][Y];
  491. $seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
  492. # dot product:
  493. my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
  494. if ($proj < 0) {
  495. $dist_to_seg = $seg_len;
  496. } else {
  497. # compare to floater
  498. $vecX = $points->[$i][X] - $points->[$floater][X];
  499. $vecY = $points->[$i][Y] - $points->[$floater][Y];
  500. $seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
  501. # dot product:
  502. $proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
  503. if ($proj < 0) {
  504. $dist_to_seg = $seg_len
  505. } else { # calculate perpendicular distance to line (pythagorean theorem):
  506. $dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2)));
  507. }
  508. if ($max_dist < $dist_to_seg) {
  509. $max_dist = $dist_to_seg;
  510. $farthest = $i;
  511. }
  512. }
  513. }
  514. if ($max_dist <= $tolerance) { # use line segment
  515. $keep{$_} = 1 for $anchor, $floater;
  516. } else {
  517. push @stack, [$anchor, $farthest];
  518. push @stack, [$farthest, $floater];
  519. }
  520. }
  521. return [ map $points->[$_], sort keys %keep ];
  522. }
  523. sub arrange {
  524. my ($total_parts, $partx, $party, $dist, $bb) = @_;
  525. my $linint = sub {
  526. my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
  527. return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
  528. };
  529. # use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
  530. $partx += $dist;
  531. $party += $dist;
  532. my ($areax, $areay);
  533. if (defined $bb) {
  534. my $size = $bb->size;
  535. ($areax, $areay) = @$size[X,Y];
  536. } else {
  537. # bogus area size, large enough not to trigger the error below
  538. $areax = $partx * $total_parts;
  539. $areay = $party * $total_parts;
  540. }
  541. # this is how many cells we have available into which to put parts
  542. my $cellw = int(($areax + $dist) / $partx);
  543. my $cellh = int(($areay + $dist) / $party);
  544. die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
  545. # width and height of space used by cells
  546. my $w = $cellw * $partx;
  547. my $h = $cellh * $party;
  548. # left and right border positions of space used by cells
  549. my $l = ($areax - $w) / 2;
  550. my $r = $l + $w;
  551. # top and bottom border positions
  552. my $t = ($areay - $h) / 2;
  553. my $b = $t + $h;
  554. # list of cells, sorted by distance from center
  555. my @cellsorder;
  556. # work out distance for all cells, sort into list
  557. for my $i (0..$cellw-1) {
  558. for my $j (0..$cellh-1) {
  559. my $cx = $linint->($i + 0.5, 0, $cellw, $l, $r);
  560. my $cy = $linint->($j + 0.5, 0, $cellh, $t, $b);
  561. my $xd = abs(($areax / 2) - $cx);
  562. my $yd = abs(($areay / 2) - $cy);
  563. my $c = {
  564. location => [$cx, $cy],
  565. index => [$i, $j],
  566. distance => $xd * $xd + $yd * $yd - abs(($cellw / 2) - ($i + 0.5)),
  567. };
  568. BINARYINSERTIONSORT: {
  569. my $index = $c->{distance};
  570. my $low = 0;
  571. my $high = @cellsorder;
  572. while ($low < $high) {
  573. my $mid = ($low + (($high - $low) / 2)) | 0;
  574. my $midval = $cellsorder[$mid]->[0];
  575. if ($midval < $index) {
  576. $low = $mid + 1;
  577. } elsif ($midval > $index) {
  578. $high = $mid;
  579. } else {
  580. splice @cellsorder, $mid, 0, [$index, $c];
  581. last BINARYINSERTIONSORT;
  582. }
  583. }
  584. splice @cellsorder, $low, 0, [$index, $c];
  585. }
  586. }
  587. }
  588. # the extents of cells actually used by objects
  589. my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
  590. # now find cells actually used by objects, map out the extents so we can position correctly
  591. for my $i (1..$total_parts) {
  592. my $c = $cellsorder[$i - 1];
  593. my $cx = $c->[1]->{index}->[0];
  594. my $cy = $c->[1]->{index}->[1];
  595. if ($i == 1) {
  596. $lx = $rx = $cx;
  597. $ty = $by = $cy;
  598. } else {
  599. $rx = $cx if $cx > $rx;
  600. $lx = $cx if $cx < $lx;
  601. $by = $cy if $cy > $by;
  602. $ty = $cy if $cy < $ty;
  603. }
  604. }
  605. # now we actually place objects into cells, positioned such that the left and bottom borders are at 0
  606. my @positions = ();
  607. for (1..$total_parts) {
  608. my $c = shift @cellsorder;
  609. my $cx = $c->[1]->{index}->[0] - $lx;
  610. my $cy = $c->[1]->{index}->[1] - $ty;
  611. push @positions, [$cx * $partx, $cy * $party];
  612. }
  613. if (defined $bb) {
  614. $_->[X] += $bb->x_min for @positions;
  615. $_->[Y] += $bb->y_min for @positions;
  616. }
  617. return @positions;
  618. }
  619. 1;