diff --git a/packages/excalidraw/element/bounds.ts b/packages/excalidraw/element/bounds.ts index 2e6d117e41..3e52ee5f7d 100644 --- a/packages/excalidraw/element/bounds.ts +++ b/packages/excalidraw/element/bounds.ts @@ -34,6 +34,9 @@ import { ellipse, arc, radians, + cartesian2Polar, + normalizeRadians, + radiansToDegrees, } from "../../math"; import type { Mutable } from "../utility-types"; import { getCurvePathOps } from "../../utils/geometry/shape"; @@ -694,17 +697,18 @@ export const createDiamondSide = ( export const createDiamondArc = ( start: GlobalPoint, end: GlobalPoint, + c: GlobalPoint, r: number, ) => { - const c = pointFrom( - (start[0] + end[0]) / 2, - (start[1] + end[1]) / 2, + const [, startAngle] = cartesian2Polar( + pointFrom(start[0] - c[0], start[1] - c[1]), ); + const [, endAngle] = cartesian2Polar(pointFrom(end[0] - c[0], end[1] - c[1])); return arc( c, r, - radians(Math.asin((start[1] - c[1]) / r)), - radians(Math.asin((end[1] - c[1]) / r)), + normalizeRadians(startAngle), // normalizeRadians(radians(startAngle - Math.PI / 2)), + normalizeRadians(endAngle), // normalizeRadians(radians(endAngle - Math.PI / 2)), ); }; diff --git a/packages/excalidraw/element/collision.test.tsx b/packages/excalidraw/element/collision.test.tsx new file mode 100644 index 0000000000..7f1fb0688e --- /dev/null +++ b/packages/excalidraw/element/collision.test.tsx @@ -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)]); + }); +}); diff --git a/packages/excalidraw/element/collision.ts b/packages/excalidraw/element/collision.ts index c4bec4c6ee..d6c5225c55 100644 --- a/packages/excalidraw/element/collision.ts +++ b/packages/excalidraw/element/collision.ts @@ -22,11 +22,7 @@ import { isImageElement, isTextElement, } from "./typeChecks"; -import { - getBoundTextShape, - getCornerRadius, - getDiamondPoints, -} from "../shapes"; +import { getBoundTextShape, getCornerRadius } from "../shapes"; import type { Arc, GlobalPoint, Polygon } from "../../math"; import { pathIsALoop, @@ -43,6 +39,7 @@ import { pointDistanceSq, ellipse, ellipseLineIntersectionPoints, + pointsEqual, } from "../../math"; import { LINE_CONFIRM_THRESHOLD } from "../constants"; @@ -152,7 +149,7 @@ export const intersectElementWithLine = ( element: ExcalidrawElement, a: GlobalPoint, b: GlobalPoint, - offset: number, + offset: number = 0, ): GlobalPoint[] => { switch (element.type) { case "rectangle": @@ -172,7 +169,7 @@ export const intersectElementWithLine = ( } }; -export const intersectRectanguloidWithLine = ( +const intersectRectanguloidWithLine = ( element: ExcalidrawRectanguloidElement, a: GlobalPoint, b: GlobalPoint, @@ -234,8 +231,8 @@ export const intersectRectanguloidWithLine = ( arc( pointFrom(r[0][0] + roundness, r[0][1] + roundness), roundness, - radians(Math.PI), radians((3 / 4) * Math.PI), + radians(0), ), arc( pointFrom(r[1][0] - roundness, r[0][1] + roundness), @@ -261,8 +258,13 @@ export const intersectRectanguloidWithLine = ( .map((j) => pointRotateRads(j, center, element.angle)) : []; - return [...sideIntersections, ...cornerIntersections].sort( - (g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b), + return ( + [...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 * @returns */ -export const intersectDiamondWithLine = ( +const intersectDiamondWithLine = ( element: ExcalidrawDiamondElement, a: GlobalPoint, b: GlobalPoint, offset: number = 0, ): GlobalPoint[] => { - const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = - getDiamondPoints(element, offset); - const center = pointFrom( - (topX + bottomX) / 2, - (topY + bottomY) / 2, + const top = pointFrom(element.x + element.width / 2, element.y); + const right = pointFrom( + element.x + element.width, + element.y + element.height / 2, + ); + const bottom = pointFrom( + element.x + element.width / 2, + element.y + element.height, + ); + const left = pointFrom( + element.x, + element.y + element.height / 2, + ); + const center = pointFrom( + 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 // points. It's all the same distance-wise. const rotatedA = pointRotateRads(a, 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( segment(top, right), @@ -322,10 +332,42 @@ export const intersectDiamondWithLine = ( const arcs: Arc[] = element.roundness ? [ - createDiamondArc(topLeft[0], topRight[0], verticalRadius), // TOP - createDiamondArc(topRight[1], bottomRight[1], horizontalRadius), // RIGHT - createDiamondArc(bottomRight[0], bottomLeft[0], verticalRadius), // BOTTOM - createDiamondArc(bottomLeft[1], topLeft[1], horizontalRadius), // LEFT + createDiamondArc( + topLeft[0], + topRight[0], + 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 .map((p) => pointRotateRads(p, center, element.angle)); - return [...sides, ...corners].sort( - (g, h) => pointDistanceSq(g!, b) - pointDistanceSq(h!, b), + return ( + [...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 * @returns */ -export const intersectEllipseWithLine = ( +const intersectEllipseWithLine = ( element: ExcalidrawEllipseElement, a: GlobalPoint, b: GlobalPoint, diff --git a/packages/excalidraw/element/distance.ts b/packages/excalidraw/element/distance.ts index 66cbaf3c3d..5797367505 100644 --- a/packages/excalidraw/element/distance.ts +++ b/packages/excalidraw/element/distance.ts @@ -172,10 +172,30 @@ export const distanceToDiamondElement = ( const arcs = element.roundness ? [ - createDiamondArc(topLeft[0], topRight[0], verticalRadius), // TOP - createDiamondArc(topRight[1], bottomRight[1], horizontalRadius), // RIGHT - createDiamondArc(bottomRight[0], bottomLeft[0], verticalRadius), // BOTTOM - createDiamondArc(bottomLeft[1], topLeft[1], horizontalRadius), // LEFT + createDiamondArc( + topLeft[0], + topRight[0], + 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 ] : []; diff --git a/packages/math/angle.ts b/packages/math/angle.ts index 74cda1c16d..0d477a9e1a 100644 --- a/packages/math/angle.ts +++ b/packages/math/angle.ts @@ -60,7 +60,8 @@ export const normalizeRadians = (angle: Radians): Radians => { export const cartesian2Polar =

([ x, 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 diff --git a/packages/math/ga/ga.test.ts b/packages/math/ga/ga.test.ts deleted file mode 100644 index 767b5b65b6..0000000000 --- a/packages/math/ga/ga.test.ts +++ /dev/null @@ -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))); - }); - }); -}); diff --git a/packages/math/ga/ga.ts b/packages/math/ga/ga.ts deleted file mode 100644 index 271aa7ae9d..0000000000 --- a/packages/math/ga/ga.ts +++ /dev/null @@ -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; diff --git a/packages/math/ga/gadirections.ts b/packages/math/ga/gadirections.ts deleted file mode 100644 index 2f631fa6ab..0000000000 --- a/packages/math/ga/gadirections.ts +++ /dev/null @@ -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); diff --git a/packages/math/ga/galines.ts b/packages/math/ga/galines.ts deleted file mode 100644 index f5058ce690..0000000000 --- a/packages/math/ga/galines.ts +++ /dev/null @@ -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]); diff --git a/packages/math/ga/gapoints.ts b/packages/math/ga/gapoints.ts deleted file mode 100644 index 909e8ffe6b..0000000000 --- a/packages/math/ga/gapoints.ts +++ /dev/null @@ -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); diff --git a/packages/math/ga/gatransforms.ts b/packages/math/ga/gatransforms.ts deleted file mode 100644 index 2301d979ef..0000000000 --- a/packages/math/ga/gatransforms.ts +++ /dev/null @@ -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)));