mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Feature: Multi Point Arrows (#338)
* Add points to arrow on double click * Use line generator instead of path to generate line segments * Switch color of the circle when it is on an existing point in the segment * Check point against both ends of the line segment to find collinearity * Keep drawing the arrow based on mouse position until shape is changed * Always select the arrow when in multi element mode * Use curves instead of lines when drawing arrow points * Add basic collision detection with some debug points * Use roughjs shape when performing hit testing * Draw proper handler rectangles for arrows * Add argument to renderScene in export * Globally resize all points on the arrow when bounds are resized * Hide handler rectangles if an arrow has no size - Allow continuing adding arrows when selected element is deleted * Add dragging functionality to arrows * Add SHIFT functionality to two point arrows - Fix arrow positions when scrolling - Revert the element back to selection when not in multi select mode * Clean app state for export (JSON) * Set curve options manually instead of using global options - For some reason, this fixed the flickering issue in all shapes when arrows are rendered * Set proper options for the arrow * Increaase accuracy of hit testing arrows - Additionally, skip testing if point is outside the domain of arrow and each curve * Calculate bounding box of arrow based on roughjs curves - Remove domain check per curve * Change bounding box threshold to 10 and remove unnecessary code * Fix handler rectangles for 2 and multi point arrows - Fix margins of handler rectangles when using arrows - Show handler rectangles in endpoints of 2-point arrows * Remove unnecessary values from app state for export * Use `resetTransform` instead of "retranslating" canvas space after each element rendering * Allow resizing 2-point arrows - Fix position of one of the handler rectangles * refactor variable initialization * Refactored to extract out mult-point generation to the abstracted function * prevent dragging on arrow creation if under threshold * Finalize selection during multi element mode when ENTER or ESC is clicked * Set dragging element to null when finalizing * Remove pathSegmentCircle from code * Check if element is any "non-value" instead of NULL * Show two points on any two point arrow and fix visibility of arrows during scroll * Resume recording when done with drawing - When deleting a multi select element, revert back to selection element type * Resize arrow starting points perfectly * Fix direction of arrow resize based for NW * Resume recording history when there is more than one arrow * Set dragging element to NULL when element is not locked * Blur active element when finalizing * Disable undo/redo for multielement, editingelement, and resizing element - Allow undoing parts of the arrow * Disable element visibility for arrow * Use points array for arrow bounds when bezier curve shape is not available Co-authored-by: David Luzar <luzar.david@gmail.com> Co-authored-by: Preet <833927+pshihn@users.noreply.github.com>
This commit is contained in:
parent
9a17abcb34
commit
16263e942b
19 changed files with 769 additions and 110 deletions
65
src/math.ts
65
src/math.ts
|
@ -1,3 +1,5 @@
|
|||
import { Point } from "roughjs/bin/geometry";
|
||||
|
||||
// https://stackoverflow.com/a/6853926/232122
|
||||
export function distanceBetweenPointAndSegment(
|
||||
x: number,
|
||||
|
@ -52,3 +54,66 @@ export function rotate(
|
|||
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
|
||||
];
|
||||
}
|
||||
|
||||
export const getPointOnAPath = (point: Point, path: Point[]) => {
|
||||
const [px, py] = point;
|
||||
const [start, ...other] = path;
|
||||
let [lastX, lastY] = start;
|
||||
let kLine: number = 0;
|
||||
let idx: number = 0;
|
||||
|
||||
// if any item in the array is true, it means that a point is
|
||||
// on some segment of a line based path
|
||||
const retVal = other.some(([x2, y2], i) => {
|
||||
// we always take a line when dealing with line segments
|
||||
const x1 = lastX;
|
||||
const y1 = lastY;
|
||||
|
||||
lastX = x2;
|
||||
lastY = y2;
|
||||
|
||||
// if a point is not within the domain of the line segment
|
||||
// it is not on the line segment
|
||||
if (px < x1 || px > x2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if all points lie on the same line
|
||||
// y1 = kx1 + b, y2 = kx2 + b
|
||||
// y2 - y1 = k(x2 - x2) -> k = (y2 - y1) / (x2 - x1)
|
||||
|
||||
// coefficient for the line (p0, p1)
|
||||
const kL = (y2 - y1) / (x2 - x1);
|
||||
|
||||
// coefficient for the line segment (p0, point)
|
||||
const kP1 = (py - y1) / (px - x1);
|
||||
|
||||
// coefficient for the line segment (point, p1)
|
||||
const kP2 = (py - y2) / (px - x2);
|
||||
|
||||
// because we are basing both lines from the same starting point
|
||||
// the only option for collinearity is having same coefficients
|
||||
|
||||
// using it for floating point comparisons
|
||||
const epsilon = 0.3;
|
||||
|
||||
// if coefficient is more than an arbitrary epsilon,
|
||||
// these lines are nor collinear
|
||||
if (Math.abs(kP1 - kL) > epsilon && Math.abs(kP2 - kL) > epsilon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store the coefficient because we are goint to need it
|
||||
kLine = kL;
|
||||
idx = i;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Return a coordinate that is always on the line segment
|
||||
if (retVal === true) {
|
||||
return { x: point[0], y: kLine * point[0], segment: idx };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue