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