~ubuntu-branches/ubuntu/utopic/slic3r/utopic

« back to all changes in this revision

Viewing changes to lib/Slic3r/Geometry.pm

  • Committer: Package Import Robot
  • Author(s): Chow Loong Jin
  • Date: 2014-06-17 01:27:26 UTC
  • Revision ID: package-import@ubuntu.com-20140617012726-2wrs4zdo251nr4vg
Tags: upstream-1.1.4+dfsg
ImportĀ upstreamĀ versionĀ 1.1.4+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package Slic3r::Geometry;
 
2
use strict;
 
3
use warnings;
 
4
 
 
5
require Exporter;
 
6
our @ISA = qw(Exporter);
 
7
our @EXPORT_OK = qw(
 
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
 
14
    deg2rad rad2deg
 
15
    rotate_points move_points
 
16
    dot perp polygon_points_visibility
 
17
    line_intersection bounding_box bounding_box_intersect
 
18
    angle3points
 
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
 
24
);
 
25
 
 
26
 
 
27
use constant PI => 4 * atan2(1, 1);
 
28
use constant A => 0;
 
29
use constant B => 1;
 
30
use constant X => 0;
 
31
use constant Y => 1;
 
32
use constant Z => 2;
 
33
use constant X1 => 0;
 
34
use constant Y1 => 1;
 
35
use constant X2 => 2;
 
36
use constant Y2 => 3;
 
37
use constant Z1 => 4;
 
38
use constant Z2 => 5;
 
39
use constant MIN => 0;
 
40
use constant MAX => 1;
 
41
our $parallel_degrees_limit = abs(deg2rad(0.1));
 
42
 
 
43
sub epsilon () { 1E-4 }
 
44
sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
 
45
 
 
46
sub scale   ($) { $_[0] / &Slic3r::SCALING_FACTOR }
 
47
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
 
48
 
 
49
sub tan {
 
50
    my ($angle) = @_;
 
51
    return (sin $angle) / (cos $angle);
 
52
}
 
53
 
 
54
sub slope {
 
55
    my ($line) = @_;
 
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]);
 
58
}
 
59
 
 
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
 
62
# the segment
 
63
sub line_point_belongs_to_segment {
 
64
    my ($point, $segment) = @_;
 
65
    
 
66
    #printf "   checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
 
67
    #    @$point, map @$_, @$segment;
 
68
    
 
69
    my @segment_extents = (
 
70
        [ sort { $a <=> $b } map $_->[X], @$segment ],
 
71
        [ sort { $a <=> $b } map $_->[Y], @$segment ],
 
72
    );
 
73
    
 
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);
 
76
    return 1;
 
77
}
 
78
 
 
79
sub points_coincide {
 
80
    my ($p1, $p2) = @_;
 
81
    return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
 
82
    return 0;
 
83
}
 
84
 
 
85
sub distance_between_points {
 
86
    my ($p1, $p2) = @_;
 
87
    return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
 
88
}
 
89
 
 
90
# this will check whether a point is in a polygon regardless of polygon orientation
 
91
sub point_in_polygon {
 
92
    my ($point, $polygon) = @_;
 
93
    
 
94
    my ($x, $y) = @$point;
 
95
    my $n = @$polygon;
 
96
    my @x = map $_->[X], @$polygon;
 
97
    my @y = map $_->[Y], @$polygon;
 
98
    
 
99
    # Derived from the comp.graphics.algorithms FAQ,
 
100
    # courtesy of Wm. Randolph Franklin
 
101
    my ($i, $j);
 
102
    my $side = 0;                           # 0 = outside; 1 = inside
 
103
    for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
 
104
        if (
 
105
            # If the y is between the (y-) borders...
 
106
            ($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
 
107
            and
 
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])
 
111
        ) {
 
112
            $side = not $side;  # Jump the fence
 
113
        }
 
114
    }
 
115
    
 
116
    # if point is not in polygon, let's check whether it belongs to the contour
 
117
    if (!$side && 0) {
 
118
        return 1 if polygon_segment_having_point($polygon, $point);
 
119
    }
 
120
    
 
121
    return $side;
 
122
}
 
123
 
 
124
sub point_in_segment {
 
125
    my ($point, $line) = @_;
 
126
    
 
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];
 
131
    
 
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);
 
135
    
 
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;
 
139
    }
 
140
    
 
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;
 
145
}
 
146
 
 
147
sub segment_in_segment {
 
148
    my ($needle, $haystack) = @_;
 
149
    
 
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);
 
152
}
 
153
 
 
154
sub polyline_lines {
 
155
    my ($polyline) = @_;
 
156
    my @points = @$polyline;
 
157
    return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
 
158
}
 
159
 
 
160
sub polygon_lines {
 
161
    my ($polygon) = @_;
 
162
    return polyline_lines([ @$polygon, $polygon->[0] ]);
 
163
}
 
164
 
 
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) = @_;
 
168
    
 
169
    my $point = [ @$p1 ];
 
170
    
 
171
    my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
 
172
    for (X, Y) {
 
173
        if ($p1->[$_] != $p2->[$_]) {
 
174
            $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
 
175
        }
 
176
    }
 
177
    
 
178
    return Slic3r::Point->new(@$point);
 
179
}
 
180
 
 
181
# given a $polygon, return the (first) segment having $point
 
182
sub polygon_segment_having_point {
 
183
    my ($polygon, $point) = @_;
 
184
    
 
185
    foreach my $line (@{ $polygon->lines }) {
 
186
        return $line if point_in_segment($point, $line);
 
187
    }
 
188
    return undef;
 
189
}
 
190
 
 
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);
 
196
    }
 
197
    return 0;
 
198
}
 
199
 
 
200
# polygon must be simple (non complex) and ccw
 
201
sub polygon_is_convex {
 
202
    my ($points) = @_;
 
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;
 
206
    }
 
207
    return 1;
 
208
}
 
209
 
 
210
sub deg2rad {
 
211
    my ($degrees) = @_;
 
212
    return PI() * $degrees / 180;
 
213
}
 
214
 
 
215
sub rad2deg {
 
216
    my ($rad) = @_;
 
217
    return $rad / PI() * 180;
 
218
}
 
219
 
 
220
sub rad2deg_dir {
 
221
    my ($rad) = @_;
 
222
    $rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2);
 
223
    $rad += PI if $rad < 0;
 
224
    return rad2deg($rad);
 
225
}
 
226
 
 
227
sub rotate_points {
 
228
    my ($radians, $center, @points) = @_;
 
229
    $center //= [0,0];
 
230
    return map {
 
231
        [
 
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]),
 
234
        ]
 
235
    } @points;
 
236
}
 
237
 
 
238
sub move_points {
 
239
    my ($shift, @points) = @_;
 
240
    return map {
 
241
        my @p = @$_;
 
242
        Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]);
 
243
    } @points;
 
244
}
 
245
 
 
246
sub move_points_3D {
 
247
    my ($shift, @points) = @_;
 
248
    return map [
 
249
        $shift->[X] + $_->[X],
 
250
        $shift->[Y] + $_->[Y],
 
251
        $shift->[Z] + $_->[Z],
 
252
    ], @points;
 
253
}
 
254
 
 
255
sub normal {
 
256
    my ($line1, $line2) = @_;
 
257
    
 
258
    return [
 
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]),
 
262
    ];
 
263
}
 
264
 
 
265
sub triangle_normal {
 
266
    my ($v1, $v2, $v3) = @_;
 
267
    
 
268
    my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ];
 
269
    my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ];
 
270
    
 
271
    return normal($u, $v);
 
272
}
 
273
 
 
274
sub normalize {
 
275
    my ($line) = @_;
 
276
    
 
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 ];
 
280
}
 
281
 
 
282
# 2D dot product
 
283
sub dot {
 
284
    my ($u, $v) = @_;
 
285
    return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
 
286
}
 
287
 
 
288
# 2D perp product
 
289
sub perp {
 
290
    my ($u, $v) = @_;
 
291
    return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
 
292
}
 
293
 
 
294
sub polygon_points_visibility {
 
295
    my ($polygon, $p1, $p2) = @_;
 
296
    
 
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;
 
301
        return 0;
 
302
    }
 
303
    
 
304
    return 1;
 
305
}
 
306
 
 
307
sub line_intersects_any {
 
308
    my ($line, $lines) = @_;
 
309
    for (@$lines) {
 
310
        return 1 if line_intersection($line, $_, 1);
 
311
    }
 
312
    return 0;
 
313
}
 
314
 
 
315
sub line_intersection {
 
316
    my ($line1, $line2, $require_crossing) = @_;
 
317
    $require_crossing ||= 0;
 
318
    
 
319
    my $intersection = _line_intersection(map @$_, @$line1, @$line2);
 
320
    return (ref $intersection && $intersection->[1] == $require_crossing) 
 
321
        ? $intersection->[0] 
 
322
        : undef;
 
323
}
 
324
 
 
325
sub collinear {
 
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));
 
331
    
 
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 );
 
336
    }
 
337
    
 
338
    return 1;
 
339
}
 
340
 
 
341
sub _line_intersection {
 
342
  my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
 
343
 
 
344
  my ($x, $y);  # The as-yet-undetermined intersection point.
 
345
 
 
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;
 
350
 
 
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;
 
355
 
 
356
  my $dyx10;                            # The slopes.
 
357
  my $dyx32;
 
358
  
 
359
  $dyx10 = $dy10 / $dx10 unless $dx10z;
 
360
  $dyx32 = $dy32 / $dx32 unless $dx32z;
 
361
 
 
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.
 
365
 
 
366
  unless ( defined $dyx10 or defined $dyx32 ) {
 
367
    return "parallel vertical";
 
368
  }
 
369
  elsif ( $dy10z and not $dy32z ) { # First line horizontal.
 
370
    $y = $y0;
 
371
    $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
 
372
  }
 
373
  elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
 
374
    $y = $y2;
 
375
    $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
 
376
  }
 
377
  elsif ( $dx10z and not $dx32z ) { # First line vertical.
 
378
    $x = $x0;
 
379
    $y = $y2 + $dyx32 * ( $x - $x2 );
 
380
  }
 
381
  elsif ( not $dx10z and $dx32z ) { # Second line vertical.
 
382
    $x = $x2;
 
383
    $y = $y0 + $dyx10 * ( $x - $x0 );
 
384
  }
 
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.
 
388
 
 
389
    # The bounding box checks have already weeded the cases
 
390
    # "parallel horizontal" and "parallel vertical" away.
 
391
 
 
392
    my $ya = $y0 - $dyx10 * $x0;
 
393
    my $yb = $y2 - $dyx32 * $x2;
 
394
    
 
395
    return "parallel collinear" if abs( $ya - $yb ) < epsilon;
 
396
    return "parallel";
 
397
  }
 
398
  else {
 
399
    # None of the special cases matched.
 
400
    # We have a "honest" line intersection.
 
401
 
 
402
    $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
 
403
    $y = $y0 + $dyx10 * ($x - $x0);
 
404
  }
 
405
 
 
406
  my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
 
407
  my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
 
408
 
 
409
  return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
 
410
}
 
411
 
 
412
# http://paulbourke.net/geometry/lineline2d/
 
413
sub _line_intersection2 {
 
414
    my ($line1, $line2) = @_;
 
415
    
 
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]);
 
422
    
 
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,
 
428
        );
 
429
    }
 
430
    
 
431
    # are the lines parallel?
 
432
    if (abs($denom) < epsilon) {
 
433
        return undef;
 
434
    }
 
435
    
 
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) {
 
440
        return undef;
 
441
    }
 
442
    
 
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]),
 
446
    );
 
447
}
 
448
 
 
449
# 2D
 
450
sub bounding_box {
 
451
    my ($points) = @_;
 
452
    
 
453
    my @x = map $_->x, @$points;
 
454
    my @y = map $_->y, @$points;    #,,
 
455
    my @bb = (undef, undef, undef, undef);
 
456
    for (0..$#x) {
 
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];
 
461
    }
 
462
    
 
463
    return @bb[X1,Y1,X2,Y2];
 
464
}
 
465
 
 
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,
 
471
    );
 
472
}
 
473
 
 
474
sub size_2D {
 
475
    my @bounding_box = bounding_box(@_);
 
476
    return (
 
477
        ($bounding_box[X2] - $bounding_box[X1]),
 
478
        ($bounding_box[Y2] - $bounding_box[Y1]),
 
479
    );
 
480
}
 
481
 
 
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.)
 
489
    
 
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 ];
 
495
    }
 
496
    
 
497
    return 1;
 
498
}
 
499
 
 
500
# 3D
 
501
sub bounding_box_3D {
 
502
    my ($points) = @_;
 
503
    
 
504
    my @extents = (map [undef, undef], X,Y,Z);
 
505
    foreach my $point (@$points) {
 
506
        for (X,Y,Z) {
 
507
            $extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
 
508
            $extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
 
509
        }
 
510
    }
 
511
    return @extents;
 
512
}
 
513
 
 
514
sub size_3D {
 
515
    my ($points) = @_;
 
516
    
 
517
    my @extents = bounding_box_3D($points);
 
518
    return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
 
519
}
 
520
 
 
521
# this assumes a CCW rotation from $p2 to $p3 around $p1
 
522
sub angle3points {
 
523
    my ($p1, $p2, $p3) = @_;
 
524
    # p1 is the center
 
525
    
 
526
    my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
 
527
              - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
 
528
    
 
529
    # we only want to return only positive angles
 
530
    return $angle <= 0 ? $angle + 2*PI() : $angle;
 
531
}
 
532
 
 
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;
 
539
            $i--;
 
540
        }
 
541
    }
 
542
}
 
543
 
 
544
sub douglas_peucker {
 
545
    my ($points, $tolerance) = @_;
 
546
    no warnings "recursion";
 
547
    
 
548
    my $results = [];
 
549
    my $dmax = 0;
 
550
    my $index = 0;
 
551
    for my $i (1..$#$points) {
 
552
        my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1]));
 
553
        if ($d > $dmax) {
 
554
            $index = $i;
 
555
            $dmax = $d;
 
556
        }
 
557
    }
 
558
    if ($dmax >= $tolerance) {
 
559
        my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
 
560
        $results = [
 
561
            @$dp1[0..($#$dp1-1)],
 
562
            @{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
 
563
        ];
 
564
    } else {
 
565
        $results = [ $points->[0], $points->[-1] ];
 
566
    }
 
567
    return $results;
 
568
}
 
569
 
 
570
sub douglas_peucker2 {
 
571
    my ($points, $tolerance) = @_;
 
572
    
 
573
    my $anchor = 0;
 
574
    my $floater = $#$points;
 
575
    my @stack = ();
 
576
    my %keep = ();
 
577
    
 
578
    push @stack, [$anchor, $floater];
 
579
    while (@stack) {
 
580
        ($anchor, $floater) = @{pop @stack};
 
581
        
 
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;
 
591
        } else {
 
592
            $anchor_x = $anchor_y = $seg_len = 0;
 
593
        }
 
594
        
 
595
        # inner loop:
 
596
        my $max_dist = 0;
 
597
        my $farthest = $anchor + 1;
 
598
        for my $i (($anchor + 1) .. $floater) {
 
599
            my $dist_to_seg = 0;
 
600
            # compare to anchor
 
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));
 
604
            # dot product:
 
605
            my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
 
606
            if ($proj < 0) {
 
607
                $dist_to_seg = $seg_len;
 
608
            } else {
 
609
                # compare to floater
 
610
                $vecX = $points->[$i][X] - $points->[$floater][X];
 
611
                $vecY = $points->[$i][Y] - $points->[$floater][Y];
 
612
                $seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
 
613
                # dot product:
 
614
                $proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
 
615
                if ($proj < 0) {
 
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)));
 
619
                }
 
620
                if ($max_dist < $dist_to_seg) {
 
621
                    $max_dist = $dist_to_seg;
 
622
                    $farthest = $i;
 
623
                }
 
624
            }
 
625
        }
 
