1 #define INKSCAPE_LPE_EXTRUDE_CPP
2 /** \file
3 * @brief LPE effect for extruding paths (making them "3D").
4 *
5 */
6 /* Authors:
7 * Johan Engelen <j.b.c.engelen@utwente.nl>
8 *
9 * Copyright (C) 2009 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #include "live_effects/lpe-extrude.h"
16 #include <2geom/path.h>
17 #include <2geom/piecewise.h>
18 #include <2geom/transforms.h>
19 #include <algorithm>
21 #include "sp-item.h"
23 namespace Inkscape {
24 namespace LivePathEffect {
26 LPEExtrude::LPEExtrude(LivePathEffectObject *lpeobject) :
27 Effect(lpeobject),
28 extrude_vector(_("Direction"), _("Defines the direction and magnitude of the extrusion"), "extrude_vector", &wr, this, Geom::Point(-10,10))
29 {
30 show_orig_path = true;
31 concatenate_before_pwd2 = false;
33 registerParameter( dynamic_cast<Parameter *>(&extrude_vector) );
34 }
36 LPEExtrude::~LPEExtrude()
37 {
39 }
41 static bool are_colinear(Geom::Point a, Geom::Point b) {
42 return Geom::are_near(cross(a,b), 0., 0.5);
43 }
45 // find cusps, except at start/end for closed paths.
46 // this should be factored out later.
47 static std::vector<double> find_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in ) {
48 using namespace Geom;
49 Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
50 std::vector<double> cusps;
51 // cusps are spots where the derivative jumps.
52 for (unsigned i = 1 ; i < deriv.size() ; ++i) {
53 if ( ! are_colinear(deriv[i-1].at1(), deriv[i].at0()) ) {
54 // there is a jump in the derivative, so add it to the cusps list
55 cusps.push_back(deriv.cuts[i]);
56 }
57 }
58 return cusps;
59 }
61 Geom::Piecewise<Geom::D2<Geom::SBasis> >
62 LPEExtrude::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
63 {
64 using namespace Geom;
66 // generate connecting lines (the 'sides' of the extrusion)
67 Path path(Point(0.,0.));
68 path.appendNew<Geom::LineSegment>( extrude_vector.getVector() );
69 Piecewise<D2<SBasis> > connector = path.toPwSb();
71 switch( 1 ) {
72 case 0: {
73 /* This one results in the following subpaths: the original, a displaced copy, and connector lines between the two
74 */
76 Piecewise<D2<SBasis> > pwd2_out = pwd2_in;
77 // generate extrusion bottom: (just a copy of original path, displaced a bit)
78 pwd2_out.concat( pwd2_in + extrude_vector.getVector() );
80 // connecting lines should be put at start and end of path if it is not closed
81 // it is not possible to check whether a piecewise<T> path is closed,
82 // so we check whether start and end are close
83 if ( ! are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
84 pwd2_out.concat( connector + pwd2_in.firstValue() );
85 pwd2_out.concat( connector + pwd2_in.lastValue() );
86 }
87 // connecting lines should be put at cusps
88 Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
89 std::vector<double> cusps; // = roots(deriv);
90 for (unsigned i = 0; i < cusps.size() ; ++i) {
91 pwd2_out.concat( connector + pwd2_in.valueAt(cusps[i]) );
92 }
93 // connecting lines should be put where the tangent of the path equals the extrude_vector in direction
94 std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector())));
95 for (unsigned i = 0; i < rts.size() ; ++i) {
96 pwd2_out.concat( connector + pwd2_in.valueAt(rts[i]) );
97 }
98 return pwd2_out;
99 }
101 default:
102 case 1: {
103 /* This one creates separate closed subpaths that correspond to the faces of the extruded shape.
104 * When the LPE is complete, one can convert the shape to a normal path, then break subpaths apart and start coloring them.
105 */
107 Piecewise<D2<SBasis> > pwd2_out;
108 // split input path in pieces between points where deriv == vector
109 Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
110 std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector())));
112 std::vector<double> cusps = find_cusps(pwd2_in);
114 // see if we should treat the path as being closed.
115 bool closed_path = false;
116 if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
117 // the path is closed, however if there is a cusp at the closing point, we should treat it as being an open path.
118 if ( are_colinear(deriv.firstValue(), deriv.lastValue()) ) {
119 // there is no jump in the derivative, so treat path as being closed
120 closed_path = true;
121 }
122 }
124 std::vector<double> connector_pts;
125 if (rts.size() < 1) {
126 connector_pts = cusps;
127 } else if (cusps.size() < 1) {
128 connector_pts = rts;
129 } else {
130 connector_pts = rts;
131 connector_pts.insert(connector_pts.begin(), cusps.begin(), cusps.end());
132 sort(connector_pts.begin(), connector_pts.end());
133 }
135 double portion_t = 0.;
136 for (unsigned i = 0; i < connector_pts.size() ; ++i) {
137 Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, connector_pts[i] );
138 portion_t = connector_pts[i];
139 if (closed_path && i == 0) {
140 // if the path is closed, skip the first cut and add it to the last cut later
141 continue;
142 }
143 Piecewise<D2<SBasis> > part = cut;
144 part.continuousConcat(connector + cut.lastValue());
145 part.continuousConcat(reverse(cut) + extrude_vector.getVector());
146 part.continuousConcat(reverse(connector) + cut.firstValue());
147 pwd2_out.concat( part );
148 }
149 if (closed_path) {
150 Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() );
151 cut.continuousConcat(portion(pwd2_in, pwd2_in.domain().min(), connector_pts[0] ));
152 Piecewise<D2<SBasis> > part = cut;
153 part.continuousConcat(connector + cut.lastValue());
154 part.continuousConcat(reverse(cut) + extrude_vector.getVector());
155 part.continuousConcat(reverse(connector) + cut.firstValue());
156 pwd2_out.concat( part );
157 } else if (!are_near(portion_t, pwd2_in.domain().max())) {
158 Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() );
159 Piecewise<D2<SBasis> > part = cut;
160 part.continuousConcat(connector + cut.lastValue());
161 part.continuousConcat(reverse(cut) + extrude_vector.getVector());
162 part.continuousConcat(reverse(connector) + cut.firstValue());
163 pwd2_out.concat( part );
164 }
165 return pwd2_out;
166 }
167 }
168 }
170 void
171 LPEExtrude::resetDefaults(SPItem * item)
172 {
173 Effect::resetDefaults(item);
175 using namespace Geom;
177 Geom::OptRect bbox = item->getBounds(Geom::identity(), SPItem::GEOMETRIC_BBOX);
178 if (bbox) {
179 Interval boundingbox_X = (*bbox)[Geom::X];
180 Interval boundingbox_Y = (*bbox)[Geom::Y];
181 extrude_vector.set_and_write_new_values( Geom::Point(boundingbox_X.middle(), boundingbox_Y.middle()),
182 (boundingbox_X.extent() + boundingbox_Y.extent())*Geom::Point(-0.05,0.2) );
183 }
184 }
186 } //namespace LivePathEffect
187 } /* namespace Inkscape */
189 /*
190 Local Variables:
191 mode:c++
192 c-file-style:"stroustrup"
193 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
194 indent-tabs-mode:nil
195 fill-column:99
196 End:
197 */
198 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :