UNPKG

@js-draw/math

Version:
269 lines (268 loc) 10.5 kB
import LineSegment2 from './LineSegment2'; import Mat33 from '../Mat33'; import Rect2 from './Rect2'; import { Point2 } from '../Vec2'; import Parameterized2DShape from './Parameterized2DShape'; /** Identifiers for different path commands. These commands can make up a {@link Path}. */ export declare enum PathCommandType { LineTo = 0, MoveTo = 1, CubicBezierTo = 2, QuadraticBezierTo = 3 } export interface CubicBezierPathCommand { kind: PathCommandType.CubicBezierTo; controlPoint1: Point2; controlPoint2: Point2; endPoint: Point2; } export interface QuadraticBezierPathCommand { kind: PathCommandType.QuadraticBezierTo; controlPoint: Point2; endPoint: Point2; } export interface LinePathCommand { kind: PathCommandType.LineTo; point: Point2; } export interface MoveToPathCommand { kind: PathCommandType.MoveTo; point: Point2; } export type PathCommand = CubicBezierPathCommand | QuadraticBezierPathCommand | MoveToPathCommand | LinePathCommand; export interface IntersectionResult { curve: Parameterized2DShape; curveIndex: number; /** Parameter value for the closest point **on** the path to the intersection. @internal */ parameterValue: number; /** Point at which the intersection occured. */ point: Point2; } /** Options for {@link Path.splitNear} and {@link Path.splitAt} */ export interface PathSplitOptions { /** * Allows mapping points on newly added segments. This is useful, for example, * to round points to prevent long decimals when later saving. */ mapNewPoint?: (point: Point2) => Point2; } /** * Allows indexing a particular part of a path. * * @see {@link Path.at} {@link Path.tangentAt} */ export interface CurveIndexRecord { curveIndex: number; parameterValue: number; } /** Returns a positive number if `a` comes after `b`, 0 if equal, and negative otherwise. */ export declare const compareCurveIndices: (a: CurveIndexRecord, b: CurveIndexRecord) => number; /** * Returns a version of `index` with its parameter value incremented by `stepBy` * (which can be either positive or negative). */ export declare const stepCurveIndexBy: (index: CurveIndexRecord, stepBy: number) => CurveIndexRecord; /** * Represents a union of lines and curves. * * To create a path from a string, see {@link fromString}. * * @example * ```ts,runnable,console * import {Path, Mat33, Vec2, LineSegment2} from '@js-draw/math'; * * // Creates a path from an SVG path string. * // In this case, * // 1. Move to (0,0) * // 2. Line to (100,0) * const path = Path.fromString('M0,0 L100,0'); * * // Logs the distance from (10,0) to the curve 1 unit * // away from path. This curve forms a stroke with the path at * // its center. * const strokeRadius = 1; * console.log(path.signedDistance(Vec2.of(10,0), strokeRadius)); * * // Log a version of the path that's scaled by a factor of 4. * console.log(path.transformedBy(Mat33.scaling2D(4)).toString()); * * // Log all intersections of a stroked version of the path with * // a vertical line segment. * // (Try removing the `strokeRadius` parameter). * const segment = new LineSegment2(Vec2.of(5, -100), Vec2.of(5, 100)); * console.log(path.intersection(segment, strokeRadius).map(i => i.point)); * ``` */ export declare class Path { readonly startPoint: Point2; /** * A rough estimate of the bounding box of the path. * A slight overestimate. * See {@link getExactBBox} */ readonly bbox: Rect2; /** The individual shapes that make up this path. */ readonly parts: Readonly<PathCommand>[]; /** * Creates a new `Path` that starts at `startPoint` and is made up of the path commands, * `parts`. * * See also {@link fromString} */ constructor(startPoint: Point2, parts: Readonly<PathCommand>[]); /** * Computes and returns the full bounding box for this path. * * If a slight over-estimate of a path's bounding box is sufficient, use * {@link bbox} instead. */ getExactBBox(): Rect2; private cachedGeometry; get geometry(): Parameterized2DShape[]; /** * Iterates through the start/end points of each component in this path. * * If a start point is equivalent to the end point of the previous segment, * the point is **not** emitted twice. */ startEndPoints(): Generator<import("../Vec3").Vec3, undefined, unknown>; private cachedPolylineApproximation; polylineApproximation(): LineSegment2[]; static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2; /** * Returns the signed distance between `point` and a curve `strokeRadius` units * away from this path. * * This returns the **signed distance**, which means that points inside this shape * have their distance negated. For example, * ```ts,runnable,console * import {Path, Vec2} from '@js-draw/math'; * console.log(Path.fromString('m0,0 L100,0').signedDistance(Vec2.zero, 1)); * ``` * would print `-1` because (0,0) is on `m0,0 L100,0` and thus one unit away from its boundary. * * **Note**: `strokeRadius = strokeWidth / 2` */ signedDistance(point: Point2, strokeRadius: number): number; /** * Let `S` be a closed path a distance `strokeRadius` from this path. * * @returns Approximate intersections of `line` with `S` using ray marching, starting from * both end points of `line` and each point in `additionalRaymarchStartPoints`. */ private raymarchIntersectionWith; /** * Returns a list of intersections with this path. If `strokeRadius` is given, * intersections are approximated with the surface `strokeRadius` away from this. * * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value. * * **Note**: `strokeRadius` is half of a stroke's width. */ intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[]; /** * @returns the nearest point on this path to the given `point`. */ nearestPointTo(point: Point2): IntersectionResult; at(index: CurveIndexRecord): import("../Vec3").Vec3; tangentAt(index: CurveIndexRecord): import("../Vec3").Vec3; /** Splits this path in two near the given `point`. */ splitNear(point: Point2, options?: PathSplitOptions): [Path] | [Path, Path]; /** * Returns a copy of this path with `deleteFrom` until `deleteUntil` replaced with `insert`. * * This method is analogous to {@link Array.toSpliced}. */ spliced(deleteFrom: CurveIndexRecord, deleteTo: CurveIndexRecord, insert: Path | undefined, options?: PathSplitOptions): Path; splitAt(at: CurveIndexRecord, options?: PathSplitOptions): [Path] | [Path, Path]; splitAt(at: CurveIndexRecord[], options?: PathSplitOptions): Path[]; /** * Replaces all `MoveTo` commands with `LineTo` commands and connects the end point of this * path to the start point. */ asClosed(): Path; private static mapPathCommand; mapPoints(mapping: (point: Point2) => Point2): Path; transformedBy(affineTransfm: Mat33): Path; /** * @internal -- TODO: This method may have incorrect output in some cases. */ closedContainsPoint(point: Point2): boolean; /** * @returns `true` if this path (interpreted as a closed path) contains the given rectangle. */ closedContainsRect(rect: Rect2): boolean; union(other: Path | PathCommand[] | null, options?: { allowReverse?: boolean; }): Path; /** * @returns a version of this path with the direction reversed. * * Example: * ```ts,runnable,console * import {Path} from '@js-draw/math'; * console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0 * ``` */ reversed(): Path; /** Computes and returns the end point of this path */ getEndPoint(): import("../Vec3").Vec3; /** * Like {@link closedRoughlyIntersects} except takes stroke width into account. * * This is intended to be a very fast and rough approximation. Use {@link intersection} * and {@link signedDistance} for more accurate (but much slower) intersection calculations. * * **Note**: Unlike other methods, this accepts `strokeWidth` (and not `strokeRadius`). * * `strokeRadius` is half of `strokeWidth`. */ roughlyIntersects(rect: Rect2, strokeWidth?: number): boolean; /** * Treats this as a closed path and returns true if part of `rect` is *roughly* within * this path's interior. * * **Note**: Assumes that this is a closed, non-self-intersecting path. */ closedRoughlyIntersects(rect: Rect2): boolean; /** @returns true if all points on this are equivalent to the points on `other` */ eq(other: Path, tolerance?: number): boolean; /** * Returns a path that outlines `rect`. * * If `lineWidth` is given, the resultant path traces a `lineWidth` thick * border around `rect`. Otherwise, the resultant path is just the border * of `rect`. */ static fromRect(rect: Rect2, lineWidth?: number | null): Path; private cachedStringVersion; /** * Convert to an [SVG path representation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths). * * If `useNonAbsCommands` is given, relative path commands (e.g. `l10,0`) are to be used instead of * absolute commands (e.g. `L10,0`). * * See also {@link fromString}. */ toString(useNonAbsCommands?: boolean, ignoreCache?: boolean): string; serialize(): string; static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string; /** * Create a `Path` from a subset of the SVG path specification. * * Currently, this does not support elliptical arcs or `s` and `t` command * shorthands. See https://github.com/personalizedrefrigerator/js-draw/pull/19. * * @example * ```ts,runnable,console * import { Path } from '@js-draw/math'; * * const path = Path.fromString('m0,0l100,100'); * console.log(path.toString(true)); // true: Prefer relative to absolute path commands * ``` */ static fromString(pathString: string): Path; static fromConvexHullOf(points: Point2[]): Path; static empty: Path; } export default Path;