diff options
Diffstat (limited to 'src/grid-snapper.cpp')
-rw-r--r-- | src/grid-snapper.cpp | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/src/grid-snapper.cpp b/src/grid-snapper.cpp new file mode 100644 index 0000000..f43975c --- /dev/null +++ b/src/grid-snapper.cpp @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Grid Snapper for Rectangular and Axonometric Grids + *//* + * Authors: see git history + * + * Copyright (C) 2022 Authors + * Copyright (C) Johan Engelen 2006-2007 <johan@shouraizou.nl> + * Copyright (C) Lauris Kaplinski 2000 + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "grid-snapper.h" + +#include "desktop.h" +#include "helper/mathfns.h" +#include "object/sp-grid.h" +#include "object/sp-namedview.h" + +static int calculate_scaling_factor(double length, int major) +{ + int multiply = 1; + int step = std::max(major, 1); + int watchdog = 0; + + while (length * multiply < 8.0 && watchdog < 100) { + multiply *= step; + // First pass, go up to the major line spacing, then keep increasing by two. + step = 2; + watchdog++; + } + + return multiply; +} + +// Project a vector onto the given axis. +static auto proj(Geom::Point const &p, int dim) +{ + return dim == 0 + ? Geom::Point(p.x(), 0.0) + : Geom::Point(0.0, p.y()); +} + +// Return the unit vector along the given axis. +static auto basis(int dim) +{ + return dim == 0 + ? Geom::Point(1.0, 0.0) + : Geom::Point(0.0, 1.0); +} + +namespace Inkscape { + +GridSnapper::GridSnapper(SPGrid const *grid, SnapManager *sm, Geom::Coord const d) + : LineSnapper(sm, d) + , _grid(grid) +{ +} + +/** + * \return Snap tolerance (desktop coordinates); depends on current zoom so that it's always the same in screen pixels + */ +Geom::Coord GridSnapper::getSnapperTolerance() const +{ + SPDesktop const *dt = _snapmanager->getDesktop(); + double const zoom = dt ? dt->current_zoom() : 1; + return _snapmanager->snapprefs.getGridTolerance() / zoom; +} + +bool GridSnapper::getSnapperAlwaysSnap() const +{ + return _snapmanager->snapprefs.getGridTolerance() == 10000; //TODO: Replace this threshold of 10000 by a constant; see also tolerance-slider.cpp +} + +LineSnapper::LineList GridSnapper::_getSnapLines(Geom::Point const &p) const +{ + if (!_snapmanager->getNamedView() || !ThisSnapperMightSnap()) { + return {}; + } + + switch (_grid->getType()) { + case GridType::RECTANGULAR: return getSnapLinesXY(p); + case GridType::AXONOMETRIC: return getSnapLinesAxonom(p); + default: g_assert_not_reached(); return {}; + } +} + +void GridSnapper::_addSnappedLine(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, Geom::Point const &normal_to_line, Geom::Point const &point_on_line) const +{ + isr.grid_lines.emplace_back(snapped_point, snapped_distance, source, source_num, SNAPTARGET_GRID, getSnapperTolerance(), getSnapperAlwaysSnap(), normal_to_line, point_on_line); +} + +void GridSnapper::_addSnappedPoint(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const +{ + isr.points.emplace_back(snapped_point, source, source_num, SNAPTARGET_GRID, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); +} + +void GridSnapper::_addSnappedLinePerpendicularly(IntermSnapResults &isr, Geom::Point const &snapped_point, Geom::Coord const &snapped_distance, SnapSourceType const &source, long source_num, bool constrained_snap) const +{ + isr.points.emplace_back(snapped_point, source, source_num, SNAPTARGET_GRID_PERPENDICULAR, snapped_distance, getSnapperTolerance(), getSnapperAlwaysSnap(), constrained_snap, true); +} + +/** + * \return true if this Snapper will snap at least one kind of point. + */ +bool GridSnapper::ThisSnapperMightSnap() const +{ + return _snap_enabled && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_GRID); +} + +LineSnapper::LineList GridSnapper::getSnapLinesXY(Geom::Point const &p) const +{ + LineList s; + + auto const *desktop = _snapmanager->getDesktop(); + auto const [origin, spacing] = _grid->getEffectiveOriginAndSpacing(); + + for (int i = 0; i < 2; ++i) { + double scaled_spacing = spacing[i]; + + if (getSnapVisibleOnly() && desktop) { + // Only snap to visible grid lines. + auto const sw = proj(spacing, i) * desktop->d2w().withoutTranslation(); + int const mult = calculate_scaling_factor(sw.length(), _grid->getMajorLineInterval()); + scaled_spacing *= mult; + } + + s.emplace_back(basis(i), basis(i) * Util::round_to_upper_multiple_plus(p[i], scaled_spacing, origin[i])); + s.emplace_back(basis(i), basis(i) * Util::round_to_lower_multiple_plus(p[i], scaled_spacing, origin[i])); + } + + return s; +} + +LineSnapper::LineList GridSnapper::getSnapLinesAxonom(Geom::Point const &p) const +{ + LineList s; + + auto const *desktop = _snapmanager->getDesktop(); + auto const [origin, spacing] = _grid->getEffectiveOriginAndSpacing(); + + double ta_x = tan(Geom::rad_from_deg(_grid->getAngleX())); + double ta_z = tan(Geom::rad_from_deg(_grid->getAngleZ())); + + if (desktop && desktop->is_yaxisdown()) { + std::swap(ta_x, ta_z); + } + + double spacing_h = spacing.y() / (ta_x + ta_z); + double spacing_v = spacing.y(); + + if (getSnapVisibleOnly() && desktop) { + // Only snap to visible grid lines. + auto const lyw = spacing.y() * desktop->d2w().descrim(); + int const mult = calculate_scaling_factor(lyw, _grid->getMajorLineInterval()); + spacing_h *= mult; + spacing_v *= mult; + } + + // In an axonometric grid, any point will be surrounded by 6 grid lines: + // - 2 vertical grid lines, one left and one right from the point + // - 2 angled z grid lines, one above and one below the point + // - 2 angled x grid lines, one above and one below the point + + // Calculate the x coordinate of the vertical grid lines + Geom::Coord x_max = Util::round_to_upper_multiple_plus(p[Geom::X], spacing_h, origin[Geom::X]); + Geom::Coord x_min = Util::round_to_lower_multiple_plus(p[Geom::X], spacing_h, origin[Geom::X]); + + // Calculate the y coordinate of the intersection of the angled grid lines with the y-axis + double y_proj_along_z = p[Geom::Y] - ta_z * (p[Geom::X] - origin[Geom::X]); + double y_proj_along_x = p[Geom::Y] + ta_x * (p[Geom::X] - origin[Geom::X]); + double y_proj_along_z_max = Util::round_to_upper_multiple_plus(y_proj_along_z, spacing_v, origin[Geom::Y]); + double y_proj_along_z_min = Util::round_to_lower_multiple_plus(y_proj_along_z, spacing_v, origin[Geom::Y]); + double y_proj_along_x_max = Util::round_to_upper_multiple_plus(y_proj_along_x, spacing_v, origin[Geom::Y]); + double y_proj_along_x_min = Util::round_to_lower_multiple_plus(y_proj_along_x, spacing_v, origin[Geom::Y]); + + // Calculate the versor for the angled grid lines + Geom::Point vers_x = Geom::Point(1, -ta_x); + Geom::Point vers_z = Geom::Point(1, ta_z); + + // Calculate the normal for the angled grid lines + Geom::Point norm_x = Geom::rot90(vers_x); + Geom::Point norm_z = Geom::rot90(vers_z); + + // The four angled grid lines form a parallelogram, enclosing the point + // One of the two vertical grid lines divides this parallelogram in two triangles + // We will now try to find out in which half (i.e. triangle) our point is, and return + // only the three grid lines defining that triangle + + // The vertical grid line is at the intersection of two angled grid lines. + // Now go find that intersection! + Geom::Point p_x(0, y_proj_along_x_max); + Geom::Line line_x(p_x, p_x + vers_x); + Geom::Point p_z(0, y_proj_along_z_max); + Geom::Line line_z(p_z, p_z + vers_z); + + Geom::OptCrossing inters = Geom::OptCrossing(); // empty by default + try + { + inters = Geom::intersection(line_x, line_z); + } + catch (Geom::InfiniteSolutions &e) + { + // We're probably dealing with parallel lines; this is useless! + return s; + } + + // Determine which half of the parallelogram to use + bool use_left_half = true; + bool use_right_half = true; + + if (inters) { + Geom::Point inters_pt = line_x.pointAt((*inters).ta); + use_left_half = (p[Geom::X] - origin[Geom::X]) < inters_pt[Geom::X]; + use_right_half = !use_left_half; + } + + // Return the three grid lines which define the triangle that encloses our point + // If we didn't find an intersection above, all 6 grid lines will be returned + if (use_left_half) { + s.emplace_back(norm_z, Geom::Point(origin[Geom::X], y_proj_along_z_max)); + s.emplace_back(norm_x, Geom::Point(origin[Geom::X], y_proj_along_x_min)); + s.emplace_back(Geom::Point(1, 0), Geom::Point(x_max, 0)); + } + + if (use_right_half) { + s.emplace_back(norm_z, Geom::Point(origin[Geom::X], y_proj_along_z_min)); + s.emplace_back(norm_x, Geom::Point(origin[Geom::X], y_proj_along_x_max)); + s.emplace_back(Geom::Point(1, 0), Geom::Point(x_min, 0)); + } + + return s; +} + +} // namepsace Inkscape |