From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001 From: kj_sh604 Date: Sun, 15 Mar 2026 16:19:35 -0400 Subject: refactor: packages/ --- packages/utils/collision.ts | 136 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 packages/utils/collision.ts (limited to 'packages/utils/collision.ts') diff --git a/packages/utils/collision.ts b/packages/utils/collision.ts new file mode 100644 index 0000000..fb48f4e --- /dev/null +++ b/packages/utils/collision.ts @@ -0,0 +1,136 @@ +import type { Polycurve, Polyline } from "./geometry/shape"; +import { + pointInEllipse, + pointOnEllipse, + type GeometricShape, +} from "./geometry/shape"; +import type { Curve } from "@excalidraw/math"; +import { + lineSegment, + pointFrom, + polygonIncludesPoint, + pointOnLineSegment, + pointOnPolygon, + polygonFromPoints, + type GlobalPoint, + type LocalPoint, + type Polygon, +} from "@excalidraw/math"; + +// check if the given point is considered on the given shape's border +export const isPointOnShape = ( + point: Point, + shape: GeometricShape, + tolerance = 0, +) => { + // get the distance from the given point to the given element + // check if the distance is within the given epsilon range + switch (shape.type) { + case "polygon": + return pointOnPolygon(point, shape.data, tolerance); + case "ellipse": + return pointOnEllipse(point, shape.data, tolerance); + case "line": + return pointOnLineSegment(point, shape.data, tolerance); + case "polyline": + return pointOnPolyline(point, shape.data, tolerance); + case "curve": + return pointOnCurve(point, shape.data, tolerance); + case "polycurve": + return pointOnPolycurve(point, shape.data, tolerance); + default: + throw Error(`shape ${shape} is not implemented`); + } +}; + +// check if the given point is considered inside the element's border +export const isPointInShape = ( + point: Point, + shape: GeometricShape, +) => { + switch (shape.type) { + case "polygon": + return polygonIncludesPoint(point, shape.data); + case "line": + return false; + case "curve": + return false; + case "ellipse": + return pointInEllipse(point, shape.data); + case "polyline": { + const polygon = polygonFromPoints(shape.data.flat()); + return polygonIncludesPoint(point, polygon); + } + case "polycurve": { + return false; + } + default: + throw Error(`shape ${shape} is not implemented`); + } +}; + +// check if the given element is in the given bounds +export const isPointInBounds = ( + point: Point, + bounds: Polygon, +) => { + return polygonIncludesPoint(point, bounds); +}; + +const pointOnPolycurve = ( + point: Point, + polycurve: Polycurve, + tolerance: number, +) => { + return polycurve.some((curve) => pointOnCurve(point, curve, tolerance)); +}; + +const cubicBezierEquation = ( + curve: Curve, +) => { + const [p0, p1, p2, p3] = curve; + // B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3 + return (t: number, idx: number) => + Math.pow(1 - t, 3) * p3[idx] + + 3 * t * Math.pow(1 - t, 2) * p2[idx] + + 3 * Math.pow(t, 2) * (1 - t) * p1[idx] + + p0[idx] * Math.pow(t, 3); +}; + +const polyLineFromCurve = ( + curve: Curve, + segments = 10, +): Polyline => { + const equation = cubicBezierEquation(curve); + let startingPoint = [equation(0, 0), equation(0, 1)] as Point; + const lineSegments: Polyline = []; + let t = 0; + const increment = 1 / segments; + + for (let i = 0; i < segments; i++) { + t += increment; + if (t <= 1) { + const nextPoint: Point = pointFrom(equation(t, 0), equation(t, 1)); + lineSegments.push(lineSegment(startingPoint, nextPoint)); + startingPoint = nextPoint; + } + } + + return lineSegments; +}; + +export const pointOnCurve = ( + point: Point, + curve: Curve, + threshold: number, +) => { + return pointOnPolyline(point, polyLineFromCurve(curve), threshold); +}; + +export const pointOnPolyline = ( + point: Point, + polyline: Polyline, + threshold = 10e-5, +) => { + return polyline.some((line) => pointOnLineSegment(point, line, threshold)); +}; -- cgit v1.2.3