1
package Slic3r::Geometry;
6
our @ISA = qw(Exporter);
8
PI X Y Z A B X1 Y1 X2 Y2 Z1 Z2 MIN MAX epsilon slope
9
line_point_belongs_to_segment points_coincide distance_between_points
10
normalize tan move_points_3D
11
point_in_polygon point_in_segment segment_in_segment
12
polyline_lines polygon_lines
13
point_along_segment polygon_segment_having_point polygon_has_subsegment
15
rotate_points move_points
16
dot perp polygon_points_visibility
17
line_intersection bounding_box bounding_box_intersect
19
chained_path chained_path_from collinear scale unscale
20
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
21
polyline_remove_short_segments normal triangle_normal polygon_is_convex
22
scaled_epsilon bounding_box_3D size_3D size_2D
23
convex_hull directions_parallel directions_parallel_within
27
use constant PI => 4 * atan2(1, 1);
39
use constant MIN => 0;
40
use constant MAX => 1;
41
our $parallel_degrees_limit = abs(deg2rad(0.1));
43
sub epsilon () { 1E-4 }
44
sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
46
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
47
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
51
return (sin $angle) / (cos $angle);
56
return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
57
return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]);
60
# this subroutine checks whether a given point may belong to a given
61
# segment given the hypothesis that it belongs to the line containing
63
sub line_point_belongs_to_segment {
64
my ($point, $segment) = @_;
66
#printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
67
# @$point, map @$_, @$segment;
69
my @segment_extents = (
70
[ sort { $a <=> $b } map $_->[X], @$segment ],
71
[ sort { $a <=> $b } map $_->[Y], @$segment ],
74
return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon);
75
return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon);
81
return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
85
sub distance_between_points {
87
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
90
# this will check whether a point is in a polygon regardless of polygon orientation
91
sub point_in_polygon {
92
my ($point, $polygon) = @_;
94
my ($x, $y) = @$point;
96
my @x = map $_->[X], @$polygon;
97
my @y = map $_->[Y], @$polygon;
99
# Derived from the comp.graphics.algorithms FAQ,
100
# courtesy of Wm. Randolph Franklin
102
my $side = 0; # 0 = outside; 1 = inside
103
for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
105
# If the y is between the (y-) borders...
106
($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
108
# ...the (x,y) to infinity line crosses the edge
109
# from the ith point to the jth point...
110
($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i])
112
$side = not $side; # Jump the fence
116
# if point is not in polygon, let's check whether it belongs to the contour
118
return 1 if polygon_segment_having_point($polygon, $point);
124
sub point_in_segment {
125
my ($point, $line) = @_;
127
my ($x, $y) = @$point;
128
my $line_p = $line->pp;
129
my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X];
130
my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y];
132
# check whether the point is in the segment bounding box
133
return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
134
&& $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
136
# if line is vertical, check whether point's X is the same as the line
137
if ($line_p->[A][X] == $line_p->[B][X]) {
138
return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0;
141
# calculate the Y in line at X of the point
142
my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y])
143
* ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]);
144
return abs($y3 - $y) < epsilon ? 1 : 0;
147
sub segment_in_segment {
148
my ($needle, $haystack) = @_;
150
# a segment is contained in another segment if its endpoints are contained
151
return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack);
156
my @points = @$polyline;
157
return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
162
return polyline_lines([ @$polygon, $polygon->[0] ]);
165
# given a segment $p1-$p2, get the point at $distance from $p1 along segment
166
sub point_along_segment {
167
my ($p1, $p2, $distance) = @_;
169
my $point = [ @$p1 ];
171
my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
173
if ($p1->[$_] != $p2->[$_]) {
174
$point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
178
return Slic3r::Point->new(@$point);
181
# given a $polygon, return the (first) segment having $point
182
sub polygon_segment_having_point {
183
my ($polygon, $point) = @_;
185
foreach my $line (@{ $polygon->lines }) {
186
return $line if point_in_segment($point, $line);
191
# return true if the given segment is contained in any edge of the polygon
192
sub polygon_has_subsegment {
193
my ($polygon, $segment) = @_;
194
foreach my $line (polygon_lines($polygon)) {
195
return 1 if segment_in_segment($segment, $line);
200
# polygon must be simple (non complex) and ccw
201
sub polygon_is_convex {
203
for (my $i = 0; $i <= $#$points; $i++) {
204
my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]);
205
return 0 if $angle < PI;
212
return PI() * $degrees / 180;
217
return $rad / PI() * 180;
222
$rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2);
223
$rad += PI if $rad < 0;
224
return rad2deg($rad);
228
my ($radians, $center, @points) = @_;
232
$center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]),
233
$center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]),
239
my ($shift, @points) = @_;
242
Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]);
247
my ($shift, @points) = @_;
249
$shift->[X] + $_->[X],
250
$shift->[Y] + $_->[Y],
251
$shift->[Z] + $_->[Z],
256
my ($line1, $line2) = @_;
259
($line1->[Y] * $line2->[Z]) - ($line1->[Z] * $line2->[Y]),
260
-($line2->[Z] * $line1->[X]) + ($line2->[X] * $line1->[Z]),
261
($line1->[X] * $line2->[Y]) - ($line1->[Y] * $line2->[X]),
265
sub triangle_normal {
266
my ($v1, $v2, $v3) = @_;
268
my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ];
269
my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ];
271
return normal($u, $v);
277
my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) )
278
or return [0, 0, 0]; # to avoid illegal division by zero
279
return [ map $_ / $len, @$line ];
285
return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
291
return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
294
sub polygon_points_visibility {
295
my ($polygon, $p1, $p2) = @_;
297
my $our_line = [ $p1, $p2 ];
298
foreach my $line (polygon_lines($polygon)) {
299
my $intersection = line_intersection($our_line, $line, 1) // next;
300
next if grep points_coincide($intersection, $_), $p1, $p2;
307
sub line_intersects_any {
308
my ($line, $lines) = @_;
310
return 1 if line_intersection($line, $_, 1);
315
sub line_intersection {
316
my ($line1, $line2, $require_crossing) = @_;
317
$require_crossing ||= 0;
319
my $intersection = _line_intersection(map @$_, @$line1, @$line2);
320
return (ref $intersection && $intersection->[1] == $require_crossing)
326
my ($line1, $line2, $require_overlapping) = @_;
327
my $intersection = _line_intersection(map @$_, @$line1, @$line2);
328
return 0 unless !ref($intersection)
329
&& ($intersection eq 'parallel collinear'
330
|| ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon));
332
if ($require_overlapping) {
333
my @box_a = bounding_box([ $line1->[0], $line1->[1] ]);
334
my @box_b = bounding_box([ $line2->[0], $line2->[1] ]);
335
return 0 unless bounding_box_intersect( 2, @box_a, @box_b );
341
sub _line_intersection {
342
my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
344
my ($x, $y); # The as-yet-undetermined intersection point.
346
my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences
347
my $dx10 = $x1 - $x0; # between the points P and Q.
348
my $dy32 = $y3 - $y2;
349
my $dx32 = $x3 - $x2;
351
my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"?
352
my $dx10z = abs( $dx10 ) < epsilon;
353
my $dy32z = abs( $dy32 ) < epsilon;
354
my $dx32z = abs( $dx32 ) < epsilon;
356
my $dyx10; # The slopes.
359
$dyx10 = $dy10 / $dx10 unless $dx10z;
360
$dyx32 = $dy32 / $dx32 unless $dx32z;
362
# Now we know all differences and the slopes;
363
# we can detect horizontal/vertical special cases.
364
# E.g., slope = 0 means a horizontal line.
366
unless ( defined $dyx10 or defined $dyx32 ) {
367
return "parallel vertical";
369
elsif ( $dy10z and not $dy32z ) { # First line horizontal.
371
$x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
373
elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
375
$x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
377
elsif ( $dx10z and not $dx32z ) { # First line vertical.
379
$y = $y2 + $dyx32 * ( $x - $x2 );
381
elsif ( not $dx10z and $dx32z ) { # Second line vertical.
383
$y = $y0 + $dyx10 * ( $x - $x0 );
385
elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) {
386
# The slopes are suspiciously close to each other.
387
# Either we have parallel collinear or just parallel lines.
389
# The bounding box checks have already weeded the cases
390
# "parallel horizontal" and "parallel vertical" away.
392
my $ya = $y0 - $dyx10 * $x0;
393
my $yb = $y2 - $dyx32 * $x2;
395
return "parallel collinear" if abs( $ya - $yb ) < epsilon;
399
# None of the special cases matched.
400
# We have a "honest" line intersection.
402
$x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
403
$y = $y0 + $dyx10 * ($x - $x0);
406
my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
407
my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
409
return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
412
# http://paulbourke.net/geometry/lineline2d/
413
sub _line_intersection2 {
414
my ($line1, $line2) = @_;
416
my $denom = ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[B][X] - $line1->[A][X])
417
- ($line2->[B][X] - $line2->[A][X]) * ($line1->[B][Y] - $line1->[A][Y]);
418
my $numerA = ($line2->[B][X] - $line2->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
419
- ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
420
my $numerB = ($line1->[B][X] - $line1->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
421
- ($line1->[B][Y] - $line1->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
423
# are the lines coincident?
424
if (abs($numerA) < epsilon && abs($numerB) < epsilon && abs($denom) < epsilon) {
425
return Slic3r::Point->new(
426
($line1->[A][X] + $line1->[B][X]) / 2,
427
($line1->[A][Y] + $line1->[B][Y]) / 2,
431
# are the lines parallel?
432
if (abs($denom) < epsilon) {
436
# is the intersection along the segments?
437
my $muA = $numerA / $denom;
438
my $muB = $numerB / $denom;
439
if ($muA < 0 || $muA > 1 || $muB < 0 || $muB > 1) {
443
return Slic3r::Point->new(
444
$line1->[A][X] + $muA * ($line1->[B][X] - $line1->[A][X]),
445
$line1->[A][Y] + $muA * ($line1->[B][Y] - $line1->[A][Y]),
453
my @x = map $_->x, @$points;
454
my @y = map $_->y, @$points; #,,
455
my @bb = (undef, undef, undef, undef);
457
$bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1];
458
$bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2];
459
$bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1];
460
$bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2];
463
return @bb[X1,Y1,X2,Y2];
466
sub bounding_box_center {
467
my ($bounding_box) = @_;
468
return Slic3r::Point->new(
469
($bounding_box->[X2] + $bounding_box->[X1]) / 2,
470
($bounding_box->[Y2] + $bounding_box->[Y1]) / 2,
475
my @bounding_box = bounding_box(@_);
477
($bounding_box[X2] - $bounding_box[X1]),
478
($bounding_box[Y2] - $bounding_box[Y1]),
482
# bounding_box_intersect($d, @a, @b)
483
# Return true if the given bounding boxes @a and @b intersect
484
# in $d dimensions. Used by line_intersection().
485
sub bounding_box_intersect {
486
my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
487
my @aa = splice( @bb, 0, 2 * $d ); # The first box.
488
# (@bb is the second one.)
490
# Must intersect in all dimensions.
491
for ( my $i_min = 0; $i_min < $d; $i_min++ ) {
492
my $i_max = $i_min + $d; # The index for the maximum.
493
return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ];
494
return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ];
501
sub bounding_box_3D {
504
my @extents = (map [undef, undef], X,Y,Z);
505
foreach my $point (@$points) {
507
$extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
508
$extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
517
my @extents = bounding_box_3D($points);
518
return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
521
# this assumes a CCW rotation from $p2 to $p3 around $p1
523
my ($p1, $p2, $p3) = @_;
526
my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
527
- atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
529
# we only want to return only positive angles
530
return $angle <= 0 ? $angle + 2*PI() : $angle;
533
sub polyline_remove_short_segments {
534
my ($points, $min_length, $isPolygon) = @_;
535
for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) {
536
if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) {
537
# we can remove $points->[$i]
538
splice @$points, $i, 1;
544
sub douglas_peucker {
545
my ($points, $tolerance) = @_;
546
no warnings "recursion";
551
for my $i (1..$#$points) {
552
my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1]));
558
if ($dmax >= $tolerance) {
559
my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
561
@$dp1[0..($#$dp1-1)],
562
@{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
565
$results = [ $points->[0], $points->[-1] ];
570
sub douglas_peucker2 {
571
my ($points, $tolerance) = @_;
574
my $floater = $#$points;
578
push @stack, [$anchor, $floater];
580
($anchor, $floater) = @{pop @stack};
582
# initialize line segment
583
my ($anchor_x, $anchor_y, $seg_len);
584
if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) {
585
$anchor_x = $points->[$floater][X] - $points->[$anchor][X];
586
$anchor_y = $points->[$floater][Y] - $points->[$anchor][Y];
587
$seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2));
588
# get the unit vector
589
$anchor_x /= $seg_len;
590
$anchor_y /= $seg_len;
592
$anchor_x = $anchor_y = $seg_len = 0;
597
my $farthest = $anchor + 1;
598
for my $i (($anchor + 1) .. $floater) {
601
my $vecX = $points->[$i][X] - $points->[$anchor][X];
602
my $vecY = $points->[$i][Y] - $points->[$anchor][Y];
603
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
605
my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
607
$dist_to_seg = $seg_len;
610
$vecX = $points->[$i][X] - $points->[$floater][X];
611
$vecY = $points->[$i][Y] - $points->[$floater][Y];
612
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
614
$proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
616
$dist_to_seg = $seg_len
617
} else { # calculate perpendicular distance to line (pythagorean theorem):
618
$dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2)));
620
if ($max_dist < $dist_to_seg) {
621
$max_dist = $dist_to_seg;
627
if ($max_dist <= $tolerance) { # use line segment
628
$keep{$_} = 1 for $anchor, $floater;
630
push @stack, [$anchor, $farthest];
631
push @stack, [$farthest, $floater];
635
return [ map $points->[$_], sort keys %keep ];
639
my ($total_parts, $partx, $party, $dist, $bb) = @_;
642
my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
643
return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
646
# use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
652
my $size = $bb->size;
653
($areax, $areay) = @$size[X,Y];
655
# bogus area size, large enough not to trigger the error below
656
$areax = $partx * $total_parts;
657
$areay = $party * $total_parts;
660
# this is how many cells we have available into which to put parts
661
my $cellw = int(($areax + $dist) / $partx);
662
my $cellh = int(($areay + $dist) / $party);
664
die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
666
# width and height of space used by cells
667
my $w = $cellw * $partx;
668
my $h = $cellh * $party;
670
# left and right border positions of space used by cells
671
my $l = ($areax - $w) / 2;
674
# top and bottom border positions
675
my $t = ($areay - $h) / 2;
678
# list of cells, sorted by distance from center
681
# work out distance for all cells, sort into list
682
for my $i (0..$cellw-1) {
683
for my $j (0..$cellh-1) {
684
my $cx = $linint->($i + 0.5, 0, $cellw, $l, $r);
685
my $cy = $linint->($j + 0.5, 0, $cellh, $t, $b);
687
my $xd = abs(($areax / 2) - $cx);
688
my $yd = abs(($areay / 2) - $cy);
691
location => [$cx, $cy],
693
distance => $xd * $xd + $yd * $yd - abs(($cellw / 2) - ($i + 0.5)),
696
BINARYINSERTIONSORT: {
697
my $index = $c->{distance};
699
my $high = @cellsorder;
700
while ($low < $high) {
701
my $mid = ($low + (($high - $low) / 2)) | 0;
702
my $midval = $cellsorder[$mid]->[0];
704
if ($midval < $index) {
706
} elsif ($midval > $index) {
709
splice @cellsorder, $mid, 0, [$index, $c];
710
last BINARYINSERTIONSORT;
713
splice @cellsorder, $low, 0, [$index, $c];
718
# the extents of cells actually used by objects
719
my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
721
# now find cells actually used by objects, map out the extents so we can position correctly
722
for my $i (1..$total_parts) {
723
my $c = $cellsorder[$i - 1];
724
my $cx = $c->[1]->{index}->[0];
725
my $cy = $c->[1]->{index}->[1];
730
$rx = $cx if $cx > $rx;
731
$lx = $cx if $cx < $lx;
732
$by = $cy if $cy > $by;
733
$ty = $cy if $cy < $ty;
736
# now we actually place objects into cells, positioned such that the left and bottom borders are at 0
738
for (1..$total_parts) {
739
my $c = shift @cellsorder;
740
my $cx = $c->[1]->{index}->[0] - $lx;
741
my $cy = $c->[1]->{index}->[1] - $ty;
743
push @positions, [$cx * $partx, $cy * $party];
747
$_->[X] += $bb->x_min for @positions;
748
$_->[Y] += $bb->y_min for @positions;