summaryrefslogtreecommitdiffstats
path: root/src/live_effects/lpe-interpolate.cpp
blob: 7456c160fb663defe1bac54fcd7d4fdaafd52a0a (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
195
196
197
198
199
// SPDX-License-Identifier: GPL-2.0-or-later
/** \file
 * LPE interpolate implementation
 */
/*
 * Authors:
 *   Johan Engelen
 *
 * Copyright (C) Johan Engelen 2007-2008 <j.b.c.engelen@utwente.nl>
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */
#include "live_effects/lpe-interpolate.h"
#include <2geom/sbasis-to-bezier.h>
#include "display/curve.h"
#include "object/sp-path.h"

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


namespace Inkscape {
namespace LivePathEffect {

LPEInterpolate::LPEInterpolate(LivePathEffectObject *lpeobject)
    : Effect(lpeobject)
    , trajectory_path(_("Trajectory:"), _("Path along which intermediate steps are created."), "trajectory", &wr, this,
                      "M0,0 L0,0")
    , number_of_steps(_("Steps_:"), _("Determines the number of steps from start to end path."), "steps", &wr, this, 5)
    , equidistant_spacing(_("E_quidistant spacing"),
                          _("If true, the spacing between intermediates is constant along the length of the path. If "
                            "false, the distance depends on the location of the nodes of the trajectory path."),
                          "equidistant_spacing", &wr, this, true)
{
    show_orig_path = true;

    registerParameter(&trajectory_path);
    registerParameter(&equidistant_spacing);
    registerParameter(&number_of_steps);

    number_of_steps.param_make_integer();
    number_of_steps.param_set_range(2, std::numeric_limits<gint>::max());
}

LPEInterpolate::~LPEInterpolate() = default;


bool 
LPEInterpolate::doOnOpen(SPLPEItem const *lpeitem)
{
    if (!is_load || is_applied) {
        return false;
    }
    trajectory_path.reload();
    return false;
}


void LPEInterpolate::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
{
    if (sp_lpe_item && sp_lpe_item->pathEffectsEnabled() && sp_lpe_item->optimizeTransforms()) {
        trajectory_path.param_transform_multiply(postmul, false);
    }
}

/*
 * interpolate path_in[0] to path_in[1]
 */
Geom::PathVector LPEInterpolate::doEffect_path(Geom::PathVector const &path_in)
{
    if ((path_in.size() < 2) || (number_of_steps < 2)) {
        return path_in;
    }
    // Don't allow empty path parameter:
    if (trajectory_path.get_pathvector().empty()) {
        return path_in;
    }
    if (is_load) {
        trajectory_path.reload();
    }

    Geom::PathVector path_out;

    Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_A = path_in[0].toPwSb();
    Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_B = path_in[1].toPwSb();

    // Transform both paths to (0,0) midpoint, so they can easily be positioned along interpolate_path
    Geom::OptRect bounds_A = Geom::bounds_exact(pwd2_A);
    if (bounds_A) {
        pwd2_A -= bounds_A->midpoint();
    }
    Geom::OptRect bounds_B = Geom::bounds_exact(pwd2_B);
    if (bounds_B) {
        pwd2_B -= bounds_B->midpoint();
    }

    // Make sure both paths have the same number of segments and cuts at the same locations
    pwd2_B.setDomain(pwd2_A.domain());
    Geom::Piecewise<Geom::D2<Geom::SBasis> > pA = Geom::partition(pwd2_A, pwd2_B.cuts);
    Geom::Piecewise<Geom::D2<Geom::SBasis> > pB = Geom::partition(pwd2_B, pwd2_A.cuts);

    auto trajectory = calculate_trajectory(bounds_A, bounds_B);

    Geom::Interval trajectory_domain = trajectory.domain();

    for (int i = 0; i < number_of_steps; ++i) {
        double fraction = i / (number_of_steps - 1);

        Geom::Piecewise<Geom::D2<Geom::SBasis> > pResult = pA * (1 - fraction) + pB * fraction;
        pResult += trajectory.valueAt(trajectory_domain.min() + fraction * trajectory_domain.extent());

        Geom::PathVector pathv = Geom::path_from_piecewise(pResult, LPE_CONVERSION_TOLERANCE);
        path_out.push_back(pathv[0]);
    }

    return path_out;
}


// returns the lpe parameter trajectory_path, transformed so that it starts at the
// bounding box center of the first path and ends at the bounding box center of the
// second path
Geom::Piecewise<Geom::D2<Geom::SBasis> > LPEInterpolate::calculate_trajectory(Geom::OptRect bounds_A,
                                                                              Geom::OptRect bounds_B)
{
    Geom::Affine affine = trajectory_path.get_relative_affine();
    Geom::Piecewise<Geom::D2<Geom::SBasis> > trajectory = trajectory_path.get_pathvector()[0].toPwSb() * affine;

    if (equidistant_spacing) {
        trajectory = Geom::arc_length_parametrization(trajectory);
    }

    if (!bounds_A || !bounds_B) {
        return trajectory;
    }

    auto trajectory_start = trajectory.firstValue();
    auto trajectory_end = trajectory.lastValue();

    auto midpoint_A = bounds_A->midpoint();
    auto midpoint_B = bounds_B->midpoint();

    Geom::Ray original(trajectory_start, trajectory_end);
    Geom::Ray transformed(midpoint_A, midpoint_B);

    double rotation = transformed.angle() - original.angle();
    double scale = Geom::distance(midpoint_A, midpoint_B) / Geom::distance(trajectory_start, trajectory_end);

    Geom::Affine transformation;

    transformation *= Geom::Translate(-trajectory_start);
    transformation *= Geom::Scale(scale, scale);
    transformation *= Geom::Rotate(rotation);

    transformation *= Geom::Translate(midpoint_A);

    return trajectory * transformation;
}

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

    if (!SP_IS_PATH(item))
        return;

    SPCurve const *crv = SP_PATH(item)->curveForEdit();
    Geom::PathVector const &pathv = crv->get_pathvector();
    if ((pathv.size() < 2))
        return;

    Geom::OptRect bounds_A = pathv[0].boundsExact();
    Geom::OptRect bounds_B = pathv[1].boundsExact();

    if (bounds_A && bounds_B) {
        Geom::PathVector traj_pathv;
        traj_pathv.push_back(Geom::Path());
        traj_pathv[0].start(bounds_A->midpoint());
        traj_pathv[0].appendNew<Geom::LineSegment>(bounds_B->midpoint());
        trajectory_path.set_new_value(traj_pathv, true);
    }
    else {
        trajectory_path.param_set_and_write_default();
    }
}

} // 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 :