2013-07-16 07:49:34 +00:00
|
|
|
package Slic3r::ExPolygon;
|
2011-10-15 09:36:05 +00:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
# an ExPolygon is a polygon with holes
|
|
|
|
|
2012-04-09 09:04:32 +00:00
|
|
|
use Boost::Geometry::Utils;
|
2013-03-14 13:27:08 +00:00
|
|
|
use List::Util qw(first);
|
2011-12-30 16:17:37 +00:00
|
|
|
use Math::Geometry::Voronoi;
|
2013-03-31 17:50:22 +00:00
|
|
|
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line epsilon);
|
2013-08-26 23:26:44 +00:00
|
|
|
use Slic3r::Geometry::Clipper qw(union_ex);
|
2013-07-07 13:17:09 +00:00
|
|
|
|
2013-01-27 23:02:34 +00:00
|
|
|
sub wkt {
|
|
|
|
my $self = shift;
|
|
|
|
return sprintf "POLYGON(%s)",
|
|
|
|
join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self;
|
|
|
|
}
|
|
|
|
|
2013-07-29 11:36:22 +00:00
|
|
|
sub dump_perl {
|
|
|
|
my $self = shift;
|
|
|
|
return sprintf "[%s]",
|
|
|
|
join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self;
|
|
|
|
}
|
|
|
|
|
2011-10-15 09:36:05 +00:00
|
|
|
sub offset {
|
|
|
|
my $self = shift;
|
2013-07-16 22:48:29 +00:00
|
|
|
return Slic3r::Geometry::Clipper::offset(\@$self, @_);
|
2012-08-25 18:04:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub offset_ex {
|
|
|
|
my $self = shift;
|
2013-07-16 18:09:53 +00:00
|
|
|
return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_);
|
2011-11-16 15:35:20 +00:00
|
|
|
}
|
|
|
|
|
2012-08-25 12:23:46 +00:00
|
|
|
sub noncollapsing_offset_ex {
|
|
|
|
my $self = shift;
|
|
|
|
my ($distance, @params) = @_;
|
|
|
|
|
|
|
|
return $self->offset_ex($distance + 1, @params);
|
|
|
|
}
|
|
|
|
|
2011-11-13 17:14:02 +00:00
|
|
|
sub encloses_point {
|
|
|
|
my $self = shift;
|
|
|
|
my ($point) = @_;
|
2013-07-16 18:09:53 +00:00
|
|
|
return Boost::Geometry::Utils::point_covered_by_polygon($point->pp, $self->pp);
|
2011-11-13 17:14:02 +00:00
|
|
|
}
|
|
|
|
|
2012-06-26 22:11:46 +00:00
|
|
|
# A version of encloses_point for use when hole borders do not matter.
|
2013-06-20 16:48:11 +00:00
|
|
|
# Useful because point_on_segment is probably slower (this was true
|
|
|
|
# before the switch to Boost.Geometry, not sure about now)
|
2012-06-26 22:11:46 +00:00
|
|
|
sub encloses_point_quick {
|
|
|
|
my $self = shift;
|
|
|
|
my ($point) = @_;
|
2013-07-16 18:09:53 +00:00
|
|
|
return Boost::Geometry::Utils::point_within_polygon($point->pp, $self->pp);
|
2012-06-26 22:11:46 +00:00
|
|
|
}
|
|
|
|
|
2012-03-04 10:26:11 +00:00
|
|
|
sub encloses_line {
|
|
|
|
my $self = shift;
|
2012-08-24 16:59:23 +00:00
|
|
|
my ($line, $tolerance) = @_;
|
2012-03-04 10:26:11 +00:00
|
|
|
my $clip = $self->clip_line($line);
|
2012-08-24 16:59:23 +00:00
|
|
|
if (!defined $tolerance) {
|
|
|
|
# optimization
|
2013-07-16 15:13:01 +00:00
|
|
|
return @$clip == 1 && same_line($clip->[0]->pp, $line->pp);
|
2012-08-24 16:59:23 +00:00
|
|
|
} else {
|
2013-07-16 15:13:01 +00:00
|
|
|
return @$clip == 1 && abs(Boost::Geometry::Utils::linestring_length($clip->[0]->pp) - $line->length) < $tolerance;
|
2012-08-24 16:59:23 +00:00
|
|
|
}
|
2012-03-04 10:26:11 +00:00
|
|
|
}
|
|
|
|
|
2011-11-13 17:14:02 +00:00
|
|
|
sub bounding_box {
|
|
|
|
my $self = shift;
|
2013-06-16 10:21:25 +00:00
|
|
|
return $self->contour->bounding_box;
|
2012-02-25 13:46:21 +00:00
|
|
|
}
|
|
|
|
|
2011-11-13 17:14:02 +00:00
|
|
|
sub clip_line {
|
|
|
|
my $self = shift;
|
2012-02-25 21:15:34 +00:00
|
|
|
my ($line) = @_; # line must be a Slic3r::Line object
|
2011-11-13 17:14:02 +00:00
|
|
|
|
2013-07-16 15:13:01 +00:00
|
|
|
return [
|
|
|
|
map Slic3r::Line->new(@$_),
|
|
|
|
@{Boost::Geometry::Utils::polygon_multi_linestring_intersection($self->pp, [$line->pp])}
|
|
|
|
];
|
2011-11-13 17:14:02 +00:00
|
|
|
}
|
|
|
|
|
2013-07-20 10:22:41 +00:00
|
|
|
sub simplify_as_polygons {
|
2012-02-25 13:46:21 +00:00
|
|
|
my $self = shift;
|
2013-03-16 17:42:56 +00:00
|
|
|
my ($tolerance) = @_;
|
|
|
|
|
|
|
|
# it would be nice to have a multilinestring_simplify method in B::G::U
|
2013-08-08 00:10:34 +00:00
|
|
|
return @{Slic3r::Geometry::Clipper::simplify_polygons(
|
2013-07-15 20:57:22 +00:00
|
|
|
[ map Boost::Geometry::Utils::linestring_simplify($_, $tolerance), @{$self->pp} ],
|
2013-08-08 00:10:34 +00:00
|
|
|
)};
|
2013-07-20 10:22:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub simplify {
|
|
|
|
my $self = shift;
|
|
|
|
my ($tolerance) = @_;
|
|
|
|
|
|
|
|
return @{ Slic3r::Geometry::Clipper::union_ex([ $self->simplify_as_polygons($tolerance) ]) };
|
2012-02-25 13:46:21 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 16:17:37 +00:00
|
|
|
# this method only works for expolygons having only a contour or
|
|
|
|
# a contour and a hole, and not being thicker than the supplied
|
|
|
|
# width. it returns a polyline or a polygon
|
|
|
|
sub medial_axis {
|
2013-09-16 18:22:47 +00:00
|
|
|
my ($self, $width) = @_;
|
|
|
|
return $self->_medial_axis_clip($width);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _medial_axis_clip {
|
|
|
|
my ($self, $width) = @_;
|
|
|
|
|
|
|
|
my $grow = sub {
|
|
|
|
my ($line, $distance) = @_;
|
|
|
|
my ($a, $b) = @$line;
|
|
|
|
my $dx = $a->x - $b->x;
|
|
|
|
my $dy = $a->y - $b->y; #-
|
|
|
|
my $dist = sqrt($dx*$dx + $dy*$dy);
|
|
|
|
$dx /= $dist;
|
|
|
|
$dy /= $dist;
|
|
|
|
return Slic3r::Polygon->new(
|
|
|
|
Slic3r::Point->new($a->x + $distance*$dy, $a->y - $distance*$dx), #--
|
|
|
|
Slic3r::Point->new($b->x + $distance*$dy, $b->y - $distance*$dx), #--
|
|
|
|
Slic3r::Point->new($b->x - $distance*$dy, $b->y + $distance*$dx), #++
|
|
|
|
Slic3r::Point->new($a->x - $distance*$dy, $a->y + $distance*$dx), #++
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
my @result = ();
|
|
|
|
my $covered = [];
|
|
|
|
foreach my $polygon (@$self) {
|
|
|
|
my @polylines = ();
|
|
|
|
foreach my $line (@{$polygon->lines}) {
|
|
|
|
my $clipped = Boost::Geometry::Utils::multi_linestring_multi_polygon_difference([$line->pp], [ map $_->pp, @{union_ex($covered)} ]);
|
|
|
|
@$clipped = grep $_->length > $width/10, map Slic3r::Polyline->new(@$_), @$clipped;
|
|
|
|
push @$covered, map $grow->($_, $width*1.1), @$clipped;
|
|
|
|
if (@polylines && @$clipped && $clipped->[0]->first_point->distance_to($polylines[-1]->last_point) <= $width/10) {
|
|
|
|
$polylines[-1]->append_polyline(shift @$clipped);
|
|
|
|
}
|
|
|
|
push @polylines, @$clipped;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $polyline (@polylines) {
|
|
|
|
next if $polyline->length <= $width/10;
|
|
|
|
if ($polyline->first_point->coincides_with($polyline->last_point)) {
|
|
|
|
next if @$polyline == 2;
|
|
|
|
$polyline->pop_back;
|
|
|
|
push @result, Slic3r::Polygon->new(@$polyline);
|
|
|
|
} else {
|
|
|
|
push @result, $polyline;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @result;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _medial_axis_voronoi {
|
|
|
|
my ($self, $width) = @_;
|
2011-12-30 16:17:37 +00:00
|
|
|
|
2013-09-16 17:15:30 +00:00
|
|
|
my $voronoi;
|
|
|
|
{
|
|
|
|
my @points = ();
|
|
|
|
foreach my $polygon (@$self) {
|
|
|
|
{
|
|
|
|
my $p = $polygon->pp;
|
|
|
|
Slic3r::Geometry::polyline_remove_short_segments($p, $width / 2);
|
|
|
|
$polygon = Slic3r::Polygon->new(@$p);
|
|
|
|
}
|
|
|
|
|
|
|
|
# subdivide polygon segments so that we don't have anyone of them
|
|
|
|
# being longer than $width / 2
|
|
|
|
$polygon = $polygon->subdivide($width/2);
|
|
|
|
|
|
|
|
push @points, map $_->pp, @$polygon;
|
2013-08-26 14:25:42 +00:00
|
|
|
}
|
2013-09-16 17:15:30 +00:00
|
|
|
$voronoi = Math::Geometry::Voronoi->new(points => \@points);
|
2011-12-30 16:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$voronoi->compute;
|
2013-09-16 17:15:30 +00:00
|
|
|
my $vertices = $voronoi->vertices;
|
2011-12-30 16:17:37 +00:00
|
|
|
|
|
|
|
my @skeleton_lines = ();
|
2013-09-16 17:15:30 +00:00
|
|
|
foreach my $edge (@{ $voronoi->edges }) {
|
2011-12-30 16:17:37 +00:00
|
|
|
# ignore lines going to infinite
|
|
|
|
next if $edge->[1] == -1 || $edge->[2] == -1;
|
|
|
|
|
|
|
|
my ($a, $b);
|
2013-08-08 00:10:34 +00:00
|
|
|
$a = Slic3r::Point->new(@{$vertices->[$edge->[1]]});
|
|
|
|
$b = Slic3r::Point->new(@{$vertices->[$edge->[2]]});
|
2012-06-26 22:11:46 +00:00
|
|
|
next if !$self->encloses_point_quick($a) || !$self->encloses_point_quick($b);
|
2011-12-30 16:17:37 +00:00
|
|
|
|
|
|
|
push @skeleton_lines, [$edge->[1], $edge->[2]];
|
|
|
|
}
|
2012-06-07 13:31:51 +00:00
|
|
|
return () if !@skeleton_lines;
|
2011-12-30 16:17:37 +00:00
|
|
|
|
2012-06-06 19:27:39 +00:00
|
|
|
# now walk along the medial axis and build continuos polylines or polygons
|
|
|
|
my @polylines = ();
|
2011-12-30 16:17:37 +00:00
|
|
|
{
|
2013-09-16 15:44:30 +00:00
|
|
|
my @lines = @skeleton_lines;
|
|
|
|
push @polylines, [ map @$_, shift @lines ];
|
|
|
|
CYCLE: while (@lines) {
|
|
|
|
for my $i (0..$#lines) {
|
|
|
|
if ($lines[$i][0] == $polylines[-1][-1]) {
|
|
|
|
push @{$polylines[-1]}, $lines[$i][1];
|
|
|
|
} elsif ($lines[$i][1] == $polylines[-1][-1]) {
|
|
|
|
push @{$polylines[-1]}, $lines[$i][0];
|
|
|
|
} elsif ($lines[$i][1] == $polylines[-1][0]) {
|
|
|
|
unshift @{$polylines[-1]}, $lines[$i][0];
|
|
|
|
} elsif ($lines[$i][0] == $polylines[-1][0]) {
|
|
|
|
unshift @{$polylines[-1]}, $lines[$i][1];
|
|
|
|
} else {
|
|
|
|
next;
|
2012-06-06 19:27:39 +00:00
|
|
|
}
|
2013-09-16 15:44:30 +00:00
|
|
|
splice @lines, $i, 1;
|
|
|
|
next CYCLE;
|
2011-12-30 16:17:37 +00:00
|
|
|
}
|
2013-09-16 15:44:30 +00:00
|
|
|
push @polylines, [ map @$_, shift @lines ];
|
2011-12-30 16:17:37 +00:00
|
|
|
}
|
2012-06-06 19:27:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
my @result = ();
|
|
|
|
foreach my $polyline (@polylines) {
|
|
|
|
next unless @$polyline >= 2;
|
2011-12-30 16:17:37 +00:00
|
|
|
|
2012-06-06 19:27:39 +00:00
|
|
|
# now replace point indexes with coordinates
|
|
|
|
@$polyline = map $vertices->[$_], @$polyline;
|
2011-12-30 16:17:37 +00:00
|
|
|
|
2012-06-06 19:27:39 +00:00
|
|
|
# cleanup
|
|
|
|
$polyline = Slic3r::Geometry::douglas_peucker($polyline, $width / 7);
|
|
|
|
|
2013-09-16 15:44:30 +00:00
|
|
|
if (Slic3r::Geometry::same_point($polyline->[0], $polyline->[-1])) {
|
2012-06-06 19:27:39 +00:00
|
|
|
next if @$polyline == 2;
|
|
|
|
push @result, Slic3r::Polygon->new(@$polyline[0..$#$polyline-1]);
|
|
|
|
} else {
|
2013-07-05 12:29:57 +00:00
|
|
|
push @result, Slic3r::Polyline->new(@$polyline);
|
2011-12-30 16:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-06 19:27:39 +00:00
|
|
|
return @result;
|
2011-12-30 16:17:37 +00:00
|
|
|
}
|
|
|
|
|
2012-11-23 23:13:04 +00:00
|
|
|
package Slic3r::ExPolygon::Collection;
|
|
|
|
use Slic3r::Geometry qw(X1 Y1);
|
|
|
|
|
|
|
|
sub align_to_origin {
|
|
|
|
my $self = shift;
|
|
|
|
|
2013-07-13 22:38:01 +00:00
|
|
|
my @bb = Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$self ]);
|
|
|
|
$self->translate(-$bb[X1], -$bb[Y1]);
|
2013-06-07 21:16:02 +00:00
|
|
|
$self;
|
2012-11-23 23:13:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub size {
|
|
|
|
my $self = shift;
|
2013-07-13 22:38:01 +00:00
|
|
|
return [ Slic3r::Geometry::size_2D([ map @$_, map @$_, @$self ]) ];
|
2012-11-23 23:13:04 +00:00
|
|
|
}
|
|
|
|
|
2011-10-15 09:36:05 +00:00
|
|
|
1;
|