summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohan B. C. Engelen <jbc.engelen@swissonline.ch>2012-03-04 20:17:51 +0000
committerJohan B. C. Engelen <j.b.c.engelen@alumnus.utwente.nl>2012-03-04 20:17:51 +0000
commit530419bae52aef510b9cdd1440bb995bc92a498f (patch)
tree4e049f7a28c890e7056b230f6c7d18654ee50ed3 /src
parent2geom: isConstant and isZero should check a finite neighbourhood around zero. (diff)
downloadinkscape-530419bae52aef510b9cdd1440bb995bc92a498f.tar.gz
inkscape-530419bae52aef510b9cdd1440bb995bc92a498f.zip
powerstroke: add miter cusp type. improve tangent detection for cusps drawing
(bzr r11046)
Diffstat (limited to 'src')
-rw-r--r--src/live_effects/lpe-powerstroke.cpp95
-rw-r--r--src/live_effects/lpe-powerstroke.h1
2 files changed, 78 insertions, 18 deletions
diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp
index bb57dc2f7..66deabc87 100644
--- a/src/live_effects/lpe-powerstroke.cpp
+++ b/src/live_effects/lpe-powerstroke.cpp
@@ -27,6 +27,36 @@
#include <2geom/path-intersection.h>
#include <2geom/crossing.h>
+namespace Geom {
+
+
+Point unitTangentAt( D2<SBasis> const & a, Coord t, unsigned n = 3) {
+ std::vector<Point> derivs = a.valueAndDerivatives(t, n);
+ for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) {
+ Coord length = derivs[deriv_n].length();
+ if ( ! are_near(length, 0) ) {
+ // length of derivative is non-zero, so return unit vector
+ return derivs[deriv_n] / length;
+ }
+ }
+ return Point (0,0);
+}
+
+/** Find the point where two straight lines cross.
+*/
+boost::optional<Point> intersection_point( Point const & origin_a, Point const & vector_a,
+ Point const & origin_b, Point const & vector_b)
+{
+ Coord denom = cross(vector_b, vector_a);
+ if (!are_near(denom,0.)){
+ Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom;
+ return origin_a + t * vector_a;
+ }
+ return boost::none;
+}
+
+}
+
namespace Inkscape {
namespace LivePathEffect {
@@ -57,12 +87,14 @@ static const Util::EnumDataConverter<unsigned> LineCapTypeConverter(LineCapTypeD
enum LineCuspType {
LINECUSP_BEVEL,
LINECUSP_ROUND,
- LINECUSP_CUSP
+ LINECUSP_EXTRP_MITER,
+ LINECUSP_MITER
};
static const Util::EnumData<unsigned> LineCuspTypeData[] = {
- {LINECUSP_BEVEL , N_("Beveled"), "bevel"},
- {LINECUSP_ROUND , N_("Rounded"), "round"},
- {LINECUSP_CUSP , N_("Cusp"), "cusp"},
+ {LINECUSP_BEVEL, N_("Beveled"), "bevel"},
+ {LINECUSP_ROUND, N_("Rounded"), "round"},
+ {LINECUSP_EXTRP_MITER, N_("Extrapolated"), "extrapolated"},
+ {LINECUSP_MITER, N_("Miter"), "miter"},
};
static const Util::EnumDataConverter<unsigned> LineCuspTypeConverter(LineCuspTypeData, sizeof(LineCuspTypeData)/sizeof(*LineCuspTypeData));
@@ -74,6 +106,7 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
interpolator_beta(_("Smoothness"), _("Sets the smoothness for the CubicBezierJohan interpolator. 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2),
start_linecap_type(_("Start line cap type"), _("Determines the shape of the path's start."), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND),
cusp_linecap_type(_("Cusp line cap type"), _("Determines the shape of the cusps along the path."), "cusp_linecap_type", LineCuspTypeConverter, &wr, this, LINECUSP_ROUND),
+ miter_limit(_("Miter limit"), _("Maximum length of the miter (in units of stroke width)"), "miter_limit", &wr, this, 0.2),
end_linecap_type(_("End line cap type"), _("Determines the shape of the path's end."), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND)
{
show_orig_path = true;
@@ -89,6 +122,7 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
registerParameter( dynamic_cast<Parameter *>(&interpolator_beta) );
registerParameter( dynamic_cast<Parameter *>(&start_linecap_type) );
registerParameter( dynamic_cast<Parameter *>(&cusp_linecap_type) );
+ // registerParameter( dynamic_cast<Parameter *>(&miter_limit) );
registerParameter( dynamic_cast<Parameter *>(&end_linecap_type) );
}
@@ -173,8 +207,14 @@ Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis>
Geom::Point start = B[0].at0();
pb.moveTo(start);
build_from_sbasis(pb, B[0], tol, false);
+ unsigned prev_i = 0;
for (unsigned i=1; i < B.size(); i++) {
- if (!are_near(B[i-1].at1(), B[i].at0(), tol) )
+ // if segment is degenerate, skip it
+ // the degeneracy/constancy test had to be loosened (eps > 1e-5)
+ if (B[i].isConstant(1e-4)) {
+ continue;
+ }
+ if (!are_near(B[prev_i].at1(), B[i].at0(), tol) )
{ // discontinuity found, so fix it :-)
discontinuity_data cusp = cusps[cusp_i];
@@ -184,30 +224,29 @@ Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis>
angle_between(cusp.der0, cusp.der1), false, cusp.width < 0,
B[i].at0() );
break;
- case LINECUSP_CUSP: {
+ case LINECUSP_EXTRP_MITER: {
// first figure out whether we are on the outside or inside of the corner in the path
if ( sign*cusp.width*angle_between(cusp.der0, cusp.der1) < 0.) {
// we are on the outside, do something complicated to make it look good ;)
- Geom::D2<Geom::SBasis> newcurve1 =
- B[i-1] * Geom::reflection(rot90(derivative(B[i-1]).at1()), B[i-1].at1());
+ Geom::Point der1 = unitTangentAt(B[prev_i],1);
+ Geom::Point der2 = unitTangentAt(B[i],0);
+
+ Geom::D2<Geom::SBasis> newcurve1 = B[prev_i] * Geom::reflection(rot90(der1), B[prev_i].at1());
newcurve1 = reverse(newcurve1);
std::vector<Geom::Point> temp;
sbasis_to_bezier(temp, newcurve1, 4);
Geom::CubicBezier bzr1( temp );
- Geom::D2<Geom::SBasis> newcurve2 =
- B[i] * Geom::reflection(rot90(derivative(B[i]).at0()), B[i].at0());
+ Geom::D2<Geom::SBasis> newcurve2 = B[i] * Geom::reflection(rot90(der2), B[i].at0());
newcurve2 = reverse(newcurve2);
sbasis_to_bezier(temp, newcurve2, 4);
Geom::CubicBezier bzr2( temp );
Geom::Crossings cross = crossings(bzr1, bzr2);
if (cross.empty()) {
-g_message("empty crossing: decide what to do...");
- pb.curveTo(bzr1[1], bzr1[2], bzr1[3]);
- pb.lineTo(bzr2[0]);
- pb.curveTo(bzr2[1], bzr2[2], bzr2[3]);
+ // empty crossing: decide what to do... default to bevel?
+ pb.lineTo(B[i].at0());
} else {
std::pair<Geom::CubicBezier, Geom::CubicBezier> sub1 = bzr1.subdivide(cross[0].ta);
std::pair<Geom::CubicBezier, Geom::CubicBezier> sub2 = bzr2.subdivide(cross[0].tb);
@@ -221,6 +260,25 @@ g_message("empty crossing: decide what to do...");
}
break;
}
+ case LINECUSP_MITER: {
+ // first figure out whether we are on the outside or inside of the corner in the path
+ if ( sign*cusp.width*angle_between(cusp.der0, cusp.der1) < 0.) {
+ // we are on the outside, do something complicated to make it look good ;)
+
+ Geom::Point der1 = unitTangentAt(B[prev_i],1);
+ Geom::Point der2 = unitTangentAt(B[i],0);
+ boost::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), der1,
+ B[i].at0(), der2 );
+ if (p) {
+ pb.lineTo(*p);
+ }
+ pb.lineTo(B[i].at0());
+ } else {
+ // we are on the inside, do a simple bevel to connect the paths
+ pb.lineTo(B[i].at0()); // default to bevel for too shallow cusp angles
+ }
+ break;
+ }
case LINECUSP_BEVEL:
default:
pb.lineTo(B[i].at0());
@@ -230,6 +288,7 @@ g_message("empty crossing: decide what to do...");
cusp_i += forward_direction ? 1 : -1;
}
build_from_sbasis(pb, B[i], tol, false);
+ prev_i = i;
}
pb.finish();
return pb.peek().front();
@@ -319,7 +378,7 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
break;
case LINECAP_PEAK:
{
- Geom::Point end_deriv = der.lastValue();
+ Geom::Point end_deriv = unit_vector(der.lastValue());
double radius = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue());
Geom::Point midpoint = 0.5*(pwd2_out.lastValue() + mirrorpath.firstValue()) + radius*end_deriv;
fixed_path.appendNew<LineSegment>(midpoint);
@@ -328,7 +387,7 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
}
case LINECAP_SQUARE:
{
- Geom::Point end_deriv = der.lastValue();
+ Geom::Point end_deriv = unit_vector(der.lastValue());
double radius = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue());
fixed_path.appendNew<LineSegment>( pwd2_out.lastValue() + radius*end_deriv );
fixed_path.appendNew<LineSegment>( mirrorpath.firstValue() + radius*end_deriv );
@@ -357,7 +416,7 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
break;
case LINECAP_PEAK:
{
- Geom::Point start_deriv = der.firstValue();
+ Geom::Point start_deriv = unit_vector(der.firstValue());
double radius = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue());
Geom::Point midpoint = 0.5*(mirrorpath.lastValue() + pwd2_out.firstValue()) - radius*start_deriv;
fixed_path.appendNew<LineSegment>( midpoint );
@@ -366,7 +425,7 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
}
case LINECAP_SQUARE:
{
- Geom::Point start_deriv = der.firstValue();
+ Geom::Point start_deriv = unit_vector(der.firstValue());
double radius = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue());
fixed_path.appendNew<LineSegment>( mirrorpath.lastValue() - radius*start_deriv );
fixed_path.appendNew<LineSegment>( pwd2_out.firstValue() - radius*start_deriv );
diff --git a/src/live_effects/lpe-powerstroke.h b/src/live_effects/lpe-powerstroke.h
index d154ab165..a5bb8c836 100644
--- a/src/live_effects/lpe-powerstroke.h
+++ b/src/live_effects/lpe-powerstroke.h
@@ -40,6 +40,7 @@ private:
ScalarParam interpolator_beta;
EnumParam<unsigned> start_linecap_type;
EnumParam<unsigned> cusp_linecap_type;
+ ScalarParam miter_limit;
EnumParam<unsigned> end_linecap_type;
LPEPowerStroke(const LPEPowerStroke&);