// SPDX-License-Identifier: GPL-2.0-or-later /** \file * LPE interpolate implementation */ /* * Authors: * Johan Engelen * * Copyright (C) Johan Engelen 2007-2008 * * 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 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::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 > pwd2_A = path_in[0].toPwSb(); Geom::Piecewise > 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 > pA = Geom::partition(pwd2_A, pwd2_B.cuts); Geom::Piecewise > 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 > 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 > LPEInterpolate::calculate_trajectory(Geom::OptRect bounds_A, Geom::OptRect bounds_B) { Geom::Affine affine = trajectory_path.get_relative_affine(); Geom::Piecewise > 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(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 :