Move path related function into the math package

pull/8539/merge^2
Mark Tolmacs 6 months ago
parent 1b56cf90fb
commit 9eb08df3ea
No known key found for this signature in database

@ -15,8 +15,7 @@ import { isBindingElement, isLinearElement } from "../element/typeChecks";
import type { AppState } from "../types";
import { resetCursor } from "../cursor";
import { StoreAction } from "../store";
import { point } from "../../math";
import { isPathALoop } from "../shapes";
import { pathIsALoop, point } from "../../math";
export const actionFinalize = register({
name: "finalize",
@ -104,7 +103,7 @@ export const actionFinalize = register({
// If the multi point line closes the loop,
// set the last point to first point.
// This ensures that loop remains closed at different scales.
const isLoop = isPathALoop(multiPointElement.points, appState.zoom.value);
const isLoop = pathIsALoop(multiPointElement.points, appState.zoom.value);
if (
multiPointElement.type === "line" ||
multiPointElement.type === "freedraw"

@ -229,7 +229,6 @@ import {
getBoundTextShape,
getCornerRadius,
getElementShape,
isPathALoop,
} from "../shapes";
import { getSelectionBoxShape } from "../../utils/geometry/shape";
import { isPointInShape } from "../../utils/collision";
@ -449,6 +448,7 @@ import type {
ViewportPoint,
} from "../../math";
import {
pathIsALoop,
point,
pointCenter,
pointDistance,
@ -5605,7 +5605,9 @@ class App extends React.Component<AppProps, AppState> {
));
}
if (isPathALoop(points, this.state.zoom.value)) {
if (
pathIsALoop(points, LINE_CONFIRM_THRESHOLD / this.state.zoom.value)
) {
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
}
if (isElbowArrow(multiElement)) {
@ -7206,7 +7208,10 @@ class App extends React.Component<AppProps, AppState> {
// finalize if completing a loop
if (
multiElement.type === "line" &&
isPathALoop(multiElement.points, this.state.zoom.value)
pathIsALoop(
multiElement.points,
LINE_CONFIRM_THRESHOLD / this.state.zoom.value,
)
) {
mutateElement(multiElement, {
lastCommittedPoint:

@ -15,9 +15,10 @@ import {
isImageElement,
isTextElement,
} from "./typeChecks";
import { getBoundTextShape, isPathALoop } from "../shapes";
import { getBoundTextShape } from "../shapes";
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
import { isPointWithinBounds, point } from "../../math";
import { pathIsALoop, isPointWithinBounds, point } from "../../math";
import { LINE_CONFIRM_THRESHOLD } from "../constants";
export const shouldTestInside = (element: ExcalidrawElement) => {
if (element.type === "arrow") {
@ -31,11 +32,17 @@ export const shouldTestInside = (element: ExcalidrawElement) => {
isTextElement(element);
if (element.type === "line") {
return isDraggableFromInside && isPathALoop(element.points);
return (
isDraggableFromInside &&
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)
);
}
if (element.type === "freedraw") {
return isDraggableFromInside && isPathALoop(element.points);
return (
isDraggableFromInside &&
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)
);
}
return isDraggableFromInside || isImageElement(element);

@ -39,7 +39,7 @@ import {
} from "./typeChecks";
import { KEYS, shouldRotateWithDiscreteAngle } from "../keys";
import { getBoundTextElement, handleBindTextResize } from "./textElement";
import { DRAGGING_THRESHOLD } from "../constants";
import { DRAGGING_THRESHOLD, LINE_CONFIRM_THRESHOLD } from "../constants";
import type { Mutable } from "../utility-types";
import { ShapeCache } from "../scene/ShapeCache";
import type { Store } from "../store";
@ -57,12 +57,12 @@ import {
pointDistance,
pointSubtract,
pointFromPair,
pathIsALoop,
} from "../../math";
import {
getBezierCurveLength,
getBezierXY,
getControlPointsForBezierCurve,
isPathALoop,
mapIntervalToBezierT,
} from "../shapes";
import { getGridPoint } from "../snapping";
@ -418,7 +418,12 @@ export class LinearElementEditor {
selectedPoint === 0 ||
selectedPoint === element.points.length - 1
) {
if (isPathALoop(element.points, appState.zoom.value)) {
if (
pathIsALoop(
element.points,
LINE_CONFIRM_THRESHOLD / appState.zoom.value,
)
) {
LinearElementEditor.movePoints(
element,
[

@ -2,6 +2,7 @@ import type { Drawable } from "roughjs/bin/core";
import type { RoughSVG } from "roughjs/bin/svg";
import {
FRAME_STYLE,
LINE_CONFIRM_THRESHOLD,
MAX_DECIMALS_FOR_SVG_EXPORT,
MIME_TYPES,
SVG_NS,
@ -36,7 +37,8 @@ import type { AppState, BinaryFiles } from "../types";
import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
import { getVerticalOffset } from "../fonts";
import { getCornerRadius, isPathALoop } from "../shapes";
import { getCornerRadius } from "../shapes";
import { pathIsALoop } from "../../math";
const roughSVGDrawWithPrecision = (
rsvg: RoughSVG,
@ -341,7 +343,7 @@ const renderElementToSvg = (
);
if (
element.type === "line" &&
isPathALoop(element.points) &&
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD) &&
element.backgroundColor !== "transparent"
) {
node.setAttribute("fill-rule", "evenodd");

@ -13,7 +13,7 @@ import type {
import { generateFreeDrawShape } from "../renderer/renderElement";
import { isTransparent, assertNever } from "../utils";
import { simplify } from "points-on-curve";
import { ROUGHNESS } from "../constants";
import { LINE_CONFIRM_THRESHOLD, ROUGHNESS } from "../constants";
import {
isElbowArrow,
isEmbeddableElement,
@ -24,12 +24,13 @@ import {
import { canChangeRoundness } from "./comparisons";
import type { EmbedsValidationStatus } from "../types";
import {
pathIsALoop,
point,
pointDistance,
type GlobalPoint,
type LocalPoint,
} from "../../math";
import { getCornerRadius, isPathALoop } from "../shapes";
import { getCornerRadius } from "../shapes";
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
@ -107,7 +108,7 @@ export const generateRoughOptions = (
}
case "line":
case "freedraw": {
if (isPathALoop(element.points)) {
if (pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)) {
options.fillStyle = element.fillStyle;
options.fill =
element.backgroundColor === "transparent"
@ -473,7 +474,7 @@ export const _generateElementShape = (
let shape: ElementShapes[typeof element.type];
generateFreeDrawShape(element);
if (isPathALoop(element.points)) {
if (pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)) {
// generate rough polygon to fill freedraw shape
const simplifiedPoints = simplify(element.points, 0.75);
shape = generator.curve(simplifiedPoints as [number, number][], {

@ -1,4 +1,4 @@
import type { ViewportPoint } from "../math";
import type { GenericPoint, ViewportPoint } from "../math";
import {
isPoint,
point,
@ -33,7 +33,6 @@ import {
import {
DEFAULT_ADAPTIVE_RADIUS,
DEFAULT_PROPORTIONAL_RADIUS,
LINE_CONFIRM_THRESHOLD,
ROUNDNESS,
} from "./constants";
import { getElementAbsoluteCoords } from "./element";
@ -49,7 +48,6 @@ import type {
} from "./element/types";
import { KEYS } from "./keys";
import { ShapeCache } from "./scene/ShapeCache";
import type { NormalizedZoomValue, Zoom } from "./types";
import { invariant } from "./utils";
export const SHAPES = [
@ -222,9 +220,7 @@ export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
return null;
};
export const getControlPointsForBezierCurve = <
P extends GlobalPoint | LocalPoint,
>(
export const getControlPointsForBezierCurve = <P extends GenericPoint>(
element: NonDeleted<ExcalidrawLinearElement>,
endPoint: P,
) => {
@ -266,7 +262,7 @@ export const getControlPointsForBezierCurve = <
return controlPoints;
};
export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
export const getBezierXY = <P extends GenericPoint>(
p0: P,
p1: P,
p2: P,
@ -283,7 +279,7 @@ export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
return point(tx, ty);
};
const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
const getPointsInBezierCurve = <P extends GenericPoint>(
element: NonDeleted<ExcalidrawLinearElement>,
endPoint: P,
) => {
@ -313,7 +309,7 @@ const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
return pointsOnCurve;
};
const getBezierCurveArcLengths = <P extends GlobalPoint | LocalPoint>(
const getBezierCurveArcLengths = <P extends GenericPoint>(
element: NonDeleted<ExcalidrawLinearElement>,
endPoint: P,
) => {
@ -476,21 +472,3 @@ export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
return 0;
};
// Checks if the first and last point are close enough
// to be considered a loop
export const isPathALoop = (
points: ExcalidrawLinearElement["points"],
/** supply if you want the loop detection to account for current zoom */
zoomValue: Zoom["value"] = 1 as NormalizedZoomValue,
): boolean => {
if (points.length >= 3) {
const [first, last] = [points[0], points[points.length - 1]];
const distance = pointDistance(first, last);
// Adjusting LINE_CONFIRM_THRESHOLD to current zoom so that when zoomed in
// really close we make the threshold smaller, and vice versa.
return distance <= LINE_CONFIRM_THRESHOLD / zoomValue;
}
return false;
};

@ -2,9 +2,11 @@ export * from "./arc";
export * from "./angle";
export * from "./curve";
export * from "./line";
export * from "./path";
export * from "./point";
export * from "./polygon";
export * from "./range";
export * from "./rectangle";
export * from "./segment";
export * from "./triangle";
export * from "./types";

@ -0,0 +1,27 @@
import { pointDistance } from "./point";
import type { LocalPoint } from "./types";
/**
* Checks if the first and last point are close enough to be considered a loop
*
* @param points
* @param threshold
* @returns
*/
export const pathIsALoop = (
points: readonly LocalPoint[],
/** supply if you want the loop detection to account for current zoom */
threshold: number,
//zoomValue: Zoom["value"] = 1 as NormalizedZoomValue,
): boolean => {
if (points.length >= 3) {
const [first, last] = [points[0], points[points.length - 1]];
const distance = pointDistance(first, last);
// Adjusting LINE_CONFIRM_THRESHOLD to current zoom so that when zoomed in
// really close we make the threshold smaller, and vice versa.
return distance <= threshold; // LINE_CONFIRM_THRESHOLD / zoomValue;
}
return false;
};
Loading…
Cancel
Save