From a4ba969e1132ad58f000dc6daac80404f3a37dbb Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sat, 2 Aug 2025 12:31:16 +0300 Subject: [PATCH] Added lasso select tool --- CMakeLists.txt | 1 + include/delaunator.hpp | 561 +++++++++++++++++++++++++ src/DrawingProgram/DrawingProgram.cpp | 40 +- src/DrawingProgram/DrawingProgram.hpp | 7 + src/DrawingProgram/LassoSelectTool.cpp | 90 ++++ src/DrawingProgram/LassoSelectTool.hpp | 29 ++ src/DrawingProgram/RectSelectTool.cpp | 22 +- 7 files changed, 721 insertions(+), 29 deletions(-) create mode 100644 include/delaunator.hpp create mode 100644 src/DrawingProgram/LassoSelectTool.cpp create mode 100644 src/DrawingProgram/LassoSelectTool.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 38f7b2e..ae552bc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(sources "src/main.cpp" "src/DrawingProgram/TextBoxTool.cpp" "src/DrawingProgram/EraserTool.cpp" "src/DrawingProgram/RectSelectTool.cpp" + "src/DrawingProgram/LassoSelectTool.cpp" "src/DrawingProgram/RectDrawTool.cpp" "src/DrawingProgram/EllipseDrawTool.cpp" "src/DrawingProgram/InkDropperTool.cpp" diff --git a/include/delaunator.hpp b/include/delaunator.hpp new file mode 100644 index 0000000..e16e0c3 --- /dev/null +++ b/include/delaunator.hpp @@ -0,0 +1,561 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace delaunator { + +// Kahan and Babuska summation, Neumaier variant; accumulates less FP error +inline double sum(const std::vector& x) { + double sum = x[0]; + double err = 0.0; + + for (size_t i = 1; i < x.size(); i++) { + const double k = x[i]; + const double m = sum + k; + err += std::fabs(sum) >= std::fabs(k) ? sum - m + k : k - m + sum; + sum = m; + } + return sum + err; +} + +inline double dist( + const double ax, + const double ay, + const double bx, + const double by) { + const double dx = ax - bx; + const double dy = ay - by; + return dx * dx + dy * dy; +} + +inline double circumradius( + const double ax, + const double ay, + const double bx, + const double by, + const double cx, + const double cy) { + const double dx = bx - ax; + const double dy = by - ay; + const double ex = cx - ax; + const double ey = cy - ay; + + const double bl = dx * dx + dy * dy; + const double cl = ex * ex + ey * ey; + const double d = dx * ey - dy * ex; + + const double x = (ey * bl - dy * cl) * 0.5 / d; + const double y = (dx * cl - ex * bl) * 0.5 / d; + + if ((bl > 0.0 || bl < 0.0) && (cl > 0.0 || cl < 0.0) && (d > 0.0 || d < 0.0)) { + return x * x + y * y; + } else { + return std::numeric_limits::max(); + } +} + +inline bool orient( + const double px, + const double py, + const double qx, + const double qy, + const double rx, + const double ry) { + return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0.0; +} + +inline std::pair circumcenter( + const double ax, + const double ay, + const double bx, + const double by, + const double cx, + const double cy) { + const double dx = bx - ax; + const double dy = by - ay; + const double ex = cx - ax; + const double ey = cy - ay; + + const double bl = dx * dx + dy * dy; + const double cl = ex * ex + ey * ey; + const double d = dx * ey - dy * ex; + + const double x = ax + (ey * bl - dy * cl) * 0.5 / d; + const double y = ay + (dx * cl - ex * bl) * 0.5 / d; + + return std::make_pair(x, y); +} + +inline double compare( + std::vector const& coords, + std::size_t i, + std::size_t j, + double cx, + double cy) { + const double d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy); + const double d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy); + const double diff1 = d1 - d2; + const double diff2 = coords[2 * i] - coords[2 * j]; + const double diff3 = coords[2 * i + 1] - coords[2 * j + 1]; + + if (diff1 > 0.0 || diff1 < 0.0) { + return diff1; + } else if (diff2 > 0.0 || diff2 < 0.0) { + return diff2; + } else { + return diff3; + } +} + +struct sort_to_center { + + std::vector const& coords; + double cx; + double cy; + + bool operator()(std::size_t i, std::size_t j) { + return compare(coords, i, j, cx, cy) < 0; + } +}; + +inline bool in_circle( + double ax, + double ay, + double bx, + double by, + double cx, + double cy, + double px, + double py) { + const double dx = ax - px; + const double dy = ay - py; + const double ex = bx - px; + const double ey = by - py; + const double fx = cx - px; + const double fy = cy - py; + + const double ap = dx * dx + dy * dy; + const double bp = ex * ex + ey * ey; + const double cp = fx * fx + fy * fy; + + return (dx * (ey * cp - bp * fy) - + dy * (ex * cp - bp * fx) + + ap * (ex * fy - ey * fx)) < 0.0; +} + +constexpr double EPSILON = std::numeric_limits::epsilon(); +constexpr std::size_t INVALID_INDEX = std::numeric_limits::max(); + +inline bool check_pts_equal(double x1, double y1, double x2, double y2) { + return std::fabs(x1 - x2) <= EPSILON && + std::fabs(y1 - y2) <= EPSILON; +} + +// monotonically increases with real angle, but doesn't need expensive trigonometry +inline double pseudo_angle(double dx, double dy) { + const double p = dx / (std::abs(dx) + std::abs(dy)); + return (dy > 0.0 ? 3.0 - p : 1.0 + p) / 4.0; // [0..1) +} + +struct DelaunatorPoint { + std::size_t i; + double x; + double y; + std::size_t t; + std::size_t prev; + std::size_t next; + bool removed; +}; + +class Delaunator { + +public: + std::vector const& coords; + std::vector triangles; + std::vector halfedges; + std::vector hull_prev; + std::vector hull_next; + std::vector hull_tri; + std::size_t hull_start; + + Delaunator(std::vector const& in_coords); + + double get_hull_area(); + +private: + std::vector m_hash; + double m_center_x; + double m_center_y; + std::size_t m_hash_size; + + std::size_t legalize(std::size_t a); + std::size_t hash_key(double x, double y); + std::size_t add_triangle( + std::size_t i0, + std::size_t i1, + std::size_t i2, + std::size_t a, + std::size_t b, + std::size_t c); + void link(std::size_t a, std::size_t b); +}; + +Delaunator::Delaunator(std::vector const& in_coords) + : coords(in_coords), + triangles(), + halfedges(), + hull_prev(), + hull_next(), + hull_tri(), + hull_start(), + m_hash(), + m_center_x(), + m_center_y(), + m_hash_size() { + std::size_t n = coords.size() >> 1; + + double max_x = std::numeric_limits::min(); + double max_y = std::numeric_limits::min(); + double min_x = std::numeric_limits::max(); + double min_y = std::numeric_limits::max(); + std::vector ids; + ids.reserve(n); + + for (std::size_t i = 0; i < n; i++) { + const double x = coords[2 * i]; + const double y = coords[2 * i + 1]; + + if (x < min_x) min_x = x; + if (y < min_y) min_y = y; + if (x > max_x) max_x = x; + if (y > max_y) max_y = y; + + ids.push_back(i); + } + const double cx = (min_x + max_x) / 2; + const double cy = (min_y + max_y) / 2; + double min_dist = std::numeric_limits::max(); + + std::size_t i0 = INVALID_INDEX; + std::size_t i1 = INVALID_INDEX; + std::size_t i2 = INVALID_INDEX; + + // pick a seed point close to the centroid + for (std::size_t i = 0; i < n; i++) { + const double d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]); + if (d < min_dist) { + i0 = i; + min_dist = d; + } + } + + const double i0x = coords[2 * i0]; + const double i0y = coords[2 * i0 + 1]; + + min_dist = std::numeric_limits::max(); + + // find the point closest to the seed + for (std::size_t i = 0; i < n; i++) { + if (i == i0) continue; + const double d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]); + if (d < min_dist && d > 0.0) { + i1 = i; + min_dist = d; + } + } + + double i1x = coords[2 * i1]; + double i1y = coords[2 * i1 + 1]; + + double min_radius = std::numeric_limits::max(); + + // find the third point which forms the smallest circumcircle with the first two + for (std::size_t i = 0; i < n; i++) { + if (i == i0 || i == i1) continue; + + const double r = circumradius( + i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]); + + if (r < min_radius) { + i2 = i; + min_radius = r; + } + } + + if (!(min_radius < std::numeric_limits::max())) { + throw std::runtime_error("not triangulation"); + } + + double i2x = coords[2 * i2]; + double i2y = coords[2 * i2 + 1]; + + if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) { + std::swap(i1, i2); + std::swap(i1x, i2x); + std::swap(i1y, i2y); + } + + std::tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); + + // sort the points by distance from the seed triangle circumcenter + std::sort(ids.begin(), ids.end(), sort_to_center{ coords, m_center_x, m_center_y }); + + // initialize a hash table for storing edges of the advancing convex hull + m_hash_size = static_cast(std::llround(std::ceil(std::sqrt(n)))); + m_hash.resize(m_hash_size); + std::fill(m_hash.begin(), m_hash.end(), INVALID_INDEX); + + // initialize arrays for tracking the edges of the advancing convex hull + hull_prev.resize(n); + hull_next.resize(n); + hull_tri.resize(n); + + hull_start = i0; + + size_t hull_size = 3; + + hull_next[i0] = hull_prev[i2] = i1; + hull_next[i1] = hull_prev[i0] = i2; + hull_next[i2] = hull_prev[i1] = i0; + + hull_tri[i0] = 0; + hull_tri[i1] = 1; + hull_tri[i2] = 2; + + m_hash[hash_key(i0x, i0y)] = i0; + m_hash[hash_key(i1x, i1y)] = i1; + m_hash[hash_key(i2x, i2y)] = i2; + + std::size_t max_triangles = n < 3 ? 1 : 2 * n - 5; + triangles.reserve(max_triangles * 3); + halfedges.reserve(max_triangles * 3); + add_triangle(i0, i1, i2, INVALID_INDEX, INVALID_INDEX, INVALID_INDEX); + double xp = std::numeric_limits::quiet_NaN(); + double yp = std::numeric_limits::quiet_NaN(); + for (std::size_t k = 0; k < n; k++) { + const std::size_t i = ids[k]; + const double x = coords[2 * i]; + const double y = coords[2 * i + 1]; + + // skip near-duplicate points + if (k > 0 && check_pts_equal(x, y, xp, yp)) continue; + xp = x; + yp = y; + + // skip seed triangle points + if ( + check_pts_equal(x, y, i0x, i0y) || + check_pts_equal(x, y, i1x, i1y) || + check_pts_equal(x, y, i2x, i2y)) continue; + + // find a visible edge on the convex hull using edge hash + std::size_t start = 0; + + size_t key = hash_key(x, y); + for (size_t j = 0; j < m_hash_size; j++) { + start = m_hash[(key + j) % m_hash_size]; + if (start != INVALID_INDEX && start != hull_next[start]) break; + } + + start = hull_prev[start]; + size_t e = start; + size_t q; + + while (q = hull_next[e], !orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) { //TODO: does it works in a same way as in JS + e = q; + if (e == start) { + e = INVALID_INDEX; + break; + } + } + + if (e == INVALID_INDEX) continue; // likely a near-duplicate point; skip it + + // add the first triangle from the point + std::size_t t = add_triangle( + e, + i, + hull_next[e], + INVALID_INDEX, + INVALID_INDEX, + hull_tri[e]); + + hull_tri[i] = legalize(t + 2); + hull_tri[e] = t; + hull_size++; + + // walk forward through the hull, adding more triangles and flipping recursively + std::size_t next = hull_next[e]; + while ( + q = hull_next[next], + orient(x, y, coords[2 * next], coords[2 * next + 1], coords[2 * q], coords[2 * q + 1])) { + t = add_triangle(next, i, q, hull_tri[i], INVALID_INDEX, hull_tri[next]); + hull_tri[i] = legalize(t + 2); + hull_next[next] = next; // mark as removed + hull_size--; + next = q; + } + + // walk backward from the other side, adding more triangles and flipping + if (e == start) { + while ( + q = hull_prev[e], + orient(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) { + t = add_triangle(q, i, e, INVALID_INDEX, hull_tri[e], hull_tri[q]); + legalize(t + 2); + hull_tri[q] = t; + hull_next[e] = e; // mark as removed + hull_size--; + e = q; + } + } + + // update the hull indices + hull_prev[i] = e; + hull_start = e; + hull_prev[next] = i; + hull_next[e] = i; + hull_next[i] = next; + + m_hash[hash_key(x, y)] = i; + m_hash[hash_key(coords[2 * e], coords[2 * e + 1])] = e; + } +} + +double Delaunator::get_hull_area() { + std::vector hull_area; + size_t e = hull_start; + do { + hull_area.push_back((coords[2 * e] - coords[2 * hull_prev[e]]) * (coords[2 * e + 1] + coords[2 * hull_prev[e] + 1])); + e = hull_next[e]; + } while (e != hull_start); + return sum(hull_area); +} + +std::size_t Delaunator::legalize(std::size_t a) { + const std::size_t b = halfedges[a]; + + /* if the pair of triangles doesn't satisfy the Delaunay condition + * (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + * then do the same check/flip recursively for the new pair of triangles + * + * pl pl + * /||\ / \ + * al/ || \bl al/ \a + * / || \ / \ + * / a||b \ flip /___ar___\ + * p0\ || /p1 => p0\---bl---/p1 + * \ || / \ / + * ar\ || /br b\ /br + * \||/ \ / + * pr pr + */ + const std::size_t a0 = a - a % 3; + const std::size_t b0 = b - b % 3; + + const std::size_t al = a0 + (a + 1) % 3; + const std::size_t ar = a0 + (a + 2) % 3; + const std::size_t bl = b0 + (b + 2) % 3; + + const std::size_t p0 = triangles[ar]; + const std::size_t pr = triangles[a]; + const std::size_t pl = triangles[al]; + const std::size_t p1 = triangles[bl]; + + if (b == INVALID_INDEX) { + return ar; + } + + const bool illegal = in_circle( + coords[2 * p0], + coords[2 * p0 + 1], + coords[2 * pr], + coords[2 * pr + 1], + coords[2 * pl], + coords[2 * pl + 1], + coords[2 * p1], + coords[2 * p1 + 1]); + + if (illegal) { + triangles[a] = p1; + triangles[b] = p0; + + auto hbl = halfedges[bl]; + + // edge swapped on the other side of the hull (rare); fix the halfedge reference + if (hbl == INVALID_INDEX) { + std::size_t e = hull_start; + do { + if (hull_tri[e] == bl) { + hull_tri[e] = a; + break; + } + e = hull_next[e]; + } while (e != hull_start); + } + link(a, hbl); + link(b, halfedges[ar]); + link(ar, bl); + + std::size_t br = b0 + (b + 1) % 3; + + legalize(a); + return legalize(br); + } + return ar; +} + +std::size_t Delaunator::hash_key(double x, double y) { + const double dx = x - m_center_x; + const double dy = y - m_center_y; + return static_cast(std::llround( + std::floor(pseudo_angle(dx, dy) * static_cast(m_hash_size)))) % + m_hash_size; +} + +std::size_t Delaunator::add_triangle( + std::size_t i0, + std::size_t i1, + std::size_t i2, + std::size_t a, + std::size_t b, + std::size_t c) { + std::size_t t = triangles.size(); + triangles.push_back(i0); + triangles.push_back(i1); + triangles.push_back(i2); + link(t, a); + link(t + 1, b); + link(t + 2, c); + return t; +} + +void Delaunator::link(std::size_t a, std::size_t b) { + std::size_t s = halfedges.size(); + if (a == s) { + halfedges.push_back(b); + } else if (a < s) { + halfedges[a] = b; + } else { + throw std::runtime_error("Cannot link edge"); + } + if (b != INVALID_INDEX) { + std::size_t s2 = halfedges.size(); + if (b == s2) { + halfedges.push_back(a); + } else if (b < s2) { + halfedges[b] = a; + } else { + throw std::runtime_error("Cannot link edge"); + } + } +} + +} //namespace delaunator diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index 7f72024..e93ce4e 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -21,9 +21,13 @@ #include #include "../World.hpp" #include "../MainProgram.hpp" +#include "LassoSelectTool.hpp" #include #include #include +#include +#include +#include DrawingProgram::DrawingProgram(World& initWorld): world(initWorld), @@ -32,6 +36,7 @@ DrawingProgram::DrawingProgram(World& initWorld): brushTool(*this), eraserTool(*this), rectSelectTool(*this), + lassoSelectTool(*this), rectDrawTool(*this), ellipseDrawTool(*this), textBoxTool(*this), @@ -200,6 +205,7 @@ void DrawingProgram::toolbar_gui() { if(t.gui.svg_icon_button("Ellipse Toolbar Button", "data/icons/circle.svg", controls.selectedTool == TOOL_ELLIPSE)) { controls.selectedTool = TOOL_ELLIPSE; } if(t.gui.svg_icon_button("Rect Toolbar Button", "data/icons/rectangle.svg", controls.selectedTool == TOOL_RECTANGLE)) { controls.selectedTool = TOOL_RECTANGLE; } if(t.gui.svg_icon_button("RectSelect Toolbar Button", "data/icons/rectselect.svg", controls.selectedTool == TOOL_RECTSELECT)) { controls.selectedTool = TOOL_RECTSELECT; } + if(t.gui.svg_icon_button("LassoSelect Toolbar Button", "data/icons/close.svg", controls.selectedTool == TOOL_LASSOSELECT)) { controls.selectedTool = TOOL_LASSOSELECT; } if(t.gui.svg_icon_button("Edit Toolbar Button", "data/icons/cursor.svg", controls.selectedTool == TOOL_EDIT)) { controls.selectedTool = TOOL_EDIT; } if(t.gui.svg_icon_button("Inkdropper Toolbar Button", "data/icons/eyedropper.svg", controls.selectedTool == TOOL_INKDROPPER)) { controls.selectedTool = TOOL_INKDROPPER; } if(t.gui.svg_icon_button("Screenshot Toolbar Button", "data/icons/camera.svg", controls.selectedTool == TOOL_SCREENSHOT)) { controls.selectedTool = TOOL_SCREENSHOT; } @@ -238,6 +244,9 @@ void DrawingProgram::tool_options_gui() { case TOOL_ERASER: eraserTool.gui_toolbox(); break; + case TOOL_LASSOSELECT: + lassoSelectTool.gui_toolbox(); + break; case TOOL_RECTSELECT: rectSelectTool.gui_toolbox(); break; @@ -259,8 +268,6 @@ void DrawingProgram::tool_options_gui() { case TOOL_EDIT: editTool.gui_toolbox(); break; - default: - break; } } } @@ -335,6 +342,9 @@ void DrawingProgram::update() { case TOOL_ERASER: eraserTool.tool_update(); break; + case TOOL_LASSOSELECT: + lassoSelectTool.tool_update(); + break; case TOOL_RECTSELECT: rectSelectTool.tool_update(); break; @@ -356,8 +366,6 @@ void DrawingProgram::update() { case TOOL_EDIT: editTool.tool_update(); break; - default: - break; }; if(controls.leftClickReleased) @@ -382,6 +390,8 @@ bool DrawingProgram::prevent_undo_or_redo() { return brushTool.prevent_undo_or_redo(); case TOOL_ERASER: return eraserTool.prevent_undo_or_redo(); + case TOOL_LASSOSELECT: + return lassoSelectTool.prevent_undo_or_redo(); case TOOL_RECTSELECT: return rectSelectTool.prevent_undo_or_redo(); case TOOL_RECTANGLE: @@ -396,12 +406,24 @@ bool DrawingProgram::prevent_undo_or_redo() { return screenshotTool.prevent_undo_or_redo(); case TOOL_EDIT: return editTool.prevent_undo_or_redo(); - default: - break; }; return false; } +SkPaint DrawingProgram::select_tool_line_paint() { + SkScalar intervals[] = {10, 5}; + float timeSinceEpoch = std::chrono::duration_cast>(std::chrono::steady_clock::now().time_since_epoch()).count(); + sk_sp lassoLineDashEffect = SkDashPathEffect::Make(intervals, 2, -std::fmod(timeSinceEpoch * 50, 15)); + + SkPaint selectLinePaint; + selectLinePaint.setStyle(SkPaint::kStroke_Style); + selectLinePaint.setStrokeWidth(3); + selectLinePaint.setColor4f(world.canvasTheme.toolFrontColor); + selectLinePaint.setPathEffect(lassoLineDashEffect); + + return selectLinePaint; +} + void DrawingProgram::force_rebuild_cache() { if(controls.selectedTool == TOOL_ERASER) compCache.test_rebuild_dont_include_set_dont_include_nodes(components.client_list(), eraserTool.erasedComponents, eraserTool.erasedBVHNodes, true); @@ -477,6 +499,7 @@ void DrawingProgram::add_file_to_canvas_by_data(const std::string& fileName, std } void DrawingProgram::reset_tools() { + lassoSelectTool.reset_tool(); rectSelectTool.reset_tool(); textBoxTool.reset_tool(); brushTool.reset_tool(); @@ -689,6 +712,9 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { case TOOL_ERASER: eraserTool.draw(canvas, drawData); break; + case TOOL_LASSOSELECT: + lassoSelectTool.draw(canvas, drawData); + break; case TOOL_RECTSELECT: rectSelectTool.draw(canvas, drawData); break; @@ -710,8 +736,6 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { case TOOL_EDIT: editTool.draw(canvas, drawData); break; - default: - break; } } diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index d180175..74ba5a4 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -12,6 +12,7 @@ #include "BrushTool.hpp" #include "EraserTool.hpp" #include "RectSelectTool.hpp" +#include "LassoSelectTool.hpp" #include "RectDrawTool.hpp" #include "EllipseDrawTool.hpp" #include "TextBoxTool.hpp" @@ -84,9 +85,13 @@ class DrawingProgram { void add_undo_place_components(const std::unordered_set& objSetToUndo); void add_undo_erase_components(const std::unordered_set& objSetToUndo); + SkPaint select_tool_line_paint(); + + BrushTool brushTool; EraserTool eraserTool; RectSelectTool rectSelectTool; + LassoSelectTool lassoSelectTool; RectDrawTool rectDrawTool; EllipseDrawTool ellipseDrawTool; TextBoxTool textBoxTool; @@ -102,6 +107,7 @@ class DrawingProgram { enum Tool : int { TOOL_BRUSH = 0, TOOL_ERASER, + TOOL_LASSOSELECT, TOOL_RECTSELECT, TOOL_RECTANGLE, TOOL_ELLIPSE, @@ -142,4 +148,5 @@ class DrawingProgram { friend class ImageTool; friend class DrawingProgramCache; friend class DrawingProgramSelection; + friend class LassoSelectTool; }; diff --git a/src/DrawingProgram/LassoSelectTool.cpp b/src/DrawingProgram/LassoSelectTool.cpp new file mode 100644 index 0000000..2046cab --- /dev/null +++ b/src/DrawingProgram/LassoSelectTool.cpp @@ -0,0 +1,90 @@ +#include "LassoSelectTool.hpp" +#include "DrawingProgram.hpp" +#include "../MainProgram.hpp" +#include "../DrawData.hpp" +#include "Helpers/MathExtras.hpp" +#include "Helpers/SCollision.hpp" +#include "../CoordSpaceHelper.hpp" +#include +#include + +LassoSelectTool::LassoSelectTool(DrawingProgram& initDrawP): + drawP(initDrawP) +{ +} + +void LassoSelectTool::gui_toolbox() { + drawP.world.main.toolbar.gui.text_label_centered("Lasso Select"); +} + +void LassoSelectTool::reset_tool() { + drawP.selection.deselect_all(); + controls = LassoSelectControls(); +} + +void LassoSelectTool::tool_update() { + switch(controls.selectionMode) { + case 0: { + if(drawP.controls.leftClick) { + if(drawP.selection.is_something_selected()) { + if(!drawP.selection.mouse_collided_with_selection()) + reset_tool(); + } + else { + reset_tool(); + controls.coords = drawP.world.drawData.cam.c; + controls.lassoPoints.emplace_back(controls.coords.get_mouse_pos(drawP.world)); + controls.selectionMode = 1; + } + } + break; + } + case 1: { + using namespace SCollision; + Vector2f newLassoPoint = controls.coords.get_mouse_pos(drawP.world); + if(vec_distance(controls.lassoPoints.back(), newLassoPoint)) { + controls.lassoPoints.emplace_back(newLassoPoint); + } + if(!drawP.controls.leftClickHeld) { + std::vector dCoords; + for(Vector2f& lassoPoint : controls.lassoPoints) { + dCoords.emplace_back(lassoPoint.x()); + dCoords.emplace_back(lassoPoint.y()); + } + delaunator::Delaunator d(dCoords); + ColliderCollection cC; + for(size_t i = 0; i < d.triangles.size(); i += 3) { + SCollision::Triangle tri{ + Vector2f{dCoords[2 * d.triangles[i]], dCoords[2 * d.triangles[i] + 1]}, + Vector2f{dCoords[2 * d.triangles[i + 1]], dCoords[2 * d.triangles[i + 1] + 1]}, + Vector2f{dCoords[2 * d.triangles[i + 2]], dCoords[2 * d.triangles[i + 2] + 1]} + }; + cC.triangle.emplace_back(tri); + } + drawP.selection.add_from_cam_coord_collider_to_selection(cC); + controls.selectionMode = 0; + } + break; + } + } +} + +bool LassoSelectTool::prevent_undo_or_redo() { + return drawP.selection.is_something_selected() || controls.selectionMode == 1; +} + +void LassoSelectTool::draw(SkCanvas* canvas, const DrawData& drawData) { + if(controls.selectionMode == 1) { + canvas->save(); + controls.coords.transform_sk_canvas(canvas, drawData); + + SkPath lassoPath; + lassoPath.moveTo(convert_vec2(controls.lassoPoints.front())); + for(Vector2f& p : controls.lassoPoints | std::views::drop(1)) + lassoPath.lineTo(convert_vec2(p)); + + canvas->drawPath(lassoPath, drawP.select_tool_line_paint()); + + canvas->restore(); + } +} diff --git a/src/DrawingProgram/LassoSelectTool.hpp b/src/DrawingProgram/LassoSelectTool.hpp new file mode 100644 index 0000000..d6ad1d8 --- /dev/null +++ b/src/DrawingProgram/LassoSelectTool.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include "../DrawData.hpp" +#include +#include "../CoordSpaceHelper.hpp" +#include "../DrawComponents/DrawComponent.hpp" + +class DrawingProgram; + +class LassoSelectTool { + public: + LassoSelectTool(DrawingProgram& initDrawP); + void gui_toolbox(); + void tool_update(); + void draw(SkCanvas* canvas, const DrawData& drawData); + void reset_tool(); + + bool prevent_undo_or_redo(); + private: + struct LassoSelectControls { + int selectionMode = 0; + + CoordSpaceHelper coords; + + std::vector lassoPoints; + } controls; + + DrawingProgram& drawP; +}; diff --git a/src/DrawingProgram/RectSelectTool.cpp b/src/DrawingProgram/RectSelectTool.cpp index 60549e5..8cbb306 100644 --- a/src/DrawingProgram/RectSelectTool.cpp +++ b/src/DrawingProgram/RectSelectTool.cpp @@ -1,27 +1,10 @@ #include "RectSelectTool.hpp" -#include #include "DrawingProgram.hpp" -#include "../Server/CommandList.hpp" #include "../MainProgram.hpp" #include "../DrawData.hpp" -#include "Helpers/FixedPoint.hpp" #include "Helpers/MathExtras.hpp" #include "Helpers/SCollision.hpp" -#include "Helpers/Serializers.hpp" #include "../CoordSpaceHelper.hpp" -#include -#include -#include - -#ifdef USE_SKIA_BACKEND_GRAPHITE - #include -#elif USE_SKIA_BACKEND_GANESH - #include - #include -#endif - -#define ROTATION_POINT_RADIUS_MULTIPLIER 0.7 -#define ROTATION_POINTS_DISTANCE 20.0 RectSelectTool::RectSelectTool(DrawingProgram& initDrawP): drawP(initDrawP) @@ -83,11 +66,8 @@ void RectSelectTool::draw(SkCanvas* canvas, const DrawData& drawData) { canvas->save(); controls.coords.transform_sk_canvas(canvas, drawData); - SkPaint selectionPaint; - selectionPaint.setColor4f({0.3f, 0.6f, 0.9f, 0.4f}); - SkRect r = SkRect::MakeLTRB(controls.newT[0].x(), controls.newT[0].y(), controls.newT[2].x(), controls.newT[2].y()); - canvas->drawRect(r, selectionPaint); + canvas->drawRect(r, drawP.select_tool_line_paint()); canvas->restore(); canvas->restore();