From 4283af86e7502eb6aea202d63f06e53a89709e65 Mon Sep 17 00:00:00 2001 From: James Ding Date: Tue, 30 Jul 2024 16:34:58 -0700 Subject: [PATCH] Add line snapping --- lib/models/snap.dart | 1 + lib/widgets/editor/editor_config.dart | 1 + lib/widgets/editor/editor_logic.dart | 39 +++++++++++++++++++++ lib/widgets/editor/editor_painter.dart | 20 +++++++++++ lib/widgets/editor/editor_painter_data.dart | 36 +++++++++++++++++-- lib/widgets/editor/editor_util.dart | 13 ------- 6 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 lib/models/snap.dart create mode 100644 lib/widgets/editor/editor_logic.dart delete mode 100644 lib/widgets/editor/editor_util.dart diff --git a/lib/models/snap.dart b/lib/models/snap.dart new file mode 100644 index 0000000..f292a31 --- /dev/null +++ b/lib/models/snap.dart @@ -0,0 +1 @@ +// TODO snap candidates \ No newline at end of file diff --git a/lib/widgets/editor/editor_config.dart b/lib/widgets/editor/editor_config.dart index 6245057..144785e 100644 --- a/lib/widgets/editor/editor_config.dart +++ b/lib/widgets/editor/editor_config.dart @@ -23,6 +23,7 @@ class EditorConfig { // Painter Configuration static const crossMarkerSize = 25.0; + static const crossXMarkerSize = 15.0; static const minorGridSize = 20.0; static const majorGridDensity = 5; // 100.0 is the majorGridSize diff --git a/lib/widgets/editor/editor_logic.dart b/lib/widgets/editor/editor_logic.dart new file mode 100644 index 0000000..69d0d8d --- /dev/null +++ b/lib/widgets/editor/editor_logic.dart @@ -0,0 +1,39 @@ +import 'dart:ui'; + +import 'package:vector_math/vector_math_64.dart'; + +class EditorLogic { + static bool interceptsSquare(Offset parent, Offset child, double size) { + Rect rect = Rect.fromCenter(center: parent, width: size, height: size); + return rect.contains(child); + } + + static bool interceptsCircle(Offset parent, Offset child, double radius) { + double distance = (parent - child).distanceSquared; + return distance < radius * radius; + } + + static Offset nearestPointOnLine(Offset start, Offset end, Offset offset) { + final Vector2 startVector = Vector2(start.dx, start.dy); + final Vector2 endVector = Vector2(end.dx, end.dy); + final Vector2 pointVector = Vector2(offset.dx, offset.dy); + + final Vector2 lineVector = endVector - startVector; + + final Vector2 startToPointVector = pointVector - startVector; + + final double lineLengthSquared = lineVector.length2; + final double t = (lineVector.dot(startToPointVector)) / lineLengthSquared; + + Vector2 nearestPoint; + if (t < 0) { + nearestPoint = startVector; + } else if (t > 1) { + nearestPoint = endVector; + } else { + nearestPoint = startVector + lineVector * t; + } + + return Offset(nearestPoint.x, nearestPoint.y); + } +} diff --git a/lib/widgets/editor/editor_painter.dart b/lib/widgets/editor/editor_painter.dart index 54f44a6..c7118dd 100644 --- a/lib/widgets/editor/editor_painter.dart +++ b/lib/widgets/editor/editor_painter.dart @@ -187,6 +187,20 @@ class EditorPainter extends CustomPainter { canvas.drawRect(rect, snapPaint); } + void drawCrossXMarker(Canvas canvas, Offset offset) { + final Paint crossPaint = Paint() + ..color = Colors.blue + ..strokeWidth = 1.5 * scaleInverse + ..style = PaintingStyle.stroke; + + double halfSize = EditorConfig.crossXMarkerSize * scaleInverse / 2; + + canvas.drawLine(offset.translate(-halfSize, -halfSize), + offset.translate(halfSize, halfSize), crossPaint); + canvas.drawLine(offset.translate(-halfSize, halfSize), + offset.translate(halfSize, -halfSize), crossPaint); + } + void establishGridSnap(Canvas canvas) { if (activeShouldSnapToGrid!) { drawSnapMarker(canvas, activePointerGridSnap!); @@ -203,6 +217,12 @@ class EditorPainter extends CustomPainter { return; } + Offset? nearestLineSnap = editorData.nearestLineSnap(activePointer!); + if (nearestLineSnap != null) { + drawCrossXMarker(canvas, nearestLineSnap); + return; + } + establishGridSnap(canvas); } diff --git a/lib/widgets/editor/editor_painter_data.dart b/lib/widgets/editor/editor_painter_data.dart index e4f197e..7d22be6 100644 --- a/lib/widgets/editor/editor_painter_data.dart +++ b/lib/widgets/editor/editor_painter_data.dart @@ -4,7 +4,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:toolfoam/models/tools/tf_tool_data.dart'; import 'package:toolfoam/widgets/editor/editor_config.dart'; -import 'package:toolfoam/widgets/editor/editor_util.dart'; + +import '../../models/line.dart'; +import 'editor_logic.dart'; class EditorData extends ChangeNotifier { EditorData({ @@ -72,9 +74,35 @@ class EditorData extends ChangeNotifier { return shouldSnapToGrid(activePointer!); } + Offset? nearestLineSnap(Offset offset, {String? ignoreUuid}) { + Offset? nearestLineSnap; + double nearestDistance = double.infinity; + for (Line line in toolData.lines) { + if (line.point1 == ignoreUuid || line.point2 == ignoreUuid) continue; + + Offset start = toolData.points[line.point1]!; + Offset end = toolData.points[line.point2]!; + + Offset nearestPoint = EditorLogic.nearestPointOnLine(start, end, offset); + Offset delta = nearestPoint - offset; + double distance = delta.distanceSquared; + + bool isNearest = distance < nearestDistance; + double threshold = EditorConfig.defaultSnapTolerance / 2 * scaleInverse; + bool isSnap = distance < threshold * threshold; + + if (isNearest && isSnap) { + nearestLineSnap = nearestPoint; + nearestDistance = distance; + } + } + + return nearestLineSnap; + } + MapEntry? nearestPointSnap( Offset offset, String? ignoreUuid) { - MapEntry? nearestPointSnap = null; + MapEntry? nearestPointSnap; double nearestDistance = double.infinity; for (MapEntry entry in toolData.points.entries) { if (entry.key == ignoreUuid) continue; @@ -99,6 +127,10 @@ class EditorData extends ChangeNotifier { Offset effectivePointerCoordinates(Offset offset, {String? ignoreUuid}) { MapEntry? pointSnap = nearestPointSnap(offset, ignoreUuid); if (pointSnap != null) return pointSnap.value; + + Offset? lineSnap = nearestLineSnap(offset); + if (lineSnap != null) return lineSnap; + if (shouldSnapToGrid(offset)) return gridSnap(offset); return offset; diff --git a/lib/widgets/editor/editor_util.dart b/lib/widgets/editor/editor_util.dart deleted file mode 100644 index 736774b..0000000 --- a/lib/widgets/editor/editor_util.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:ui'; - -class EditorLogic { - static bool interceptsSquare(Offset parent, Offset child, double size) { - Rect rect = Rect.fromCenter(center: parent, width: size, height: size); - return rect.contains(child); - } - - static bool interceptsCircle(Offset parent, Offset child, double radius) { - double distance = (parent - child).distanceSquared; - return distance < radius * radius; - } -}