mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Fix arc line intersection, add tests to element line intersections
This commit is contained in:
parent
a50ac0ebff
commit
c53c37ba69
11 changed files with 292 additions and 589 deletions
|
@ -34,6 +34,9 @@ import {
|
||||||
ellipse,
|
ellipse,
|
||||||
arc,
|
arc,
|
||||||
radians,
|
radians,
|
||||||
|
cartesian2Polar,
|
||||||
|
normalizeRadians,
|
||||||
|
radiansToDegrees,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
import type { Mutable } from "../utility-types";
|
import type { Mutable } from "../utility-types";
|
||||||
import { getCurvePathOps } from "../../utils/geometry/shape";
|
import { getCurvePathOps } from "../../utils/geometry/shape";
|
||||||
|
@ -694,17 +697,18 @@ export const createDiamondSide = (
|
||||||
export const createDiamondArc = (
|
export const createDiamondArc = (
|
||||||
start: GlobalPoint,
|
start: GlobalPoint,
|
||||||
end: GlobalPoint,
|
end: GlobalPoint,
|
||||||
|
c: GlobalPoint,
|
||||||
r: number,
|
r: number,
|
||||||
) => {
|
) => {
|
||||||
const c = pointFrom<GlobalPoint>(
|
const [, startAngle] = cartesian2Polar(
|
||||||
(start[0] + end[0]) / 2,
|
pointFrom(start[0] - c[0], start[1] - c[1]),
|
||||||
(start[1] + end[1]) / 2,
|
|
||||||
);
|
);
|
||||||
|
const [, endAngle] = cartesian2Polar(pointFrom(end[0] - c[0], end[1] - c[1]));
|
||||||
|
|
||||||
return arc(
|
return arc(
|
||||||
c,
|
c,
|
||||||
r,
|
r,
|
||||||
radians(Math.asin((start[1] - c[1]) / r)),
|
normalizeRadians(startAngle), // normalizeRadians(radians(startAngle - Math.PI / 2)),
|
||||||
radians(Math.asin((end[1] - c[1]) / r)),
|
normalizeRadians(endAngle), // normalizeRadians(radians(endAngle - Math.PI / 2)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
179
packages/excalidraw/element/collision.test.tsx
Normal file
179
packages/excalidraw/element/collision.test.tsx
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
import { intersectElementWithLine } from "./collision";
|
||||||
|
import { newElement } from "./newElement";
|
||||||
|
import { pointFrom } from "../../math";
|
||||||
|
import { ROUNDNESS } from "..";
|
||||||
|
|
||||||
|
describe("intersection with element", () => {
|
||||||
|
// it("intersect with rectangle", () => {
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "rectangle",
|
||||||
|
// x: -5,
|
||||||
|
// y: -5,
|
||||||
|
// width: 10,
|
||||||
|
// height: 10,
|
||||||
|
// roundness: null,
|
||||||
|
// }),
|
||||||
|
// pointFrom(1, 1),
|
||||||
|
// pointFrom(10, 10),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(5, 5), pointFrom(-5, -5)]);
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "rectangle",
|
||||||
|
// x: -5,
|
||||||
|
// y: -5,
|
||||||
|
// width: 10,
|
||||||
|
// height: 10,
|
||||||
|
// roundness: null,
|
||||||
|
// }),
|
||||||
|
// pointFrom(-1, -1),
|
||||||
|
// pointFrom(-10, -10),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(-5, -5), pointFrom(5, 5)]);
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "rectangle",
|
||||||
|
// x: -5,
|
||||||
|
// y: -5,
|
||||||
|
// width: 10,
|
||||||
|
// height: 10,
|
||||||
|
// roundness: {
|
||||||
|
// type: ROUNDNESS.ADAPTIVE_RADIUS,
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// pointFrom(1, 1),
|
||||||
|
// pointFrom(10, 10),
|
||||||
|
// ).map((p) =>
|
||||||
|
// pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(4.27, 4.27), pointFrom(-4.27, -4.27)]);
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "rectangle",
|
||||||
|
// x: -5,
|
||||||
|
// y: -5,
|
||||||
|
// width: 10,
|
||||||
|
// height: 10,
|
||||||
|
// roundness: {
|
||||||
|
// type: ROUNDNESS.ADAPTIVE_RADIUS,
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// pointFrom(-1, -1),
|
||||||
|
// pointFrom(-10, -10),
|
||||||
|
// ).map((p) =>
|
||||||
|
// pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(-4.27, -4.27), pointFrom(4.27, 4.27)]);
|
||||||
|
// });
|
||||||
|
|
||||||
|
it("intersect with diamond", () => {
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "diamond",
|
||||||
|
// x: -20,
|
||||||
|
// y: -20,
|
||||||
|
// width: 40,
|
||||||
|
// height: 40,
|
||||||
|
// roundness: null,
|
||||||
|
// }),
|
||||||
|
// pointFrom(-30, 0),
|
||||||
|
// pointFrom(-25, 0),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(-20, 0), pointFrom(20, 0)]);
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "diamond",
|
||||||
|
// x: -20,
|
||||||
|
// y: -20,
|
||||||
|
// width: 40,
|
||||||
|
// height: 40,
|
||||||
|
// roundness: null,
|
||||||
|
// }),
|
||||||
|
// pointFrom(0, -30),
|
||||||
|
// pointFrom(0, -25),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(0, -20), pointFrom(0, 20)]);
|
||||||
|
// expect(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "diamond",
|
||||||
|
// x: -20,
|
||||||
|
// y: -20,
|
||||||
|
// width: 40,
|
||||||
|
// height: 40,
|
||||||
|
// roundness: {
|
||||||
|
// type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// pointFrom(-30, 0),
|
||||||
|
// pointFrom(-25, 0),
|
||||||
|
// ).map((p) =>
|
||||||
|
// pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
// ),
|
||||||
|
// ).toEqual([pointFrom(-21.46, 0), pointFrom(21.46, 0)]);
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// intersectElementWithLine(
|
||||||
|
// newElement({
|
||||||
|
// type: "diamond",
|
||||||
|
// x: -20,
|
||||||
|
// y: -20,
|
||||||
|
// width: 40,
|
||||||
|
// height: 40,
|
||||||
|
// roundness: {
|
||||||
|
// type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// pointFrom(0, -30),
|
||||||
|
// pointFrom(0, -25),
|
||||||
|
// ).map((p) =>
|
||||||
|
// pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
intersectElementWithLine(
|
||||||
|
newElement({
|
||||||
|
type: "diamond",
|
||||||
|
x: -20,
|
||||||
|
y: -20,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
roundness: {
|
||||||
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pointFrom(0, -30),
|
||||||
|
pointFrom(0, -25),
|
||||||
|
).map((p) =>
|
||||||
|
pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
),
|
||||||
|
).toEqual([pointFrom(0, -17.93), pointFrom(0, 17.93)]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
intersectElementWithLine(
|
||||||
|
newElement({
|
||||||
|
type: "diamond",
|
||||||
|
x: -20,
|
||||||
|
y: -20,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
roundness: {
|
||||||
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pointFrom(-30, 0),
|
||||||
|
pointFrom(-25, 0),
|
||||||
|
).map((p) =>
|
||||||
|
pointFrom(Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100),
|
||||||
|
),
|
||||||
|
).toEqual([pointFrom(-17.93, 0), pointFrom(17.93, 0)]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -22,11 +22,7 @@ import {
|
||||||
isImageElement,
|
isImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import {
|
import { getBoundTextShape, getCornerRadius } from "../shapes";
|
||||||
getBoundTextShape,
|
|
||||||
getCornerRadius,
|
|
||||||
getDiamondPoints,
|
|
||||||
} from "../shapes";
|
|
||||||
import type { Arc, GlobalPoint, Polygon } from "../../math";
|
import type { Arc, GlobalPoint, Polygon } from "../../math";
|
||||||
import {
|
import {
|
||||||
pathIsALoop,
|
pathIsALoop,
|
||||||
|
@ -43,6 +39,7 @@ import {
|
||||||
pointDistanceSq,
|
pointDistanceSq,
|
||||||
ellipse,
|
ellipse,
|
||||||
ellipseLineIntersectionPoints,
|
ellipseLineIntersectionPoints,
|
||||||
|
pointsEqual,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
import { LINE_CONFIRM_THRESHOLD } from "../constants";
|
import { LINE_CONFIRM_THRESHOLD } from "../constants";
|
||||||
|
|
||||||
|
@ -152,7 +149,7 @@ export const intersectElementWithLine = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
a: GlobalPoint,
|
a: GlobalPoint,
|
||||||
b: GlobalPoint,
|
b: GlobalPoint,
|
||||||
offset: number,
|
offset: number = 0,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
@ -172,7 +169,7 @@ export const intersectElementWithLine = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const intersectRectanguloidWithLine = (
|
const intersectRectanguloidWithLine = (
|
||||||
element: ExcalidrawRectanguloidElement,
|
element: ExcalidrawRectanguloidElement,
|
||||||
a: GlobalPoint,
|
a: GlobalPoint,
|
||||||
b: GlobalPoint,
|
b: GlobalPoint,
|
||||||
|
@ -234,8 +231,8 @@ export const intersectRectanguloidWithLine = (
|
||||||
arc<GlobalPoint>(
|
arc<GlobalPoint>(
|
||||||
pointFrom(r[0][0] + roundness, r[0][1] + roundness),
|
pointFrom(r[0][0] + roundness, r[0][1] + roundness),
|
||||||
roundness,
|
roundness,
|
||||||
radians(Math.PI),
|
|
||||||
radians((3 / 4) * Math.PI),
|
radians((3 / 4) * Math.PI),
|
||||||
|
radians(0),
|
||||||
),
|
),
|
||||||
arc<GlobalPoint>(
|
arc<GlobalPoint>(
|
||||||
pointFrom(r[1][0] - roundness, r[0][1] + roundness),
|
pointFrom(r[1][0] - roundness, r[0][1] + roundness),
|
||||||
|
@ -261,8 +258,13 @@ export const intersectRectanguloidWithLine = (
|
||||||
.map((j) => pointRotateRads(j, center, element.angle))
|
.map((j) => pointRotateRads(j, center, element.angle))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return [...sideIntersections, ...cornerIntersections].sort(
|
return (
|
||||||
(g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b),
|
[...sideIntersections, ...cornerIntersections]
|
||||||
|
// Remove duplicates
|
||||||
|
.filter(
|
||||||
|
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||||
|
)
|
||||||
|
.sort((g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -273,31 +275,39 @@ export const intersectRectanguloidWithLine = (
|
||||||
* @param b
|
* @param b
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const intersectDiamondWithLine = (
|
const intersectDiamondWithLine = (
|
||||||
element: ExcalidrawDiamondElement,
|
element: ExcalidrawDiamondElement,
|
||||||
a: GlobalPoint,
|
a: GlobalPoint,
|
||||||
b: GlobalPoint,
|
b: GlobalPoint,
|
||||||
offset: number = 0,
|
offset: number = 0,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
const top = pointFrom<GlobalPoint>(element.x + element.width / 2, element.y);
|
||||||
getDiamondPoints(element, offset);
|
const right = pointFrom<GlobalPoint>(
|
||||||
const center = pointFrom<GlobalPoint>(
|
element.x + element.width,
|
||||||
(topX + bottomX) / 2,
|
element.y + element.height / 2,
|
||||||
(topY + bottomY) / 2,
|
);
|
||||||
|
const bottom = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height,
|
||||||
|
);
|
||||||
|
const left = pointFrom<GlobalPoint>(
|
||||||
|
element.x,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
const verticalRadius = getCornerRadius(Math.abs(top[0] - left[0]), element);
|
||||||
|
const horizontalRadius = getCornerRadius(
|
||||||
|
Math.abs(right[1] - top[1]),
|
||||||
|
element,
|
||||||
);
|
);
|
||||||
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
|
|
||||||
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
|
|
||||||
|
|
||||||
// Rotate the point to the inverse direction to simulate the rotated diamond
|
// Rotate the point to the inverse direction to simulate the rotated diamond
|
||||||
// points. It's all the same distance-wise.
|
// points. It's all the same distance-wise.
|
||||||
const rotatedA = pointRotateRads(a, center, radians(-element.angle));
|
const rotatedA = pointRotateRads(a, center, radians(-element.angle));
|
||||||
const rotatedB = pointRotateRads(b, center, radians(-element.angle));
|
const rotatedB = pointRotateRads(b, center, radians(-element.angle));
|
||||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
|
||||||
pointFrom(element.x + topX, element.y + topY),
|
|
||||||
pointFrom(element.x + rightX, element.y + rightY),
|
|
||||||
pointFrom(element.x + bottomX, element.y + bottomY),
|
|
||||||
pointFrom(element.x + leftX, element.y + leftY),
|
|
||||||
];
|
|
||||||
|
|
||||||
const topRight = createDiamondSide(
|
const topRight = createDiamondSide(
|
||||||
segment<GlobalPoint>(top, right),
|
segment<GlobalPoint>(top, right),
|
||||||
|
@ -322,10 +332,42 @@ export const intersectDiamondWithLine = (
|
||||||
|
|
||||||
const arcs: Arc<GlobalPoint>[] = element.roundness
|
const arcs: Arc<GlobalPoint>[] = element.roundness
|
||||||
? [
|
? [
|
||||||
createDiamondArc(topLeft[0], topRight[0], verticalRadius), // TOP
|
createDiamondArc(
|
||||||
createDiamondArc(topRight[1], bottomRight[1], horizontalRadius), // RIGHT
|
topLeft[0],
|
||||||
createDiamondArc(bottomRight[0], bottomLeft[0], verticalRadius), // BOTTOM
|
topRight[0],
|
||||||
createDiamondArc(bottomLeft[1], topLeft[1], horizontalRadius), // LEFT
|
pointFrom(
|
||||||
|
top[0],
|
||||||
|
top[1] + Math.sqrt(2 * Math.pow(verticalRadius, 2)),
|
||||||
|
),
|
||||||
|
verticalRadius,
|
||||||
|
), // TOP
|
||||||
|
createDiamondArc(
|
||||||
|
topRight[1],
|
||||||
|
bottomRight[1],
|
||||||
|
pointFrom(
|
||||||
|
right[0] - Math.sqrt(2 * Math.pow(horizontalRadius, 2)),
|
||||||
|
right[1],
|
||||||
|
),
|
||||||
|
horizontalRadius,
|
||||||
|
), // RIGHT
|
||||||
|
createDiamondArc(
|
||||||
|
bottomRight[0],
|
||||||
|
bottomLeft[0],
|
||||||
|
pointFrom(
|
||||||
|
bottom[0],
|
||||||
|
bottom[1] - Math.sqrt(2 * Math.pow(verticalRadius, 2)),
|
||||||
|
),
|
||||||
|
verticalRadius,
|
||||||
|
), // BOTTOM
|
||||||
|
createDiamondArc(
|
||||||
|
bottomLeft[1],
|
||||||
|
topLeft[1],
|
||||||
|
pointFrom(
|
||||||
|
left[0] + Math.sqrt(2 * Math.pow(horizontalRadius, 2)),
|
||||||
|
left[1],
|
||||||
|
),
|
||||||
|
horizontalRadius,
|
||||||
|
), // LEFT
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
@ -342,8 +384,13 @@ export const intersectDiamondWithLine = (
|
||||||
// Rotate back intersection points
|
// Rotate back intersection points
|
||||||
.map((p) => pointRotateRads(p, center, element.angle));
|
.map((p) => pointRotateRads(p, center, element.angle));
|
||||||
|
|
||||||
return [...sides, ...corners].sort(
|
return (
|
||||||
(g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b),
|
[...sides, ...corners]
|
||||||
|
// Remove duplicates
|
||||||
|
.filter(
|
||||||
|
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||||
|
)
|
||||||
|
.sort((g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -354,7 +401,7 @@ export const intersectDiamondWithLine = (
|
||||||
* @param b
|
* @param b
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const intersectEllipseWithLine = (
|
const intersectEllipseWithLine = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
a: GlobalPoint,
|
a: GlobalPoint,
|
||||||
b: GlobalPoint,
|
b: GlobalPoint,
|
||||||
|
|
|
@ -172,10 +172,30 @@ export const distanceToDiamondElement = (
|
||||||
|
|
||||||
const arcs = element.roundness
|
const arcs = element.roundness
|
||||||
? [
|
? [
|
||||||
createDiamondArc(topLeft[0], topRight[0], verticalRadius), // TOP
|
createDiamondArc(
|
||||||
createDiamondArc(topRight[1], bottomRight[1], horizontalRadius), // RIGHT
|
topLeft[0],
|
||||||
createDiamondArc(bottomRight[0], bottomLeft[0], verticalRadius), // BOTTOM
|
topRight[0],
|
||||||
createDiamondArc(bottomLeft[1], topLeft[1], horizontalRadius), // LEFT
|
pointFrom(top[0], top[1] + verticalRadius),
|
||||||
|
verticalRadius,
|
||||||
|
), // TOP
|
||||||
|
createDiamondArc(
|
||||||
|
topRight[1],
|
||||||
|
bottomRight[1],
|
||||||
|
pointFrom(right[0] - horizontalRadius, right[1]),
|
||||||
|
horizontalRadius,
|
||||||
|
), // RIGHT
|
||||||
|
createDiamondArc(
|
||||||
|
bottomRight[0],
|
||||||
|
bottomLeft[0],
|
||||||
|
pointFrom(bottom[0], bottom[1] - verticalRadius),
|
||||||
|
verticalRadius,
|
||||||
|
), // BOTTOM
|
||||||
|
createDiamondArc(
|
||||||
|
bottomLeft[1],
|
||||||
|
topLeft[1],
|
||||||
|
pointFrom(right[0] + horizontalRadius, right[1]),
|
||||||
|
horizontalRadius,
|
||||||
|
), // LEFT
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,8 @@ export const normalizeRadians = (angle: Radians): Radians => {
|
||||||
export const cartesian2Polar = <P extends GenericPoint>([
|
export const cartesian2Polar = <P extends GenericPoint>([
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
]: P): PolarCoords => polar(Math.hypot(x, y), radians(Math.atan2(y, x)));
|
]: P): PolarCoords =>
|
||||||
|
polar(Math.hypot(x, y), normalizeRadians(radians(Math.atan2(y, x))));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an angle in degrees into randians
|
* Convert an angle in degrees into randians
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
import * as GA from "./ga";
|
|
||||||
import { point, toString, direction, offset } from "./ga";
|
|
||||||
import * as GAPoint from "./gapoints";
|
|
||||||
import * as GALine from "./galines";
|
|
||||||
import * as GATransform from "./gatransforms";
|
|
||||||
|
|
||||||
describe("geometric algebra", () => {
|
|
||||||
describe("points", () => {
|
|
||||||
it("distanceToLine", () => {
|
|
||||||
const point = GA.point(3, 3);
|
|
||||||
const line = GALine.equation(0, 1, -1);
|
|
||||||
expect(GAPoint.distanceToLine(point, line)).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("distanceToLine neg", () => {
|
|
||||||
const point = GA.point(-3, -3);
|
|
||||||
const line = GALine.equation(0, 1, -1);
|
|
||||||
expect(GAPoint.distanceToLine(point, line)).toEqual(-4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("lines", () => {
|
|
||||||
it("through", () => {
|
|
||||||
const a = GA.point(0, 0);
|
|
||||||
const b = GA.point(2, 0);
|
|
||||||
expect(toString(GALine.through(a, b))).toEqual(
|
|
||||||
toString(GALine.equation(0, 2, 0)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("parallel", () => {
|
|
||||||
const point = GA.point(3, 3);
|
|
||||||
const line = GALine.equation(0, 1, -1);
|
|
||||||
const parallel = GALine.parallel(line, 2);
|
|
||||||
expect(GAPoint.distanceToLine(point, parallel)).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("translation", () => {
|
|
||||||
it("points", () => {
|
|
||||||
const start = point(2, 2);
|
|
||||||
const move = GATransform.translation(direction(0, 1));
|
|
||||||
const end = GATransform.apply(move, start);
|
|
||||||
expect(toString(end)).toEqual(toString(point(2, 3)));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("points 2", () => {
|
|
||||||
const start = point(2, 2);
|
|
||||||
const move = GATransform.translation(offset(3, 4));
|
|
||||||
const end = GATransform.apply(move, start);
|
|
||||||
expect(toString(end)).toEqual(toString(point(5, 6)));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("lines", () => {
|
|
||||||
const original = GALine.through(point(2, 2), point(3, 4));
|
|
||||||
const move = GATransform.translation(offset(3, 4));
|
|
||||||
const parallel = GATransform.apply(move, original);
|
|
||||||
expect(toString(parallel)).toEqual(
|
|
||||||
toString(GALine.through(point(5, 6), point(6, 8))),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("rotation", () => {
|
|
||||||
it("points", () => {
|
|
||||||
const start = point(2, 2);
|
|
||||||
const pivot = point(1, 1);
|
|
||||||
const rotate = GATransform.rotation(pivot, Math.PI / 2);
|
|
||||||
const end = GATransform.apply(rotate, start);
|
|
||||||
expect(toString(end)).toEqual(toString(point(2, 0)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,317 +0,0 @@
|
||||||
/**
|
|
||||||
* This is a 2D Projective Geometric Algebra implementation.
|
|
||||||
*
|
|
||||||
* For wider context on geometric algebra visit see https://bivector.net.
|
|
||||||
*
|
|
||||||
* For this specific algebra see cheatsheet https://bivector.net/2DPGA.pdf.
|
|
||||||
*
|
|
||||||
* Converted from generator written by enki, with a ton of added on top.
|
|
||||||
*
|
|
||||||
* This library uses 8-vectors to represent points, directions and lines
|
|
||||||
* in 2D space.
|
|
||||||
*
|
|
||||||
* An array `[a, b, c, d, e, f, g, h]` represents a n(8)vector:
|
|
||||||
* a + b*e0 + c*e1 + d*e2 + e*e01 + f*e20 + g*e12 + h*e012
|
|
||||||
*
|
|
||||||
* See GAPoint, GALine, GADirection and GATransform modules for common
|
|
||||||
* operations.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type Point = NVector;
|
|
||||||
export type Direction = NVector;
|
|
||||||
export type Line = NVector;
|
|
||||||
export type Transform = NVector;
|
|
||||||
|
|
||||||
export const point = (x: number, y: number): Point => [0, 0, 0, 0, y, x, 1, 0];
|
|
||||||
|
|
||||||
export const origin = (): Point => [0, 0, 0, 0, 0, 0, 1, 0];
|
|
||||||
|
|
||||||
export const direction = (x: number, y: number): Direction => {
|
|
||||||
const norm = Math.hypot(x, y); // same as `inorm(direction(x, y))`
|
|
||||||
return [0, 0, 0, 0, y / norm, x / norm, 0, 0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const offset = (x: number, y: number): Direction => [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
y,
|
|
||||||
x,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// This is the "implementation" part of the library
|
|
||||||
|
|
||||||
type NVector = readonly [
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
];
|
|
||||||
|
|
||||||
// These are labels for what each number in an nvector represents
|
|
||||||
const NVECTOR_BASE = ["1", "e0", "e1", "e2", "e01", "e20", "e12", "e012"];
|
|
||||||
|
|
||||||
// Used to represent points, lines and transformations
|
|
||||||
export const nvector = (value: number = 0, index: number = 0): NVector => {
|
|
||||||
const result = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
||||||
if (index < 0 || index > 7) {
|
|
||||||
throw new Error(`Expected \`index\` between 0 and 7, got \`${index}\``);
|
|
||||||
}
|
|
||||||
if (value !== 0) {
|
|
||||||
result[index] = value;
|
|
||||||
}
|
|
||||||
return result as unknown as NVector;
|
|
||||||
};
|
|
||||||
|
|
||||||
const STRING_EPSILON = 0.000001;
|
|
||||||
export const toString = (nvector: NVector): string => {
|
|
||||||
const result = nvector
|
|
||||||
.map((value, index) =>
|
|
||||||
Math.abs(value) > STRING_EPSILON
|
|
||||||
? value.toFixed(7).replace(/(\.|0+)$/, "") +
|
|
||||||
(index > 0 ? NVECTOR_BASE[index] : "")
|
|
||||||
: null,
|
|
||||||
)
|
|
||||||
.filter((representation) => representation != null)
|
|
||||||
.join(" + ");
|
|
||||||
return result === "" ? "0" : result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reverse the order of the basis blades.
|
|
||||||
export const reverse = (nvector: NVector): NVector => [
|
|
||||||
nvector[0],
|
|
||||||
nvector[1],
|
|
||||||
nvector[2],
|
|
||||||
nvector[3],
|
|
||||||
-nvector[4],
|
|
||||||
-nvector[5],
|
|
||||||
-nvector[6],
|
|
||||||
-nvector[7],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Poincare duality operator.
|
|
||||||
export const dual = (nvector: NVector): NVector => [
|
|
||||||
nvector[7],
|
|
||||||
nvector[6],
|
|
||||||
nvector[5],
|
|
||||||
nvector[4],
|
|
||||||
nvector[3],
|
|
||||||
nvector[2],
|
|
||||||
nvector[1],
|
|
||||||
nvector[0],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Clifford Conjugation
|
|
||||||
export const conjugate = (nvector: NVector): NVector => [
|
|
||||||
nvector[0],
|
|
||||||
-nvector[1],
|
|
||||||
-nvector[2],
|
|
||||||
-nvector[3],
|
|
||||||
-nvector[4],
|
|
||||||
-nvector[5],
|
|
||||||
-nvector[6],
|
|
||||||
nvector[7],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Main involution
|
|
||||||
export const involute = (nvector: NVector): NVector => [
|
|
||||||
nvector[0],
|
|
||||||
-nvector[1],
|
|
||||||
-nvector[2],
|
|
||||||
-nvector[3],
|
|
||||||
nvector[4],
|
|
||||||
nvector[5],
|
|
||||||
nvector[6],
|
|
||||||
-nvector[7],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Multivector addition
|
|
||||||
export const add = (a: NVector, b: NVector | number): NVector => {
|
|
||||||
if (isNumber(b)) {
|
|
||||||
return [a[0] + b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
a[0] + b[0],
|
|
||||||
a[1] + b[1],
|
|
||||||
a[2] + b[2],
|
|
||||||
a[3] + b[3],
|
|
||||||
a[4] + b[4],
|
|
||||||
a[5] + b[5],
|
|
||||||
a[6] + b[6],
|
|
||||||
a[7] + b[7],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Multivector subtraction
|
|
||||||
export const sub = (a: NVector, b: NVector | number): NVector => {
|
|
||||||
if (isNumber(b)) {
|
|
||||||
return [a[0] - b, a[1], a[2], a[3], a[4], a[5], a[6], a[7]];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
a[0] - b[0],
|
|
||||||
a[1] - b[1],
|
|
||||||
a[2] - b[2],
|
|
||||||
a[3] - b[3],
|
|
||||||
a[4] - b[4],
|
|
||||||
a[5] - b[5],
|
|
||||||
a[6] - b[6],
|
|
||||||
a[7] - b[7],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// The geometric product.
|
|
||||||
export const mul = (a: NVector, b: NVector | number): NVector => {
|
|
||||||
if (isNumber(b)) {
|
|
||||||
return [
|
|
||||||
a[0] * b,
|
|
||||||
a[1] * b,
|
|
||||||
a[2] * b,
|
|
||||||
a[3] * b,
|
|
||||||
a[4] * b,
|
|
||||||
a[5] * b,
|
|
||||||
a[6] * b,
|
|
||||||
a[7] * b,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
mulScalar(a, b),
|
|
||||||
b[1] * a[0] +
|
|
||||||
b[0] * a[1] -
|
|
||||||
b[4] * a[2] +
|
|
||||||
b[5] * a[3] +
|
|
||||||
b[2] * a[4] -
|
|
||||||
b[3] * a[5] -
|
|
||||||
b[7] * a[6] -
|
|
||||||
b[6] * a[7],
|
|
||||||
b[2] * a[0] + b[0] * a[2] - b[6] * a[3] + b[3] * a[6],
|
|
||||||
b[3] * a[0] + b[6] * a[2] + b[0] * a[3] - b[2] * a[6],
|
|
||||||
b[4] * a[0] +
|
|
||||||
b[2] * a[1] -
|
|
||||||
b[1] * a[2] +
|
|
||||||
b[7] * a[3] +
|
|
||||||
b[0] * a[4] +
|
|
||||||
b[6] * a[5] -
|
|
||||||
b[5] * a[6] +
|
|
||||||
b[3] * a[7],
|
|
||||||
b[5] * a[0] -
|
|
||||||
b[3] * a[1] +
|
|
||||||
b[7] * a[2] +
|
|
||||||
b[1] * a[3] -
|
|
||||||
b[6] * a[4] +
|
|
||||||
b[0] * a[5] +
|
|
||||||
b[4] * a[6] +
|
|
||||||
b[2] * a[7],
|
|
||||||
b[6] * a[0] + b[3] * a[2] - b[2] * a[3] + b[0] * a[6],
|
|
||||||
b[7] * a[0] +
|
|
||||||
b[6] * a[1] +
|
|
||||||
b[5] * a[2] +
|
|
||||||
b[4] * a[3] +
|
|
||||||
b[3] * a[4] +
|
|
||||||
b[2] * a[5] +
|
|
||||||
b[1] * a[6] +
|
|
||||||
b[0] * a[7],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mulScalar = (a: NVector, b: NVector): number =>
|
|
||||||
b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6];
|
|
||||||
|
|
||||||
// The outer/exterior/wedge product.
|
|
||||||
export const meet = (a: NVector, b: NVector): NVector => [
|
|
||||||
b[0] * a[0],
|
|
||||||
b[1] * a[0] + b[0] * a[1],
|
|
||||||
b[2] * a[0] + b[0] * a[2],
|
|
||||||
b[3] * a[0] + b[0] * a[3],
|
|
||||||
b[4] * a[0] + b[2] * a[1] - b[1] * a[2] + b[0] * a[4],
|
|
||||||
b[5] * a[0] - b[3] * a[1] + b[1] * a[3] + b[0] * a[5],
|
|
||||||
b[6] * a[0] + b[3] * a[2] - b[2] * a[3] + b[0] * a[6],
|
|
||||||
b[7] * a[0] +
|
|
||||||
b[6] * a[1] +
|
|
||||||
b[5] * a[2] +
|
|
||||||
b[4] * a[3] +
|
|
||||||
b[3] * a[4] +
|
|
||||||
b[2] * a[5] +
|
|
||||||
b[1] * a[6],
|
|
||||||
];
|
|
||||||
|
|
||||||
// The regressive product.
|
|
||||||
export const join = (a: NVector, b: NVector): NVector => [
|
|
||||||
joinScalar(a, b),
|
|
||||||
a[1] * b[7] + a[4] * b[5] - a[5] * b[4] + a[7] * b[1],
|
|
||||||
a[2] * b[7] - a[4] * b[6] + a[6] * b[4] + a[7] * b[2],
|
|
||||||
a[3] * b[7] + a[5] * b[6] - a[6] * b[5] + a[7] * b[3],
|
|
||||||
a[4] * b[7] + a[7] * b[4],
|
|
||||||
a[5] * b[7] + a[7] * b[5],
|
|
||||||
a[6] * b[7] + a[7] * b[6],
|
|
||||||
a[7] * b[7],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const joinScalar = (a: NVector, b: NVector): number =>
|
|
||||||
a[0] * b[7] +
|
|
||||||
a[1] * b[6] +
|
|
||||||
a[2] * b[5] +
|
|
||||||
a[3] * b[4] +
|
|
||||||
a[4] * b[3] +
|
|
||||||
a[5] * b[2] +
|
|
||||||
a[6] * b[1] +
|
|
||||||
a[7] * b[0];
|
|
||||||
|
|
||||||
// The inner product.
|
|
||||||
export const dot = (a: NVector, b: NVector): NVector => [
|
|
||||||
b[0] * a[0] + b[2] * a[2] + b[3] * a[3] - b[6] * a[6],
|
|
||||||
b[1] * a[0] +
|
|
||||||
b[0] * a[1] -
|
|
||||||
b[4] * a[2] +
|
|
||||||
b[5] * a[3] +
|
|
||||||
b[2] * a[4] -
|
|
||||||
b[3] * a[5] -
|
|
||||||
b[7] * a[6] -
|
|
||||||
b[6] * a[7],
|
|
||||||
b[2] * a[0] + b[0] * a[2] - b[6] * a[3] + b[3] * a[6],
|
|
||||||
b[3] * a[0] + b[6] * a[2] + b[0] * a[3] - b[2] * a[6],
|
|
||||||
b[4] * a[0] + b[7] * a[3] + b[0] * a[4] + b[3] * a[7],
|
|
||||||
b[5] * a[0] + b[7] * a[2] + b[0] * a[5] + b[2] * a[7],
|
|
||||||
b[6] * a[0] + b[0] * a[6],
|
|
||||||
b[7] * a[0] + b[0] * a[7],
|
|
||||||
];
|
|
||||||
|
|
||||||
export const norm = (a: NVector): number =>
|
|
||||||
Math.sqrt(Math.abs(a[0] * a[0] - a[2] * a[2] - a[3] * a[3] + a[6] * a[6]));
|
|
||||||
|
|
||||||
export const inorm = (a: NVector): number =>
|
|
||||||
Math.sqrt(Math.abs(a[7] * a[7] - a[5] * a[5] - a[4] * a[4] + a[1] * a[1]));
|
|
||||||
|
|
||||||
export const normalized = (a: NVector): NVector => {
|
|
||||||
const n = norm(a);
|
|
||||||
if (n === 0 || n === 1) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
const sign = a[6] < 0 ? -1 : 1;
|
|
||||||
return mul(a, sign / n);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const inormalized = (a: NVector): NVector => {
|
|
||||||
const n = inorm(a);
|
|
||||||
if (n === 0 || n === 1) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return mul(a, 1 / n);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNumber = (a: any): a is number => typeof a === "number";
|
|
||||||
|
|
||||||
export const E0: NVector = nvector(1, 1);
|
|
||||||
export const E1: NVector = nvector(1, 2);
|
|
||||||
export const E2: NVector = nvector(1, 3);
|
|
||||||
export const E01: NVector = nvector(1, 4);
|
|
||||||
export const E20: NVector = nvector(1, 5);
|
|
||||||
export const E12: NVector = nvector(1, 6);
|
|
||||||
export const E012: NVector = nvector(1, 7);
|
|
||||||
export const I = E012;
|
|
|
@ -1,26 +0,0 @@
|
||||||
import * as GA from "./ga";
|
|
||||||
import type { Line, Direction, Point } from "./ga";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A direction is stored as an array `[0, 0, 0, 0, y, x, 0, 0]` representing
|
|
||||||
* vector `(x, y)`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const from = (point: Point): Point => [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
point[4],
|
|
||||||
point[5],
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const fromTo = (from: Point, to: Point): Direction =>
|
|
||||||
GA.inormalized([0, 0, 0, 0, to[4] - from[4], to[5] - from[5], 0, 0]);
|
|
||||||
|
|
||||||
export const orthogonal = (direction: Direction): Direction =>
|
|
||||||
GA.inormalized([0, 0, 0, 0, -direction[5], direction[4], 0, 0]);
|
|
||||||
|
|
||||||
export const orthogonalToLine = (line: Line): Direction => GA.mul(line, GA.I);
|
|
|
@ -1,52 +0,0 @@
|
||||||
import * as GA from "./ga";
|
|
||||||
import type { Line, Point } from "./ga";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A line is stored as an array `[0, c, a, b, 0, 0, 0, 0]` representing:
|
|
||||||
* c * e0 + a * e1 + b*e2
|
|
||||||
*
|
|
||||||
* This maps to a standard formula `a * x + b * y + c`.
|
|
||||||
*
|
|
||||||
* `(-b, a)` corresponds to a 2D vector parallel to the line. The lines
|
|
||||||
* have a natural orientation, corresponding to that vector.
|
|
||||||
*
|
|
||||||
* The magnitude ("norm") of the line is `sqrt(a ^ 2 + b ^ 2)`.
|
|
||||||
* `c / norm(line)` is the oriented distance from line to origin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Returns line with direction (x, y) through origin
|
|
||||||
export const vector = (x: number, y: number): Line =>
|
|
||||||
GA.normalized([0, 0, -y, x, 0, 0, 0, 0]);
|
|
||||||
|
|
||||||
// For equation ax + by + c = 0.
|
|
||||||
export const equation = (a: number, b: number, c: number): Line =>
|
|
||||||
GA.normalized([0, c, a, b, 0, 0, 0, 0]);
|
|
||||||
|
|
||||||
export const through = (from: Point, to: Point): Line =>
|
|
||||||
GA.normalized(GA.join(to, from));
|
|
||||||
|
|
||||||
export const orthogonal = (line: Line, point: Point): Line =>
|
|
||||||
GA.dot(line, point);
|
|
||||||
|
|
||||||
// Returns a line perpendicular to the line through `against` and `intersection`
|
|
||||||
// going through `intersection`.
|
|
||||||
export const orthogonalThrough = (against: Point, intersection: Point): Line =>
|
|
||||||
orthogonal(through(against, intersection), intersection);
|
|
||||||
|
|
||||||
export const parallel = (line: Line, distance: number): Line => {
|
|
||||||
const result = line.slice();
|
|
||||||
result[1] -= distance;
|
|
||||||
return result as unknown as Line;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parallelThrough = (line: Line, point: Point): Line =>
|
|
||||||
orthogonal(orthogonal(point, line), point);
|
|
||||||
|
|
||||||
export const distance = (line1: Line, line2: Line): number =>
|
|
||||||
GA.inorm(GA.meet(line1, line2));
|
|
||||||
|
|
||||||
export const angle = (line1: Line, line2: Line): number =>
|
|
||||||
Math.acos(GA.dot(line1, line2)[0]);
|
|
||||||
|
|
||||||
// The orientation of the line
|
|
||||||
export const sign = (line: Line): number => Math.sign(line[1]);
|
|
|
@ -1,42 +0,0 @@
|
||||||
import * as GA from "./ga";
|
|
||||||
import * as GALine from "./galines";
|
|
||||||
import type { Point, Line } from "./ga";
|
|
||||||
import { join } from "./ga";
|
|
||||||
|
|
||||||
export const from = ([x, y]: readonly [number, number]): Point => [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
y,
|
|
||||||
x,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const toTuple = (point: Point): [number, number] => [point[5], point[4]];
|
|
||||||
|
|
||||||
export const abs = (point: Point): Point => [
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Math.abs(point[4]),
|
|
||||||
Math.abs(point[5]),
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const intersect = (line1: Line, line2: Line): Point =>
|
|
||||||
GA.normalized(GA.meet(line1, line2));
|
|
||||||
|
|
||||||
// Projects `point` onto the `line`.
|
|
||||||
// The returned point is the closest point on the `line` to the `point`.
|
|
||||||
export const project = (point: Point, line: Line): Point =>
|
|
||||||
intersect(GALine.orthogonal(line, point), line);
|
|
||||||
|
|
||||||
export const distance = (point1: Point, point2: Point): number =>
|
|
||||||
GA.norm(join(point1, point2));
|
|
||||||
|
|
||||||
export const distanceToLine = (point: Point, line: Line): number =>
|
|
||||||
GA.joinScalar(point, line);
|
|
|
@ -1,41 +0,0 @@
|
||||||
import * as GA from "./ga";
|
|
||||||
import type { Line, Direction, Point, Transform } from "./ga";
|
|
||||||
import * as GADirection from "./gadirections";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: docs
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const rotation = (pivot: Point, angle: number): Transform =>
|
|
||||||
GA.add(GA.mul(pivot, Math.sin(angle / 2)), Math.cos(angle / 2));
|
|
||||||
|
|
||||||
export const translation = (direction: Direction): Transform => [
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
-(0.5 * direction[5]),
|
|
||||||
0.5 * direction[4],
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const translationOrthogonal = (
|
|
||||||
direction: Direction,
|
|
||||||
distance: number,
|
|
||||||
): Transform => {
|
|
||||||
const scale = 0.5 * distance;
|
|
||||||
return [1, 0, 0, 0, scale * direction[4], scale * direction[5], 0, 0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const translationAlong = (line: Line, distance: number): Transform =>
|
|
||||||
GA.add(GA.mul(GADirection.orthogonalToLine(line), 0.5 * distance), 1);
|
|
||||||
|
|
||||||
export const compose = (motor1: Transform, motor2: Transform): Transform =>
|
|
||||||
GA.mul(motor2, motor1);
|
|
||||||
|
|
||||||
export const apply = (
|
|
||||||
motor: Transform,
|
|
||||||
nvector: Point | Direction | Line,
|
|
||||||
): Point | Direction | Line =>
|
|
||||||
GA.normalized(GA.mul(GA.mul(motor, nvector), GA.reverse(motor)));
|
|
Loading…
Add table
Add a link
Reference in a new issue