626
        
 
627
        if ($max_dist <= $tolerance) { # use line segment
 
628
            $keep{$_} = 1 for $anchor, $floater;
 
629
        } else {
 
630
            push @stack, [$anchor, $farthest];
 
631
            push @stack, [$farthest, $floater];
 
632
        }
 
633
    }
 
634
    
 
635
    return [ map $points->[$_], sort keys %keep ];
 
636
}
 
637
 
 
638
sub arrange {
 
639
    my ($total_parts, $partx, $party, $dist, $bb) = @_;
 
640
    
 
641
    my $linint = sub {
 
642
        my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
 
643
        return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
 
644
    };
 
645
    
 
646
    # use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
 
647
    $partx += $dist;
 
648
    $party += $dist;
 
649
    
 
650
    my ($areax, $areay);
 
651
    if (defined $bb) {
 
652
        my $size = $bb->size;
 
653
        ($areax, $areay) = @$size[X,Y];
 
654
    } else {
 
655
        # bogus area size, large enough not to trigger the error below
 
656
        $areax = $partx * $total_parts;
 
657
        $areay = $party * $total_parts;
 
658
    }
 
659
    
 
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);
 
663
    
 
664
    die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
 
665
    
 
666
    # width and height of space used by cells
 
667
    my $w = $cellw * $partx;
 
668
    my $h = $cellh * $party;
 
669
    
 
670
    # left and right border positions of space used by cells
 
671
    my $l = ($areax - $w) / 2;
 
672
    my $r = $l + $w;
 
673
    
 
674
    # top and bottom border positions
 
675
    my $t = ($areay - $h) / 2;
 
676
    my $b = $t + $h;
 
677
    
 
678
    # list of cells, sorted by distance from center
 
679
    my @cellsorder;
 
680
    
 
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);
 
686
            
 
687
            my $xd = abs(($areax / 2) - $cx);
 
688
            my $yd = abs(($areay / 2) - $cy);
 
689
            
 
690
            my $c = {
 
691
                location => [$cx, $cy],
 
692
                index => [$i, $j],
 
693
                distance => $xd * $xd + $yd * $yd - abs(($cellw / 2) - ($i + 0.5)),
 
694
            };
 
695
            
 
696
            BINARYINSERTIONSORT: {
 
697
                my $index = $c->{distance};
 
698
                my $low = 0;
 
699
                my $high = @cellsorder;
 
700
                while ($low < $high) {
 
701
                    my $mid = ($low + (($high - $low) / 2)) | 0;
 
702
                    my $midval = $cellsorder[$mid]->[0];
 
703
                    
 
704
                    if ($midval < $index) {
 
705
                        $low = $mid + 1;
 
706
                    } elsif ($midval > $index) {
 
707
                        $high = $mid;
 
708
                    } else {
 
709
                        splice @cellsorder, $mid, 0, [$index, $c];
 
710
                        last BINARYINSERTIONSORT;
 
711
                    }
 
712
                }
 
713
                splice @cellsorder, $low, 0, [$index, $c];
 
714
            }
 
715
        }
 
716
    }
 
717
    
 
718
    # the extents of cells actually used by objects
 
719
    my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
 
720
 
 
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];
 
726
        if ($i == 1) {
 
727
            $lx = $rx = $cx;
 
728
            $ty = $by = $cy;
 
729
        } else {
 
730
            $rx = $cx if $cx > $rx;
 
731
            $lx = $cx if $cx < $lx;
 
732
            $by = $cy if $cy > $by;
 
733
            $ty = $cy if $cy < $ty;
 
734
        }
 
735
    }
 
736
    # now we actually place objects into cells, positioned such that the left and bottom borders are at 0
 
737
    my @positions = ();
 
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;
 
742
 
 
743
        push @positions, [$cx * $partx, $cy * $party];
 
744
    }
 
745
    
 
746
    if (defined $bb) {
 
747
        $_->[X] += $bb->x_min for @positions;
 
748
        $_->[Y] += $bb->y_min for @positions;
 
749
    }
 
750
    return @positions;
 
751
}
 
752
 
 
753
1;