summaryrefslogtreecommitdiffstats
path: root/src/live_effects/lpe-extrude.cpp
blob: 57e5ca2ceaac3df12e3ccf77f78af850727f6553 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @file
 * LPE effect for extruding paths (making them "3D").
 *
 */
/* Authors:
 *   Johan Engelen <j.b.c.engelen@utwente.nl>
 *
 * Copyright (C) 2009 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "live_effects/lpe-extrude.h"

// TODO due to internal breakage in glibmm headers, this must be last:
#include <glibmm/i18n.h>


namespace Inkscape {
namespace LivePathEffect {

LPEExtrude::LPEExtrude(LivePathEffectObject *lpeobject) :
    Effect(lpeobject),
    extrude_vector(_("Direction"), _("Defines the direction and magnitude of the extrusion"), "extrude_vector", &wr, this, Geom::Point(-10,10))
{
    show_orig_path = true;
    concatenate_before_pwd2 = false;

    registerParameter(&extrude_vector);
}

LPEExtrude::~LPEExtrude()
= default;

static bool are_colinear(Geom::Point a, Geom::Point b) {
    return Geom::are_near(cross(a,b), 0., 0.5);
}

// find cusps, except at start/end for closed paths.
// this should be factored out later.
static std::vector<double> find_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in ) {
    using namespace Geom;
    Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
    std::vector<double> cusps;
    // cusps are spots where the derivative jumps.
    for (unsigned i = 1 ; i < deriv.size() ; ++i) {
        if ( ! are_colinear(deriv[i-1].at1(), deriv[i].at0()) ) {
            // there is a jump in the derivative, so add it to the cusps list
            cusps.push_back(deriv.cuts[i]);
        }
    }
    return cusps;
}

Geom::Piecewise<Geom::D2<Geom::SBasis> >
LPEExtrude::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
{
    using namespace Geom;

    // generate connecting lines (the 'sides' of the extrusion)
    Geom::Path path(Point(0.,0.));
    path.appendNew<Geom::LineSegment>( extrude_vector.getVector() );
    Piecewise<D2<SBasis> > connector = path.toPwSb();

    switch( 1 ) {
    case 0: {
        /* This one results in the following subpaths: the original, a displaced copy, and connector lines between the two
         */

        Piecewise<D2<SBasis> > pwd2_out = pwd2_in;
        // generate extrusion bottom: (just a copy of original path, displaced a bit)
        pwd2_out.concat( pwd2_in + extrude_vector.getVector() );

        // connecting lines should be put at start and end of path if it is not closed
        // it is not possible to check whether a piecewise<T> path is closed, 
        // so we check whether start and end are close
        if ( ! are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
            pwd2_out.concat( connector + pwd2_in.firstValue() );
            pwd2_out.concat( connector + pwd2_in.lastValue() );
        }
        // connecting lines should be put at cusps
        Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
        std::vector<double> cusps; // = roots(deriv);
        for (double cusp : cusps) {
            pwd2_out.concat( connector + pwd2_in.valueAt(cusp) );
        }
        // connecting lines should be put where the tangent of the path equals the extrude_vector in direction
        std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector())));
        for (double rt : rts) {
            pwd2_out.concat( connector + pwd2_in.valueAt(rt) );
        }
        return pwd2_out;
    }

    default:
    case 1: {
        /* This one creates separate closed subpaths that correspond to the faces of the extruded shape.
         * When the LPE is complete, one can convert the shape to a normal path, then break subpaths apart and start coloring them.
         */

        Piecewise<D2<SBasis> > pwd2_out;
        // split input path in pieces between points where deriv == vector
        Piecewise<D2<SBasis> > deriv = derivative(pwd2_in);
        std::vector<double> rts = roots(dot(deriv, rot90(extrude_vector.getVector())));

        std::vector<double> cusps = find_cusps(pwd2_in);

        // see if we should treat the path as being closed.
        bool closed_path = false;
        if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
            // the path is closed, however if there is a cusp at the closing point, we should treat it as being an open path.
            if ( are_colinear(deriv.firstValue(), deriv.lastValue()) ) {
                // there is no jump in the derivative, so treat path as being closed
                closed_path = true;
            }
        }

        std::vector<double> connector_pts;
        if (rts.empty()) {
            connector_pts = cusps;
        } else if (cusps.empty()) {
            connector_pts = rts;
        } else {
            connector_pts = rts;
            connector_pts.insert(connector_pts.begin(), cusps.begin(), cusps.end());
            sort(connector_pts.begin(), connector_pts.end());
        }

        double portion_t = 0.;
        for (unsigned i = 0; i < connector_pts.size() ; ++i) {
            Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, connector_pts[i] );
            portion_t = connector_pts[i];
            if (closed_path && i == 0) {
                // if the path is closed, skip the first cut and add it to the last cut later
                continue;
            }
            Piecewise<D2<SBasis> > part = cut;
            part.continuousConcat(connector + cut.lastValue());
            part.continuousConcat(reverse(cut) + extrude_vector.getVector());
            part.continuousConcat(reverse(connector) + cut.firstValue());
            pwd2_out.concat( part );
        }
        if (closed_path) {
            Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() );
            cut.continuousConcat(portion(pwd2_in, pwd2_in.domain().min(), connector_pts[0] ));
            Piecewise<D2<SBasis> > part = cut;
            part.continuousConcat(connector + cut.lastValue());
            part.continuousConcat(reverse(cut) + extrude_vector.getVector());
            part.continuousConcat(reverse(connector) + cut.firstValue());
            pwd2_out.concat( part );
        } else if (!are_near(portion_t, pwd2_in.domain().max())) {
            Piecewise<D2<SBasis> > cut = portion(pwd2_in, portion_t, pwd2_in.domain().max() );
            Piecewise<D2<SBasis> > part = cut;
            part.continuousConcat(connector + cut.lastValue());
            part.continuousConcat(reverse(cut) + extrude_vector.getVector());
            part.continuousConcat(reverse(connector) + cut.firstValue());
            pwd2_out.concat( part );
        }
        return pwd2_out;
    }
    }
}

void
LPEExtrude::resetDefaults(SPItem const* item)
{
    Effect::resetDefaults(item);

    using namespace Geom;

    Geom::OptRect bbox = item->geometricBounds();
    if (bbox) {
        Interval const &boundingbox_X = (*bbox)[Geom::X];
        Interval const &boundingbox_Y = (*bbox)[Geom::Y];
        extrude_vector.set_and_write_new_values( Geom::Point(boundingbox_X.middle(), boundingbox_Y.middle()), 
                                                 (boundingbox_X.extent() + boundingbox_Y.extent())*Geom::Point(-0.05,0.2) );
    }
}

} //namespace LivePathEffect
} /* namespace Inkscape */

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :