import { Bounds, Entity } from '../tree/Entity'; import { IRenderer } from '../renderer/IRenderer'; /** One piecewise-cubic segment: x(t) and y(t) as `[a,b,c,d]` polynomial coefficients. */ export interface SplineSegment { start_t: number; end_t: number; x_poly: number[]; y_poly: number[]; } /** * Color of a spline equation: an `[r,g,b]` triple in `0..1`, a linear-gradient * descriptor, or `null` (use the entity's default color). */ export type SplineColor = [number, number, number] | { stops: [number, [number, number, number]][]; start_pos: [number, number]; end_pos: [number, number]; } | null; /** A single curve (one stroke color) made of consecutive {@link SplineSegment}s. */ export interface SplineEquation { color_rgb: SplineColor; data: SplineSegment[]; } /** The native vectomancy `Spline` document. */ export interface SplineDocument { type: 'Spline' | 'Polyline'; equations?: SplineEquation[]; paths?: { color_rgb: SplineColor; data: { x: number; y: number; }[]; }[]; bounding_box?: [number, number, number, number]; } /** Construction options for {@link SplineEntity}. */ export interface SplineOptions { /** Stroke width in local units. Default `2`. */ lineWidth?: number; /** Bake to an OffscreenCanvas once and `drawImage` each frame. Default `true`. */ cache?: boolean; /** Color used when an equation's `color_rgb` is `null`. Default `#e2e8f0`. */ defaultColor?: string; /** * Hit-test strategy: * - `'curve'` (default): precise — a point hits only within `lineWidth/2 + * hitTolerance` of an actual curve. * - `'aabb'`: coarse — anywhere in the bounding box hits. */ hitTest?: 'curve' | 'aabb'; /** Extra pick padding (local units) added to `lineWidth/2` in `'curve'` mode. Default `0`. */ hitTolerance?: number; } /** Cubic Bézier control points produced from a {@link SplineSegment}. */ export interface BezierControlPoints { x0: number; y0: number; cp1x: number; cp1y: number; cp2x: number; cp2y: number; x3: number; y3: number; } /** * Convert one cubic-polynomial segment to Bézier control points. * * For a coefficient vector `[a,b,c,d]` describing `f(t)=a+bt+ct²+dt³` on `t∈[0,1]`, * the equivalent Bézier control values are `a`, `a+b/3`, `a+2b/3+c/3`, `a+b+c+d`. * Applied independently to the x and y polynomials. * * @param seg - The polynomial segment. * @returns The cubic Bézier control points. */ export declare function polySegmentToBezier(seg: SplineSegment): BezierControlPoints; /** * Renders a native vectomancy `Spline` document (piecewise-cubic curves) to canvas. * * Bounds come from the document's `bounding_box` (or are computed from segment * endpoints), so the entity participates in {@link Scene} viewport culling. By * default the curves are baked once into an `OffscreenCanvas` and blitted each * frame; without `OffscreenCanvas` it strokes the Bézier paths per frame. * * @example * const doc = await loadSpline('/ast/logo.json'); * scene.add(new SplineEntity(doc).setPosition(100, 100)); */ export declare class SplineEntity extends Entity { private _doc; private _lineWidth; defaultColor: string; hitTolerance: number; private cache; private hitMode; private bounds; private offscreen; private baked; /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */ private bakedWidth; private bakedHeight; /** * Device-pixel ratio the bitmap was rasterized at. Tracked so a DPR change * (browser zoom, a monitor move, a renderer whose clamp changed) re-bakes * instead of blitting a bitmap at the wrong device density forever. */ private bakedDPR; /** * Gradient strokes can't be baked to a solid-color bitmap; they render * per-frame. Derived from `_doc`, so it is NOT readonly — assigning a new * document has to recompute it (see the `doc` setter). */ private containsGradient; /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */ private polylines; /** * The spline document to render. Assigning a new document invalidates all * caches (baked canvas, flattened polylines, bounds). */ get doc(): SplineDocument; set doc(value: SplineDocument); /** * Stroke width in local units. Assigning a new value invalidates the baked * canvas and hit-test polylines so visual and hit geometry stay consistent. */ get lineWidth(): number; set lineWidth(value: number); /** * When `true`, the renderer draws a rounded-rect outline of the entity's * local bounds after painting the curves. Useful for drag feedback and * debugging hit areas. Defaults to `false`. */ showBounds: boolean; constructor(doc: SplineDocument, opts?: SplineOptions); /** * Whether any stroke in the current document is a gradient. Shared by the * constructor and the `doc` setter so both stay in agreement — the flag picks * the render path, so a stale value renders a gradient as a flat color. */ private computeContainsGradient; /** Clear cached baked canvas and hit-test polylines. */ private invalidateCache; private computeBounds; /** @inheritdoc */ getBounds(): Bounds; /** * AABB hit-test against the document bounds in world space. * * Curve-accurate hit-testing can be layered on later via {@link hitTestCurve}; * this method already calls it as a refinement when it is overridden. */ isPointInside(globalX: number, globalY: number): boolean; /** * Curve-accurate refinement of {@link isPointInside}: hit only when the local * point lies within `lineWidth/2 + hitTolerance` of an actual curve. * * Returns `null` in `hitTest: 'aabb'` mode (keep the bounding-box result). * Curves are flattened to polylines once and cached. Override for custom logic. * * @param localX - X in the entity's local space. * @param localY - Y in the entity's local space. * @returns `true`/`false`, or `null` to keep the AABB result. */ protected hitTestCurve(localX: number, localY: number): boolean | null; /** Flatten every Bézier segment into a sampled polyline once, then cache. */ private getPolylines; private resolveColor; private strokeEquations; /** Bake all equations into an OffscreenCanvas once (when available). */ private bake; render(r: IRenderer): void; } /** * Fetch and parse a vectomancy `Spline` JSON document (browser only). * * @param url - URL of the `.json` spline document. * @returns The parsed {@link SplineDocument}. */ export declare function loadSpline(url: string): Promise;