diff options
Diffstat (limited to 'src/helper/geom-pathstroke.cpp')
| -rw-r--r-- | src/helper/geom-pathstroke.cpp | 200 |
1 files changed, 144 insertions, 56 deletions
diff --git a/src/helper/geom-pathstroke.cpp b/src/helper/geom-pathstroke.cpp index 1908f2db7..eb0c432c6 100644 --- a/src/helper/geom-pathstroke.cpp +++ b/src/helper/geom-pathstroke.cpp @@ -159,6 +159,7 @@ void miter_join_internal(Geom::Path& res, Geom::Curve const& outgoing, double mi Geom::Point p = Geom::intersection_point(incoming.finalPoint(), tang1, outgoing.initialPoint(), tang2); bool satisfied = false; + bool inc_ls = res.back_open().degreesOfFreedom() <= 4; if (p.isFinite()) { // check size of miter @@ -166,32 +167,20 @@ void miter_join_internal(Geom::Path& res, Geom::Curve const& outgoing, double mi satisfied = Geom::distance(p, point_on_path) <= miter * 2.0 * width; if (satisfied) { // miter OK, check to see if we can do a relocation - bool ls = res.back_open().degreesOfFreedom() <= 4; - if (ls) { + if (inc_ls) { res.setFinal(p); } else { res.appendNew<Geom::LineSegment>(p); } } else if (clip) { // miter needs clipping, find two points - Geom::Line bisector(point_on_path, p); - Geom::Point point_limit = point_on_path + miter * 2.0 * width * bisector.versor(); + Geom::Point bisector_versor = Geom::Line(point_on_path, p).versor(); + Geom::Point point_limit = point_on_path + miter * 2.0 * width * bisector_versor; - Geom::Line line_limit = - Geom::Line::from_origin_and_versor( point_limit, bisector.versor().cw() ); + Geom::Point p1 = Geom::intersection_point(incoming.finalPoint(), tang1, point_limit, bisector_versor.cw()); + Geom::Point p2 = Geom::intersection_point(outgoing.initialPoint(), tang2, point_limit, bisector_versor.cw()); - Geom::Line incoming_line( incoming.finalPoint(), p ); - Geom::Line outgoing_line( p, outgoing.initialPoint() ); - - Geom::OptCrossing i1 = intersection( line_limit, incoming_line ); - Geom::OptCrossing i2 = intersection( line_limit, outgoing_line ); - - // It would be nice to have a simple point returned by intersection! - Geom::Point p1 = line_limit.pointAt( (*i1).ta ); - Geom::Point p2 = line_limit.pointAt( (*i2).ta ); - - bool ls = res.back_open().degreesOfFreedom() <= 4; - if (ls) { + if (inc_ls) { res.setFinal(p1); } else { res.appendNew<Geom::LineSegment>(p1); @@ -203,9 +192,9 @@ void miter_join_internal(Geom::Path& res, Geom::Curve const& outgoing, double mi res.appendNew<Geom::LineSegment>(outgoing.initialPoint()); // check if we can do another relocation - bool ls = outgoing.degreesOfFreedom() <= 4; + bool out_ls = outgoing.degreesOfFreedom() <= 4; - if ( (satisfied || clip) && ls) { + if ( (satisfied || clip) && out_ls) { res.setFinal(outgoing.finalPoint()); } else { res.append(outgoing); @@ -236,12 +225,15 @@ Geom::Point pick_solution(Geom::Point points[2], Geom::Point tang2, Geom::Point return sol; } -void extrapolate_join(Geom::Path& path_builder, Geom::Curve const& outgoing, double miter_limit, double line_width) +void extrapolate_join(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width) { using namespace Geom; - Geom::Curve const& incoming = path_builder.back(); + + Geom::Curve const& incoming = res.back(); + Geom::Point startPt = incoming.finalPoint(); Geom::Point endPt = outgoing.initialPoint(); - Geom::Point tang2 = Geom::unitTangentAt(outgoing.toSBasis(), 0); + Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.); + Geom::Point tang2 = outgoing.unitTangentAt(0); Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(incoming.toSBasis()), 0.); Geom::Circle circle2 = Geom::touching_circle(outgoing.toSBasis(), 0); @@ -252,52 +244,146 @@ void extrapolate_join(Geom::Path& path_builder, Geom::Curve const& outgoing, dou Geom::Point points[2]; int solutions = 0; - Geom::EllipticalArc *arc0 = NULL; Geom::EllipticalArc *arc1 = NULL; + Geom::EllipticalArc *arc2 = NULL; + Geom::Point sol; + Geom::Point p1; + Geom::Point p2; if (!inc_ls && !out_ls) { + // Two circles solutions = Geom::circle_circle_intersection(circle1.center(), circle1.ray(), circle2.center(), circle2.ray(), points[0], points[1]); if (solutions == 2) { - Geom::Point sol = pick_solution(points, tang2, endPt); - - arc0 = circle1.arc(incoming.finalPoint(), 0.5*(incoming.finalPoint()+sol), sol, true); - arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + sol = pick_solution(points, tang2, endPt); + arc1 = circle1.arc(startPt, 0.5*(startPt+sol), sol, true); + arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); } } else if (inc_ls && !out_ls) { + // Line and circle solutions = Geom::circle_line_intersection(circle2, incoming.initialPoint(), incoming.finalPoint(), points[0], points[1]); if (solutions == 2) { - Geom::Point sol = pick_solution(points, tang2, endPt); - path_builder.setFinal(sol); - arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + sol = pick_solution(points, tang2, endPt); + arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); } } else if (!inc_ls && out_ls) { + // Circle and line solutions = Geom::circle_line_intersection(circle1, outgoing.initialPoint(), outgoing.finalPoint(), points[0], points[1]); if (solutions == 2) { - Geom::Point sol = pick_solution(points, tang2, endPt); - arc0 = circle1.arc(incoming.finalPoint(), 0.5*(sol+incoming.finalPoint()), sol, true); + sol = pick_solution(points, tang2, endPt); + arc1 = circle1.arc(startPt, 0.5*(sol+startPt), sol, true); } } if (solutions != 2) // no solutions available, fall back to miter - return miter_join(path_builder, outgoing, miter_limit, line_width); + return miter_clip_join(res, outgoing, miter, width); - if (arc0) - path_builder.append(*arc0); - if (arc1) - path_builder.append(*arc1); + // We have a solution, thus sol is defined. + p1 = sol; + + // See if we need to clip. Miter length is measured along a circular arc that is tangent to the + // bisector of the incoming and out going angles and passes through the end point (sol) of the + // line join. + + // Center of circle is intersection of a line orthogonal to bisector and a line bisecting + // a chord connecting the path end point (point_on_path) and the join end point (sol). + Geom::Point point_on_path = startPt + Geom::rot90(tang1)*width; + Geom::Line bisector = make_angle_bisector_line(startPt, point_on_path, endPt); + Geom::Line ortho = make_orthogonal_line(point_on_path, bisector); + + Geom::LineSegment chord(point_on_path, sol); + Geom::Line bisector_chord = make_bisector_line(chord); + + Geom::Line limit_line; + double miter_limit = 2.0 * width * miter; + bool clipped = false; + + if (are_parallel(bisector_chord, ortho)) { + // No intersection (can happen if curvatures are equal but opposite) + if (Geom::distance(point_on_path, sol) > miter_limit) { + clipped = true; + Geom::Point limit_point = point_on_path + miter_limit * bisector.versor(); + limit_line = make_parallel_line( limit_point, ortho ); + } + } else { + Geom::Point center = + Geom::intersection_point( bisector_chord.pointAt(0), bisector_chord.versor(), + ortho.pointAt(0), ortho.versor() ); + Geom::Coord radius = distance(center, point_on_path); + Geom::Circle circle_center(center, radius); - delete arc0; - delete arc1; + double limit_angle = miter_limit / radius; + + Geom::Ray start_ray(center, point_on_path); + Geom::Ray end_ray(center, sol); + Geom::Line limit_line(center, 0); // Angle set below + + if (Geom::cross(start_ray.versor(), end_ray.versor()) > 0) { + limit_line.setAngle(start_ray.angle() - limit_angle); + } else { + limit_line.setAngle(start_ray.angle() + limit_angle); + } + + Geom::EllipticalArc *arc_center = circle_center.arc(point_on_path, 0.5*(point_on_path + sol), sol, true); + if (arc_center && arc_center->sweepAngle() > limit_angle) { + // We need to clip + clipped = true; + + if (!inc_ls) { + // Incoming circular + solutions = Geom::circle_line_intersection(circle1, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]); + + if (solutions == 2) { + p1 = pick_solution(points, tang2, endPt); + delete arc1; + arc1 = circle1.arc(startPt, 0.5*(p1+startPt), p1, true); + } + } else { + p1 = Geom::intersection_point(startPt, tang1, limit_line.pointAt(0), limit_line.versor()); + } + + if (!out_ls) { + // Outgoing circular + solutions = Geom::circle_line_intersection(circle2, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]); + + if (solutions == 2) { + p2 = pick_solution(points, tang1, endPt); + delete arc2; + arc2 = circle2.arc(p2, 0.5*(p2+endPt), endPt, true); + } + } else { + p2 = Geom::intersection_point(endPt, tang2, limit_line.pointAt(0), limit_line.versor()); + } + } + } + + // Add initial + if (arc1) { + res.append(*arc1); + } else { + // Straight line segment: move last point + res.setFinal(p1); + } + + if (clipped) { + res.appendNew<Geom::LineSegment>(p2); + } - if (!inc_ls && out_ls) - path_builder.appendNew<Geom::LineSegment>(outgoing.finalPoint()); - else - path_builder.append(outgoing); + // Add outgoing + if (arc2) { + res.append(*arc2); + res.append(outgoing); + } else { + // Straight line segment: + res.appendNew<Geom::LineSegment>(outgoing.finalPoint()); + } + + delete arc1; + delete arc2; } void join_inside(Geom::Path& res, Geom::Curve const& outgoing) @@ -322,7 +408,14 @@ void join_inside(Geom::Path& res, Geom::Curve const& outgoing) } } -void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, double miter, Inkscape::LineJoinType join) +bool decide(Geom::Curve const& incoming, Geom::Curve const& outgoing) +{ + Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.); + Geom::Point tang2 = outgoing.unitTangentAt(0.); + return (Geom::cross(tang1, tang2) < 0); +} + +void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, bool on_outside, double miter, Inkscape::LineJoinType join) { if (res.size() == 0 || to_add.size() == 0) return; @@ -335,14 +428,6 @@ void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, dou return; } - Geom::Point tang1 = Geom::unitTangentAt(reverse(res.back().toSBasis()), 0.); - Geom::Point tang2 = Geom::unitTangentAt(to_add.front().toSBasis(), 0.); - // Geom::Point discontinuity_vec = to_add.initialPoint() - res.finalPoint(); - bool on_outside = (Geom::cross(tang1, tang2) < 0); - // std::cout << std::fixed << std::setprecision(3) - // << " in: " << tang1 << " out: " << tang2 - // << " side: " << (on_outside?"inside":"outside") << std::endl; - if (on_outside) { join_func *jf; switch (join) { @@ -635,7 +720,8 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin if (u == 0) { res.append(temp); } else { - outline_helper(res, temp, width, miter, join); + bool on_outside = decide(input[u-1], input[u]); + outline_helper(res, temp, width, on_outside, miter, join); if (temp.size() > 0) res.insert(res.end(), ++temp.begin(), temp.end()); } @@ -644,7 +730,8 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin if (u < k - 1) { temp = Geom::Path(); offset_curve(temp, &input[u+1], width); - outline_helper(res, temp, width, miter, join); + bool on_outside = decide(input[u], input[u+1]); + outline_helper(res, temp, width, on_outside, miter, join); if (temp.size() > 0) res.insert(res.end(), ++temp.begin(), temp.end()); } @@ -657,7 +744,8 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin temp.append(c1); Geom::Path temp2; temp2.append(c2); - outline_helper(temp, temp2, width, miter, join); + bool on_outside = decide(input.back(), input.front()); + outline_helper(temp, temp2, width, on_outside, miter, join); res.erase(res.begin()); res.erase_last(); // |
