chore: Unify math types, utils and functions (#8389)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Márk Tolmács 2024-09-03 00:23:38 +02:00 committed by GitHub
parent e3d1dee9d0
commit f4dd23fc31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 4291 additions and 3661 deletions

View file

@ -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];
}