summaryrefslogtreecommitdiffstats
path: root/src/live_effects/lpe-interpolate.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/live_effects/lpe-interpolate.cpp199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/live_effects/lpe-interpolate.cpp b/src/live_effects/lpe-interpolate.cpp
new file mode 100644
index 0000000..7456c16
--- /dev/null
+++ b/src/live_effects/lpe-interpolate.cpp
@@ -0,0 +1,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 :