mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
chore: Unify math types, utils and functions (#8389)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
e3d1dee9d0
commit
f4dd23fc31
98 changed files with 4291 additions and 3661 deletions
|
@ -1,7 +1,5 @@
|
|||
import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
|
||||
import { rescalePoints } from "../points";
|
||||
|
||||
import { rotate, centerPoint, rotatePoint } from "../math";
|
||||
import type {
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
|
@ -38,7 +36,7 @@ import type {
|
|||
MaybeTransformHandleType,
|
||||
TransformHandleDirection,
|
||||
} from "./transformHandles";
|
||||
import type { Point, PointerDownState } from "../types";
|
||||
import type { PointerDownState } from "../types";
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
getApproxMinLineWidth,
|
||||
|
@ -55,16 +53,15 @@ import {
|
|||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { isInGroup } from "../groups";
|
||||
import { mutateElbowArrow } from "./routing";
|
||||
|
||||
export const normalizeAngle = (angle: number): number => {
|
||||
if (angle < 0) {
|
||||
return angle + 2 * Math.PI;
|
||||
}
|
||||
if (angle >= 2 * Math.PI) {
|
||||
return angle - 2 * Math.PI;
|
||||
}
|
||||
return angle;
|
||||
};
|
||||
import type { GlobalPoint } from "../../math";
|
||||
import {
|
||||
pointCenter,
|
||||
normalizeRadians,
|
||||
point,
|
||||
pointFromPair,
|
||||
pointRotateRads,
|
||||
type Radians,
|
||||
} from "../../math";
|
||||
|
||||
// Returns true when transform (resizing/rotation) happened
|
||||
export const transformElements = (
|
||||
|
@ -158,16 +155,17 @@ const rotateSingleElement = (
|
|||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
let angle: number;
|
||||
let angle: Radians;
|
||||
if (isFrameLikeElement(element)) {
|
||||
angle = 0;
|
||||
angle = 0 as Radians;
|
||||
} else {
|
||||
angle = (5 * Math.PI) / 2 + Math.atan2(pointerY - cy, pointerX - cx);
|
||||
angle = ((5 * Math.PI) / 2 +
|
||||
Math.atan2(pointerY - cy, pointerX - cx)) as Radians;
|
||||
if (shouldRotateWithDiscreteAngle) {
|
||||
angle += SHIFT_LOCKING_ANGLE / 2;
|
||||
angle -= angle % SHIFT_LOCKING_ANGLE;
|
||||
angle = (angle + SHIFT_LOCKING_ANGLE / 2) as Radians;
|
||||
angle = (angle - (angle % SHIFT_LOCKING_ANGLE)) as Radians;
|
||||
}
|
||||
angle = normalizeAngle(angle);
|
||||
angle = normalizeRadians(angle as Radians);
|
||||
}
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
|
||||
|
@ -240,12 +238,10 @@ const resizeSingleTextElement = (
|
|||
elementsMap,
|
||||
);
|
||||
// rotation pointer with reverse angle
|
||||
const [rotatedX, rotatedY] = rotate(
|
||||
pointerX,
|
||||
pointerY,
|
||||
cx,
|
||||
cy,
|
||||
-element.angle,
|
||||
const [rotatedX, rotatedY] = pointRotateRads(
|
||||
point(pointerX, pointerY),
|
||||
point(cx, cy),
|
||||
-element.angle as Radians,
|
||||
);
|
||||
let scaleX = 0;
|
||||
let scaleY = 0;
|
||||
|
@ -279,20 +275,26 @@ const resizeSingleTextElement = (
|
|||
const startBottomRight = [x2, y2];
|
||||
const startCenter = [cx, cy];
|
||||
|
||||
let newTopLeft = [x1, y1] as [number, number];
|
||||
let newTopLeft = point<GlobalPoint>(x1, y1);
|
||||
if (["n", "w", "nw"].includes(transformHandleType)) {
|
||||
newTopLeft = [
|
||||
newTopLeft = point<GlobalPoint>(
|
||||
startBottomRight[0] - Math.abs(nextWidth),
|
||||
startBottomRight[1] - Math.abs(nextHeight),
|
||||
];
|
||||
);
|
||||
}
|
||||
if (transformHandleType === "ne") {
|
||||
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(nextHeight)];
|
||||
newTopLeft = point<GlobalPoint>(
|
||||
bottomLeft[0],
|
||||
bottomLeft[1] - Math.abs(nextHeight),
|
||||
);
|
||||
}
|
||||
if (transformHandleType === "sw") {
|
||||
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||
newTopLeft = [topRight[0] - Math.abs(nextWidth), topRight[1]];
|
||||
newTopLeft = point<GlobalPoint>(
|
||||
topRight[0] - Math.abs(nextWidth),
|
||||
topRight[1],
|
||||
);
|
||||
}
|
||||
|
||||
if (["s", "n"].includes(transformHandleType)) {
|
||||
|
@ -308,13 +310,17 @@ const resizeSingleTextElement = (
|
|||
}
|
||||
|
||||
const angle = element.angle;
|
||||
const rotatedTopLeft = rotatePoint(newTopLeft, [cx, cy], angle);
|
||||
const newCenter: Point = [
|
||||
const rotatedTopLeft = pointRotateRads(newTopLeft, point(cx, cy), angle);
|
||||
const newCenter = point<GlobalPoint>(
|
||||
newTopLeft[0] + Math.abs(nextWidth) / 2,
|
||||
newTopLeft[1] + Math.abs(nextHeight) / 2,
|
||||
];
|
||||
const rotatedNewCenter = rotatePoint(newCenter, [cx, cy], angle);
|
||||
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
||||
);
|
||||
const rotatedNewCenter = pointRotateRads(newCenter, point(cx, cy), angle);
|
||||
newTopLeft = pointRotateRads(
|
||||
rotatedTopLeft,
|
||||
rotatedNewCenter,
|
||||
-angle as Radians,
|
||||
);
|
||||
const [nextX, nextY] = newTopLeft;
|
||||
|
||||
mutateElement(element, {
|
||||
|
@ -334,14 +340,14 @@ const resizeSingleTextElement = (
|
|||
stateAtResizeStart.height,
|
||||
true,
|
||||
);
|
||||
const startTopLeft: Point = [x1, y1];
|
||||
const startBottomRight: Point = [x2, y2];
|
||||
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
||||
const startTopLeft = point<GlobalPoint>(x1, y1);
|
||||
const startBottomRight = point<GlobalPoint>(x2, y2);
|
||||
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
||||
|
||||
const rotatedPointer = rotatePoint(
|
||||
[pointerX, pointerY],
|
||||
const rotatedPointer = pointRotateRads(
|
||||
point(pointerX, pointerY),
|
||||
startCenter,
|
||||
-stateAtResizeStart.angle,
|
||||
-stateAtResizeStart.angle as Radians,
|
||||
);
|
||||
|
||||
const [esx1, , esx2] = getResizedElementAbsoluteCoords(
|
||||
|
@ -407,13 +413,21 @@ const resizeSingleTextElement = (
|
|||
|
||||
// adjust topLeft to new rotation point
|
||||
const angle = stateAtResizeStart.angle;
|
||||
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
||||
const newCenter: Point = [
|
||||
const rotatedTopLeft = pointRotateRads(
|
||||
pointFromPair(newTopLeft),
|
||||
startCenter,
|
||||
angle,
|
||||
);
|
||||
const newCenter = point(
|
||||
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||
];
|
||||
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
||||
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
||||
);
|
||||
const rotatedNewCenter = pointRotateRads(newCenter, startCenter, angle);
|
||||
newTopLeft = pointRotateRads(
|
||||
rotatedTopLeft,
|
||||
rotatedNewCenter,
|
||||
-angle as Radians,
|
||||
);
|
||||
|
||||
const resizedElement: Partial<ExcalidrawTextElement> = {
|
||||
width: Math.abs(newWidth),
|
||||
|
@ -446,15 +460,15 @@ export const resizeSingleElement = (
|
|||
stateAtResizeStart.height,
|
||||
true,
|
||||
);
|
||||
const startTopLeft: Point = [x1, y1];
|
||||
const startBottomRight: Point = [x2, y2];
|
||||
const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
|
||||
const startTopLeft = point(x1, y1);
|
||||
const startBottomRight = point(x2, y2);
|
||||
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
||||
|
||||
// Calculate new dimensions based on cursor position
|
||||
const rotatedPointer = rotatePoint(
|
||||
[pointerX, pointerY],
|
||||
const rotatedPointer = pointRotateRads(
|
||||
point(pointerX, pointerY),
|
||||
startCenter,
|
||||
-stateAtResizeStart.angle,
|
||||
-stateAtResizeStart.angle as Radians,
|
||||
);
|
||||
|
||||
// Get bounds corners rendered on screen
|
||||
|
@ -628,13 +642,21 @@ export const resizeSingleElement = (
|
|||
|
||||
// adjust topLeft to new rotation point
|
||||
const angle = stateAtResizeStart.angle;
|
||||
const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
|
||||
const newCenter: Point = [
|
||||
const rotatedTopLeft = pointRotateRads(
|
||||
pointFromPair(newTopLeft),
|
||||
startCenter,
|
||||
angle,
|
||||
);
|
||||
const newCenter = point(
|
||||
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||
];
|
||||
const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
|
||||
newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
|
||||
);
|
||||
const rotatedNewCenter = pointRotateRads(newCenter, startCenter, angle);
|
||||
newTopLeft = pointRotateRads(
|
||||
rotatedTopLeft,
|
||||
rotatedNewCenter,
|
||||
-angle as Radians,
|
||||
);
|
||||
|
||||
// For linear elements (x,y) are the coordinates of the first drawn point not the top-left corner
|
||||
// So we need to readjust (x,y) to be where the first point should be
|
||||
|
@ -793,21 +815,21 @@ export const resizeMultipleElements = (
|
|||
|
||||
const direction = transformHandleType;
|
||||
|
||||
const anchorsMap: Record<TransformHandleDirection, Point> = {
|
||||
ne: [minX, maxY],
|
||||
se: [minX, minY],
|
||||
sw: [maxX, minY],
|
||||
nw: [maxX, maxY],
|
||||
e: [minX, minY + height / 2],
|
||||
w: [maxX, minY + height / 2],
|
||||
n: [minX + width / 2, maxY],
|
||||
s: [minX + width / 2, minY],
|
||||
const anchorsMap: Record<TransformHandleDirection, GlobalPoint> = {
|
||||
ne: point(minX, maxY),
|
||||
se: point(minX, minY),
|
||||
sw: point(maxX, minY),
|
||||
nw: point(maxX, maxY),
|
||||
e: point(minX, minY + height / 2),
|
||||
w: point(maxX, minY + height / 2),
|
||||
n: point(minX + width / 2, maxY),
|
||||
s: point(minX + width / 2, minY),
|
||||
};
|
||||
|
||||
// anchor point must be on the opposite side of the dragged selection handle
|
||||
// or be the center of the selection if shouldResizeFromCenter
|
||||
const [anchorX, anchorY]: Point = shouldResizeFromCenter
|
||||
? [midX, midY]
|
||||
const [anchorX, anchorY] = shouldResizeFromCenter
|
||||
? point(midX, midY)
|
||||
: anchorsMap[direction];
|
||||
|
||||
const resizeFromCenterScale = shouldResizeFromCenter ? 2 : 1;
|
||||
|
@ -898,7 +920,9 @@ export const resizeMultipleElements = (
|
|||
|
||||
const width = orig.width * scaleX;
|
||||
const height = orig.height * scaleY;
|
||||
const angle = normalizeAngle(orig.angle * flipFactorX * flipFactorY);
|
||||
const angle = normalizeRadians(
|
||||
(orig.angle * flipFactorX * flipFactorY) as Radians,
|
||||
);
|
||||
|
||||
const isLinearOrFreeDraw = isLinearElement(orig) || isFreeDrawElement(orig);
|
||||
const offsetX = orig.x - anchorX;
|
||||
|
@ -1029,12 +1053,10 @@ const rotateMultipleElements = (
|
|||
const cy = (y1 + y2) / 2;
|
||||
const origAngle =
|
||||
originalElements.get(element.id)?.angle ?? element.angle;
|
||||
const [rotatedCX, rotatedCY] = rotate(
|
||||
cx,
|
||||
cy,
|
||||
centerX,
|
||||
centerY,
|
||||
centerAngle + origAngle - element.angle,
|
||||
const [rotatedCX, rotatedCY] = pointRotateRads(
|
||||
point(cx, cy),
|
||||
point(centerX, centerY),
|
||||
(centerAngle + origAngle - element.angle) as Radians,
|
||||
);
|
||||
|
||||
if (isArrowElement(element) && isElbowArrow(element)) {
|
||||
|
@ -1046,7 +1068,7 @@ const rotateMultipleElements = (
|
|||
{
|
||||
x: element.x + (rotatedCX - cx),
|
||||
y: element.y + (rotatedCY - cy),
|
||||
angle: normalizeAngle(centerAngle + origAngle),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
@ -1063,7 +1085,7 @@ const rotateMultipleElements = (
|
|||
{
|
||||
x: boundText.x + (rotatedCX - cx),
|
||||
y: boundText.y + (rotatedCY - cy),
|
||||
angle: normalizeAngle(centerAngle + origAngle),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
@ -1086,25 +1108,43 @@ export const getResizeOffsetXY = (
|
|||
: getCommonBounds(selectedElements);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
const angle = selectedElements.length === 1 ? selectedElements[0].angle : 0;
|
||||
[x, y] = rotate(x, y, cx, cy, -angle);
|
||||
const angle = (
|
||||
selectedElements.length === 1 ? selectedElements[0].angle : 0
|
||||
) as Radians;
|
||||
[x, y] = pointRotateRads(point(x, y), point(cx, cy), -angle as Radians);
|
||||
switch (transformHandleType) {
|
||||
case "n":
|
||||
return rotate(x - (x1 + x2) / 2, y - y1, 0, 0, angle);
|
||||
return pointRotateRads(
|
||||
point(x - (x1 + x2) / 2, y - y1),
|
||||
point(0, 0),
|
||||
angle,
|
||||
);
|
||||
case "s":
|
||||
return rotate(x - (x1 + x2) / 2, y - y2, 0, 0, angle);
|
||||
return pointRotateRads(
|
||||
point(x - (x1 + x2) / 2, y - y2),
|
||||
point(0, 0),
|
||||
angle,
|
||||
);
|
||||
case "w":
|
||||
return rotate(x - x1, y - (y1 + y2) / 2, 0, 0, angle);
|
||||
return pointRotateRads(
|
||||
point(x - x1, y - (y1 + y2) / 2),
|
||||
point(0, 0),
|
||||
angle,
|
||||
);
|
||||
case "e":
|
||||
return rotate(x - x2, y - (y1 + y2) / 2, 0, 0, angle);
|
||||
return pointRotateRads(
|
||||
point(x - x2, y - (y1 + y2) / 2),
|
||||
point(0, 0),
|
||||
angle,
|
||||
);
|
||||
case "nw":
|
||||
return rotate(x - x1, y - y1, 0, 0, angle);
|
||||
return pointRotateRads(point(x - x1, y - y1), point(0, 0), angle);
|
||||
case "ne":
|
||||
return rotate(x - x2, y - y1, 0, 0, angle);
|
||||
return pointRotateRads(point(x - x2, y - y1), point(0, 0), angle);
|
||||
case "sw":
|
||||
return rotate(x - x1, y - y2, 0, 0, angle);
|
||||
return pointRotateRads(point(x - x1, y - y2), point(0, 0), angle);
|
||||
case "se":
|
||||
return rotate(x - x2, y - y2, 0, 0, angle);
|
||||
return pointRotateRads(point(x - x2, y - y2), point(0, 0), angle);
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue