import * as THREE from "three"; import type { AnnotationVertex } from "./types"; import { MeshGraph } from "./MeshGraph"; /** * State machine for mode B (geodesic): click anchors one by one, find the shortest path between * adjacent anchors with MeshGraph, and stitch them into a polyline that hugs the surface. * Enter finishes and can close the loop (computing one more segment between first and last). */ export declare class GeodesicContour { private graph; private mesh; private anchors; private segments; private history; constructor(graph: MeshGraph, mesh: THREE.Mesh); /** Save a snapshot before any anchor change (the vertex-index array is tiny, so snapshot cost is negligible). */ private snapshot; /** Given a local hit point, snap to the nearest vertex and compute the path to the previous anchor. */ addAnchor(localHitPoint: THREE.Vector3): void; /** Move the anchor at `index` to the vertex nearest `localHitPoint`, rebuilding adjacent segments live. */ moveAnchorTo(index: number, localHitPoint: THREE.Vector3): void; /** Anchor vertex indices (persisted on a committed geodesic so it can be re-opened for editing). */ getAnchorIndices(): number[]; /** Rebuild an editable contour from stored anchor vertex indices (re-opening a committed geodesic). */ static fromAnchors(graph: MeshGraph, mesh: THREE.Mesh, indices: number[]): GeodesicContour; /** Insert a new anchor (snapped to the nearest vertex) at position `index`, rebuilding segments. */ insertAnchorAt(index: number, localHitPoint: THREE.Vector3): void; /** * Which anchor interval's surface path passes closest to `worldPoint` — i.e. the edge the user * clicked on, so a new anchor can be inserted there. Returns the interval index (insert after that * anchor, i.e. at index+1), or -1 when there are fewer than 2 anchors or the click is farther than * `maxDist` (world units) from every edge. For a closed contour the trailing closing edge * (last→first) is considered and maps to appending at the end. */ nearestInsertIndex(worldPoint: THREE.Vector3, matrixWorld: THREE.Matrix4, closed: boolean, maxDist: number): number; /** Remove the anchor at the given index and recompute adjacent segments (allows canceling any middle point). */ removeAnchorAt(index: number): void; /** Whether there is still an editable change to undo (add/remove point). */ canUndo(): boolean; /** Undo the last anchor edit (add → remove it; remove → restore it). Returns false when no history. */ undoEdit(): boolean; /** Recompute all adjacent-segment paths from the current anchor sequence. */ private rebuildSegments; /** Local vertex of each anchor (used to draw visible anchor markers). */ getAnchorLocals(): AnnotationVertex[]; get anchorCount(): number; /** Stitch all path vertices (world) + normals into a polyline; when closed, add one first-to-last segment. */ buildVertices(closed: boolean): AnnotationVertex[]; /** * Relax the stitched shortest-path polyline so it reads as a smooth curve instead of the raw * voxel staircase, WITHOUT changing the point count (keeps editing/index mapping simple) and * WITHOUT moving the anchors (they stay exact control points). A few Laplacian passes pull each * interior point toward the midpoint of its neighbours; anchors and (for open paths) the two * endpoints are pinned. Normals are carried over from each point's original graph vertex — the * points only shift slightly and the line already floats above the surface by `epsilon`, so the * approximation is invisible and avoids an O(V) nearest-vertex scan per point on every redraw. */ private smoothPath; }