diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 7849ca055..9a4d6cb51 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -1,5 +1,6 @@ #include "Point.hpp" #include "Line.hpp" +#include "MultiPoint.hpp" #include #include @@ -138,6 +139,63 @@ Point::ccw(const Line &line) const return this->ccw(line.a, line.b); } +Point +Point::projection_onto(const MultiPoint &poly) const +{ + Point running_projection = poly.first_point(); + double running_min = this->distance_to(running_projection); + + Lines lines = poly.lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + Point point_temp = this->projection_onto(*line); + if (this->distance_to(point_temp) < running_min) { + running_projection = point_temp; + running_min = this->distance_to(running_projection); + } + } + return running_projection; +} + +Point +Point::projection_onto(const Line &line) const +{ + if (line.a.coincides_with(line.b)) return line.a; + + /* + (Ported from VisiLibity by Karl J. Obermeyer) + The projection of point_temp onto the line determined by + line_segment_temp can be represented as an affine combination + expressed in the form projection of + Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. + If theta is outside the interval [0,1], then one of the Line_Segment's endpoints + must be closest to calling Point. + */ + double theta = ( (double)(line.b.x - this->x)*(double)(line.b.x - line.a.x) + (double)(line.b.y- this->y)*(double)(line.b.y - line.a.y) ) + / ( (double)pow(line.b.x - line.a.x, 2) + (double)pow(line.b.y - line.a.y, 2) ); + + if (0.0 <= theta && theta <= 1.0) + return theta * line.a + (1.0-theta) * line.b; + + // Else pick closest endpoint. + if (this->distance_to(line.a) < this->distance_to(line.b)) { + return line.a; + } else { + return line.b; + } +} + +Point +operator+(const Point& point1, const Point& point2) +{ + return Point(point1.x + point2.x, point1.y + point2.y); +} + +Point +operator*(double scalar, const Point& point2) +{ + return Point(scalar * point2.x, scalar * point2.y); +} + #ifdef SLIC3RXS REGISTER_CLASS(Point, "Point"); diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 0d50bf376..330073a61 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class Line; +class MultiPoint; class Point; class Pointf; typedef Point Vector; @@ -37,6 +38,8 @@ class Point double distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; double ccw(const Line &line) const; + Point projection_onto(const MultiPoint &poly) const; + Point projection_onto(const Line &line) const; #ifdef SLIC3RXS void from_SV(SV* point_sv); @@ -45,6 +48,9 @@ class Point #endif }; +Point operator+(const Point& point1, const Point& point2); +Point operator*(double scalar, const Point& point2); + class Point3 : public Point { public: diff --git a/xs/t/03_point.t b/xs/t/03_point.t index e1c88977f..44e99ad26 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 10; +use Test::More tests => 13; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; @@ -46,4 +46,16 @@ ok !$point->coincides_with($point2), 'coincides_with'; ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } +{ + my $point = Slic3r::Point->new(15,15); + my $line = Slic3r::Line->new([10,10], [20,10]); + is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; + + $point = Slic3r::Point->new(0, 15); + is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; + + $point = Slic3r::Point->new(25, 15); + is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; +} + __END__ diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 4335c6eb8..12d1928b8 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -29,6 +29,12 @@ %code{% RETVAL = THIS->distance_to(*line); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw(*p1, *p2); %}; + Clone projection_onto_polygon(Polygon* polygon) + %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; + Clone projection_onto_polyline(Polyline* polyline) + %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; + Clone projection_onto_line(Line* line) + %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; %{