mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
ellipse and arc refactors
This commit is contained in:
parent
3efa8735ef
commit
392dd5b0b8
9 changed files with 125 additions and 94 deletions
|
@ -42,8 +42,6 @@ const calculateTranslation = (
|
||||||
maxY: selectionBounds[3],
|
maxY: selectionBounds[3],
|
||||||
midX: (selectionBounds[0] + selectionBounds[2]) / 2,
|
midX: (selectionBounds[0] + selectionBounds[2]) / 2,
|
||||||
midY: (selectionBounds[1] + selectionBounds[3]) / 2,
|
midY: (selectionBounds[1] + selectionBounds[3]) / 2,
|
||||||
width: selectionBounds[2] - selectionBounds[0],
|
|
||||||
height: selectionBounds[3] - selectionBounds[1],
|
|
||||||
};
|
};
|
||||||
const groupBounds = getCommonBounds(group);
|
const groupBounds = getCommonBounds(group);
|
||||||
const groupBoundingBox = {
|
const groupBoundingBox = {
|
||||||
|
@ -53,8 +51,6 @@ const calculateTranslation = (
|
||||||
maxY: groupBounds[3],
|
maxY: groupBounds[3],
|
||||||
midX: (groupBounds[0] + groupBounds[2]) / 2,
|
midX: (groupBounds[0] + groupBounds[2]) / 2,
|
||||||
midY: (groupBounds[1] + groupBounds[3]) / 2,
|
midY: (groupBounds[1] + groupBounds[3]) / 2,
|
||||||
width: groupBounds[2] - groupBounds[0],
|
|
||||||
height: groupBounds[3] - groupBounds[1],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
|
const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { getBoundTextShape } from "../shapes";
|
import { getBoundTextShape } from "../shapes";
|
||||||
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
|
import type { GlobalPoint, Polygon } from "../../math";
|
||||||
import { pathIsALoop, isPointWithinBounds, point } from "../../math";
|
import { pathIsALoop, isPointWithinBounds, point } from "../../math";
|
||||||
import { LINE_CONFIRM_THRESHOLD } from "../constants";
|
import { LINE_CONFIRM_THRESHOLD } from "../constants";
|
||||||
|
|
||||||
|
@ -48,10 +48,10 @@ export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||||
return isDraggableFromInside || isImageElement(element);
|
return isDraggableFromInside || isImageElement(element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HitTestArgs<Point extends GlobalPoint | LocalPoint> = {
|
export type HitTestArgs = {
|
||||||
sceneCoords: Point;
|
sceneCoords: GlobalPoint;
|
||||||
element: ExcalidrawElement;
|
element: ExcalidrawElement;
|
||||||
shape: GeometricShape<Point>;
|
shape: GeometricShape<GlobalPoint>;
|
||||||
threshold?: number;
|
threshold?: number;
|
||||||
frameNameBound?: FrameNameBounds | null;
|
frameNameBound?: FrameNameBounds | null;
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,7 @@ export const hitElementItself = ({
|
||||||
shape,
|
shape,
|
||||||
threshold = 10,
|
threshold = 10,
|
||||||
frameNameBound = null,
|
frameNameBound = null,
|
||||||
}: HitTestArgs<GlobalPoint>) => {
|
}: HitTestArgs) => {
|
||||||
let hit = shouldTestInside(element)
|
let hit = shouldTestInside(element)
|
||||||
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
||||||
// we would need `onShape` as well to include the "borders"
|
// we would need `onShape` as well to include the "borders"
|
||||||
|
@ -97,7 +97,7 @@ export const hitElementBoundingBox = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hitElementBoundingBoxOnly = (
|
export const hitElementBoundingBoxOnly = (
|
||||||
hitArgs: HitTestArgs<GlobalPoint>,
|
hitArgs: HitTestArgs,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,7 +6,7 @@ describe("point on arc", () => {
|
||||||
it("should detect point on simple arc", () => {
|
it("should detect point on simple arc", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
isPointOnSymmetricArc(
|
||||||
arc(1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(0.92291667, 0.385),
|
point(0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
@ -14,7 +14,7 @@ describe("point on arc", () => {
|
||||||
it("should not detect point outside of a simple arc", () => {
|
it("should not detect point outside of a simple arc", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
isPointOnSymmetricArc(
|
||||||
arc(1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(-0.92291667, 0.385),
|
point(-0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
@ -22,7 +22,7 @@ describe("point on arc", () => {
|
||||||
it("should not detect point with good angle but incorrect radius", () => {
|
it("should not detect point with good angle but incorrect radius", () => {
|
||||||
expect(
|
expect(
|
||||||
isPointOnSymmetricArc(
|
isPointOnSymmetricArc(
|
||||||
arc(1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
arc(point(0, 0), 1, radians(-Math.PI / 4), radians(Math.PI / 4)),
|
||||||
point(-0.5, 0.5),
|
point(-0.5, 0.5),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { cartesian2Polar } from "./angle";
|
import { cartesian2Polar, radians } from "./angle";
|
||||||
import type { GenericPoint, Radians, SymmetricArc } from "./types";
|
import { ellipse, interceptPointsOfLineAndEllipse } from "./ellipse";
|
||||||
|
import { point } from "./point";
|
||||||
|
import type { GenericPoint, LineSegment, Radians, SymmetricArc } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,8 +14,13 @@ import { PRECISION } from "./utils";
|
||||||
* @param endAngle The end angle with 0 radians being the "northest" point
|
* @param endAngle The end angle with 0 radians being the "northest" point
|
||||||
* @returns The constructed symmetric arc
|
* @returns The constructed symmetric arc
|
||||||
*/
|
*/
|
||||||
export function arc(radius: number, startAngle: Radians, endAngle: Radians) {
|
export function arc<Point extends GenericPoint>(
|
||||||
return { radius, startAngle, endAngle } as SymmetricArc;
|
center: Point,
|
||||||
|
radius: number,
|
||||||
|
startAngle: Radians,
|
||||||
|
endAngle: Radians,
|
||||||
|
) {
|
||||||
|
return { center, radius, startAngle, endAngle } as SymmetricArc<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,10 +28,12 @@ export function arc(radius: number, startAngle: Radians, endAngle: Radians) {
|
||||||
* is part of a circle contour centered on 0, 0.
|
* is part of a circle contour centered on 0, 0.
|
||||||
*/
|
*/
|
||||||
export function isPointOnSymmetricArc<P extends GenericPoint>(
|
export function isPointOnSymmetricArc<P extends GenericPoint>(
|
||||||
{ radius: arcRadius, startAngle, endAngle }: SymmetricArc,
|
{ center, radius: arcRadius, startAngle, endAngle }: SymmetricArc<P>,
|
||||||
point: P,
|
p: P,
|
||||||
): boolean {
|
): boolean {
|
||||||
const [radius, angle] = cartesian2Polar(point);
|
const [radius, angle] = cartesian2Polar(
|
||||||
|
point(p[0] - center[0], p[1] - center[1]),
|
||||||
|
);
|
||||||
|
|
||||||
return startAngle < endAngle
|
return startAngle < endAngle
|
||||||
? Math.abs(radius - arcRadius) < PRECISION &&
|
? Math.abs(radius - arcRadius) < PRECISION &&
|
||||||
|
@ -32,3 +41,27 @@ export function isPointOnSymmetricArc<P extends GenericPoint>(
|
||||||
endAngle >= angle
|
endAngle >= angle
|
||||||
: startAngle <= angle || endAngle >= angle;
|
: startAngle <= angle || endAngle >= angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the intersection point(s) of a line segment represented by a start
|
||||||
|
* point and end point and a symmetric arc.
|
||||||
|
*/
|
||||||
|
export function interceptOfSymmetricArcAndSegment<Point extends GenericPoint>(
|
||||||
|
a: Readonly<SymmetricArc<Point>>,
|
||||||
|
l: Readonly<LineSegment<Point>>,
|
||||||
|
): Point[] {
|
||||||
|
return interceptPointsOfLineAndEllipse(
|
||||||
|
ellipse(a.center, radians(0), a.radius, a.radius),
|
||||||
|
l,
|
||||||
|
).filter((candidate) => {
|
||||||
|
const [candidateRadius, candidateAngle] = cartesian2Polar(
|
||||||
|
point(candidate[0] - a.center[0], candidate[1] - a.center[1]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return a.startAngle < a.endAngle
|
||||||
|
? Math.abs(a.radius - candidateRadius) < 0.0000001 &&
|
||||||
|
a.startAngle <= candidateAngle &&
|
||||||
|
a.endAngle >= candidateAngle
|
||||||
|
: a.startAngle <= candidateAngle || a.endAngle >= candidateAngle;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,45 +1,54 @@
|
||||||
import { radians } from "./angle";
|
import { radians } from "./angle";
|
||||||
import { pointInEllipse, pointOnEllipse } from "./ellipse";
|
import {
|
||||||
|
ellipse,
|
||||||
|
interceptPointsOfLineAndEllipse,
|
||||||
|
pointInEllipse,
|
||||||
|
pointOnEllipse,
|
||||||
|
} from "./ellipse";
|
||||||
import { point } from "./point";
|
import { point } from "./point";
|
||||||
|
import { lineSegment } from "./segment";
|
||||||
import type { Ellipse, GlobalPoint } from "./types";
|
import type { Ellipse, GlobalPoint } from "./types";
|
||||||
|
|
||||||
describe("point and ellipse", () => {
|
describe("point and ellipse", () => {
|
||||||
const ellipse: Ellipse<GlobalPoint> = {
|
const target: Ellipse<GlobalPoint> = ellipse(point(0, 0), radians(0), 2, 1);
|
||||||
center: point(0, 0),
|
|
||||||
angle: radians(0),
|
|
||||||
halfWidth: 2,
|
|
||||||
halfHeight: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
it("point on ellipse", () => {
|
it("point on ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
||||||
expect(pointOnEllipse(p, ellipse)).toBe(true);
|
expect(pointOnEllipse(p, target)).toBe(true);
|
||||||
});
|
});
|
||||||
expect(pointOnEllipse(point(-1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(point(-1.4, 0.7), target, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(-1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(point(-1.4, 0.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(point(1.4, 0.7), target, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(point(1.4, 0.71), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(point(1, -0.86), target, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(point(1, -0.86), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(point(-1, -0.86), target, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(-1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(point(-1, -0.86), target, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, 0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(point(-1, 0.8), target)).toBe(false);
|
||||||
expect(pointOnEllipse(point(1, -0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(point(1, -0.8), target)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point in ellipse", () => {
|
it("point in ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
||||||
expect(pointInEllipse(p, ellipse)).toBe(true);
|
expect(pointInEllipse(p, target)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(pointInEllipse(point(-1, 0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(point(-1, 0.8), target)).toBe(true);
|
||||||
expect(pointInEllipse(point(1, -0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(point(1, -0.8), target)).toBe(true);
|
||||||
|
|
||||||
expect(pointInEllipse(point(-1, 1), ellipse)).toBe(false);
|
expect(pointInEllipse(point(-1, 1), target)).toBe(false);
|
||||||
expect(pointInEllipse(point(-1.4, 0.8), ellipse)).toBe(false);
|
expect(pointInEllipse(point(-1.4, 0.8), target)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("line and ellipse", () => {
|
||||||
|
it("detects outside segment", () => {
|
||||||
|
const l = lineSegment<GlobalPoint>(point(-100, 0), point(-10, 0));
|
||||||
|
const e = ellipse(point(0, 0), radians(0), 2, 2);
|
||||||
|
expect(interceptPointsOfLineAndEllipse(e, l).length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
} from "./point";
|
} from "./point";
|
||||||
import type { Ellipse, GenericPoint, Line } from "./types";
|
import type { Ellipse, GenericPoint, LineSegment, Radians } from "./types";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
import {
|
import {
|
||||||
vector,
|
vector,
|
||||||
|
@ -16,6 +16,20 @@ import {
|
||||||
vectorScale,
|
vectorScale,
|
||||||
} from "./vector";
|
} from "./vector";
|
||||||
|
|
||||||
|
export function ellipse<Point extends GenericPoint>(
|
||||||
|
center: Point,
|
||||||
|
angle: Radians,
|
||||||
|
halfWidth: number,
|
||||||
|
halfHeight: number,
|
||||||
|
): Ellipse<Point> {
|
||||||
|
return {
|
||||||
|
center,
|
||||||
|
angle,
|
||||||
|
halfWidth,
|
||||||
|
halfHeight,
|
||||||
|
} as Ellipse<Point>;
|
||||||
|
}
|
||||||
|
|
||||||
export const pointInEllipse = <Point extends GenericPoint>(
|
export const pointInEllipse = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
|
@ -162,19 +176,19 @@ const distanceToEllipse = <Point extends GenericPoint>(
|
||||||
* ellipse.
|
* ellipse.
|
||||||
*/
|
*/
|
||||||
export function interceptPointsOfLineAndEllipse<Point extends GenericPoint>(
|
export function interceptPointsOfLineAndEllipse<Point extends GenericPoint>(
|
||||||
ellipse: Readonly<Ellipse<Point>>,
|
e: Readonly<Ellipse<Point>>,
|
||||||
l: Readonly<Line<Point>>,
|
l: Readonly<LineSegment<Point>>,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
const rx = ellipse.halfWidth;
|
const rx = e.halfWidth;
|
||||||
const ry = ellipse.halfHeight;
|
const ry = e.halfHeight;
|
||||||
const nonRotatedLine = line(
|
const nonRotatedLine = line(
|
||||||
pointRotateRads(l[0], ellipse.center, radians(-ellipse.angle)),
|
pointRotateRads(l[0], e.center, radians(-e.angle)),
|
||||||
pointRotateRads(l[1], ellipse.center, radians(-ellipse.angle)),
|
pointRotateRads(l[1], e.center, radians(-e.angle)),
|
||||||
);
|
);
|
||||||
const dir = vectorFromPoint(nonRotatedLine[1], nonRotatedLine[0]);
|
const dir = vectorFromPoint(nonRotatedLine[1], nonRotatedLine[0]);
|
||||||
const diff = vector(
|
const diff = vector(
|
||||||
nonRotatedLine[0][0] - ellipse.center[0],
|
nonRotatedLine[0][0] - e.center[0],
|
||||||
nonRotatedLine[0][1] - ellipse.center[1],
|
nonRotatedLine[0][1] - e.center[1],
|
||||||
);
|
);
|
||||||
const mDir = vector(dir[0] / (rx * rx), dir[1] / (ry * ry));
|
const mDir = vector(dir[0] / (rx * rx), dir[1] / (ry * ry));
|
||||||
const mDiff = vector(diff[0] / (rx * rx), diff[1] / (ry * ry));
|
const mDiff = vector(diff[0] / (rx * rx), diff[1] / (ry * ry));
|
||||||
|
@ -226,6 +240,6 @@ export function interceptPointsOfLineAndEllipse<Point extends GenericPoint>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return intersections.map((point) =>
|
return intersections.map((point) =>
|
||||||
pointRotateRads(point, ellipse.center, ellipse.angle),
|
pointRotateRads(point, e.center, e.angle),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,8 @@ export type Curve<Point extends GlobalPoint | LocalPoint | ViewportPoint> = [
|
||||||
* Angles are in radians and centered on 0, 0. Zero radians on a 1 radius circle
|
* Angles are in radians and centered on 0, 0. Zero radians on a 1 radius circle
|
||||||
* corresponds to (1, 0) cartesian coordinates (point), i.e. to the "right"
|
* corresponds to (1, 0) cartesian coordinates (point), i.e. to the "right"
|
||||||
*/
|
*/
|
||||||
export type SymmetricArc = {
|
export type SymmetricArc<Point extends GenericPoint> = {
|
||||||
|
center: Point;
|
||||||
radius: number;
|
radius: number;
|
||||||
startAngle: Radians;
|
startAngle: Radians;
|
||||||
endAngle: Radians;
|
endAngle: Radians;
|
||||||
|
@ -152,4 +153,6 @@ export type Ellipse<Point extends GenericPoint> = {
|
||||||
angle: Radians;
|
angle: Radians;
|
||||||
halfWidth: number;
|
halfWidth: number;
|
||||||
halfHeight: number;
|
halfHeight: number;
|
||||||
|
} & {
|
||||||
|
_brand: "excalimath_ellipse";
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Polycurve, Polyline } from "./geometry/shape";
|
import type { Polycurve, Polyline } from "./geometry/shape";
|
||||||
import { type GeometricShape } from "./geometry/shape";
|
import { type GeometricShape } from "./geometry/shape";
|
||||||
import type { Curve, ViewportPoint } from "../math";
|
import type { Curve, GenericPoint } from "../math";
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
point,
|
||||||
|
@ -8,23 +8,16 @@ import {
|
||||||
pointOnLineSegment,
|
pointOnLineSegment,
|
||||||
pointOnPolygon,
|
pointOnPolygon,
|
||||||
polygonFromPoints,
|
polygonFromPoints,
|
||||||
type GlobalPoint,
|
|
||||||
type LocalPoint,
|
|
||||||
type Polygon,
|
|
||||||
pointOnEllipse,
|
pointOnEllipse,
|
||||||
pointInEllipse,
|
pointInEllipse,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
|
|
||||||
// check if the given point is considered on the given shape's border
|
// check if the given point is considered on the given shape's border
|
||||||
export const isPointOnShape = <
|
export const isPointOnShape = <Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
point: Point,
|
point: Point,
|
||||||
shape: GeometricShape<Point>,
|
shape: GeometricShape<Point>,
|
||||||
tolerance = 0,
|
tolerance = 0,
|
||||||
) => {
|
): boolean => {
|
||||||
// get the distance from the given point to the given element
|
|
||||||
// check if the distance is within the given epsilon range
|
|
||||||
switch (shape.type) {
|
switch (shape.type) {
|
||||||
case "polygon":
|
case "polygon":
|
||||||
return pointOnPolygon(point, shape.data, tolerance);
|
return pointOnPolygon(point, shape.data, tolerance);
|
||||||
|
@ -39,12 +32,12 @@ export const isPointOnShape = <
|
||||||
case "polycurve":
|
case "polycurve":
|
||||||
return pointOnPolycurve(point, shape.data, tolerance);
|
return pointOnPolycurve(point, shape.data, tolerance);
|
||||||
default:
|
default:
|
||||||
throw Error(`shape ${shape} is not implemented`);
|
throw Error(`Shape ${shape} is not implemented`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if the given point is considered inside the element's border
|
// check if the given point is considered inside the element's border
|
||||||
export const isPointInShape = <Point extends GlobalPoint | LocalPoint>(
|
export const isPointInShape = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
shape: GeometricShape<Point>,
|
shape: GeometricShape<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -69,17 +62,7 @@ export const isPointInShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if the given element is in the given bounds
|
const pointOnPolycurve = <Point extends GenericPoint>(
|
||||||
export const isPointInBounds = <Point extends GlobalPoint | LocalPoint>(
|
|
||||||
point: Point,
|
|
||||||
bounds: Polygon<Point>,
|
|
||||||
) => {
|
|
||||||
return polygonIncludesPoint(point, bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
const pointOnPolycurve = <
|
|
||||||
Point extends LocalPoint | GlobalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
point: Point,
|
point: Point,
|
||||||
polycurve: Polycurve<Point>,
|
polycurve: Polycurve<Point>,
|
||||||
tolerance: number,
|
tolerance: number,
|
||||||
|
@ -87,9 +70,7 @@ const pointOnPolycurve = <
|
||||||
return polycurve.some((curve) => pointOnCurve(point, curve, tolerance));
|
return polycurve.some((curve) => pointOnCurve(point, curve, tolerance));
|
||||||
};
|
};
|
||||||
|
|
||||||
const cubicBezierEquation = <
|
const cubicBezierEquation = <Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
) => {
|
) => {
|
||||||
const [p0, p1, p2, p3] = curve;
|
const [p0, p1, p2, p3] = curve;
|
||||||
|
@ -101,9 +82,7 @@ const cubicBezierEquation = <
|
||||||
p0[idx] * Math.pow(t, 3);
|
p0[idx] * Math.pow(t, 3);
|
||||||
};
|
};
|
||||||
|
|
||||||
const polyLineFromCurve = <
|
const polyLineFromCurve = <Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
segments = 10,
|
segments = 10,
|
||||||
): Polyline<Point> => {
|
): Polyline<Point> => {
|
||||||
|
@ -125,9 +104,7 @@ const polyLineFromCurve = <
|
||||||
return lineSegments;
|
return lineSegments;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnCurve = <
|
export const pointOnCurve = <Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
point: Point,
|
point: Point,
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
|
@ -135,9 +112,7 @@ export const pointOnCurve = <
|
||||||
return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
|
return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnPolyline = <
|
export const pointOnPolyline = <Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint | ViewportPoint,
|
|
||||||
>(
|
|
||||||
point: Point,
|
point: Point,
|
||||||
polyline: Polyline<Point>,
|
polyline: Polyline<Point>,
|
||||||
threshold = 10e-5,
|
threshold = 10e-5,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import type {
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
import {
|
import {
|
||||||
curve,
|
curve,
|
||||||
|
ellipse,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
point,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
|
@ -178,12 +179,12 @@ export const getEllipseShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "ellipse",
|
type: "ellipse",
|
||||||
data: {
|
data: ellipse(
|
||||||
center: point(x + width / 2, y + height / 2),
|
point(x + width / 2, y + height / 2),
|
||||||
angle,
|
angle,
|
||||||
halfWidth: width / 2,
|
width / 2,
|
||||||
halfHeight: height / 2,
|
height / 2,
|
||||||
},
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue