mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Master merge
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
b4d8b04d9e
commit
336fa9d002
21 changed files with 189 additions and 157 deletions
|
@ -38,7 +38,7 @@ import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
||||||
import type { ViewportBounds } from "../element/bounds";
|
import type { ViewportBounds } from "../element/bounds";
|
||||||
import { setCursor } from "../cursor";
|
import { setCursor } from "../cursor";
|
||||||
import { StoreAction } from "../store";
|
import { StoreAction } from "../store";
|
||||||
import { clamp, point, roundToStep } from "../../math";
|
import { clamp, pointFrom, roundToStep } from "../../math";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
|
@ -324,7 +324,7 @@ export const zoomToFitBounds = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const centerScroll = centerScrollOn({
|
const centerScroll = centerScrollOn({
|
||||||
scenePoint: point(centerX, centerY),
|
scenePoint: pointFrom(centerX, centerY),
|
||||||
viewportDimensions: {
|
viewportDimensions: {
|
||||||
width: appState.width,
|
width: appState.width,
|
||||||
height: appState.height,
|
height: appState.height,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { AppState } from "./types";
|
||||||
import { getSvgPathFromStroke, sceneCoordsToViewportCoords } from "./utils";
|
import { getSvgPathFromStroke, sceneCoordsToViewportCoords } from "./utils";
|
||||||
import type App from "./components/App";
|
import type App from "./components/App";
|
||||||
import { SVG_NS } from "./constants";
|
import { SVG_NS } from "./constants";
|
||||||
import { point } from "../math";
|
import { pointFrom } from "../math";
|
||||||
|
|
||||||
export interface Trail {
|
export interface Trail {
|
||||||
start(container: SVGSVGElement): void;
|
start(container: SVGSVGElement): void;
|
||||||
|
@ -136,7 +136,7 @@ export class AnimatedTrail implements Trail {
|
||||||
private drawTrail(trail: LaserPointer, state: AppState): string {
|
private drawTrail(trail: LaserPointer, state: AppState): string {
|
||||||
const stroke = trail
|
const stroke = trail
|
||||||
.getStrokeOutline(trail.options.size / state.zoom.value)
|
.getStrokeOutline(trail.options.size / state.zoom.value)
|
||||||
.map((p) => sceneCoordsToViewportCoords(point(p[0], p[1]), state));
|
.map((p) => sceneCoordsToViewportCoords(pointFrom(p[0], p[1]), state));
|
||||||
|
|
||||||
return getSvgPathFromStroke(stroke, true);
|
return getSvgPathFromStroke(stroke, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import type {
|
||||||
} from "../../element/types";
|
} from "../../element/types";
|
||||||
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
import { isRenderThrottlingEnabled } from "../../reactUtils";
|
||||||
import { renderInteractiveScene } from "../../renderer/interactiveScene";
|
import { renderInteractiveScene } from "../../renderer/interactiveScene";
|
||||||
import { point } from "../../../math";
|
import { pointFrom } from "../../../math";
|
||||||
|
|
||||||
type InteractiveCanvasProps = {
|
type InteractiveCanvasProps = {
|
||||||
containerRef: React.RefObject<HTMLDivElement>;
|
containerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
@ -104,7 +104,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
|
||||||
remotePointerViewportCoords.set(
|
remotePointerViewportCoords.set(
|
||||||
socketId,
|
socketId,
|
||||||
sceneCoordsToViewportCoords(
|
sceneCoordsToViewportCoords(
|
||||||
point(user.pointer.x, user.pointer.y),
|
pointFrom(user.pointer.x, user.pointer.y),
|
||||||
props.appState,
|
props.appState,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -81,7 +81,7 @@ export const isPointHittingLink = (
|
||||||
if (
|
if (
|
||||||
!isMobile &&
|
!isMobile &&
|
||||||
appState.viewModeEnabled &&
|
appState.viewModeEnabled &&
|
||||||
hitElementBoundingBox(point(x, y), element, elementsMap)
|
hitElementBoundingBox(pointFrom(x, y), element, elementsMap)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { getElementAbsoluteCoords } from ".";
|
||||||
import { useExcalidrawAppState } from "../components/App";
|
import { useExcalidrawAppState } from "../components/App";
|
||||||
|
|
||||||
import "./ElementCanvasButtons.scss";
|
import "./ElementCanvasButtons.scss";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const CONTAINER_PADDING = 5;
|
const CONTAINER_PADDING = 5;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ const getContainerCoords = (
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const [viewportX, viewportY] = sceneCoordsToViewportCoords(
|
const [viewportX, viewportY] = sceneCoordsToViewportCoords(
|
||||||
point(x1 + element.width, y1),
|
pointFrom(x1 + element.width, y1),
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
const x = viewportX - appState.offsetLeft + 10;
|
const x = viewportX - appState.offsetLeft + 10;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Drawable } from "roughjs/bin/core";
|
||||||
import {
|
import {
|
||||||
degrees,
|
degrees,
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
point,
|
pointFrom,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
radians,
|
radians,
|
||||||
|
@ -55,21 +55,21 @@ export const getArrowheadPoints = (
|
||||||
|
|
||||||
invariant(data.length === 6, "Op data length is not 6");
|
invariant(data.length === 6, "Op data length is not 6");
|
||||||
|
|
||||||
const p3 = point(data[4], data[5]);
|
const p3 = pointFrom(data[4], data[5]);
|
||||||
const p2 = point(data[2], data[3]);
|
const p2 = pointFrom(data[2], data[3]);
|
||||||
const p1 = point(data[0], data[1]);
|
const p1 = pointFrom(data[0], data[1]);
|
||||||
|
|
||||||
// We need to find p0 of the bezier curve.
|
// We need to find p0 of the bezier curve.
|
||||||
// It is typically the last point of the previous
|
// It is typically the last point of the previous
|
||||||
// curve; it can also be the position of moveTo operation.
|
// curve; it can also be the position of moveTo operation.
|
||||||
const prevOp = ops[index - 1];
|
const prevOp = ops[index - 1];
|
||||||
let p0 = point(0, 0);
|
let p0 = pointFrom(0, 0);
|
||||||
if (prevOp.op === "move") {
|
if (prevOp.op === "move") {
|
||||||
const p = pointFromArray(prevOp.data);
|
const p = pointFromArray(prevOp.data);
|
||||||
invariant(p != null, "Op data is not a point");
|
invariant(p != null, "Op data is not a point");
|
||||||
p0 = p;
|
p0 = p;
|
||||||
} else if (prevOp.op === "bcurveTo") {
|
} else if (prevOp.op === "bcurveTo") {
|
||||||
p0 = point(prevOp.data[4], prevOp.data[5]);
|
p0 = pointFrom(prevOp.data[4], prevOp.data[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
||||||
|
@ -135,13 +135,13 @@ export const getArrowheadPoints = (
|
||||||
|
|
||||||
// Return points
|
// Return points
|
||||||
const [x3, y3] = pointRotateRads(
|
const [x3, y3] = pointRotateRads(
|
||||||
point(xs, ys),
|
pointFrom(xs, ys),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
radians((-angle * Math.PI) / 180),
|
radians((-angle * Math.PI) / 180),
|
||||||
);
|
);
|
||||||
const [x4, y4] = pointRotateRads(
|
const [x4, y4] = pointRotateRads(
|
||||||
point(xs, ys),
|
pointFrom(xs, ys),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
degreesToRadians(angle),
|
degreesToRadians(angle),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -154,8 +154,8 @@ export const getArrowheadPoints = (
|
||||||
const [px, py] = element.points.length > 1 ? element.points[1] : [0, 0];
|
const [px, py] = element.points.length > 1 ? element.points[1] : [0, 0];
|
||||||
|
|
||||||
[ox, oy] = pointRotateRads(
|
[ox, oy] = pointRotateRads(
|
||||||
point(x2 + minSize * 2, y2),
|
pointFrom(x2 + minSize * 2, y2),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
radians(Math.atan2(py - y2, px - x2)),
|
radians(Math.atan2(py - y2, px - x2)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -165,8 +165,8 @@ export const getArrowheadPoints = (
|
||||||
: [0, 0];
|
: [0, 0];
|
||||||
|
|
||||||
[ox, oy] = pointRotateRads(
|
[ox, oy] = pointRotateRads(
|
||||||
point(x2 - minSize * 2, y2),
|
pointFrom(x2 - minSize * 2, y2),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
radians(Math.atan2(y2 - py, x2 - px)),
|
radians(Math.atan2(y2 - py, x2 - px)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,9 @@ export const intersectRectanguloidWithLine = (
|
||||||
lineSegmentIntersectionPoints(line<GlobalPoint>(rotatedA, rotatedB), s),
|
lineSegmentIntersectionPoints(line<GlobalPoint>(rotatedA, rotatedB), s),
|
||||||
)
|
)
|
||||||
.filter((x) => x != null)
|
.filter((x) => x != null)
|
||||||
.map((j) => pointRotateRads<GlobalPoint>(j, center, element.angle));
|
.map((j: GlobalPoint) =>
|
||||||
|
pointRotateRads<GlobalPoint>(j, center, element.angle),
|
||||||
|
);
|
||||||
const cornerIntersections: GlobalPoint[] =
|
const cornerIntersections: GlobalPoint[] =
|
||||||
roundness > 0
|
roundness > 0
|
||||||
? [
|
? [
|
||||||
|
@ -335,7 +337,9 @@ export const intersectDiamondWithLine = (
|
||||||
)
|
)
|
||||||
.filter((x) => x != null)
|
.filter((x) => x != null)
|
||||||
// Rotate back intersection points
|
// Rotate back intersection points
|
||||||
.map((p) => pointRotateRads<GlobalPoint>(p, center, element.angle));
|
.map((p: GlobalPoint) =>
|
||||||
|
pointRotateRads<GlobalPoint>(p, center, element.angle),
|
||||||
|
);
|
||||||
const corners = arcs
|
const corners = arcs
|
||||||
.flatMap((x) => arcLineInterceptPoints(x, line(rotatedA, rotatedB)))
|
.flatMap((x) => arcLineInterceptPoints(x, line(rotatedA, rotatedB)))
|
||||||
.filter((x) => x != null)
|
.filter((x) => x != null)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
arcDistanceFromPoint,
|
arcDistanceFromPoint,
|
||||||
ellipse,
|
ellipse,
|
||||||
ellipseDistanceFromPoint,
|
ellipseDistanceFromPoint,
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
radians,
|
radians,
|
||||||
rectangle,
|
rectangle,
|
||||||
|
@ -53,14 +53,14 @@ export const distanceToRectangleElement = (
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
) => {
|
) => {
|
||||||
const r = rectangle(
|
const r = rectangle(
|
||||||
point(element.x, element.y),
|
pointFrom(element.x, element.y),
|
||||||
point(element.x + element.width, element.y + element.height),
|
pointFrom(element.x + element.width, element.y + element.height),
|
||||||
);
|
);
|
||||||
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||||
// instead. It's all the same distance-wise.
|
// instead. It's all the same distance-wise.
|
||||||
const rotatedPoint = pointRotateRads(
|
const rotatedPoint = pointRotateRads(
|
||||||
p,
|
p,
|
||||||
point(element.x + element.width / 2, element.y + element.height / 2),
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
radians(-element.angle),
|
radians(-element.angle),
|
||||||
);
|
);
|
||||||
const roundness = getCornerRadius(
|
const roundness = getCornerRadius(
|
||||||
|
@ -69,45 +69,45 @@ export const distanceToRectangleElement = (
|
||||||
);
|
);
|
||||||
const sideDistances = [
|
const sideDistances = [
|
||||||
segment(
|
segment(
|
||||||
point(r[0][0] + roundness, r[0][1]),
|
pointFrom(r[0][0] + roundness, r[0][1]),
|
||||||
point(r[1][0] - roundness, r[0][1]),
|
pointFrom(r[1][0] - roundness, r[0][1]),
|
||||||
),
|
),
|
||||||
segment(
|
segment(
|
||||||
point(r[1][0], r[0][1] + roundness),
|
pointFrom(r[1][0], r[0][1] + roundness),
|
||||||
point(r[1][0], r[1][1] - roundness),
|
pointFrom(r[1][0], r[1][1] - roundness),
|
||||||
),
|
),
|
||||||
segment(
|
segment(
|
||||||
point(r[1][0] - roundness, r[1][1]),
|
pointFrom(r[1][0] - roundness, r[1][1]),
|
||||||
point(r[0][0] + roundness, r[1][1]),
|
pointFrom(r[0][0] + roundness, r[1][1]),
|
||||||
),
|
),
|
||||||
segment(
|
segment(
|
||||||
point(r[0][0], r[1][1] - roundness),
|
pointFrom(r[0][0], r[1][1] - roundness),
|
||||||
point(r[0][0], r[0][1] + roundness),
|
pointFrom(r[0][0], r[0][1] + roundness),
|
||||||
),
|
),
|
||||||
].map((s) => segmentDistanceToPoint(rotatedPoint, s));
|
].map((s) => segmentDistanceToPoint(rotatedPoint, s));
|
||||||
const cornerDistances =
|
const cornerDistances =
|
||||||
roundness > 0
|
roundness > 0
|
||||||
? [
|
? [
|
||||||
arc(
|
arc(
|
||||||
point(r[0][0] + roundness, r[0][1] + roundness),
|
pointFrom(r[0][0] + roundness, r[0][1] + roundness),
|
||||||
roundness,
|
roundness,
|
||||||
radians(Math.PI),
|
radians(Math.PI),
|
||||||
radians((3 / 4) * Math.PI),
|
radians((3 / 4) * Math.PI),
|
||||||
),
|
),
|
||||||
arc(
|
arc(
|
||||||
point(r[1][0] - roundness, r[0][1] + roundness),
|
pointFrom(r[1][0] - roundness, r[0][1] + roundness),
|
||||||
roundness,
|
roundness,
|
||||||
radians((3 / 4) * Math.PI),
|
radians((3 / 4) * Math.PI),
|
||||||
radians(0),
|
radians(0),
|
||||||
),
|
),
|
||||||
arc(
|
arc(
|
||||||
point(r[1][0] - roundness, r[1][1] - roundness),
|
pointFrom(r[1][0] - roundness, r[1][1] - roundness),
|
||||||
roundness,
|
roundness,
|
||||||
radians(0),
|
radians(0),
|
||||||
radians((1 / 2) * Math.PI),
|
radians((1 / 2) * Math.PI),
|
||||||
),
|
),
|
||||||
arc(
|
arc(
|
||||||
point(r[0][0] + roundness, r[1][1] - roundness),
|
pointFrom(r[0][0] + roundness, r[1][1] - roundness),
|
||||||
roundness,
|
roundness,
|
||||||
radians((1 / 2) * Math.PI),
|
radians((1 / 2) * Math.PI),
|
||||||
radians(Math.PI),
|
radians(Math.PI),
|
||||||
|
@ -132,7 +132,10 @@ export const distanceToDiamondElement = (
|
||||||
): number => {
|
): number => {
|
||||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||||
getDiamondPoints(element);
|
getDiamondPoints(element);
|
||||||
const center = point<GlobalPoint>((topX + bottomX) / 2, (topY + bottomY) / 2);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
(topX + bottomX) / 2,
|
||||||
|
(topY + bottomY) / 2,
|
||||||
|
);
|
||||||
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
|
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
|
||||||
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
|
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
|
||||||
|
|
||||||
|
@ -140,10 +143,10 @@ export const distanceToDiamondElement = (
|
||||||
// points. It's all the same distance-wise.
|
// points. It's all the same distance-wise.
|
||||||
const rotatedPoint = pointRotateRads(p, center, radians(-element.angle));
|
const rotatedPoint = pointRotateRads(p, center, radians(-element.angle));
|
||||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
const [top, right, bottom, left]: GlobalPoint[] = [
|
||||||
point(element.x + topX, element.y + topY),
|
pointFrom(element.x + topX, element.y + topY),
|
||||||
point(element.x + rightX, element.y + rightY),
|
pointFrom(element.x + rightX, element.y + rightY),
|
||||||
point(element.x + bottomX, element.y + bottomY),
|
pointFrom(element.x + bottomX, element.y + bottomY),
|
||||||
point(element.x + leftX, element.y + leftY),
|
pointFrom(element.x + leftX, element.y + leftY),
|
||||||
];
|
];
|
||||||
|
|
||||||
const topRight = createDiamondSide(
|
const topRight = createDiamondSide(
|
||||||
|
@ -198,7 +201,7 @@ export const distanceToEllipseElement = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
): number => {
|
): number => {
|
||||||
const center = point(
|
const center = pointFrom(
|
||||||
element.x + element.width / 2,
|
element.x + element.width / 2,
|
||||||
element.y + element.height / 2,
|
element.y + element.height / 2,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { SHIFT_LOCKING_ANGLE } from "../constants";
|
||||||
import type { AppState, Offsets, Zoom } from "../types";
|
import type { AppState, Offsets, Zoom } from "../types";
|
||||||
import { getCommonBounds, getElementBounds } from "./bounds";
|
import { getCommonBounds, getElementBounds } from "./bounds";
|
||||||
import { viewportCoordsToSceneCoords } from "../utils";
|
import { viewportCoordsToSceneCoords } from "../utils";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
// TODO: remove invisible elements consistently actions, so that invisible elements are not recorded by the store, exported, broadcasted or persisted
|
// TODO: remove invisible elements consistently actions, so that invisible elements are not recorded by the store, exported, broadcasted or persisted
|
||||||
// - perhaps could be as part of a standalone 'cleanup' action, in addition to 'finalize'
|
// - perhaps could be as part of a standalone 'cleanup' action, in addition to 'finalize'
|
||||||
|
@ -34,11 +34,11 @@ export const isElementInViewport = (
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates
|
||||||
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
||||||
point(viewTransformations.offsetLeft, viewTransformations.offsetTop),
|
pointFrom(viewTransformations.offsetLeft, viewTransformations.offsetTop),
|
||||||
viewTransformations,
|
viewTransformations,
|
||||||
);
|
);
|
||||||
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
||||||
point(
|
pointFrom(
|
||||||
viewTransformations.offsetLeft + width,
|
viewTransformations.offsetLeft + width,
|
||||||
viewTransformations.offsetTop + height,
|
viewTransformations.offsetTop + height,
|
||||||
),
|
),
|
||||||
|
@ -69,14 +69,14 @@ export const isElementCompletelyInViewport = (
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getCommonBounds(elements, elementsMap); // scene coordinates
|
const [x1, y1, x2, y2] = getCommonBounds(elements, elementsMap); // scene coordinates
|
||||||
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
||||||
point(
|
pointFrom(
|
||||||
viewTransformations.offsetLeft + (padding?.left || 0),
|
viewTransformations.offsetLeft + (padding?.left || 0),
|
||||||
viewTransformations.offsetTop + (padding?.top || 0),
|
viewTransformations.offsetTop + (padding?.top || 0),
|
||||||
),
|
),
|
||||||
viewTransformations,
|
viewTransformations,
|
||||||
);
|
);
|
||||||
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
||||||
point(
|
pointFrom(
|
||||||
viewTransformations.offsetLeft + width - (padding?.right || 0),
|
viewTransformations.offsetLeft + width - (padding?.right || 0),
|
||||||
viewTransformations.offsetTop + height - (padding?.bottom || 0),
|
viewTransformations.offsetTop + height - (padding?.bottom || 0),
|
||||||
),
|
),
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
} from "./containerCache";
|
} from "./containerCache";
|
||||||
import type { ExtractSetType } from "../utility-types";
|
import type { ExtractSetType } from "../utility-types";
|
||||||
import type { GlobalPoint } from "../../math";
|
import type { GlobalPoint } from "../../math";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
export const normalizeText = (text: string) => {
|
export const normalizeText = (text: string) => {
|
||||||
return (
|
return (
|
||||||
|
@ -678,7 +678,7 @@ export const getContainerCenter = (
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
if (!isArrowElement(container)) {
|
if (!isArrowElement(container)) {
|
||||||
return point(
|
return pointFrom(
|
||||||
container.x + container.width / 2,
|
container.x + container.width / 2,
|
||||||
container.y + container.height / 2,
|
container.y + container.height / 2,
|
||||||
);
|
);
|
||||||
|
@ -694,7 +694,7 @@ export const getContainerCenter = (
|
||||||
container.points[index],
|
container.points[index],
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
return point(midPoint[0], midPoint[1]);
|
return pointFrom(midPoint[0], midPoint[1]);
|
||||||
}
|
}
|
||||||
const index = container.points.length / 2 - 1;
|
const index = container.points.length / 2 - 1;
|
||||||
let midSegmentMidpoint = LinearElementEditor.getEditorMidPoints(
|
let midSegmentMidpoint = LinearElementEditor.getEditorMidPoints(
|
||||||
|
@ -711,7 +711,7 @@ export const getContainerCenter = (
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return point(midSegmentMidpoint[0], midSegmentMidpoint[1]);
|
return pointFrom(midSegmentMidpoint[0], midSegmentMidpoint[1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getContainerCoords = (container: NonDeletedExcalidrawElement) => {
|
export const getContainerCoords = (container: NonDeletedExcalidrawElement) => {
|
||||||
|
|
|
@ -11,16 +11,16 @@ import {
|
||||||
tupleToCoors,
|
tupleToCoors,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import { point, type GlobalPoint } from "../../math";
|
import { pointFrom, type GlobalPoint } from "../../math";
|
||||||
|
|
||||||
const isOutsideViewPort = (appState: AppState, cords: Array<number>) => {
|
const isOutsideViewPort = (appState: AppState, cords: Array<number>) => {
|
||||||
const [x1, y1, x2, y2] = cords;
|
const [x1, y1, x2, y2] = cords;
|
||||||
const [viewportX1, viewportY1] = sceneCoordsToViewportCoords(
|
const [viewportX1, viewportY1] = sceneCoordsToViewportCoords(
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
const [viewportX2, viewportY2] = sceneCoordsToViewportCoords(
|
const [viewportX2, viewportY2] = sceneCoordsToViewportCoords(
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -77,7 +77,7 @@ export const calculateScrollCenter = (
|
||||||
elements,
|
elements,
|
||||||
tupleToCoors(
|
tupleToCoors(
|
||||||
viewportCoordsToSceneCoords(
|
viewportCoordsToSceneCoords(
|
||||||
point(appState.scrollX, appState.scrollY),
|
pointFrom(appState.scrollX, appState.scrollY),
|
||||||
appState,
|
appState,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -88,7 +88,7 @@ export const calculateScrollCenter = (
|
||||||
const centerY = (y1 + y2) / 2;
|
const centerY = (y1 + y2) / 2;
|
||||||
|
|
||||||
return centerScrollOn({
|
return centerScrollOn({
|
||||||
scenePoint: point(centerX, centerY),
|
scenePoint: pointFrom(centerX, centerY),
|
||||||
viewportDimensions: { width: appState.width, height: appState.height },
|
viewportDimensions: { width: appState.width, height: appState.height },
|
||||||
zoom: appState.zoom,
|
zoom: appState.zoom,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { GlobalPoint, ViewportPoint } from "../math";
|
import type { GlobalPoint, ViewportPoint } from "../math";
|
||||||
import { average, point } from "../math";
|
import { average, pointFrom } from "../math";
|
||||||
import { COLOR_PALETTE } from "./colors";
|
import { COLOR_PALETTE } from "./colors";
|
||||||
import type { EVENT } from "./constants";
|
import type { EVENT } from "./constants";
|
||||||
import {
|
import {
|
||||||
|
@ -436,7 +436,7 @@ export const viewportCoordsToSceneCoords = (
|
||||||
const x = (clientX - offsetLeft) / zoom.value - scrollX;
|
const x = (clientX - offsetLeft) / zoom.value - scrollX;
|
||||||
const y = (clientY - offsetTop) / zoom.value - scrollY;
|
const y = (clientY - offsetTop) / zoom.value - scrollY;
|
||||||
|
|
||||||
return point<GlobalPoint>(x, y);
|
return pointFrom<GlobalPoint>(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sceneCoordsToViewportCoords = (
|
export const sceneCoordsToViewportCoords = (
|
||||||
|
@ -457,7 +457,7 @@ export const sceneCoordsToViewportCoords = (
|
||||||
): ViewportPoint => {
|
): ViewportPoint => {
|
||||||
const x = (sceneX + scrollX) * zoom.value + offsetLeft;
|
const x = (sceneX + scrollX) * zoom.value + offsetLeft;
|
||||||
const y = (sceneY + scrollY) * zoom.value + offsetTop;
|
const y = (sceneY + scrollY) * zoom.value + offsetTop;
|
||||||
return point(x, y);
|
return pointFrom(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGlobalCSSVariable = (name: string) =>
|
export const getGlobalCSSVariable = (name: string) =>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { cartesian2Polar, polar, radians } from "./angle";
|
import { cartesian2Polar, polar, radians } from "./angle";
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
|
|
||||||
describe("cartesian to polar coordinate conversion", () => {
|
describe("cartesian to polar coordinate conversion", () => {
|
||||||
it("converts values properly", () => {
|
it("converts values properly", () => {
|
||||||
expect(cartesian2Polar(point(12, 5))).toEqual(
|
expect(cartesian2Polar(pointFrom(12, 5))).toEqual(
|
||||||
polar(13, radians(Math.atan(5 / 12))),
|
polar(13, radians(Math.atan(5 / 12))),
|
||||||
);
|
);
|
||||||
expect(cartesian2Polar(point(5, 5))).toEqual(
|
expect(cartesian2Polar(pointFrom(5, 5))).toEqual(
|
||||||
polar(5 * Math.sqrt(2), radians(Math.PI / 4)),
|
polar(5 * Math.sqrt(2), radians(Math.PI / 4)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
ellipseLineIntersectionPoints,
|
ellipseLineIntersectionPoints,
|
||||||
ellipseSegmentInterceptPoints,
|
ellipseSegmentInterceptPoints,
|
||||||
} from "./ellipse";
|
} from "./ellipse";
|
||||||
import { point, pointDistance } from "./point";
|
import { pointFrom, pointDistance } from "./point";
|
||||||
import type { GenericPoint, Segment, Radians, Arc, Line } from "./types";
|
import type { GenericPoint, Segment, Radians, Arc, Line } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export function arcIncludesPoint<P extends GenericPoint>(
|
||||||
p: P,
|
p: P,
|
||||||
): boolean {
|
): boolean {
|
||||||
const [radius, angle] = cartesian2Polar(
|
const [radius, angle] = cartesian2Polar(
|
||||||
point(p[0] - center[0], p[1] - center[1]),
|
pointFrom(p[0] - center[0], p[1] - center[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return startAngle < endAngle
|
return startAngle < endAngle
|
||||||
|
@ -69,14 +69,14 @@ export function arcDistanceFromPoint<Point extends GenericPoint>(
|
||||||
return Math.min(
|
return Math.min(
|
||||||
pointDistance(
|
pointDistance(
|
||||||
p,
|
p,
|
||||||
point(
|
pointFrom(
|
||||||
a.center[0] + a.radius + Math.cos(a.startAngle),
|
a.center[0] + a.radius + Math.cos(a.startAngle),
|
||||||
a.center[1] + a.radius + Math.sin(a.startAngle),
|
a.center[1] + a.radius + Math.sin(a.startAngle),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pointDistance(
|
pointDistance(
|
||||||
p,
|
p,
|
||||||
point(
|
pointFrom(
|
||||||
a.center[0] + a.radius + Math.cos(a.endAngle),
|
a.center[0] + a.radius + Math.cos(a.endAngle),
|
||||||
a.center[1] + a.radius + Math.sin(a.endAngle),
|
a.center[1] + a.radius + Math.sin(a.endAngle),
|
||||||
),
|
),
|
||||||
|
@ -97,7 +97,7 @@ export function arcSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
s,
|
s,
|
||||||
).filter((candidate) => {
|
).filter((candidate) => {
|
||||||
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
||||||
point(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return a.startAngle < a.endAngle
|
return a.startAngle < a.endAngle
|
||||||
|
@ -125,7 +125,7 @@ export function arcLineInterceptPoints<Point extends GenericPoint>(
|
||||||
l,
|
l,
|
||||||
).filter((candidate) => {
|
).filter((candidate) => {
|
||||||
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
||||||
point(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
pointFrom(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return a.startAngle < a.endAngle
|
return a.startAngle < a.endAngle
|
||||||
|
|
|
@ -6,105 +6,121 @@ import {
|
||||||
ellipseLineIntersectionPoints,
|
ellipseLineIntersectionPoints,
|
||||||
} from "./ellipse";
|
} from "./ellipse";
|
||||||
import { line } from "./line";
|
import { line } from "./line";
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { segment } from "./segment";
|
import { segment } from "./segment";
|
||||||
import type { Ellipse, GlobalPoint } from "./types";
|
import type { Ellipse, GlobalPoint } from "./types";
|
||||||
|
|
||||||
describe("point and ellipse", () => {
|
describe("point and ellipse", () => {
|
||||||
it("point on ellipse", () => {
|
it("point on ellipse", () => {
|
||||||
const target: Ellipse<GlobalPoint> = ellipse(point(1, 2), 2, 1);
|
const target: Ellipse<GlobalPoint> = ellipse(pointFrom(1, 2), 2, 1);
|
||||||
[point(1, 3), point(1, 1), point(3, 2), point(-1, 2)].forEach((p) => {
|
[
|
||||||
|
pointFrom(1, 3),
|
||||||
|
pointFrom(1, 1),
|
||||||
|
pointFrom(3, 2),
|
||||||
|
pointFrom(-1, 2),
|
||||||
|
].forEach((p) => {
|
||||||
expect(ellipseTouchesPoint(p, target)).toBe(true);
|
expect(ellipseTouchesPoint(p, target)).toBe(true);
|
||||||
});
|
});
|
||||||
expect(ellipseTouchesPoint(point(-0.4, 2.7), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(-0.4, 2.7), target, 0.1)).toBe(true);
|
||||||
expect(ellipseTouchesPoint(point(-0.4, 2.71), target, 0.01)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(-0.4, 2.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(ellipseTouchesPoint(point(2.4, 2.7), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(2.4, 2.7), target, 0.1)).toBe(true);
|
||||||
expect(ellipseTouchesPoint(point(2.4, 2.71), target, 0.01)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(2.4, 2.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(ellipseTouchesPoint(point(2, 1.14), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(2, 1.14), target, 0.1)).toBe(true);
|
||||||
expect(ellipseTouchesPoint(point(2, 1.14), target, 0.01)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(2, 1.14), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(ellipseTouchesPoint(point(0, 1.14), target, 0.1)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(0, 1.14), target, 0.1)).toBe(true);
|
||||||
expect(ellipseTouchesPoint(point(0, 1.14), target, 0.01)).toBe(true);
|
expect(ellipseTouchesPoint(pointFrom(0, 1.14), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(ellipseTouchesPoint(point(0, 2.8), target)).toBe(false);
|
expect(ellipseTouchesPoint(pointFrom(0, 2.8), target)).toBe(false);
|
||||||
expect(ellipseTouchesPoint(point(2, 1.2), target)).toBe(false);
|
expect(ellipseTouchesPoint(pointFrom(2, 1.2), target)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point in ellipse", () => {
|
it("point in ellipse", () => {
|
||||||
const target: Ellipse<GlobalPoint> = ellipse(point(0, 0), 2, 1);
|
const target: Ellipse<GlobalPoint> = ellipse(pointFrom(0, 0), 2, 1);
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[
|
||||||
|
pointFrom(0, 1),
|
||||||
|
pointFrom(0, -1),
|
||||||
|
pointFrom(2, 0),
|
||||||
|
pointFrom(-2, 0),
|
||||||
|
].forEach((p) => {
|
||||||
expect(ellipseIncludesPoint(p, target)).toBe(true);
|
expect(ellipseIncludesPoint(p, target)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(ellipseIncludesPoint(point(-1, 0.8), target)).toBe(true);
|
expect(ellipseIncludesPoint(pointFrom(-1, 0.8), target)).toBe(true);
|
||||||
expect(ellipseIncludesPoint(point(1, -0.8), target)).toBe(true);
|
expect(ellipseIncludesPoint(pointFrom(1, -0.8), target)).toBe(true);
|
||||||
|
|
||||||
// Point on outline
|
// Point on outline
|
||||||
expect(ellipseIncludesPoint(point(2, 0), target)).toBe(true);
|
expect(ellipseIncludesPoint(pointFrom(2, 0), target)).toBe(true);
|
||||||
|
|
||||||
expect(ellipseIncludesPoint(point(-1, 1), target)).toBe(false);
|
expect(ellipseIncludesPoint(pointFrom(-1, 1), target)).toBe(false);
|
||||||
expect(ellipseIncludesPoint(point(-1.4, 0.8), target)).toBe(false);
|
expect(ellipseIncludesPoint(pointFrom(-1.4, 0.8), target)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("segment and ellipse", () => {
|
describe("segment and ellipse", () => {
|
||||||
it("detects outside segment", () => {
|
it("detects outside segment", () => {
|
||||||
const e = ellipse(point(0, 0), 2, 2);
|
const e = ellipse(pointFrom(0, 0), 2, 2);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
ellipseSegmentInterceptPoints(
|
ellipseSegmentInterceptPoints(
|
||||||
e,
|
e,
|
||||||
segment<GlobalPoint>(point(-100, 0), point(-10, 0)),
|
segment<GlobalPoint>(pointFrom(-100, 0), pointFrom(-10, 0)),
|
||||||
),
|
),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
expect(
|
expect(
|
||||||
ellipseSegmentInterceptPoints(
|
ellipseSegmentInterceptPoints(
|
||||||
e,
|
e,
|
||||||
segment<GlobalPoint>(point(-10, 0), point(10, 0)),
|
segment<GlobalPoint>(pointFrom(-10, 0), pointFrom(10, 0)),
|
||||||
),
|
),
|
||||||
).toEqual([point(-2, 0), point(2, 0)]);
|
).toEqual([pointFrom(-2, 0), pointFrom(2, 0)]);
|
||||||
expect(
|
expect(
|
||||||
ellipseSegmentInterceptPoints(
|
ellipseSegmentInterceptPoints(
|
||||||
e,
|
e,
|
||||||
segment<GlobalPoint>(point(-10, -2), point(10, -2)),
|
segment<GlobalPoint>(pointFrom(-10, -2), pointFrom(10, -2)),
|
||||||
),
|
),
|
||||||
).toEqual([point(0, -2)]);
|
).toEqual([pointFrom(0, -2)]);
|
||||||
expect(
|
expect(
|
||||||
ellipseSegmentInterceptPoints(
|
ellipseSegmentInterceptPoints(
|
||||||
e,
|
e,
|
||||||
segment<GlobalPoint>(point(0, -1), point(0, 1)),
|
segment<GlobalPoint>(pointFrom(0, -1), pointFrom(0, 1)),
|
||||||
),
|
),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("line and ellipse", () => {
|
describe("line and ellipse", () => {
|
||||||
const e = ellipse(point(0, 0), 2, 2);
|
const e = ellipse(pointFrom(0, 0), 2, 2);
|
||||||
|
|
||||||
it("detects outside line", () => {
|
it("detects outside line", () => {
|
||||||
expect(
|
expect(
|
||||||
ellipseLineIntersectionPoints(
|
ellipseLineIntersectionPoints(
|
||||||
e,
|
e,
|
||||||
line<GlobalPoint>(point(-10, -10), point(10, -10)),
|
line<GlobalPoint>(pointFrom(-10, -10), pointFrom(10, -10)),
|
||||||
),
|
),
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
it("detects line intersecting ellipse", () => {
|
it("detects line intersecting ellipse", () => {
|
||||||
expect(
|
expect(
|
||||||
ellipseLineIntersectionPoints(e, line<GlobalPoint>(point(0, -1), point(0, 1))),
|
ellipseLineIntersectionPoints(
|
||||||
).toEqual([point(0, 2), point(0, -2)]);
|
e,
|
||||||
|
line<GlobalPoint>(pointFrom(0, -1), pointFrom(0, 1)),
|
||||||
|
),
|
||||||
|
).toEqual([pointFrom(0, 2), pointFrom(0, -2)]);
|
||||||
expect(
|
expect(
|
||||||
ellipseLineIntersectionPoints(
|
ellipseLineIntersectionPoints(
|
||||||
e,
|
e,
|
||||||
line<GlobalPoint>(point(-100, 0), point(-10, 0)),
|
line<GlobalPoint>(pointFrom(-100, 0), pointFrom(-10, 0)),
|
||||||
).map(([x, y]) => point(Math.round(x), Math.round(y))),
|
).map(([x, y]) => pointFrom(Math.round(x), Math.round(y))),
|
||||||
).toEqual([point(2, 0), point(-2, 0)]);
|
).toEqual([pointFrom(2, 0), pointFrom(-2, 0)]);
|
||||||
});
|
});
|
||||||
it("detects line touching ellipse", () => {
|
it("detects line touching ellipse", () => {
|
||||||
expect(
|
expect(
|
||||||
ellipseLineIntersectionPoints(e, line<GlobalPoint>(point(-2, -2), point(2, -2))),
|
ellipseLineIntersectionPoints(
|
||||||
).toEqual([point(0, -2)]);
|
e,
|
||||||
|
line<GlobalPoint>(pointFrom(-2, -2), pointFrom(2, -2)),
|
||||||
|
),
|
||||||
|
).toEqual([pointFrom(0, -2)]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { point, pointDistance, pointFromVector, pointsEqual } from "./point";
|
import {
|
||||||
|
pointFrom,
|
||||||
|
pointDistance,
|
||||||
|
pointFromVector,
|
||||||
|
pointsEqual,
|
||||||
|
} from "./point";
|
||||||
import type { Ellipse, GenericPoint, Line, Segment } from "./types";
|
import type { Ellipse, GenericPoint, Line, Segment } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
import {
|
import {
|
||||||
|
@ -119,7 +124,7 @@ export const ellipseDistanceFromPoint = <Point extends GenericPoint>(
|
||||||
b * ty * Math.sign(translatedPoint[1]),
|
b * ty * Math.sign(translatedPoint[1]),
|
||||||
];
|
];
|
||||||
|
|
||||||
return pointDistance(pointFromVector(translatedPoint), point(minX, minY));
|
return pointDistance(pointFromVector(translatedPoint), pointFrom(minX, minY));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -151,7 +156,7 @@ export function ellipseSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
|
|
||||||
if (0 <= t_a && t_a <= 1) {
|
if (0 <= t_a && t_a <= 1) {
|
||||||
intersections.push(
|
intersections.push(
|
||||||
point(
|
pointFrom(
|
||||||
s[0][0] + (s[1][0] - s[0][0]) * t_a,
|
s[0][0] + (s[1][0] - s[0][0]) * t_a,
|
||||||
s[0][1] + (s[1][1] - s[0][1]) * t_a,
|
s[0][1] + (s[1][1] - s[0][1]) * t_a,
|
||||||
),
|
),
|
||||||
|
@ -160,7 +165,7 @@ export function ellipseSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
|
|
||||||
if (0 <= t_b && t_b <= 1) {
|
if (0 <= t_b && t_b <= 1) {
|
||||||
intersections.push(
|
intersections.push(
|
||||||
point(
|
pointFrom(
|
||||||
s[0][0] + (s[1][0] - s[0][0]) * t_b,
|
s[0][0] + (s[1][0] - s[0][0]) * t_b,
|
||||||
s[0][1] + (s[1][1] - s[0][1]) * t_b,
|
s[0][1] + (s[1][1] - s[0][1]) * t_b,
|
||||||
),
|
),
|
||||||
|
@ -170,7 +175,7 @@ export function ellipseSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
const t = -b / a;
|
const t = -b / a;
|
||||||
if (0 <= t && t <= 1) {
|
if (0 <= t && t <= 1) {
|
||||||
intersections.push(
|
intersections.push(
|
||||||
point(
|
pointFrom(
|
||||||
s[0][0] + (s[1][0] - s[0][0]) * t,
|
s[0][0] + (s[1][0] - s[0][0]) * t,
|
||||||
s[0][1] + (s[1][1] - s[0][1]) * t,
|
s[0][1] + (s[1][1] - s[0][1]) * t,
|
||||||
),
|
),
|
||||||
|
@ -204,8 +209,8 @@ export function ellipseLineIntersectionPoints<Point extends GenericPoint>(
|
||||||
const t1 = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a);
|
const t1 = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a);
|
||||||
const t2 = (-b - Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a);
|
const t2 = (-b - Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a);
|
||||||
const candidates = [
|
const candidates = [
|
||||||
point<Point>(x1 + t1 * (x2 - x1) + cx, y1 + t1 * (y2 - y1) + cy),
|
pointFrom<Point>(x1 + t1 * (x2 - x1) + cx, y1 + t1 * (y2 - y1) + cy),
|
||||||
point<Point>(x1 + t2 * (x2 - x1) + cx, y1 + t2 * (y2 - y1) + cy),
|
pointFrom<Point>(x1 + t2 * (x2 - x1) + cx, y1 + t2 * (y2 - y1) + cy),
|
||||||
].filter((p) => !isNaN(p[0]) && !isNaN(p[1]));
|
].filter((p) => !isNaN(p[0]) && !isNaN(p[1]));
|
||||||
|
|
||||||
if (candidates.length === 2 && pointsEqual(candidates[0], candidates[1])) {
|
if (candidates.length === 2 && pointsEqual(candidates[0], candidates[1])) {
|
||||||
|
|
|
@ -1,31 +1,35 @@
|
||||||
import { line, lineLineIntersectionPoint, lineSegmentIntersectionPoints } from "./line";
|
import {
|
||||||
import { point } from "./point";
|
line,
|
||||||
|
lineLineIntersectionPoint,
|
||||||
|
lineSegmentIntersectionPoints,
|
||||||
|
} from "./line";
|
||||||
|
import { pointFrom } from "./point";
|
||||||
import { segment } from "./segment";
|
import { segment } from "./segment";
|
||||||
|
|
||||||
describe("line-line intersections", () => {
|
describe("line-line intersections", () => {
|
||||||
it("should correctly detect intersection at origin", () => {
|
it("should correctly detect intersection at origin", () => {
|
||||||
expect(
|
expect(
|
||||||
lineLineIntersectionPoint(
|
lineLineIntersectionPoint(
|
||||||
line(point(-5, -5), point(5, 5)),
|
line(pointFrom(-5, -5), pointFrom(5, 5)),
|
||||||
line(point(5, -5), point(-5, 5)),
|
line(pointFrom(5, -5), pointFrom(-5, 5)),
|
||||||
),
|
),
|
||||||
).toEqual(point(0, 0));
|
).toEqual(pointFrom(0, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should correctly detect intersection at non-origin", () => {
|
it("should correctly detect intersection at non-origin", () => {
|
||||||
expect(
|
expect(
|
||||||
lineLineIntersectionPoint(
|
lineLineIntersectionPoint(
|
||||||
line(point(0, 0), point(10, 10)),
|
line(pointFrom(0, 0), pointFrom(10, 10)),
|
||||||
line(point(10, 0), point(0, 10)),
|
line(pointFrom(10, 0), pointFrom(0, 10)),
|
||||||
),
|
),
|
||||||
).toEqual(point(5, 5));
|
).toEqual(pointFrom(5, 5));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should correctly detect parallel lines", () => {
|
it("should correctly detect parallel lines", () => {
|
||||||
expect(
|
expect(
|
||||||
lineLineIntersectionPoint(
|
lineLineIntersectionPoint(
|
||||||
line(point(0, 0), point(0, 10)),
|
line(pointFrom(0, 0), pointFrom(0, 10)),
|
||||||
line(point(10, 0), point(10, 10)),
|
line(pointFrom(10, 0), pointFrom(10, 10)),
|
||||||
),
|
),
|
||||||
).toBe(null);
|
).toBe(null);
|
||||||
});
|
});
|
||||||
|
@ -35,16 +39,16 @@ describe("line-segment intersections", () => {
|
||||||
it("should correctly detect intersection", () => {
|
it("should correctly detect intersection", () => {
|
||||||
expect(
|
expect(
|
||||||
lineSegmentIntersectionPoints(
|
lineSegmentIntersectionPoints(
|
||||||
line(point(0, 0), point(5, 0)),
|
line(pointFrom(0, 0), pointFrom(5, 0)),
|
||||||
segment(point(2, -2), point(3, 2)),
|
segment(pointFrom(2, -2), pointFrom(3, 2)),
|
||||||
),
|
),
|
||||||
).toEqual(point(2.5, -0));
|
).toEqual(pointFrom(2.5, -0));
|
||||||
});
|
});
|
||||||
it("should correctly detect non-intersection", () => {
|
it("should correctly detect non-intersection", () => {
|
||||||
expect(
|
expect(
|
||||||
lineSegmentIntersectionPoints(
|
lineSegmentIntersectionPoints(
|
||||||
line(point(0, 0), point(5, 0)),
|
line(pointFrom(0, 0), pointFrom(5, 0)),
|
||||||
segment(point(3, 1), point(4, 4)),
|
segment(pointFrom(3, 1), pointFrom(4, 4)),
|
||||||
),
|
),
|
||||||
).toEqual(null);
|
).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ellipseLineIntersectionPoints } from "./ellipse";
|
import { ellipseLineIntersectionPoints } from "./ellipse";
|
||||||
import { point, pointCenter, pointRotateRads } from "./point";
|
import { pointFrom, pointCenter, pointRotateRads } from "./point";
|
||||||
import { segmentIncludesPoint } from "./segment";
|
import { segmentIncludesPoint } from "./segment";
|
||||||
import type { GenericPoint, Line, Radians, Segment } from "./types";
|
import type { GenericPoint, Line, Radians, Segment } from "./types";
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ export function lineLineIntersectionPoint<Point extends GenericPoint>(
|
||||||
const xnum = a * (x3 - x4) - (x1 - x2) * c;
|
const xnum = a * (x3 - x4) - (x1 - x2) * c;
|
||||||
const ynum = a * (y3 - y4) - (y1 - y2) * c;
|
const ynum = a * (y3 - y4) - (y1 - y2) * c;
|
||||||
|
|
||||||
return point<Point>(xnum / den, ynum / den);
|
return pointFrom<Point>(xnum / den, ynum / den);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { rectangle, rectangleDistanceFromPoint } from "./rectangle";
|
import { rectangle, rectangleDistanceFromPoint } from "./rectangle";
|
||||||
|
|
||||||
describe("rectangle distance", () => {
|
describe("rectangle distance", () => {
|
||||||
it("finds the shortest distance", () => {
|
it("finds the shortest distance", () => {
|
||||||
expect(
|
expect(
|
||||||
rectangleDistanceFromPoint(
|
rectangleDistanceFromPoint(
|
||||||
rectangle(point(-1, -1), point(1, 1)),
|
rectangle(pointFrom(-1, -1), pointFrom(1, 1)),
|
||||||
point(2, 0),
|
pointFrom(2, 0),
|
||||||
),
|
),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
rectangleDistanceFromPoint(
|
rectangleDistanceFromPoint(
|
||||||
rectangle(point(-1, -1), point(1, 1)),
|
rectangle(pointFrom(-1, -1), pointFrom(1, 1)),
|
||||||
point(0, 2),
|
pointFrom(0, 2),
|
||||||
),
|
),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
rectangleDistanceFromPoint(
|
rectangleDistanceFromPoint(
|
||||||
rectangle(point(-1, -1), point(1, 1)),
|
rectangle(pointFrom(-1, -1), pointFrom(1, 1)),
|
||||||
point(-2, 0),
|
pointFrom(-2, 0),
|
||||||
),
|
),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
rectangleDistanceFromPoint(
|
rectangleDistanceFromPoint(
|
||||||
rectangle(point(-1, -1), point(1, 1)),
|
rectangle(pointFrom(-1, -1), pointFrom(1, 1)),
|
||||||
point(0, -2),
|
pointFrom(0, -2),
|
||||||
),
|
),
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
});
|
});
|
||||||
it("finds the corner as closest point", () => {
|
it("finds the corner as closest point", () => {
|
||||||
expect(
|
expect(
|
||||||
rectangleDistanceFromPoint(
|
rectangleDistanceFromPoint(
|
||||||
rectangle(point(-1, -1), point(1, 1)),
|
rectangle(pointFrom(-1, -1), pointFrom(1, 1)),
|
||||||
point(2, 2),
|
pointFrom(2, 2),
|
||||||
),
|
),
|
||||||
).toBe(Math.sqrt(2));
|
).toBe(Math.sqrt(2));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { invariant } from "../excalidraw/utils";
|
import { invariant } from "../excalidraw/utils";
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { segment, segmentDistanceToPoint } from "./segment";
|
import { segment, segmentDistanceToPoint } from "./segment";
|
||||||
import type { GenericPoint, Rectangle } from "./types";
|
import type { GenericPoint, Rectangle } from "./types";
|
||||||
|
|
||||||
|
@ -32,10 +32,10 @@ export function rectangleDistanceFromPoint<Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
): number {
|
): number {
|
||||||
const sides = [
|
const sides = [
|
||||||
segment(point(r[0][0], r[0][1]), point(r[1][0], r[0][1])),
|
segment(pointFrom(r[0][0], r[0][1]), pointFrom(r[1][0], r[0][1])),
|
||||||
segment(point(r[1][0], r[0][1]), point(r[1][0], r[1][1])),
|
segment(pointFrom(r[1][0], r[0][1]), pointFrom(r[1][0], r[1][1])),
|
||||||
segment(point(r[1][0], r[1][1]), point(r[0][0], r[1][1])),
|
segment(pointFrom(r[1][0], r[1][1]), pointFrom(r[0][0], r[1][1])),
|
||||||
segment(point(r[0][0], r[1][1]), point(r[0][0], r[0][1])),
|
segment(pointFrom(r[0][0], r[1][1]), pointFrom(r[0][0], r[0][1])),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Math.min(...sides.map((side) => segmentDistanceToPoint(p, side)));
|
return Math.min(...sides.map((side) => segmentDistanceToPoint(p, side)));
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { segment, segmentsIntersectAt } from "./segment";
|
import { segment, segmentsIntersectAt } from "./segment";
|
||||||
import type { GlobalPoint, Segment } from "./types";
|
import type { GlobalPoint, Segment } from "./types";
|
||||||
|
|
||||||
describe("segment intersects segment", () => {
|
describe("segment intersects segment", () => {
|
||||||
const lineA: Segment<GlobalPoint> = segment(point(1, 4), point(3, 4));
|
const lineA: Segment<GlobalPoint> = segment(pointFrom(1, 4), pointFrom(3, 4));
|
||||||
const lineB: Segment<GlobalPoint> = segment(point(2, 1), point(2, 7));
|
const lineB: Segment<GlobalPoint> = segment(pointFrom(2, 1), pointFrom(2, 7));
|
||||||
const lineC: Segment<GlobalPoint> = segment(point(1, 8), point(3, 8));
|
const lineC: Segment<GlobalPoint> = segment(pointFrom(1, 8), pointFrom(3, 8));
|
||||||
const lineD: Segment<GlobalPoint> = segment(point(1, 8), point(3, 8));
|
const lineD: Segment<GlobalPoint> = segment(pointFrom(1, 8), pointFrom(3, 8));
|
||||||
const lineE: Segment<GlobalPoint> = segment(point(1, 9), point(3, 9));
|
const lineE: Segment<GlobalPoint> = segment(pointFrom(1, 9), pointFrom(3, 9));
|
||||||
const lineF: Segment<GlobalPoint> = segment(point(1, 2), point(3, 4));
|
const lineF: Segment<GlobalPoint> = segment(pointFrom(1, 2), pointFrom(3, 4));
|
||||||
const lineG: Segment<GlobalPoint> = segment(point(0, 1), point(2, 3));
|
const lineG: Segment<GlobalPoint> = segment(pointFrom(0, 1), pointFrom(2, 3));
|
||||||
|
|
||||||
it("intersection", () => {
|
it("intersection", () => {
|
||||||
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue