import { BoxGeometry } from 'three'; import { BufferGeometry } from 'three'; import { BufferGeometryEventMap } from 'three'; import { CircleGeometry } from 'three'; import { Color } from 'three'; import { ColorRepresentation } from 'three'; import { Curve } from 'three'; import { CylinderGeometry } from 'three'; import { DataTexture } from 'three'; import { DirectionalLight } from 'three'; import { ExtrudeGeometry } from 'three'; import { GridHelper } from 'three'; import { Group } from 'three'; import { InstancedMesh } from 'three'; import { LatheGeometry } from 'three'; import { Material } from 'three'; import { Matrix4 } from 'three'; import { Mesh } from 'three'; import { MeshBasicMaterial } from 'three'; import { MeshLambertMaterial } from 'three'; import { MeshPhongMaterial } from 'three'; import { MeshPhysicalMaterial } from 'three'; import { MeshStandardMaterial } from 'three'; import { NormalBufferAttributes } from 'three'; import { Object3D } from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { Path } from 'three'; import { PerspectiveCamera } from 'three'; import { Plane } from 'three'; import { PlaneGeometry } from 'three'; import { PointLight } from 'three'; import { Quaternion } from 'three'; import { Shape } from 'three'; import { ShapeGeometry } from 'three'; import { Sprite } from 'three'; import { SpriteMaterial } from 'three'; import { Vector2 } from 'three'; import { Vector3 } from 'three'; /** * Align a BufferGeometry to a surface by adjusting its vertices. */ export declare function alignBufferGeometryToSurface(geometry: BufferGeometry, targetPositionY: number): void; /** * Align a specific instance in an InstancedMesh to a surface. */ export declare function alignInstancedMeshIndexToSurface(instancedMesh: InstancedMesh, targetPosition: Vector3, instanceIndex: number, offset?: Vector3): void; /** * Align an InstancedMesh to a surface. */ export declare function alignInstancedMeshToSurface(instancedMesh: InstancedMesh, targetPosition: Vector3, offset?: Vector3): void; /** * Align an Object3D to a surface by adjusting its position. */ export declare function alignObjectToSurface(object: Object3D, targetPosition: Vector3, offset?: Vector3): void; /** * Clone rings and cyclically align each to its aligned predecessor; the first seam stays fixed. * Unequal neighboring counts keep their order; winding is never reversed. */ export declare function alignRings(rings: Vector3[][]): Vector3[][]; /** * Aligns an array of Object3D objects (or subclasses) to a specified side * (left, right, top, bottom, front, or back) based on their world-space bounding boxes. */ export declare function alignToEdge(objects: T[], side: BoxSide): void; /** * Aligns an array of `Object3D` objects along a specified direction with optional spacing. */ export declare function alignToRow(objects: T[], direction?: Vector3, spacing?: number): void; declare function analogous({ hue, spread, saturation, lightness }: AnalogousColorOptions): ColorSampler; /** HSL coordinates in sRGB; ranges are absolute percentages, not offsets. */ export declare interface AnalogousColorOptions { /** Center hue in degrees; wraps around the color wheel. */ readonly hue: number; /** Half-width in degrees, from 0 to 180. */ readonly spread: number; readonly saturation: readonly [number, number]; readonly lightness: readonly [number, number]; } /** * A flat ring with a bore, square in section — a washer, a pipe collar, a well rim, a coin blank. * * Built as a **surface of revolution**: the four sides you see are four points of a rectangular profile spun * around `+Y`. That is why it costs so little — at 8 sides it is 64 triangles across 45 shared vertices, * where the same ring extruded from a 2D shape with a hole runs to 384 unshared vertices for no visual gain. * * Local frame: **rests on the `y = 0` plane**, occupying `+Y` up to `depth`, centered on the origin in XZ. * Ground contact, like the rest of the library — no translate needed to stand it on a floor. * * Material groups: **none** — one continuous surface, one material. * * **On shading:** the profile's corner vertices are shared between the faces that meet there, so under * *smooth* shading those edges soften. With `flatShading: true` — which this library uses throughout — normals * are computed per face and the edges are hard, which is what a washer wants. If you ever need smooth shading * elsewhere on the mesh, duplicate the corner profile points to split the rings; it costs 72 vertices instead * of 45. * * @example * ```typescript * const washer = new Mesh(new AnnulusGeometry({ radius: 0.5, holeRadius: 0.2, depth: 0.06 }), iron); * ``` */ export declare class AnnulusGeometry extends LatheGeometry { readonly radius: number; /** The bore radius actually used, after clamping inside `radius`. */ readonly holeRadius: number; readonly depth: number; readonly sides: number; constructor({ radius, holeRadius, depth, sides, rotation, }?: AnnulusGeometryOptions); } export declare interface AnnulusGeometryOptions { /** Outer radius — center to the outer wall. Defaults to `1`. */ radius?: number; /** * Radius of the bore. Defaults to `0.5`. * * Clamped to stay strictly inside `radius`, so a bore wider than the ring cannot invert the wall. A value * of `0` or less is clamped to a hair above zero rather than producing a solid disc — a solid disc is a * different shape, and {@link PolygonGeometry} says it more plainly. */ holeRadius?: number; /** Thickness, along `+Y` from the resting plane. Defaults to `0.15`. */ depth?: number; /** * Sides around the ring. Defaults to `24`. * * **This is the low-poly dial.** `24` reads as smooth, `8` is visibly faceted, and `4` is a genuine square * washer — the same construction throughout, so faceting is a parameter rather than a different shape. */ sides?: number; /** Rotation about `+Y` in radians. Defaults to `0`. Only visible on a low `sides` count. */ rotation?: number; } /** * Apothecary jar with a cork stopper — glass shell, a fitted cork lid, and an optional fill. * * A spatial factory, not a baked geometry: the glass is transparent, so shell, cork and liquid must be * SEPARATE meshes (transparency sorts per object). The cork is fitted to the jar's opening and sealed at * any depth by {@link createCorkStopper} — the same measured-seating idea as {@link FlorenceFlaskStand}. * Rests on Y=0. */ export declare class ApothecaryJar extends Group { constructor({ jar, fill, cork, corkDepth, glassMaterial, corkMaterial }?: ApothecaryJarOptions); } /** * Apothecary jar — a round, oblong glass jar with a rolled rim, corked by {@link ApothecaryJar}. * * A lathe of {@link vesselShell} over {@link apothecaryJarProfile}; the silhouette is exposed as `.profile` * for the fill and for seating a cork in the rim. Local frame: base on Y=0, opening up +Y. */ export declare class ApothecaryJarGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: ApothecaryJarGeometryOptions); } export declare interface ApothecaryJarGeometryOptions extends ApothecaryJarProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `20`. */ radialSegments?: number; } export declare interface ApothecaryJarOptions { /** Jar geometry — resize the body, neck, etc. The cork re-sizes and re-seats to the resulting rim. */ jar?: ApothecaryJarGeometryOptions; /** Optional liquid inside the jar — colour, opacity, glow, fill level. */ fill?: FillOptions; /** Cork shape — vertical cap height (`upperHeight`), plug depth (`lowerHeight`), tip radius. */ cork?: CorkGeometryOptions; /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */ corkDepth?: number; /** Jar (glass) material. A translucent default is supplied. */ glassMaterial?: MeshStandardMaterial; /** Cork material. A cork-brown default is supplied. */ corkMaterial?: MeshStandardMaterial; } /** * Apothecary jar silhouette — a round, oblong body drawn in to a short neck. Base on Y=0, ends at the rim. */ export declare function apothecaryJarProfile({ radius, baseRadius, neckRadius, height, }?: ApothecaryJarProfileOptions): Vector2[]; export declare interface ApothecaryJarProfileOptions { /** Widest body radius. Defaults to `1.5`. */ radius?: number; /** Base (foot) radius. Defaults to `0.8 ×` the body radius. */ baseRadius?: number; /** Neck (mouth) radius — where the cork seats. Defaults to `0.4 ×` the body radius. */ neckRadius?: number; /** Overall height. Defaults to `3.5`. */ height?: number; } /** * Sample a bottom-to-top ellipsoidal lathe profile centered at sphereStartY. * Hole radii truncate the ends; radii must fit within sphereRadiusX. * * ``` * const points: Vector2[] = [ * new Vector2(1, 0), * ...appendSphericalCurve( * 2, // Radius x * 2, // Radius y * 5, // Start y * 0, // Hole top radius * 1, // Hole bottom radius * 32, // Segments * ), * ]; * * const latheGeometry = new LatheGeometry(points, 32); * ``` * * ``` * const points: Vector2[] = [ * ...appendSphericalCurve( * 2, // Radius x * 2, // Radius y * 1, // Start y * 1, // Hole top radius * 0, // Hole bottom radius * 32, // Segments * ), * new Vector2(1, 5), * ]; * * const latheGeometry = new LatheGeometry(points, 32); * ``` */ export declare function appendSphericalCurve(sphereRadiusX: number, sphereRadiusY: number, sphereStartY: number, holeTopRadius?: number, holeBottomRadius?: number, segments?: number): Vector2[]; /** * A compact cultivated apple tree with a low, rounded crown. * * Six primary branches leave a short trunk, each carrying a shoulder, a tip, and two twigs — orchard form * rather than the recursive gnarl of {@link DeciduousTree}. **Deliberately independent of it:** branching rules, * foliage, and fruit all live here, because a pruned orchard tree is a different thing from a wild one, not a * reparameterization of it. * * Three draw calls at any size — merged wood, one {@link InstancedMesh} of leaf clusters tinted per instance, * and one of apples. Exposed as {@link wood}, {@link leaves}, and {@link apples}. * * Local frame: **grows from the origin**, base flat on the `y = 0` plane, occupying `+Y`. See * {@link AppleTreeOptions.baseRise}. * * **This factory owns its materials**, faithful to the scene it came from, where bark, leaf, and apple colors * are part of the asset's identity. Call {@link dispose} to release them. * * @example * ```typescript * const tree = new AppleTree({ seed: 0xa991, appleCount: 24 }); * scene.add(tree); * ``` */ export declare class AppleTree extends Group { #private; /** The merged trunk, branches, and twigs — one draw call. */ readonly wood: Mesh; /** Leaf clusters, tinted per instance. */ readonly leaves: InstancedMesh; /** Apples scattered through the crown. */ readonly apples: InstancedMesh; constructor({ seed, height, crownRadius, leafDensity, leafColors, appleCount, baseRise, }?: AppleTreeOptions); /** Release the geometries and materials this factory created. */ dispose(): void; } export declare interface AppleTreeOptions { /** Seed for the deterministic stream. Defaults to `0xa991`. */ seed?: number; /** Overall tree height, which the branch reach and rise are derived from. Defaults to `3.4`. */ height?: number; /** Horizontal reach of the crown. Defaults to `1.5`. */ crownRadius?: number; /** Fraction of crown anchors that receive leaf clusters. Defaults to `0.82`. */ leafDensity?: number; /** Overrides the built-in leaf palette. Index counts visible clusters; seeded color draws do not alter wood, foliage placement or apples. */ leafColors?: ColorSampler; /** Number of apples scattered through the crown. Defaults to `18`. */ appleCount?: number; /** * Height of the straight vertical rise before the trunk leans. Defaults to `0.25`. * * The trunk's top carries a small random offset, so without a rise the trunk tilts from the ground up and * its bottom face tilts with it, sinking the low edge below `y = 0`. One vertical segment makes the base * tangent exactly UP so the face lies flat. Set `0` to see the original tilt. */ baseRise?: number; } export declare function applySnapshot(camera: PerspectiveCamera, controls: OrbitControls | undefined, snapshot: CameraSnapshot): void; export declare interface ArchedDoorOptions { /** Width of the door. Defaults to `1.3`. */ width?: number; /** * Which jamb the door hangs from. Defaults to `"left"`. * * This places the strap hinges — and, because the door's origin is its hinge, it also decides which * edge lands on `x = 0`. See the note on the returned mesh's frame. */ hinge?: DoorHinge; /** Height of the rectangular body, up to where the arch springs. Defaults to `1.9`. */ height?: number; /** * Rise of the arch above the springing — the ellipse's VERTICAL RADIUS, in world units. Defaults to * `0.65`, which against the default `width` of `1.3` is a **perfect semicircle**. * * **`archHeight === width / 2` is the arch you want.** Equal radii make the ellipse a circle: a Roman * arch. Below that it flattens (segmental — a gatehouse); above it, it stretches tall. * * | Opening | Semicircle rise | * | --- | --- | * | `1.3` | `0.65` | * | `2.6` | `1.3` | * | `w` | `w / 2` | * * **The rise does not follow the width.** Resize the door and the arch keeps whatever rise it had, so * it quietly changes character — `width: 2.6` with the default `0.65` is a squat segmental arch, not a * bigger version of the same door. */ archHeight?: number; /** * Which arch tops the door. Defaults to `elliptical`. See {@link ArchStyle}. * * Give a door the same arch as the opening it hangs in and the two match exactly, because they draw * the same curve. A double door splits at the CROWN, so a pointed or ogee arch still parts cleanly * down the middle — each leaf just carries half the point. */ arch?: ArchStyle; /** Thickness of the slab. Defaults to `0.12`. */ thickness?: number; /** How finely the arch is tessellated — the low-poly knob. `3` is chiseled; `24` is cast. Defaults to `16`. */ curveSegments?: number; /** Number of strap hinges. Defaults to `3`. */ hinges?: number; /** How far each strap reaches across the door. Defaults to `0.85`. */ hingeLength?: number; /** Width of a strap at the pin. Defaults to `0.22`. */ hingeWidth?: number; /** How far a strap's edges bow inward. `0.5` is straight; less is forged. Defaults to `0.28`. */ hingeSweep?: number; /** The decorative shape forged onto a strap's tip. Defaults to `"spade"`. */ hingeTerminal?: "spade" | "club" | "none"; /** Rows of studs across the face. Defaults to `4`. */ studRows?: number; /** Columns of studs across the face. Defaults to `3`. */ studCols?: number; /** Radius of a stud head. Defaults to `0.035`. */ studRadius?: number; /** Wood material. Omit to build a flat-shaded standard material from `woodColor`. */ woodMaterial?: Material; /** Wood tint when `woodMaterial` is omitted. Defaults to `#6b4f34`. */ woodColor?: ColorRepresentation; /** Iron material. Omit to build a flat-shaded standard material from `ironColor`. */ ironMaterial?: Material; /** Iron tint when `ironMaterial` is omitted. Defaults to `#2b2b2b`. */ ironColor?: ColorRepresentation; } /** * Extruded arched slab — a door, an arched window, or a shouldered headstone, depending on the arch's * span. See {@link ArchedSlabShape}. * * @example * ```ts * const door = new ArchedSlabGeometry({ width: 1.2, height: 1.4, archHeight: 0.6 }); * const headstone = new ArchedSlabGeometry({ width: 1.2, height: 1.1, archWidth: 0.7 }); * ``` */ export declare class ArchedSlabGeometry extends ExtrudeGeometry { constructor({ depth, curveSegments, ...shapeOptions }?: ArchedSlabGeometryOptions); } export declare interface ArchedSlabGeometryOptions extends ArchedSlabShapeOptions { /** Extrusion depth. Defaults to `0.18`. */ depth?: number; /** * Segments in the arc — the low-poly knob. Defaults to `16`. * * `3` gives a chiseled, faceted arch; `24` a smooth cast one. Same outline, chosen resolution. */ curveSegments?: number; } /** One side of a slab, split down the middle — see {@link ArchedSlabShapeOptions.half}. */ export declare type ArchedSlabHalf = "left" | "right"; /** * A rectangle with an arched top. * * One outline for three things that look like three: a **door**, an arched **window**, and a * **headstone**. Only the arch's span changes. * * ``` * archWidth == width a smooth line across the top -> a door, a window * * archWidth < width /\ the arch sits ON the slab, * ___/ \___ leaving shoulders -> a headstone * | | * ``` * * The arch is an ELLIPSE, not a circle, so its rise is independent of its span: a squat Roman arch and * a tall pointed one are the same outline with a different `archHeight`. * * **Which makes the semicircle a value, not a mode: `archHeight === archWidth / 2`.** Set the vertical * radius equal to the horizontal one and the ellipse is a circle. That is the arch most callers actually * want, and it is the one thing to remember here — see {@link ArchedSlabShapeOptions.archHeight}. * * Note this is a FILLED outline, not a swept band. An archway you walk through is a sweep — it follows * the curve. A door is an extrude — it fills it. Same arc, different operation. * * Pass {@link ArchedSlabShapeOptions.half} to get one leaf of a double door. Either way the outline is * drawn in the SLAB's frame — the arch stays centered on `x = 0` — so a half sits on its own side of * the centerline rather than being re-centered. Callers that want it elsewhere translate it; that is * how the door factory puts a leaf's origin on its hinge. */ export declare class ArchedSlabShape extends Shape { constructor({ width, height, archWidth, archHeight, half, arch, }?: ArchedSlabShapeOptions); } export declare interface ArchedSlabShapeOptions { /** Width of the slab. Defaults to `1.2`. */ width?: number; /** Height of the rectangular body, up to where the arch springs. Defaults to `1.4`. */ height?: number; /** * Span of the arch. Defaults to `width`. * * Equal to `width` gives a smooth line sweeping across the top — a door, or a window. Pull it in and * SHOULDERS appear at the corners, and the arch sits *on* the slab: a headstone. The shoulders are * not modeled; they are what is left over. */ archWidth?: number; /** * Rise of the arch above the springing — the ellipse's VERTICAL RADIUS, in world units. Defaults * to `0.6`. * * **`archHeight === archWidth / 2` is a perfect semicircle**, and it is the one you almost always * want. At that value the two radii are equal, so the ellipse *is* a circle — a Roman arch. * * | Span | Semicircle rise | * | --- | --- | * | `1.2` | `0.6` | * | `2.6` | `1.3` | * | `w` | `w / 2` | * * Below `w / 2` the arch flattens into a segmental one — wide and low, a gatehouse. Above it, the * arch stretches taller than it is wide. * * **The rise does NOT follow the span.** They are independent radii, which is what lets this be a * styling knob rather than a proportion — but it also means halving `width` leaves a rise that is * now too tall for it. Keep them in step yourself, or the arch quietly changes character when you * resize the slab. */ archHeight?: number; /** * Return only the `left` or `right` half of the slab — the leaf of a double door. Omit for the whole * slab. * * **The half is CARVED OUT of the full outline; it is not a slab of half the width.** Halving `width` * instead would build a new, narrower ellipse, and each leaf would crown at its own center — stand * the pair side by side and you get an `M`, not an arch. The arc here is the same arc: the full * span's ellipse, sampled over half its sweep. Which makes the half a QUARTER ellipse, because the * whole arch was already half of one. * * The result is asymmetric, and that is the point: it is short at its outer edge (`height`, where the * arch springs) and tall at its inner edge (`height + archHeight`, the crown). The tall edge is the * meeting stile, where the two leaves come together. */ half?: ArchedSlabHalf; /** * Which arch sits on top. Defaults to `elliptical`, which springs vertically out of the slab's sides * at any rise. * * `semicircle` is the one most callers want and forces `archHeight` to half the span. `pointed` and * `ogee` come to a point at the crown — and a half slab still splits cleanly there, so one leaf of an * ogee-arched double door works exactly like one leaf of a round-arched one. See {@link ArchStyle}. */ arch?: ArchStyle; } /** Named endpoints for a partial or complete arch trace. */ export declare type ArchEnd = "left" | "crown" | "right"; export declare class ArchGeometry extends BufferGeometry { readonly span: number; readonly legHeight: number; /** The arch drawn over the legs. */ readonly arch: ArchStyle; /** Overall height, crown included. */ readonly totalHeight: number; constructor({ span, legHeight, arch, archHeight, profile, thickness, depth, tubeRadius, tubeSides, segments, legSegments, }?: ArchGeometryOptions); } export declare interface ArchGeometryOptions { /** Opening width, outer leg to outer leg. Defaults to `4`. */ span?: number; /** Straight rise before the arc springs. Defaults to `2`. */ legHeight?: number; /** * Which arch the legs rise into. Defaults to `semicircle`. See {@link ArchStyle}. * * **The same seven names a doorway takes** — so an archway you walk through and the opening it frames * can be drawn from one curve. `square` gives a flat lintel on two posts, which is still a portal. * * Note what each style does where it MEETS the legs. `semicircle`, `elliptical`, `pointed` and `ogee` * all spring VERTICALLY, so they flow out of the legs with no corner. `segmental` and `horseshoe` do * not — they arrive at an angle and leave a visible break at the springing. That break is not an * artifact: it is the impost, and a real segmental arch has one. */ arch?: ArchStyle; /** * Rise of the arch above the springing. Defaults to `span / 2` — a semicircle. * * A radius, not an angle, and it does not follow the span. Some styles override it: `square` has no * rise, `semicircle` forces `span / 2`. */ archHeight?: number; /** * Cross-section carried around the arch. Defaults to `"bar"`. * * The path does not care. A rectangle gives a masonry band; a circle gives wrought iron tubing * arching over a gate. Same arc, same frames, same flat base caps — nothing else changes. */ profile?: "bar" | "tube"; /** Radial depth of the band. Bar only. Defaults to `0.4`. */ thickness?: number; /** Depth out of the arch's plane. Bar only. Defaults to `0.5`. */ depth?: number; /** Radius of the tube. Tube only. Defaults to `0.08`. */ tubeRadius?: number; /** Sides of the tube. `4` gives square tubing, which is what wrought iron actually is. Defaults to `8`. */ tubeSides?: number; /** * Smoothness of the arc — the low-poly knob. Defaults to `24`. * * Rounded UP to an even number for `pointed` and `ogee`, whose crown is a point sitting exactly * halfway along the arc: an odd count never samples it, and the tip gets chamfered off. */ segments?: number; /** Stations along each straight leg. Defaults to `2`. */ legSegments?: number; } export declare interface ArchProfileOptions { /** Arch shape; determines the curve and permitted rise. */ style?: ArchStyle; /** Arch centerline X coordinate. */ x?: number; /** The springing line — the Y where the arch leaves the jambs. */ y: number; /** Half the arch's span. */ halfSpan: number; /** Rise above the springing, in coordinate units; archRise applies style-specific limits. */ rise?: number; /** Endpoint where the existing path ends. */ from?: ArchEnd; /** Endpoint where the appended trace ends. */ to?: ArchEnd; } /** Resolved rise: square 0; semicircle halfSpan; segmental ≤ halfSpan; horseshoe and pointed ≥ halfSpan. */ export declare function archRise({ style, halfSpan, rise }: ArchProfileOptions): number; /** * Arch outlines traced between springings and crown. * * ``` * square semicircle segmental horseshoe * ________ _______ _______ _______ * | | / \ / \ | | * | | | | | | \ / * * elliptical pointed ogee * _______ /\ /\ * / \ / \ ( ) * | | | | | | * ``` */ export declare type ArchStyle = /** Flat lintel at the springing height. */ "square" /** Half-circle; rise equals halfSpan. */ | "semicircle" /** Circular arc with rise limited to halfSpan; meets the jamb at an angle. */ | "segmental" /** Circular arc with rise at least halfSpan; can extend beyond the springing span. */ | "horseshoe" /** Elliptical arc with vertical tangents at the springings. */ | "elliptical" /** Two circular arcs meeting at an apex; rise = halfSpan * √3 gives an equilateral arch. */ | "pointed" /** Two tangent-continuous quadratic segments per side, meeting at a pointed crown. */ | "ogee"; /** * Circular arc in XY. A full 2π turn omits the repeated endpoint; use a closed sweep to join its seam. * * ```ts * const semicircle = arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 }); * const ring = arcPath({ radius: 0.3, startAngle: 0, endAngle: Math.PI * 2 }); * ``` */ export declare function arcPath({ radius, startAngle, endAngle, center, segments, }?: ArcPathOptions): PathPoint[]; export declare interface ArcPathOptions { /** Radius in the XY plane. */ radius?: number; /** Start angle in radians; 0 is +X. */ startAngle?: number; /** End angle in radians. */ endAngle?: number; /** Circle center; its Z coordinate sets the arc plane. */ center?: Vector3; /** Number of arc intervals. */ segments?: number; } /** * Geometric and mathematical role of the vectors as axes in 3D space. * * Example usages: * * Defining a geometry's up vector: * ``` * const upVector = Axis.Z; // Set the Z-axis as the "up" direction * object.up.copy(upVector); * ``` * * Rotating or Orienting an Object * ``` * const axis = Axis.XY; // Diagonal upward * object.lookAt(object.position.clone().add(axis)); * ``` * * Rotate around axis: * ``` * const angle = Math.PI / 4; // 45-degree rotation * const rotationAxis = Axis.Y; // Rotate around the Y-axis * * object.rotateOnAxis(rotationAxis, angle); * ``` * * Scaling along an axis: * ``` * const scalingAxis = Axis.XZ; // Scale equally along X and Z * const scaleFactor = 2; * * object.scale.multiply(scalingAxis.clone().multiplyScalar(scaleFactor)); * ``` * * Aligning a Camera * ``` * const cameraAxis = Axis.XZ; // Align the camera diagonally on the XZ plane * * camera.lookAt(camera.position.clone().add(cameraAxis)); * ``` * * Directional lighting * ``` * const lightDirection = Axis.YZ; // Diagonal light along Y and Z axes * directionalLight.position.copy(lightDirection.clone().multiplyScalar(10)); * ``` * * Aligning an Object * ``` * const axis = Axis.X; // Align object to the X-axis * object.lookAt(object.position.clone().add(axis)); * ``` * * Spawning objects along an axis * ``` * const spawnAxis = Axis.XZ; // Arrange objects diagonally on XZ * const count = 10; * const spacing = 5; * * for (let i = 0; i < count; i++) { * const position = spawnAxis.clone().multiplyScalar(i * spacing); * const newObject = object.clone(); * newObject.position.copy(position); * scene.add(newObject); * } * ``` * * Vector projections * ``` * const vector = new Vector3(3, 5, 7); * const projectionAxis = Axis.Z; // Project the vector onto the Z-axis * * const projection = vector.clone().projectOnVector(projectionAxis); * ``` * * Plane definitions * ``` * const normal = Axis.Y; // Y-axis is the normal for a horizontal plane * const distanceFromOrigin = 5; * * const plane = new THREE.Plane(normal, distanceFromOrigin); * ``` * * Plane intersections * ``` * const planeNormal = Axis.Y; // Define a plane normal (horizontal plane) * const plane = new THREE.Plane(planeNormal); * * const rayDirection = Axis.Z; // Ray pointing along Z-axis * const rayOrigin = new Vector3(0, 5, 0); * const ray = new THREE.Ray(rayOrigin, rayDirection); * * // Find intersection point * const intersectionPoint = new Vector3(); * plane.intersectLine(new THREE.Line3(rayOrigin, rayOrigin.clone().add(rayDirection)), intersectionPoint); * ``` * * Defining bounds * ``` * const boundsAxis = Axis.XY; // Restrict an object’s movement within XY bounds * const maxBounds = 10; * * object.position.clamp( * new Vector3(-maxBounds, -maxBounds, -Infinity), * new Vector3(maxBounds, maxBounds, Infinity) * ); * ``` * * Procedural Geometry * ``` * const vertex = new Vector3(0, 0, 0); * const axis = Axis.XZ; // Diagonal axis on XZ plane * * const offset = axis.clone().multiplyScalar(5); * vertex.add(offset); // Move vertex in axis * geometry.vertices.push(vertex); * ``` */ export declare const Axis: { X: Vector3; Y: Vector3; Z: Vector3; XY: Vector3; XZ: Vector3; YZ: Vector3; XYZ: Vector3; }; /** * Beaker — a straight-walled cylinder with a flat base and a pour spout. * * The body is a lathe of its silhouette (exposed as `.profile`, so the fill works like any vessel). The * SPOUT is not a lathe — it breaks rotational symmetry — so it is a post-pass: the top rings of the wall * are pushed radially outward over a narrow arc (centered on +Z), tapering to nothing below the lip and at * the arc's edges. Normals are recomputed afterward. * * Local frame: flat base on Y=0, opening up +Y, spout facing +Z. */ export declare class BeakerGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor({ radius, height, spout, spoutWidth, radialSegments }?: BeakerGeometryOptions); } export declare interface BeakerGeometryOptions { /** Body radius. Defaults to `0.8`. */ radius?: number; /** Overall height. Defaults to `1.6`. */ height?: number; /** Pour-spout reach, as a fraction of the radius — how far the lip juts out. `0` is a plain cylinder. Defaults to `0.3`. */ spout?: number; /** Angular half-width of the spout, in radians. Defaults to `0.5`. */ spoutWidth?: number; /** Circumference segments — also the spout's smoothness. Defaults to `48`. */ radialSegments?: number; } /** Bend around an analytic circular guide; default guide length is the source-axis extent. */ export declare function bendGeometry(source: BufferGeometry, options: BendGeometryOptions): { diagnostics: { minDet: number; folded: number; chordError: number; selfIntersectionsChecked: false; }; map: (point: Vector3) => Vector3; guide: Vector3[]; length: number; guideLength: number; usedLength: number; extension: number; anchorPoint: Vector3; geometry: BufferGeometry< NormalBufferAttributes, BufferGeometryEventMap>; }; export declare interface BendGeometryOptions extends CurveDeformationOptions { /** Signed arc angle in radians. Zero produces a straight guide. */ angle: number; } /** * Choose the cyclic offset minimizing summed a[i]-to-b[(i+k) % n] distance in O(n²) for equal counts. * Ties retain the first offset; winding is never reversed. */ export declare function bestRingOffset(a: Vector3[], b: Vector3[]): number; declare function between(start: ColorRepresentation, end: ColorRepresentation): ColorSampler; /** Rebuild all edges of a closed convex mesh. Source attributes/materials are not transferred. */ export declare function bevelConvexGeometry(source: BufferGeometry, options: BevelConvexGeometryOptions): BevelConvexGeometryResult; export declare interface BevelConvexGeometryOptions { /** Rolling-ball radius in geometry-local units. Zero rebuilds the source triangles without bevels. */ radius: number; /** 1 for chamfers; 2–12 for faceted rounding. Defaults to 3. */ segments?: number; } export declare interface BevelConvexGeometryResult { /** New nonindexed geometry with flat normals. Groups 0/1/2: original faces / edge bands / corners. */ geometry: BufferGeometry; /** Inset-core corners in source coordinates, useful for inspection. */ core: Vector3[]; /** Number of source supporting planes after merging coplanar triangles. */ faces: number; /** Triangle counts for original faces, edge bands, and corners, respectively. */ patches: [number, number, number]; } /** * Bevel gear — teeth cut on a **pitch cone**, for shafts whose axes intersect. * * Where {@link GearGeometry} extrudes a fixed profile into a cylinder, a bevel gear's teeth **taper toward the * cone's apex**. That convergence is the whole signature, and it is why this cannot be an extrusion: it is a * **loft** between the full profile at the back face and the same profile uniformly scaled at the front. Every * tooth flank therefore lies on a plane through the apex, correct by construction rather than by adjustment. * * A 45° pair of equal wheels is a **miter gear** — right angle, 1:1. Change * {@link BevelGearGeometryOptions.pitchAngle} and the same construction spans a spur-like cone near `0` through * to a flat crown wheel near `90°`. This is the wheel a gearbox or differential is built from; the * differential's crown wheel, pinion, and spider gears are all bevels. * * A pair meshes when their pitch cones share an apex, which makes the shaft angle the **sum** of the two cone * angles — so 45° + 45° is the right-angle case, and unequal angles give unequal ratios. * * Not a **worm** gear, which is a different mechanism: a helical screw driving a wheel, thread-based rather than * conical. * * Local frame: the **back face** (largest teeth) sits on `z = 0`, and the gear tapers toward `+Z`, with the cone's * apex on the axis at {@link apexZ}. The bore runs straight through — it is cylindrical, not tapered, because a * shaft is. * * Material groups: **none** — one material for the whole wheel. * * @example * ```typescript * // A miter pair: two identical 45° wheels meshing at a right angle. * const wheel = new Mesh(new BevelGearGeometry({ teeth: 16 }), steel); * const mate = new Mesh(new BevelGearGeometry({ teeth: 16 }), steel); * mate.rotation.x = Math.PI / 2; * ``` */ export declare class BevelGearGeometry extends BufferGeometry { /** The bore radius actually used, after clamping inside the smaller front outline. */ readonly holeRadius: number; /** Uniform scale of the front outline against the back — how far the teeth converge. */ readonly frontScale: number; /** Z of the front face, where the teeth are smallest. */ readonly frontZ: number; /** Z of the pitch cone's apex on the axis. Teeth stop short of it by construction. */ readonly apexZ: number; /** The face width actually used, after clamping short of the apex. */ readonly faceWidth: number; constructor({ pitchAngle, faceWidth, ...gearOptions }?: BevelGearGeometryOptions); } export declare interface BevelGearGeometryOptions extends GearShapeOptions { /** * Half-angle of the pitch cone, measured from the axis, in radians. Defaults to `Math.PI / 4` (45°). * * A 45° pair of equal wheels meshes at a right angle with a 1:1 ratio — a **miter gear**. Approaching `90°` * flattens the cone into a **crown wheel**, its teeth standing on the face; approaching `0` stretches it into * a long thin cone, which is a plain spur gear. */ pitchAngle?: number; /** * Tooth length measured **along the cone element**, not along the axis. Defaults to `0.35`. * * Clamped so the teeth cannot run past the cone's apex. */ faceWidth?: number; } /** * Bone Geometry, a simple bone shape * @extends BufferGeometry * * @example * // Create a bone * const boneGeometry = new BoneGeometry(); * const boneMaterial = new MeshStandardMaterial({ color: 0xffffff }); * const bone = new Mesh(boneGeometry, boneMaterial); * scene.add(bone); */ export declare class BoneGeometry extends BufferGeometry { constructor(radiusTop?: number, radiusBottom?: number, height?: number, radialSegments?: number); } /** Material slot for the cover shell. */ export declare const BOOK_COVER_MATERIAL = 0; /** Material slot for the page block. */ export declare const BOOK_PAGES_MATERIAL = 1; /** * A closed book — cover shell (group 0) and page block (group 1), merge-baked into one geometry. * * Fourteen quads make the shell: three outer boards, three inner faces, three top edges, three bottom * edges, and the two fore-edges. The inner faces are what make it a SHELL rather than a slab — a book * seen from its fore-edge shows the inside of both boards and the page block held between them. * * Local frame: **spine at X = 0, fore-edge at +X, sitting on Y = 0**, with the book extending to −Z. Not * centred in XZ, and deliberately: books are placed against each other, so the spine is the useful * anchor. A row lays them out along Z; a shelf stands them along X. * * ## The two groups are the point * * A cover is red and its pages are white, so the two need different materials — and merging them with * groups is what keeps a single book to one geometry and one draw pair. It also rules something out: * Three's `InstancedMesh` carries ONE colour per instance for the whole object, so a shelf of books with * differently coloured spines cannot be a single instanced mesh here. (Metal can do it — `setColorAt` * against a material group — which is why the Swift port of this reads differently.) Merging a whole * shelf into one baked geometry is the way that works here, and it is what the row and stack factories * do. * * ## The cover UV wraps front to back * * `u` runs continuously across **back cover → spine → front cover**, in proportion to `2·width + depth`, * so the three outer boards share one unbroken 0→1 span. That is the layout a real dust jacket is * printed on: one flat sheet, folded around the boards. Apply a paper texture and it wraps correctly * across the spine instead of restarting at every face. * * The three INNER faces carry the same spans reversed, so a texture continues around the fold rather * than mirroring at it. The edge, top and bottom strips take a plain 0→1: they are thin, and nothing on * a jacket is registered to them. * * @example * ```ts * const book = new Mesh(new BookGeometry({ depth: 0.32 }), [ * new MeshStandardMaterial({ color: 0x8c2f2f, roughness: 0.62 }), // cover * new MeshStandardMaterial({ color: 0xe8e0cc, roughness: 0.92 }), // pages * ]); * ``` */ export declare class BookGeometry extends BufferGeometry { readonly width: number; readonly height: number; readonly depth: number; readonly coverThickness: number; readonly pageIndent: number; constructor({ width, height, depth, coverThickness, pageIndent, }?: BookGeometryOptions); } export declare interface BookGeometryOptions { /** Cover width, spine to fore-edge. Defaults to `1`. */ width?: number; /** Cover height. Defaults to `1.5`. */ height?: number; /** Spine depth, cover to cover. Defaults to `0.5`. */ depth?: number; /** Cover board thickness. Defaults to `0.05`. */ coverThickness?: number; /** Inset of the page block from the cover edges. Defaults to `0.05`. */ pageIndent?: number; } declare interface BookScaleOptions { coverMaterial: T; pagesMaterial: T; scaleXMin?: number; scaleXMax?: number; scaleYMin?: number; scaleYMax?: number; scaleZMin?: number; scaleZMax?: number; /** Optional seed for reproducible layout. Omit for unique runtime. */ seed?: number; } /** * Bookshelf frame with optional back panel and evenly spaced shelves. * * Local frame: sits on the Y=0 plane, centered on X/Z. */ export declare class BookshelfGeometry extends BufferGeometry { readonly width: number; readonly height: number; readonly depth: number; readonly shelves: number; constructor({ width, height, depth, shelves, frameThickness, open, }?: BookshelfGeometryOptions); } export declare interface BookshelfGeometryOptions { /** Overall width. Defaults to `5`. */ width?: number; /** Overall height. Defaults to `8`. */ height?: number; /** Shelf depth. Defaults to `1`. */ depth?: number; /** Number of interior shelves. Defaults to `4`. */ shelves?: number; /** Frame board thickness. Defaults to `0.1`. */ frameThickness?: number; /** Omit the back panel when `true`. Defaults to `false`. */ open?: boolean; } /** * Union, intersection or A-minus-B in a shared geometry-local frame. No external CSG dependency. * A bounded, tolerance-based BSP solver for small meshes (1500 triangles per input), not exact CSG. * Empty results are supported; empty inputs and inward cavity shells as inputs are rejected. * Output normals/UVs interpolate source attributes; new cavity faces inherit B and reverse winding. * Caller disposes result.geometry. Default failure mode never returns known-invalid topology. */ export declare function booleanGeometry(a: BufferGeometry, b: BufferGeometry, operation: BooleanOperation, { onInvalid, materialIndices }?: BooleanGeometryOptions): BooleanGeometryResult; export declare interface BooleanGeometryOptions { /** Default throw disposes output that fails topology checks; report returns it for inspection. */ onInvalid?: "throw" | "report"; /** Optional operand colors. Otherwise A indices are preserved and B indices are offset above A. */ materialIndices?: { a: number; b: number; }; } export declare interface BooleanGeometryResult { /** Caller-owned geometry, possibly empty. Inputs remain unchanged. */ geometry: BufferGeometry; /** Add this to B's source material indices. Null when materialIndices overrides both operands. */ materialOffsetB: number | null; diagnostics: { /** Closure, winding, degeneracy and volume checks only; not a global solid certificate. */ topologyValid: boolean; /** Null for an empty result. */ inspection: GeometryInspection | null; selfIntersectionsChecked: false; }; } /** Bounded BSP Boolean operations on closed triangle meshes. */ export declare type BooleanOperation = "Union" | "Intersection" | "Subtract"; /** * Boulder — an icosphere lumped by coherent 3D fbm ({@link fbm3}) displaced along * each vertex's radial direction. The 3D counterpart to the terrain heightfields: * same noise strategy, applied over a closed surface instead of a height map. * * The icosphere is welded (`mergeVertices` after stripping seam UVs) so coincident * vertices share one displacement — the coherent noise then moves neighbors together, * so the surface stays watertight and never cracks (the failure mode of displacing a * non-indexed polyhedron along normals). Real baked geometry, so shadows, raycasts, * and physics colliders match what's drawn, on WebGL and WebGPU/TSL alike. * * Centered on the origin. Pair with a `flatShading` material for a faceted low-poly * look. Vary `seed` per instance to fill a field with unique boulders. */ export declare class BoulderGeometry extends BufferGeometry { readonly radius: number; constructor({ radius, detail, noiseHeight, noiseScale, octaves, persistence, seed, }?: BoulderGeometryOptions); } export declare interface BoulderGeometryOptions { /** Base radius before displacement (world units). Defaults to `1`. */ radius?: number; /** Icosahedron subdivision — more detail = more, finer facets. Defaults to `2`. */ detail?: number; /** Radial relief amplitude (world units, ±). Keep below `radius`. Defaults to `0.35`. */ noiseHeight?: number; /** Noise frequency over the unit sphere — higher packs more, smaller lumps. Defaults to `1.6`. */ noiseScale?: number; /** fbm octaves (detail layers). Defaults to `3`. */ octaves?: number; /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */ persistence?: number; /** Seed for reproducible shape. Defaults to `1`. */ seed?: number; } export declare const BoxSide: { readonly LEFT: "left"; readonly RIGHT: "right"; readonly TOP: "top"; readonly BOTTOM: "bottom"; readonly FRONT: "front"; readonly BACK: "back"; }; export declare type BoxSide = (typeof BoxSide)[keyof typeof BoxSide]; /** * Extruded burst / starburst prism. */ export declare class BurstGeometry extends ExtrudeGeometry { constructor({ depth, ...shapeOptions }?: BurstGeometryOptions); } export declare interface BurstGeometryOptions extends BurstShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Starburst profile — radial points joined by concave quadratic curves. * * Rests with a point up. */ export declare class BurstShape extends Shape { constructor({ points, innerRadius, outerRadius, rotation }?: BurstShapeOptions); } export declare interface BurstShapeOptions { /** Number of burst points. Defaults to `5`. */ points?: number; /** Inner vertex radius. Defaults to `0.5`. */ innerRadius?: number; /** Outer vertex radius. Defaults to `1`. */ outerRadius?: number; /** Rotation in radians from the resting state. Defaults to `0`. */ rotation?: number; } /** * Calculate the sum of absolute differences for each channel * difference = |r1 - r2| + |g1 - g2| + |b1 - b2| */ export declare function calculateChannelDifference(color1: [number, number, number], color2: [number, number, number]): number; /** * Calculate the Euclidean distance between two colors in RGB space * distance = sqrt((r1 - r2)^2 + (g1 - g2)^2 + (b1 - b2)^2) */ export declare function calculateDistance(color1: [number, number, number], color2: [number, number, number]): number; /** Componentwise UV bounds; an empty input returns infinite bounds. */ export declare function calculateUVBounds(uvs: [number, number][]): { minBounds: [number, number]; maxBounds: [number, number]; }; /** * Calculate the x-coordinate for a given y-coordinate using the slope-intercept equation of a line. * x = x1 + (y - y1) / m * * Example usage * ``` * const x1 = 0.8, y1 = 0, x2 = 1, y2 = 1.5, y = 1.0; * const x = calculateXForY(x1, y1, x2, y2, y); * console.log(`The x-position for y=${y} is x=${x.toFixed(4)}`); * ``` */ export declare function calculateXFromSlopeIntercept(x1: number, y1: number, x2: number, y2: number, y: number): number; /** * Calculate the y-coordinate for a given x-coordinate using the slope-intercept equation of a line. * y = y1 + m * (x - x1) * * Example usage * ``` * const x1 = 0.8, y1 = 0, x2 = 1, y2 = 1.5, x = 0.9333; * const y = calculateYForX(x1, y1, x2, y2, x); * console.log(`The y-position for x=${x} is y=${y.toFixed(4)}`); * ``` */ export declare function calculateYFromSlopeIntercept(x1: number, y1: number, x2: number, y2: number, x: number): number; export declare interface CameraClip { /** Effects are temporary offsets evaluated after the movement/transition. */ readonly kind?: "movement" | "transition" | "effect"; readonly label: string; readonly duration: number; start(runtime: ClipRuntime): void; /** Evaluate the current time, including the endpoint. Playback owns completion. * Legacy phase returns are accepted but no longer control the clock. */ update(runtime: ClipRuntime, dt: number): ClipPhase | void; /** Cleanup on interruption. Must preserve the base camera pose. */ cancel?(runtime: ClipRuntime): void; } export declare interface CameraClipTiming { /** Duration in seconds. */ duration: number; /** Progress easing for normalized time `t` in [0, 1]. Defaults to smoothstep. */ ease?: EasingFunction; } /** * Delta-time camera playback: one movement/transition plus one temporary effect. * Play captures the current pose; stop preserves it. Only reset restores a saved view. * * Optional OrbitControls integration disables input and synchronizes the target. * The host must also skip controls.update() while isPlaying: disabling input alone * does not stop controls or other external systems from writing to the camera. * Clips contain per-run state; do not share a clip between simultaneous players. * * @example * ```ts * const playback = new CameraPlayback(camera, controls); * playback.timeScale = 0.5; // half speed; changing speed never restarts a clip * playback.play(createOrbitClip({ target: new Vector3(), duration: 10 })); * // In your existing render loop (dt is seconds): * if (!playback.isPlaying) controls.update(); * playback.update(dt); * // playback.stop() keeps the view; playback.reset() restores the saved view. * ``` */ export declare class CameraPlayback { private readonly camera; private readonly controls?; private speed; private active; private effect; private readonly focus; private rest; private base; private controlsEnabled; constructor(camera: PerspectiveCamera, controls?: OrbitControls | undefined); /** Playback clock multiplier: 1 = normal, 0.5 = half speed, 2 = double. * Zero freezes the current frame while retaining camera ownership. * Applies to both movement and effects; may change during a run. */ get timeScale(): number; set timeScale(value: number); get isPlaying(): boolean; get isMoving(): boolean; get isEffectPlaying(): boolean; /** Capture an explicit restore point. Neither play nor stop restores it. */ setRest(): void; setRestSnapshot(snapshot: CameraSnapshot): void; /** Start a movement/transition at the current base pose, or trigger an effect. */ play(clip: CameraClip): void; /** Stop all playback, preserving the base pose and removing temporary offsets. */ stop(): void; /** Explicitly restore the saved view. Intended for repeatable example setup. */ reset(): void; /** Advance in seconds. The controller owns completion, independent of easing. */ update(dt: number): void; dispose(): void; private advance; private removeEffectPose; private releaseControls; private runtime; } /** A camera view captured explicitly for later restoration. */ export declare interface CameraSnapshot { position: Vector3; quaternion: Quaternion; fov: number; target: Vector3; } /** * Calculate the height of a spherical cap. * h = R * (1 - cos(thetaLength)) */ export declare const capHeightFromRadius: (radius: number, thetaLength: number) => number; export declare function captureSnapshot(camera: PerspectiveCamera, controls?: OrbitControls): CameraSnapshot; /** * Calculate the width of a spherical cap. * w = 2 * R * sin(thetaLength) */ export declare const capWidthFromRadius: (radius: number, thetaLength: number) => number; /** * Convert Cartesian coordinates to spherical coordinates. * @param {number} x - The x-coordinate. * @param {number} y - The y-coordinate. * @param {number} z - The z-coordinate. * @returns {{radius: number, theta: number, phi: number}} The spherical coordinates. */ export declare const cartesianToSpherical: (x: number, y: number, z: number) => { radius: number; theta: number; phi: number; }; /** * A cascade — the pleated tail hanging beside a swag. Also called a jabot. * * An accordion of cloth hung vertically and trimmed along ONE STRAIGHT DIAGONAL. The sawtooth hem is not * modelled and no step is placed: only forward-facing creases show, each meets the diagonal at a * different place along the cloth, and so each terminates at a different height. There are exactly as * many steps as there are pleats, because a step IS a fold. * * The bias is straight in the FABRIC — in arc length along the pleat wave — because a jabot is cut flat * and folded afterwards. Cutting straight in the projected width instead moves the hem by under 1% of * the bias and does not change the treads at all, since the deviation between the two is periodic with * the pleats and every crease samples it at the same phase. * * **Origin is the board**, at `y = 0`, with the cloth hanging to negative Y — the same convention as * {@link SwagGeometry}. A hanging thing is anchored where it is fixed. * * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`. * * @example * ```ts * const cascade = new Mesh( * new CascadeGeometry({ pleats: 6, longDrop: 1.8 }), * new MeshStandardMaterial({ color: 0x1f5b45, roughness: 0.95, side: DoubleSide, flatShading: true }), * ); * ``` */ export declare class CascadeGeometry extends BufferGeometry { constructor({ fabricWidth, topWidth, bottomWidth, pleats, pleat, shortDrop, longDrop, roll, widthSegments, heightSegments, }?: CascadeGeometryOptions); } export declare interface CascadeGeometryOptions { /** * Width of the flat cloth before it is folded. Defaults to `2.4`. * * **This is the conserved quantity and the reason nothing else has to be told what to do.** The cloth * is cut once; every fold depth below is solved so the accordion's arc length comes back to it. */ fabricWidth?: number; /** Finished width where it is stapled to the board. Defaults to `0.34`. */ topWidth?: number; /** * Finished width at the hem. Defaults to `0.62`. * * Wider than {@link topWidth} is the flare. Since the cloth is fixed, opening the flare LOWERS the * local fullness and the folds shallow out on their own — you never set a fold depth. */ bottomWidth?: number; /** Number of pleats. Defaults to `6`. Each forward-facing crease becomes one step of the hem. */ pleats?: number; /** The plan section. Defaults to `"knife"`. See {@link CascadePleat}. */ pleat?: CascadePleat; /** Drop at the short (inner) edge. Defaults to `0.55`. */ shortDrop?: number; /** Drop at the long (outer) tail. Defaults to `1.8`. The bias is the difference between the two. */ longDrop?: number; /** * How far the stack tips into a cone — inner pleats tucking back, leading edge throwing forward. * Defaults to `0.06`. * * Zero at the board and growing with the drop, because at the board the cloth is stapled flat to a * straight piece of timber and cannot lean. Applied at full strength throughout, it shears the whole * panel and its top edge tilts about 10° away from the board it is fixed to. */ roll?: number; /** * Samples across the width. Defaults to `240`. * * **Rounded UP so each pleat gets a multiple of four samples**, which puts one on every extremum of * the wave — a knife turns at phases 0 and 0.5, a sine at 0.25 and 0.75. A pleat has real apexes at * known parameters, and a sampling that steps over them clips the fold rather than approximating it: * unsnapped, the fold depth wanders with the phase instead of holding still. Snapped, it is identical * to nine digits at every count, which is what `segments` changes tessellation, never silhouette * actually demands of a shape with features in it. */ widthSegments?: number; /** Samples down the drop. Defaults to `40`. */ heightSegments?: number; } /** * The plan section of the accordion. * * - `knife` — a triangle. Every fold leans the same way, which is what the name says. Constant slope, * so its arc length is proportional to its projected width. * - `sine` — a soft, rounded pleat. */ export declare type CascadePleat = "knife" | "sine"; /** * Celtic cross headstone — a cross with gently flared arms, ringed by a nimbus at the crossing. * * **It is two outlines, extruded and merged** — the same method as everything else in this vocabulary. The * cross is one closed silhouette (the shaft rises unbroken through the crossing, the three free arms splay * at their tips, the base stays flat), and the ring is an annulus (a disc with a disc-shaped hole). No * boxes, no booleans — a drawing given depth. * * The ring stands a hair PROUD of the cross, rather than flush with it. Two coplanar faces at the same * depth z-fight; lifting the ring's faces just clear of the arms' avoids it and reads as a raised ring, * which is what a carved Celtic cross actually has. * * Set `ring: false` and it is a plain flared cross — the nimbus is the only thing the ring adds. * * Local frame: base on Y=0, centered on X/Z. * * @example * ```ts * const celtic = new CelticCrossHeadstoneGeometry(); * const flared = new CelticCrossHeadstoneGeometry({ ring: false, flare: 0.06 }); * ``` */ export declare class CelticCrossHeadstoneGeometry extends BufferGeometry { readonly height: number; readonly span: number; constructor({ height, span, thickness, crossing, flare, ring, ringWidth, depth, curveSegments, }?: CelticCrossHeadstoneGeometryOptions); } export declare interface CelticCrossHeadstoneGeometryOptions { /** Total height, base to the top of the upper arm. Defaults to `1.3`. */ height?: number; /** Arm span — the full horizontal extent. Defaults to `0.62`. */ span?: number; /** Arm thickness at the neck, where it leaves the crossing. Defaults to `0.12`. */ thickness?: number; /** * Where the arms cross, as a fraction of `height`. Defaults to `0.7`. * * A cross carries its arms high — the shaft below is the long part. */ crossing?: number; /** * How far each arm splays at its tip, beyond the neck half-width. Defaults to `0.03`. `0` gives a * plain straight cross. The base never flares — it sits flat on the ground. */ flare?: number; /** * The nimbus — the ring at the crossing. `true` for a default radius, a number to set the outer radius, * `false` for a plain flared cross with no ring. Defaults to `true`. */ ring?: boolean | number; /** Width of the ring band. Defaults to `0.085`. */ ringWidth?: number; /** Slab depth. Defaults to `0.14`. */ depth?: number; /** Curve resolution of the flares and the ring — the low-poly knob. Defaults to `20`. */ curveSegments?: number; } export declare const Center: { object: typeof centerObject; objectGeometry: typeof centerObjectGeometry; meshGeometry: typeof centerMeshGeometry; }; /** * Centers the geometry of a `Mesh` relative to a target position with an optional offset. * * This function calculates the bounding box center of the `Mesh` geometry and adjusts its * position by translating the geometry such that it is centered at the specified target * position, with an optional offset applied. The function modifies the geometry directly, * leaving the `Mesh`'s transformation properties (`position`, `rotation`, `scale`) unchanged. */ export declare function centerMeshGeometry(mesh: T, target?: Vector3, offset?: Vector3): void; /** * Centers an `Object3D` relative to a specified target position with an optional offset. * * This function calculates the bounding box center of the given `Object3D` and adjusts * its position so that it is centered at the specified target position, with an optional * offset applied. The centering respects the object's current transformation, including * its scale and rotation. */ export declare function centerObject(object: T, target?: Vector3, offset?: Vector3): void; /** * Centers the geometry of an `Object3D` relative to a target position with an optional offset. * * This function calculates the bounding box center of the given `Object3D` and adjusts its * geometry's position by translating it such that the geometry is centered at the specified * target position, with an optional offset applied. Unlike modifying the `position` property, * this function directly translates the geometry within the object's local space. */ export declare function centerObjectGeometry(object: T, target?: Vector3, offset?: Vector3): void; /** Sequential plane chamfers on selected original edges of a convex solid. Not rounded beveling. * Retains the intersection of the selected halfspaces; large widths can remove unrelated features. */ export declare function chamferConvexGeometry(source: BufferGeometry, options: ChamferConvexGeometryOptions): ChamferConvexGeometryResult; export declare interface ChamferConvexGeometryOptions { /** Nonnegative face setback in geometry-local units, not rolling-ball radius. */ width: number; /** Explicit original edge IDs returned by convexEdges. Empty selects nothing; duplicates are removed. */ edgeIds: readonly number[]; /** Projection policy for new chamfer faces; forwarded to slicing. */ capUV?: SliceCapUVOptions; } export declare interface ChamferConvexGeometryResult extends ConvexEdgeTopology { geometry: BufferGeometry; report: GeometryInspection; selected: number[]; /** Shared material index for new chamfers; existing source group indices are retained. */ chamferMaterialIndex: number; } export declare interface CheckerboardTextureOptions { /** * Texture edge length in texels, which is also the number of alternating squares per tile. * Defaults to `2` — the smallest true checker, and all you need when tiling a large plane via * `texture.repeat`. * * **Rounded up to an even number.** The pattern alternates on `(x ^ y) & 1`, so with an odd * count the parity repeats where the tile wraps and two same-colored rows meet at every seam. */ size?: number; } /** * CCW circle in the station (normal, binormal) plane; rotation sets the seam angle in radians. * A half-segment offset aligns flats with the axes; θ=0 aligns vertices. */ export declare function circleProfile(radius: number, segments: number, rotation?: number): Vec2[]; /** * A tall, open-trunked gnarled tree — the single tree from a clearing forest. * * One recursive routine grows the whole skeleton: a branch is a short walk of tapered frustums, gnarled by a * small random bend at each step, splitting into thinner children when it runs out of steps. Merged to a * single geometry. * * It differs from {@link GnarledTreeGeometry} in the one way that matters: **the lower trunk is kept open.** * The trunk is continually nudged back toward vertical and its first limbs are withheld until several steps * up, so the bole rises clear before it branches. That is what lets something ring a clean trunk — an iron * guard, a bench, a lantern — rather than fouling a thicket of low boughs. * * Material groups: **none** — bare bark, one material for the whole skeleton. * * Local frame: **grows from the origin**, base flat on the `y = 0` plane, occupying `+Y`. See * {@link ClearingTreeGeometryOptions.baseRise}. * * @example * ```typescript * const tree = new Mesh(new ClearingTreeGeometry({ seed: 1 }), bark); * ``` */ export declare class ClearingTreeGeometry extends BufferGeometry { readonly trunkRadius: number; constructor({ trunkRadius, segmentLength, trunkSteps, seed, baseRise, }?: ClearingTreeGeometryOptions); } export declare interface ClearingTreeGeometryOptions { /** Radius at the foot of the trunk. Defaults to `0.28`. */ trunkRadius?: number; /** Length of one trunk growth step. Defaults to `0.76`. */ segmentLength?: number; /** Growth steps in the trunk before it forks. Defaults to `7` — this is what makes the tree tall. */ trunkSteps?: number; /** Growth seed — a tree is an address, not an accident. Defaults to `1`. */ seed?: number; /** * Height of the straight vertical rise before the trunk leans. Defaults to `0.3`. * * The trunk starts a hair off vertical so no two trees stand to identical attention, which tilts its bottom * face and sinks the low edge below `y = 0`. One vertical segment makes the base tangent exactly UP so the * face lies flat. A correction to the PATH, not the geometry — the same fix as * {@link GnarledTreeGeometry}'s `baseRise`. Set `0` to see the original tilt. */ baseRise?: number; } /** * Sequentially retain distanceToPoint <= 0 for each plane. Outward planes describe a convex cutting * region; the source itself need not be convex. Stops when the retained geometry becomes empty. * Preserves source materials, normals and UVs under sliceGeometry's input constraints. New cap * indices start above all source materials. All returned geometries belong to the caller. * An empty plane list returns an unchanged clone after validating the source and options. */ export declare function clipGeometryByPlanes(source: BufferGeometry, planes: readonly Plane[], options?: ClipGeometryByPlanesOptions): ClipGeometryByPlanesResult; /** Closed cuts always produce caps. UV options affect only newly created faces. */ export declare type ClipGeometryByPlanesOptions = Omit; export declare interface ClipGeometryByPlanesResult { /** Caller-owned retained negative halfspace intersection. May be empty. */ geometry: BufferGeometry; /** Caller-owned positive halves, one per executed cut, including empty halves. */ offcuts: BufferGeometry[]; /** Historical sections at each step; later cuts do not update these contours. */ cuts: PlaneGeometryCut[]; } export declare type ClipPhase = "running" | "complete"; /** Per-frame mutable state shared with the active clip. */ export declare interface ClipRuntime { camera: PerspectiveCamera; controls: OrbitControls | null; /** Seconds elapsed in the active clip. */ elapsed: number; /** Clip duration in seconds. */ duration: number; /** Look-at point the clip is focused on — synced to OrbitControls on complete. */ focus: Vector3; } /** * Extruded club prism. */ export declare class ClubGeometry extends ExtrudeGeometry { constructor({ depth, ...shapeOptions }?: ClubGeometryOptions); } export declare interface ClubGeometryOptions extends ClubShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Club profile — three lobes on a stem, drawn counter-clockwise from the stem. * * The lobes are bezier bulges rather than true circles: circles have to be tangent to each other or * they leave a notch where they meet, and pinning that down turns a silhouette into a solved * equation. Bezier lobes just overlap, and you can move them. */ export declare class ClubShape extends Shape { constructor({ size, width, height, stemWidth, stemDepth, stemConcavity, }?: ClubShapeOptions); } export declare interface ClubShapeOptions { /** Overall scale factor. Defaults to `1`. */ size?: number; /** Club width across the side lobes. Defaults to `1.84`. */ width?: number; /** Height of the top lobe above the origin. Defaults to `1`. */ height?: number; /** Stem width. Defaults to `0.56`. */ stemWidth?: number; /** Depth of the stem below the origin. Defaults to `0.85`. */ stemDepth?: number; /** * How far the stem's sides bow INWARD, as a fraction of the way to the centerline. Defaults to `0.18`. * * `0` is a straight trapezoid; higher pinches the waist and flares the foot — the concave sweep of a * printed club. Same idea as {@link DiamondShapeOptions.concavity}, applied to the stem. */ stemConcavity?: number; } /** * Wrought-iron coach lantern — a tapered four-sided cage under a pyramidal cap, glazed on all four * faces, with a candle standing on the floor plate. * * Material groups: `0` iron (bail, rod, cap, roof plate, posts, rails, floor plate, finial), `1` glass * (four panes), `2` wax (the candle). Group `2` is absent when `candle` is `false`. * * Local frame: **origin at the hang point** — the topmost metal of the bail, so the lantern hangs into −Y * and `drop` lengthens the rod without moving where it attaches. That is the point a consumer positions * against a ceiling or a bracket, and it is also the natural pivot if the lantern swings. * * {@link wickY} is where a flame, glow, and light belong. The flame is deliberately **not** part of this * geometry: welded into the vertices it could not move, and a flame that cannot move is not a flame. * Position it at `wickY` and let it pivot there. * * @example * ```typescript * const lantern = new Mesh(new CoachLanternGeometry({ drop: 0.6 }), [iron, glass, wax]); * ``` */ export declare class CoachLanternGeometry extends BufferGeometry { readonly drop: number; readonly width: number; readonly height: number; /** * Y of the cage top — the upper rail's centerline. The roof plate rests on that rail and the pyramid cap * rests on the plate, so both sit *above* this. */ readonly capY: number; /** Y of the cage bottom — the lower rail's centerline, and the floor plate's centerline. */ readonly baseY: number; /** * Y of the floor plate's upper face — the surface the candle stands on. The plate stacks below the lower * rail, so this is flush with that rail's underside and does **not** move with * {@link CoachLanternGeometryOptions.plateThickness}. */ readonly trayY: number; /** Y of the candle's wick. Attach the flame, glow, and light here. */ readonly wickY: number; constructor({ drop, width, height, taper, barWidth, capHeight, capSpread, roofSpread, roofThickness, plateSpread, plateThickness, bail, bailRadius, bailThickness, bailSegments, bailSides, finial, candle, candleHeight, }?: CoachLanternGeometryOptions); } export declare interface CoachLanternGeometryOptions { /** Distance from the hang point down to the top of the cage. Defaults to `0.42`. */ drop?: number; /** Half-width of the cage at its base. Defaults to `0.15`. */ width?: number; /** Cage height, cap underside to floor plate. Defaults to `0.4`. */ height?: number; /** * How far the cage narrows toward the top, as a fraction of `width`. Defaults to `0.72`. * * `1` gives straight sides; smaller values rake the posts inward, which is what reads as a coach * lantern rather than a box. */ taper?: number; /** Post bar radius. Defaults to `0.015`. */ barWidth?: number; /** Pyramidal cap height. Defaults to `0.15`. */ capHeight?: number; /** * Pyramid cap size as a multiple of the **roof plate**. Defaults to `1.4`. * * Measured against the plate rather than `width` so the two are directly comparable, which makes `1` the * boundary between the lantern's two roof styles: * * - **above `1`** — the cap oversails the plate and reads as a **roof** over the whole lantern. The plate * vanishes beneath it, and a consumer need not know it is there. * - **below `1`** — the cap sits inset on a flat roof and reads as a centered **gable**. This is the * country-lantern look. * * The cap always stands *on* the plate, so it cannot intersect or pass through it at any value. */ capSpread?: number; /** * Roof plate size as a multiple of the cage's **top** corner distance. Defaults to `1.05` — just proud of * the top corners, so the plate closes the cage rather than leaving a gap you can see through. * * Measured at the top so it tracks {@link CoachLanternGeometryOptions.taper} automatically and does not * need re-tuning whenever the cage is re-raked. */ roofSpread?: number; /** Roof plate thickness as a multiple of `barWidth`. Defaults to `2`. */ roofThickness?: number; /** The ring at the hang point that a chain or hook passes through. Defaults to `true`. */ bail?: boolean; /** * Bail ring radius as a multiple of `barWidth`. Defaults to `3`. * * This is what a chain or hook has to fit through, so it is the one dimension a consumer may need to match * against something else. Raising it also lowers the cap, since the rod starts below the ring. */ bailRadius?: number; /** Bail wire thickness as a multiple of `barWidth`. Defaults to `0.8` — slightly lighter than the bars. */ bailThickness?: number; /** * Segments around the bail's ring — its roundness. Defaults to `10`. Minimum `3`. * * **The bail is the only round part of this geometry**, so it is the only place a segment count changes * anything. The cap, roof plate, and floor plate are 4-sided because 4 *is* the square they are meant to * be, not because they are coarse approximations of a circle. */ bailSegments?: number; /** Sides on the bail's wire cross-section. Defaults to `6`. Minimum `3`. */ bailSides?: number; /** * Floor plate size, as a multiple of the distance from center to the cage's bottom corners. * Defaults to `1.15` — just proud of the posts, so the plate closes the cage rather than leaving a * gap you can see the interior through. * * `1` lands exactly on the corner centerlines (the bars' outer halves stay proud). Raise past `1.3` * for a plate that oversails the cage like a country lantern's tray. */ plateSpread?: number; /** * Floor plate thickness as a multiple of `barWidth`. Defaults to `2`, matching * {@link CoachLanternGeometryOptions.roofThickness}. * * The plate **stacks below** the lower rail, mirroring the roof plate stacking above the upper rail, so no * thickness can bury the cage — both plates sit outside it and the frame stays fully visible. Thickening * this one grows it downward and leaves the candle where it is. */ plateThickness?: number; /** The dropped spike beneath the floor plate. Defaults to `true`. */ finial?: boolean; /** A candle standing on the floor plate. Defaults to `true`. */ candle?: boolean; /** Candle height as a fraction of `height`. Defaults to `0.5`. */ candleHeight?: number; } /** An explicit gradient position in [0, 1]. Stops must strictly increase from 0 to 1. */ export declare interface ColorGradientStop { readonly at: number; readonly color: ColorRepresentation; } export declare const ColorPalette: { CADMIUM_RED: number; CARDINAL_RED: number; CHERRY_RED: number; CRIMSON: number; ALIZARIN_CRIMSON: number; DARK_RED: number; RUST: number; CORAL_ORANGE: number; TANGERINE: number; ORANGE_PEEL: number; CADMIUM_ORANGE: number; ORANGE: number; AMBER: number; CADMIUM_YELLOW: number; GOLD: number; YELLOW: number; LIME_GREEN: number; SPRING_GREEN: number; MOSS_GREEN: number; FERN_GREEN: number; FOREST_GREEN: number; SAP_GREEN: number; OLIVE_DRAB: number; VIRIDIAN_GREEN: number; MINT_GREEN: number; AQUAMARINE: number; PHTHALO_BLUE: number; AETHER_BLUE: number; SKY_BLUE: number; CERULEAN_BLUE: number; AZURE: number; OCEAN_BLUE: number; ROYAL_BLUE: number; MIDNIGHT_BLUE: number; ULTRAMARINE_BLUE: number; COBALT_VIOLET: number; DEEP_VIOLET: number; CORAL_PINK: number; VIVID_MAGENTA: number; MAGENTA: number; HOT_PINK: number; PINK_SHERBET: number; SOFT_PINK: number; BURNT_SIENNA: number; BURNT_UMBER: number; SIENNA: number; SADDLE_BROWN: number; COFFEE_BROWN: number; DARK_UMBER: number; RAW_UMBER: number; YELLOW_OCHRE: number; RAW_SIENNA: number; TAUPE: number; ONYX: number; CARBON: number; CHARCOAL: number; SLATE_GRAY: number; ASH_GRAY: number; GRAPHITE: number; STEEL_GRAY: number; DIM_GRAY: number; IRON: number; GRAY: number; STONE: number; SILVER: number; LIGHT_GRAY: number; PALE_GRAY: number; WHITE_SMOKE: number; TITANIUM_WHITE: number; }; /** Per-item input, owned by the caller. Index identifies a logical item, not a vertex. */ export declare interface ColorSampleContext { readonly index: number; readonly random: RandomSource; } /** * Fully writes a reusable color in Three's working color space. Do not retain or * mutate the context, retain the target, or depend on its previous contents. * Randomness comes only from the supplied source; fixed draw counts are not promised. */ export declare type ColorSampler = (target: Color, context: ColorSampleContext) => void; declare function constant(color: ColorRepresentation): ColorSampler; export declare interface ConvexEdge { /** Endpoints in ConvexEdgeTopology.points. Edges are original mesh segments, not merged polylines. */ a: number; b: number; normals: [Vector3, Vector3]; /** Angle between outward incident face normals in radians. */ angle: number; } /** Geometric edge topology of one outward closed convex triangle mesh. Coplanar diagonals are excluded. */ export declare function convexEdges(source: BufferGeometry): ConvexEdgeTopology; export declare interface ConvexEdgeTopology { points: Vector3[]; /** Array index is the edge ID, valid only for this source's unchanged position/index buffers. */ edges: ConvexEdge[]; } /** * Cork stopper — a bi-taper barrel referenced from its MIDDLE (the seal plane). * * Middle → top is the upper taper (sits above the vessel); middle → bottom is the lower taper (goes into the * neck). The head may flare *wider* than the middle (a lipped stopper, `\===/`) or taper narrower (a barrel). * `upperHeight: 0` gives a flat-topped lid; equal upper/lower tapers make a wine cork. * * {@link ApothecaryJar} scales the cork uniformly so the lower taper's radius meets the opening exactly at * the chosen depth — a watertight seal at any height. * * Local frame: bottom on Y=0, seal middle at Y=`lowerHeight`. */ export declare class CorkGeometry extends LatheGeometry { readonly radius: number; readonly bottomRadius: number; readonly middleY: number; readonly height: number; constructor({ radius, topRadius, bottomRadius, upperHeight, lowerHeight, radialSegments, }?: CorkGeometryOptions); } export declare interface CorkGeometryOptions { /** Radius at the MIDDLE — the seal, where the cork meets the rim. Defaults to `0.5`. */ radius?: number; /** Radius at the top (head). Wider than the middle flares a lipped head; narrower tapers it in. Defaults to `1.15 ×`. */ topRadius?: number; /** Radius at the bottom (tip). Defaults to `0.7 ×` the middle radius. */ bottomRadius?: number; /** Upper taper height, middle → top. `0` gives a flat-topped lid. Defaults to `0.18`. */ upperHeight?: number; /** Lower taper height, middle → bottom (the part that goes into the neck). Defaults to `0.28`. */ lowerHeight?: number; /** Circumference segments. Defaults to `16`. */ radialSegments?: number; } export declare interface CorkStopperOptions { /** Cork shape — upper (vertical collar) and lower (plug) heights, and tip radius. Top is locked to the seal. */ cork?: CorkGeometryOptions; /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */ corkDepth?: number; /** Cork material. A cork-brown default is supplied. */ material?: MeshStandardMaterial; } /** Resample outlines to a common point count; seam alignment and winding remain unchanged. */ export declare function correspondLoops(loops: Vector2[][], { count, method }?: CorrespondOptions): Vector2[][]; export declare interface CorrespondOptions { /** Requested output point count; resampling can omit authored corners. */ count?: number; /** Resampling method for each input loop. */ method?: ResampleMethod; } export declare interface CraneRevealClipOptions extends CameraClipTiming { /** Subject to reveal as the camera rises. */ target: Vector3; /** Vertical displacement in world units. Positive rises; negative descends. */ height: number; } /** * A medieval arched door: a plank slab under an arch, with wrought strap hinges and iron studs. * * **Every part of this is the same operation.** The slab, the hinge strap, and the hinge's terminal are * not three techniques — they are three OUTLINES, each closed, filled, and given depth. Once that lands, * the whole medieval vocabulary opens up: fleurs, quatrefoils, escutcheons, escapes. They are drawings. * * Note what is NOT here: a hole. The arch is part of the door's OUTLINE, not a void cut out of a * rectangle. `Shape.holes` is for strictly INTERIOR voids — a "hole" that reaches an edge is not a hole * at all, and the triangulator will fill straight across it and hand you a face you never asked for. * * **The origin is the HINGE, not the center.** `y = 0` is the sill, as everywhere else in this library, * but `x = 0` is the hinge edge and `z = 0` is the front face, where the straps are bolted and the pin * stands. Those two together are the hinge AXIS, so the door opens with `door.rotation.y` and nothing * more. The rule is the same one that seam-anchors a tile: anchor a thing where it JOINS the world. A * door joins at its hinge. * * The slab therefore lies entirely to one side of the origin — in `+x` for a left-hung door — and * entirely BEHIND it, in `-z`, with the ironwork proud of the face in `+z`. * * **Which way it opens is the sign of the angle, and the sign depends on the hand.** A door opens * OUTWARD, toward `+z`, because that is the side its hinges are on: * * ```ts * left.rotation.y = -angle; // outward * right.rotation.y = angle; // outward — mirrored, so the sign flips * ``` * * Reverse the signs and it swings inward instead, through where the wall would be. * * Returned as a single {@link Mesh} carrying a material ARRAY, with the wood and the iron in their own * geometry groups. A `Group` of two meshes would have worked too, and it would have pushed the cost onto * every caller: `door.castShadow = true` would silently do nothing, and you would be writing * `traverse(child => …)` forever. One mesh, one transform, one shadow flag. * * Dispose the geometry and both materials when finished. * * @example * ```ts * const door = createArchedDoor({ width: 1.4, hingeTerminal: "club" }); * door.position.x = -1.4 / 2; // hang the hinge on the jamb * scene.add(door); * * door.rotation.y = -0.9; // swings out on its hinge, because the hinge is its origin * ``` */ export declare function createArchedDoor(options?: ArchedDoorOptions): DoorLeaf; /** * A hard-edged checkerboard as a {@link DataTexture} — the classic chessboard or tile floor. * * Nearest filtering keeps the squares crisp instead of blurring them, and the texture repeats, so * a two-texel array can cover an arbitrarily large plane: * * @example * ```typescript * const texture = createCheckerboardTexture({ size: 2 }); * texture.repeat.set(8, 8); // 8×8 squares across the plane * * const floor = new Mesh(new PlaneGeometry(10, 10), new MeshStandardMaterial({ map: texture })); * floor.rotation.x = -Math.PI / 2; * ``` */ export declare const createCheckerboardTexture: ({ size }?: CheckerboardTextureOptions) => DataTexture; /** * A cork stopper seated in a vessel's rim — the shared cork-fit for corked vessels (jar, potion bottle, …). * * A lid: the top radius equals the middle, so any `upperHeight` rises as a VERTICAL collar above the seal, * never a wider head that could intersect the rolled rim. The cork is scaled UNIFORMLY so that, at * `corkDepth` up its lower taper, its radius equals the vessel's opening — a watertight seal at any depth; * rise the cork and it scales up to keep it. * * Give it the vessel's `.profile`, its `rim` option (`rimRoll` — the rolled rim shrinks the opening), and * the circumference segments to match. Returns the cork mesh, seated; add it to the vessel's group. */ export declare function createCorkStopper(profile: Vector2[], rimRoll: number, segments: number, { cork, corkDepth, material }?: CorkStopperOptions): Mesh; /** * Rise from the current view while gradually framing a subject; keep the final view. * Position rises along world Y. The starting look direction blends toward the * subject over the full duration, so the reveal remains gradual. * * @example * ```ts * playback.play(createCraneRevealClip({ * target: new Vector3(0, 0.5, 0), height: 6, duration: 5, * })); * ``` */ export declare function createCraneRevealClip(options: CraneRevealClipOptions): CameraClip; /** * Sample a cubic Bézier in XY, including both endpoints; segments must be positive. * * ``` * const points = [ * ...ParametricCurveUtils.createCubicCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the cubic curve * new THREE.Vector2(1.5, 3), // First control point (outward curve) * new THREE.Vector2(1.5, 4), // Second control point (outward curve) * new THREE.Vector2(0.5, 5), // End of the curve * 24, // Resolution of the quadratic curve * ), * ] * ``` */ export declare const createCubicCurvePoints: (start: Vector2, control1: Vector2, control2: Vector2, end: Vector2, segments?: number) => Vector2[]; /** * Sample damped radius x and linear height y; the scalar damping function determines endpoint x values. * * ``` * const points = [ * ...ParametricCurveUtils.createDampedCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the damped curve * new THREE.Vector2(1.5, 5), // End of the damped curve * 5, // Damping factor * 24, // Resolution of the damped curve * ), * ] * ``` */ export declare const createDampedCurvePoints: (start: Vector2, end: Vector2, damping: number, segments?: number) => Vector2[]; /** * Dolly in or out along the current view direction. * * @example * ```ts * playback.play(createDollyClip({ distance: -3, duration: 4 })); // dolly in * // Use distance: 3 to dolly out. Position changes; FOV stays the same. * ``` */ export declare function createDollyClip(options: DollyClipOptions): CameraClip; /** * Two leaves under one arch, each hung on its own jamb. * * **The arch is shared, and that is the entire trick.** Each leaf carries half of ONE ellipse spanning * the full opening — not its own smaller arch. Build two doors of half the width instead and each * crowns at its own center: you get an `M`. So the leaves are asymmetric, short at the hinge and tall at * the meeting stile, and they only make sense as a pair. * * **Opening them.** Each leaf's origin is its own hinge axis — the hinge edge, on the front face — so a * leaf swings with `rotation.y` alone: no pivot group, no offset. The leaves mirror, so **their angles * are opposite in sign**, and doors open OUTWARD, toward the face their hinges are on: * * ```ts * doors.left.rotation.y = -angle; // outward, toward +Z * doors.right.rotation.y = angle; * ``` * * Flip both signs and they swing inward. Give them different magnitudes and one stands ajar. The leaves * are ordinary meshes and the {@link Group} does not own their motion, which is deliberate — a door that * owns its own animation is a door you cannot animate any other way. * * Dispose each leaf's geometry and materials when finished. * * @example * ```ts * const doors = createDoubleDoor({ width: 2.6, archHeight: 0.9 }); * scene.add(doors); * * doors.left.rotation.y = -0.8; * doors.right.rotation.y = 0.8; * ``` * * @example * The default single door, split down the middle — same opening, same circle, two leaves. `archHeight` * is half the OPENING (not half a leaf), so it stays `0.65`, and the ironwork scales to the narrower * leaf. * * ```ts * const doors = createDoubleDoor({ * width: 1.3, // the whole opening — each leaf is 0.65 * archHeight: 0.65, // = width / 2, so the arch is a true semicircle * hingeLength: 0.42, // the stock 0.85 would overshoot a 0.65-wide leaf * studCols: 2, * }); * ``` */ export declare function createDoubleDoor(options?: DoubleDoorOptions): DoubleDoor; /** * Sample exponential x and linear y; x is scaled by end.x - start.x without endpoint normalization. * * ``` * const points = [ * ...ParametricCurveUtils.createExponentialCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the exponential curve * new THREE.Vector2(1.5, 5), // End of the exponential curve * 0.5, // Base of the exponential function * 2, // Exponential factor * 24 // Resolution of the exponential curve * ), * ] * ``` */ export declare const createExponentialCurvePoints: (start: Vector2, end: Vector2, base: number, factor: number, segments?: number) => Vector2[]; /** * Tour from the current view through destination waypoints; eases at each waypoint. * * @example * ```ts * // Start at the current camera position, then visit these destinations. * playback.play(createFlythroughClip({ * waypoints: [new Vector3(4, 3, 0), new Vector3(-5, 2.5, -4), new Vector3(0, 2, 5)], * duration: 12, * })); * ``` * @example * ```ts * // lookAt[i] is the subject to frame when arriving at waypoints[i]. * // Focus interpolates between subjects; omit lookAt to retain the starting focus. * playback.play(createFlythroughClip({ * waypoints: [new Vector3(4, 3, 0), new Vector3(-5, 2.5, -4), new Vector3(0, 2, 5)], * lookAt: [new Vector3(0, 0.5, 0), new Vector3(-3, 0.75, -2.5), new Vector3(1.5, 0.5, 2)], * duration: 12, * })); * ``` */ export declare function createFlythroughClip(options: FlythroughClipOptions): CameraClip; /** * Turn smoothly toward another subject while holding the camera position and FOV. * Uses a rotation blend so even a subject behind the camera has a defined turn. * * @example * ```ts * playback.play(createFocusTransferClip({ target: new Vector3(3, 0.5, -4), duration: 3 })); * ``` */ export declare function createFocusTransferClip(options: FocusTransferClipOptions): CameraClip; /** * Brief lens pulse relative to the current animated FOV, with zero offset at each end. * Safe to layer over Zoom: recovery follows its changing base FOV, not a saved value. * The resulting FOV is clamped to [1, 179] degrees. * * @example * ```ts * playback.play(createZoomClip({ target: new Vector3(), endFov: 35, duration: 3 })); * playback.play(createFovPulseClip({ amplitude: 8, duration: 0.8 })); * ``` */ export declare function createFovPulseClip(options: FovPulseClipOptions): CameraClip; export declare function createGeometryBuffers(): GeometryBuffers; /** * Gentle deterministic handheld motion, fading in and out over a finite duration. * Layers camera-local translation and pitch/yaw over the moving base without accumulating drift. * Frequency follows playback time, so timeScale slows the entire effect coherently. * * @example * ```ts * playback.play(createOrbitClip({ target: new Vector3(), duration: 20 })); * playback.play(createHandheldDriftClip({ intensity: 0.04, rotation: 0.008, duration: 20 })); * ``` */ export declare function createHandheldDriftClip(options: HandheldDriftClipOptions): CameraClip; /** * A split-timber log: a low-segment cylinder pushed off-round so the facets read as axe-hewn rather than * turned. * * Authored along local **Y at unit length** and roughly unit diameter, so callers scale it to the beam * they need — one geometry serves a whole rank of posts and rails at different sizes. * * **The perturbation is derived from the vertex's own position**, not from a random source. That keeps * the asset deterministic — the same timber every load, no seed to thread through — and keeps the end * caps watertight, because a cap vertex and the side vertex it coincides with compute the same offset. * Vertices on the axis are skipped: they have no radial direction to push along, and scaling them would * tear the cap open. * * Contrast {@link WeatheredPlankGeometry}, which is the *sawn* member — flat faces, square section, bow * and end-skew. This is the *round* one. A frame uses both: hewn posts carrying sawn boards. * * @example * ```typescript * // The default: a coarse 6-facet split log. * const post = new Mesh(createHewnTimberGeometry(), timber); * post.scale.set(0.14, 1.8, 0.14); * * // Finer and softer — the city-park footbridge tuning. * const rail = createHewnTimberGeometry({ * bottomRadius: 0.54, * radialSegments: 7, * frequency: [19.1, 13.7, 29.3], * amplitude: 0.065, * }); * ``` */ export declare function createHewnTimberGeometry({ topRadius, bottomRadius, radialSegments, heightSegments, frequency, amplitude, }?: HewnTimberGeometryOptions): CylinderGeometry; /** * Hexagonal tile floor, sized by tile *count* — you say how many tiles span the width, and the tile * radius is solved to make them fit. * * Reach for this when the tile count is what you care about ("a 10-across floor"), and let the tiles * come out whatever size they need to be. Use {@link createHexagonalTilesByRadius} when the tile * size is what you care about and the count should fall out instead. * * Tiles are laid in staggered columns and centered on the origin. Rows are filled to whatever depth * fits, so the tile count is `count * (however many rows fit)` — not `count` alone. * * @example * ```ts * // A 10x10 floor, ten tiles across. * const floor = createHexagonalTilesByCount({ * width: 10, * depth: 10, * height: 0.01, * count: 10, * gap: 0.01, * }); * scene.add(floor); * * // Tint each tile individually — InstancedMesh carries per-instance color. * floor.setColorAt(0, new Color("#c8b8a0")); * floor.instanceColor.needsUpdate = true; * ``` */ export declare function createHexagonalTilesByCount(options: HexagonalTileCountOptions): InstancedMesh; /** * Hexagonal tile floor, sized by tile *radius* — you say how big a tile is, and as many as fit are * laid down. * * The counterpart to {@link createHexagonalTilesByCount}: here the tile size is fixed and the count * falls out, which is what you want when the tiles have a real-world size. Tiles are laid in * staggered columns and centered on the origin; whatever does not fit is simply not placed, so the * floor may fall a little short of `width` / `depth`. * * @example * ```ts * // A 10x10 floor of 0.1-radius tiles — however many that turns out to be. * const floor = createHexagonalTilesByRadius({ * width: 10, * depth: 10, * height: 0.01, * radius: 0.1, * gap: 0.01, * }); * scene.add(floor); * * floor.count; // an output — you find out how many fit * ``` */ export declare function createHexagonalTilesByRadius(options: HexagonalTileRadiusOptions): InstancedMesh; /** * Directional impact with a damped rebound, layered over the current movement. * Starts and ends with zero displacement. Retriggering replaces the active effect. * * @example * ```ts * playback.play(createImpactKickClip({ * direction: new Vector3(-1, 0.3, 0), intensity: 0.3, duration: 0.7, * })); * ``` */ export declare function createImpactKickClip(options: ImpactKickClipOptions): CameraClip; /** One geometry with material groups 0 outer rind, 1 inner rind, 2 cut walls, 3 stem. * Stem dimensions are world units, matching PumpkinGeometry; they do not scale with rindRadius. */ export declare function createJackOLanternGeometry(options?: JackOLanternGeometryOptions): BufferGeometry; /** * A hollow ribbed rind with triangular eyes/nose and a toothed grin facing +Z. * Material groups: 0 outer skin, 1 inner skin, 2 cut walls. Returned geometry is non-indexed, * with flat normals. Skin UVs are spherical; each opening wall has its own unwrapped 0–1 strip. * * The face is triangulated in longitude/latitude space before wrapping. Corresponding inner and * outer boundaries are bridged to close the rind. This constructs openings; it does not subtract * from an existing mesh. The back seam and poles are closed explicitly. Caller owns the result. */ export declare function createJackOLanternRindGeometry({ rindRadius, rindSquash: squash, rindRibs: ribs, rindRibDepth: ribDepth, rindThickness: thickness, rindSubdivisions: detail, faceScale, }?: JackOLanternRindGeometryOptions): BufferGeometry; /** * Vertical landing dip followed by a damped rebound. The offset follows camera-local Y. * * @example * ```ts * playback.play(createLandingBumpClip({ intensity: 0.4, duration: 0.8 })); * ``` */ export declare function createLandingBumpClip(options: LandingBumpClipOptions): CameraClip; /** * A linear gradient as a {@link DataTexture} — the straight-ramp sibling of {@link createRadialGradientTexture}. * * A linear gradient varies only along one axis, so the image is just a tall strip a few columns wide (the * texture's V axis, first row → last). Rotate the texture (`texture.rotation`) for any other direction. * Computed in plain JS, so no DOM — builds headless, on either renderer. Stops interpolate in sRGB. * * Useful as a `scene.background` (a moody backdrop), a sky strip, or the fade of a rain/fog card. * * @example * ```typescript * scene.background = createLinearGradientTexture({ * stops: [ * { offset: 0, color: 0x28323f }, // bottom * { offset: 1, color: 0x0c1016 }, // top * ], * }); * ``` */ export declare const createLinearGradientTexture: ({ stops, size, easing, }: LinearGradientTextureOptions) => DataTexture; /** * Build the liquid mesh for a vessel from its `profile` and a {@link FillOptions}. * * The composition step: geometry from {@link LiquidFillGeometry}, appearance from the options. One function * fills any vessel. Returns `null` when the vessel is empty. The mesh is given `renderOrder = 0` so it * draws before the glass — give the glass `renderOrder = 1`, since their centres coincide and depth * sorting cannot order them. */ export declare function createLiquidFill(profile: Vector2[], options?: FillOptions, radialSegments?: number): Mesh | null; /** * Sample logarithmic x and linear y; scalar parameters must produce finite values over t ∈ [0, 1]. * * ``` * const points = [ * ...ParametricCurveUtils.createLogarithmicCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the logarithmic curve * new THREE.Vector2(1.5, 5), // End of the logarithmic curve * 0.5, // Base of the logarithmic function * 10, // Logarithmic factor * 24, // Resolution of the logarithmic curve * ), * ] * ``` */ export declare const createLogarithmicCurvePoints: (start: Vector2, end: Vector2, base: number, factor: number, segments?: number) => Vector2[]; /** * Circle the scene — showcase reel orbit. * * @example * ```ts * playback.play(createOrbitClip({ * target: new Vector3(0, 0.5, 0), * revolutions: 1, * duration: 10, // seconds; use ease: Easing.linear for steady angular speed * })); * ``` */ export declare function createOrbitClip(options: OrbitClipOptions): CameraClip; /** * Sample x = start.x + a·t² + b·t + c and linear y; end.x is unused. * * ``` * const points = [ * ...ParametricCurveUtils.createParabolicCurvePoints( * new THREE.Vector2(0.5, 2), // Start point * new THREE.Vector2(0.5, 5), // End point * 1, // Coefficient for t^2 (controls curvature) * 0, // Coefficient for t (linear component) * 0, // Constant term (vertical offset) * 24, // Resolution * ), * ] * ``` */ export declare const createParabolicCurvePoints: (start: Vector2, end: Vector2, a: number, b: number, c: number, segments?: number) => Vector2[]; /** * Travel through a point and continue beyond it, retaining the starting orientation. * The camera never turns back toward the point after passing it. Collision handling, * fades, and switching scenes belong to the host; completion keeps the endpoint. * If already at the target, travel along the current view direction instead. * * @example * ```ts * playback.play(createPassThroughClip({ target: new Vector3(0, 2.5, 0), beyond: 6, duration: 5 })); * // The host can switch scenes once playback.isMoving becomes false. * ``` */ export declare function createPassThroughClip(options: PassThroughClipOptions): CameraClip; /** * Atmospheric focus drift — slow Ken Burns sway while locked on a subject. * Not a full orbit; subtle back-and-forth for mood and screen capture. * * @example * ```ts * playback.play(createPendulumClip({ * target: new Vector3(0, 0.5, 0), * azimuthAmplitude: 0.12, oscillations: 2, duration: 24, * })); * ``` */ export declare function createPendulumClip(options: PendulumClipOptions): CameraClip; /** * Retreat from the current view with optional ascent, keeping the starting orientation. * The focus moves with the camera, so resuming manual controls preserves the view. * * @example * ```ts * playback.play(createPullAwayClip({ distance: 12, height: 4, duration: 6 })); * ``` */ export declare function createPullAwayClip(options: PullAwayClipOptions): CameraClip; /** Creates one grouped geometry: material 0 is rind, material 1 is stem. */ export declare function createPumpkinGeometry(options?: PumpkinGeometryOptions): BufferGeometry; /** Builds only the ribbed rind, resting on the local XZ plane. */ export declare function createPumpkinRindGeometry({ rindRadius, rindWidthSegments, rindHeightSegments, rindRibs, rindRibDepth, rindSquash, }?: PumpkinRindGeometryOptions): BufferGeometry; /** * Builds a standalone stem: a cylinder whose base pivot sits at the local * origin, resting on the XZ plane. This factory has no knowledge of anything it * sits on — lean, seating, and placement are the assembly layer's concern. */ export declare function createPumpkinStemGeometry({ stemTopRadius, stemBottomRadius, stemHeight, stemSegments, }?: PumpkinStemGeometryOptions): BufferGeometry; /** * Sample a quadratic Bézier in XY, including both endpoints; segments must be positive. * * ``` * const points = [ * ...ParametricCurveUtils.createQuadraticCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the quadratic curve * new THREE.Vector2(1.5, 5), // Control point (outward curve) * new THREE.Vector2(0.5, 5), // End of the curve * 24, // Resolution of the quadratic curve * ), * ] * ``` */ export declare const createQuadraticCurvePoints: (start: Vector2, control: Vector2, end: Vector2, segments?: number) => Vector2[]; /** * A soft radial falloff as a {@link DataTexture} — for the additive cards that give a light source * its bloom of haze. * * Pixels are computed here in plain JavaScript, so unlike a `CanvasTexture` this needs **no DOM** * and builds fine headless or in a worker. And unlike a TSL node gradient it produces a standard * texture, so it runs on **either renderer** rather than requiring `WebGPURenderer` — which matters * for near-field glows (lanterns, candles, flames) that have no other reason to demand WebGPU. * * Stops interpolate in **sRGB**, matching what a canvas gradient does, and the texture is tagged * `SRGBColorSpace` so color management converts it correctly. Pass `easing` to soften the kink each * stop otherwise leaves in the falloff's slope. * * @example * ```typescript * const material = new MeshBasicMaterial({ * map: createRadialGradientTexture({ * stops: [ * { offset: 0, color: 0xffcd8c, alpha: 0.45 }, * { offset: 0.25, color: 0xff963c, alpha: 0.14 }, * { offset: 1, color: 0xff6e1e, alpha: 0 }, * ], * }), * blending: AdditiveBlending, * transparent: true, * depthWrite: false, * }); * ``` */ export declare const createRadialGradientTexture: ({ stops, size, easing, }: RadialGradientTextureOptions) => DataTexture; /** * Create a random source. * * - **No seed** — wraps `Math.random()`. Unique every runtime; default for examples. * - **With seed** — {@link mulberry32} stream. Same seed ⇒ same sequence. */ export declare function createRandom(seed?: number): RandomSource; /** * Quick backward and upward kick, followed by smooth recovery to the animated base pose. * * @example * ```ts * playback.play(createRecoilClip({ distance: 0.2, pitch: 0.08, duration: 0.45 })); * ``` */ export declare function createRecoilClip(options: RecoilClipOptions): CameraClip; /** * Sample sigmoid x and linear y; sigmoid endpoint values need not equal 0 and 1. * * ``` * const points = [ * ...ParametricCurveUtils.createSigmoidCurvePoints( * new THREE.Vector2(0.5, 2), // Start of the sigmoid curve * new THREE.Vector2(1.5, 5), // End of the sigmoid curve * 20, // Sigmoid steepness factor * 24, // Resolution of the sigmoid curve * ), * ] * ``` */ export declare const createSigmoidCurvePoints: (start: Vector2, end: Vector2, a: number, segments?: number) => Vector2[]; /** * Scene-transition spiral — orbit upward while looking down at the scene. * Camera rises and optionally widens its orbit; `lookAt` stays on the ground * target so the view pitches into a bird's-eye survey (not a horizontal orbit). * * @example * ```ts * playback.play(createSpiralClip({ * target: new Vector3(0, 0.5, 0), height: 28, endRadius: 12, * revolutions: 1, duration: 12, * })); * ``` */ export declare function createSpiralClip(options: SpiralClipOptions): CameraClip; /** * A staircase of any number of flights, turning at each landing. * * A flight is the geometry ({@link StaircaseGeometry}); a *staircase* is the assembly. The landing * is the whole point of the assembly — it is where the run turns. Chain four quarter-turns and you * have wrapped a stairwell: the fifth flight climbs directly above the first, which is how a * stairwell in a tall building actually works. * * Every flight is the **same geometry, rotated** — never a second flight re-derived by hand in a * turned coordinate frame. Everything merges into one geometry, so a twenty-flight tower is still * one draw call. * * Local frame: the first flight starts at the origin, rises +Y, and runs +Z. * * @example * ```ts * // An L-shaped staircase: two flights, one landing, a quarter turn. * const stairs = createStaircase({ flights: 2, stepsPerFlight: 5 }); * scene.add(stairs); * * // A stairwell climbing five stories, wrapping a square shaft. * const tower = createStaircase({ flights: 20, stepsPerFlight: 8, turn: 90 }); * * // A straight run broken by landings, no turn. * const long = createStaircase({ flights: 3, turn: 0 }); * ``` */ export declare function createStaircase({ flights, stepsPerFlight, width, riserHeight, treadDepth, landingSize, turn, well, material, color, }?: StaircaseOptions): Mesh; /** * A window assembly, cut to fit an opening exactly — because it is cut from the SAME outline the wall was * punched with. * * ```ts * const opening = { width: 0.8, height: 1, arch: "ogee", x: -2, y: 1.5 }; * * const wall = new WallShape({ width: 6, height: 4, windows: [opening] }); * const window = createWindow({ opening, sill: true }); * window.position.set(opening.x, opening.y, wallThickness); // the wall's outer face * ``` * * The fit is not a coincidence to be maintained; it is the same `traceArch` call. Change the opening's * arch to a horseshoe and the glass, the frame and the hole all become horseshoes together. * * **Anchored at the SILL, centered on X** — `y = 0` is the sill and `z = 0` is the wall face the window * is mounted on, so hanging it is `position.set(opening.x, opening.y, faceZ)` and nothing else. The frame * and sill stand out in `+z`; the glass sits back inside the frame's depth. * * A {@link Group} with the parts named, not one merged mesh — glass has to be transparent and the frame * must not be, and a shared material array would force them to render together. Dispose each part. * * @example * ```ts * // A leaded pane in a stone wall, with the sill it needs to look built rather than cut. * const window = createWindow({ * opening: { width: 0.7, height: 0.9, arch: "pointed", archHeight: 0.5 }, * inset: 0.03, * outset: 0.05, * frameColor: "#2b2b2b", * sill: { jut: 0.12, horn: 0.06 }, * }); * ``` */ export declare function createWindow({ opening, frame, sill, glass, jamb, wallThickness, inset, outset, depth, curveSegments, frameMaterial, frameColor, glassMaterial, glassColor, glassOpacity, }: WindowOptions): WindowAssembly; /** * Impact wobble — short head-shake / recovery shake (gameplay feedback). * Decaying sinusoidal offset, not random noise. For showcase orbit rigs use * {@link createPendulumClip} instead. * * @example * ```ts * playback.play(createOrbitClip({ target: new Vector3(), duration: 10 })); * playback.play(createWobbleClip({ intensity: 0.15, duration: 0.8 })); * // Wobble layers over the orbit, then removes only its temporary offset. * ``` */ export declare function createWobbleClip(options: WobbleClipOptions): CameraClip; /** * Wood picket fence run — pointed planks on two stringers. * * Every {@link WoodPicketGeometry} option passes through, so the whole run takes its top treatment together: * `tipInset` and `tipDrop` sweep the pickets from a flat top through a dog-ear to a point, and are equal for * the 45° dog-ear the trade assumes. * * The iron fence's counterpart, and it differs in two ways that matter. Its spacing is specified as * the **gap** between planks rather than a center-to-center pitch, because a plank's width is a * lumber constant while the gap is the design choice. And its stringers sit **behind** the pickets * rather than intersecting them — nailed to the back, the way a real picket fence is built. * * Underneath it is the same run: `pitch = width + gap` feeds the same {@link resolveFenceSpan}. * * Local frame: the picket span is `[0, length]` along +X, planks inset a half-pitch from each end, * so runs tile with the gap carrying across the seam. Stringers sit behind, at -Z. * * Takes the same three forms as {@link createWroughtIronFence} — by count, by length, or both: * * @example * ```ts * // 1. BY COUNT — the run grows to fit 10 pickets. * const fence = createWoodPicketFence({ count: 10, gap: 0.18 }); * * // 2. BY LENGTH — fill a 6-unit span; the count falls out and the gap flexes to land the planks. * const fence = createWoodPicketFence({ length: 6, gap: 0.18 }); * fence.userData.span.count; // 11 * * // 3. BY BOTH — pack exactly 20 planks into 6 units; `gap` becomes an output. * const fence = createWoodPicketFence({ length: 6, count: 20 }); * fence.userData.gap; // solved * ``` */ export declare function createWoodPicketFence({ width, height, tipDrop, tipInset, thickness, gap, count, length, railHeight, railThickness, lowerRailY, upperRailY, railOverhang, material, color, }?: WoodPicketFenceOptions): Mesh; /** * Wrought-iron fence run — evenly pitched pickets held by an upper and lower rail. * * The rails pass *through* the pickets, the way a real punched-channel rail does. (Its wood * counterpart, {@link createWoodPicketFence}, nails its stringers to the back instead.) * * Local frame: the picket span is `[0, length]` along +X, with pickets inset a half-pitch from each * end. That inset is what lets runs tile: butt one against the next and the pitch carries across the * seam unbroken, so a fence assembled from several runs reads as one continuous fence. Rails extend * to `[-railOverhang, length + railOverhang]`, which is how they reach into a supporting post. * * A run is a single merged {@link Mesh} — one geometry, one material, one draw call — so * `castShadow` and `clone()` work directly on it. * * The resolved {@link FenceSpan} is on `mesh.userData.span` — read `length` from it to place the * next run, or call {@link resolveFenceSpan} up front with the same options. * * Dispose the geometry and material when finished. * * There are three ways to ask for a run, and they differ in *what you are holding fixed*: * * @example * ```ts * // 1. BY COUNT — the run grows. You control how the pickets and gaps look; the fence is as long * // as it needs to be. Reach for this when nothing constrains the length. * const fence = createWroughtIronFence({ count: 10, gap: 0.3 }); * fence.userData.span.length; // 4.0 — however long 10 pickets came out * * // 2. BY LENGTH — the span is fixed and the count falls out. The gap flexes a hair so the * // pickets land exactly on the span. Reach for this when the fence must fill an opening. * const fence = createWroughtIronFence({ length: 4.8, gap: 0.3 }); * fence.userData.span.count; // 12 * fence.userData.gap; // 0.30 — nudged, if it had to be, to fit * * // 3. BY BOTH — pack an exact number of pickets into an exact span. `gap` is then an OUTPUT, * // not an input: it is solved for, and whatever you passed is ignored. * const fence = createWroughtIronFence({ length: 4.8, count: 20 }); * fence.userData.gap; // 0.14 — tighter, because 20 pickets must fit in 4.8 * ``` * * In every case the resolved truth is on `userData.span` and `userData.gap` — read them rather than * assuming you got what you asked for. */ export declare function createWroughtIronFence({ gap, count, length, height, radius, finialHeight, finialRadius, finialScaleZ, radialSegments, railHeight, railThickness, lowerRailY, upperRailY, railOverhang, material, color, }?: WroughtIronFenceOptions): Mesh; /** * Focus punch — smooth FOV narrow toward a target. Keeps the current FOV on stop and the end FOV on complete. * * @example * ```ts * playback.play(createZoomClip({ target: new Vector3(0, 0.5, 0), endFov: 35, duration: 3 })); * // A larger endFov zooms out. FOV changes; position stays the same. * ``` */ export declare function createZoomClip(options: ZoomClipOptions): CameraClip; /** * Extruded **crossed-out wheel** — a clock or watch wheel: a toothed rim carried on radial spokes. See * {@link CrossedWheelShape} for the anatomy and the horological vocabulary. * * Where {@link GearGeometry} is a solid disc with a bore, this removes the web between hub and rim. Every tooth * option is inherited, so the crossings compose with the tooth profile — a crossed-out escapement wheel is * `{ crossings: 5, tipWidth: 0, lean: 1 }`. * * The clamps are load-bearing rather than defensive: the hub is held clear of the bore and the rim keeps * backing inward of the tooth valleys, since without either the cut-outs reach through and the teeth come away * from the wheel. {@link crossings} reports `0` when no annulus was left to cut, leaving the web solid. * * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, matching * {@link GearGeometry} so a rank of wheels on one arbor lines up on the plane they turn in. * * Material groups: **none** — one material for the whole wheel. * * @example * ```typescript * const wheel = new Mesh(new CrossedWheelGeometry({ teeth: 60, crossings: 5 }), brass); * ``` */ export declare class CrossedWheelGeometry extends ExtrudeGeometry { /** The bore radius actually used, after clamping inside the tooth profile. */ readonly holeRadius: number; /** Crossings actually cut. `0` means the web was left solid. */ readonly crossings: number; /** The hub radius actually used, after clamping clear of the bore. */ readonly hubRadius: number; /** Inner edge of the rim — where the cut-outs stop and tooth backing begins. */ readonly rimInnerRadius: number; /** The crossing width actually used, after clamping so spokes cannot overlap at the hub. */ readonly crossingWidth: number; constructor({ depth, ...shapeOptions }?: CrossedWheelGeometryOptions); } export declare interface CrossedWheelGeometryOptions extends CrossedWheelShapeOptions { /** Extrusion depth. Defaults to `0.06` — clock wheels are thin brass. */ depth?: number; } /** * A **crossed-out wheel** — a gear whose web has been cut away, leaving radial spokes. * * In horology the spokes are **crossings** and the operation is *crossing out*: clock and watch wheels were * crossed out to shed weight and brass, so the train had less inertia to drive. A wheel is described by the * count — a *five-crossing wheel*. Engineering calls the same thing spokes or arms, and the solid disc version * a web. * * The anatomy, and the parameters that control it: * * - **rim** — the toothed outer ring. Its depth inward of the valleys is {@link CrossedWheelShapeOptions.rimWidth}, * and it is what the teeth are attached to. * - **crossings** — the spokes, {@link CrossedWheelShapeOptions.crossings} of them at * {@link CrossedWheelShapeOptions.crossingWidth} thick. * - **hub** — the center disc, out to {@link CrossedWheelShapeOptions.hubRadius}. * - **bore** — the hole for the arbor, inherited from {@link GearShape}. * * Every tooth option is inherited, so a crossed wheel can also be spiked or leaning — a crossed-out ratchet is * `{ crossings: 5, tipWidth: 0, lean: 1 }`. * * **Crossings are constant width, not constant angle.** The half-angle a spoke subtends is `asin(w / 2r)`, * which narrows as the radius grows — so the spoke reads as a straight bar rather than a wedge that fattens * toward the rim. */ export declare class CrossedWheelShape extends GearShape { /** Crossings actually cut, after clamping. `0` means the web was left solid. */ readonly crossings: number; /** The hub radius actually used, after clamping clear of the bore. */ readonly hubRadius: number; /** Inner edge of the rim — where the cut-outs stop and tooth backing begins. */ readonly rimInnerRadius: number; /** The crossing width actually used, after clamping so spokes cannot overlap at the hub. */ readonly crossingWidth: number; constructor({ crossings, crossingWidth, hubRadius, rimWidth, crossingSegments, ...gearOptions }?: CrossedWheelShapeOptions); } export declare interface CrossedWheelShapeOptions extends GearShapeOptions { /** * Number of crossings — the radial spokes. Defaults to `5`. * * Fewer than `2` leaves the web solid, which is what {@link GearShape} already is. */ crossings?: number; /** Tangential thickness of each crossing, in world units. Defaults to `0.08`. */ crossingWidth?: number; /** Outer radius of the hub — the disc the crossings spring from. Defaults to `0.3`. */ hubRadius?: number; /** * Material kept inward of the tooth valleys, holding the teeth onto the rim. Defaults to `0.1`. * * Take this to zero and the teeth have nothing behind them: the cut-outs would reach the valley floor and * the rim would fall apart into loose teeth. */ rimWidth?: number; /** Segments along each cut-out's inner and outer arcs. Defaults to `6`. */ crossingSegments?: number; } /** * Cross headstone — a vertical shaft crossed by a horizontal arm. * * `height` is the REAL height: the shaft rises from the base to exactly `height`, so a `1.15` cross is * `1.15` tall. (It used to secretly build to 60% of the number you gave it, with the bar riding too high * and the slab too thick.) * * Local frame: base on Y=0, centered on X/Z. */ export declare class CrossHeadstoneGeometry extends BufferGeometry { readonly width: number; readonly height: number; readonly depth: number; constructor({ width, height, depth, crossbar, }?: CrossHeadstoneGeometryOptions); } export declare interface CrossHeadstoneGeometryOptions { /** Arm span — the full horizontal extent. Defaults to `0.55`. */ width?: number; /** Total height, base to top of the shaft. Defaults to `1.15`. */ height?: number; /** Slab depth. Defaults to `0.14`. */ depth?: number; /** * Where the crossbar crosses, as a fraction of `height`. Defaults to `0.68`. * * A Latin cross carries its bar high — a short arm above, a long shaft below. `0.5` centers it (a * Greek cross); above `0.7` starts to look top-heavy. */ crossbar?: number; } /** * Choose a coordinate projection from the largest absolute position component; this does not use face normals. * * ``` * const cubeVertices = [ * [-1, -1, 1], * [1, -1, 1], * [-1, 1, 1], * [1, 1, 1], * ]; * * const cubeUVs = cubicUVMappingBatch(cubeVertices); * ``` */ export declare function cubicUVMapping(vertex: [number, number, number]): [number, number]; export declare function cubicUVMappingBatch(vertices: [number, number, number][]): [number, number][]; /** * A curtain panel — a pleat wave lofted downward, with a tieback cinching it. * * Look DOWN on a hanging panel and its plan section is a periodic wave; the whole thing is that wave * carried down the drop. It is a LOFT and not a sweep, because the section does not keep its shape: its * amplitude is re-solved at every height as the leading edge moves, and its profile relaxes toward a * sine as the cloth gets further from the stitched heading. * * **The fabric length is the conserved quantity and everything else follows from it.** The panel is cut * once at `fullness × width`, and no dial here is allowed to change that. So when the tieback narrows * the span, the local fullness rises — same cloth, less width — and the folds deepen on their own. That * is why there is no fold-depth option: it is an output. * * The leading edge runs through THREE anchors — at the rod, at the tieback, at the hem — which is what * lets a panel drop vertically from its tie or flare back out into an hourglass without either being a * special case. See {@link CurtainPanelGeometryOptions.hemPull}. * * By default only that edge moves; the outer one is the RETURN and stays pinned against the wall, so a * single panel is an L and the hourglass belongs to the pair. {@link CurtainPanelGeometryOptions.outerPull} * releases the return, for the panels that genuinely have none. * * **Origin is the rod**, at `y = 0`, with the cloth hanging to negative Y and the panel's outer edge at * `x = 0` — so a pair is this geometry and a second built with `mirror: true`. The same convention as * {@link SwagGeometry} and {@link CascadeGeometry}. * * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`. * * @example * ```ts * // A thin curtain: straight down from the tieback rather than flaring back out. * const panel = new Mesh( * new CurtainPanelGeometry({ pull: 0.42, hemPull: 0.42 }), * new MeshStandardMaterial({ color: 0xb8ac93, roughness: 0.92, side: DoubleSide, flatShading: true }), * ); * ``` */ export declare class CurtainPanelGeometry extends BufferGeometry { constructor({ width, drop, fullness, pleats, pleat, relax, tiebackHeight, topPull, pull, hemPull, outerPull, mirror, slack, widthSegments, heightSegments, }?: CurtainPanelGeometryOptions); } export declare interface CurtainPanelGeometryOptions { /** Finished width of the panel at the rod. Defaults to `1.4`. */ width?: number; /** How far the panel drops from the rod. Defaults to `3.2`. */ drop?: number; /** * Fabric width ÷ rod width. Defaults to `2.5`. * * **The design input the trade actually uses** — 2× is skimpy, 2.5× standard, 3× luxurious. Fold depth * is not an option anywhere on this class because it is an OUTPUT of this: the cloth is a fixed length, * and how deep its folds run is whatever fitting that length into the available width demands. */ fullness?: number; /** Number of pleats across the heading. Defaults to `9`. */ pleats?: number; /** The heading. Defaults to `"pinch"`. See {@link CurtainPleat}. */ pleat?: CurtainPleat; /** * How far the plan section relaxes toward a sine as it descends. Defaults to `0.55`. * * A heading is stitched and holds whatever shape the pleat gives it; a hem is free, and free cloth * takes the smooth shape. At `0` the panel keeps its heading's crispness all the way to the floor, * which reads immediately as wrong. */ relax?: number; /** * Where the tieback cinches, `0` at the rod and `1` at the hem. Defaults to `0.62`. */ tiebackHeight?: number; /** * How far the leading edge is drawn in AT THE ROD, as a fraction of {@link width}. Defaults to `0`. * * Zero puts the panel at full width where it is hung, so a pair very nearly meets in the middle. Raise * it to start the pair already parted at the top. */ topPull?: number; /** * How far the leading edge is drawn in AT THE TIEBACK. Defaults to `0.42`. * * **This is a constraint on the panel's WIDTH, not a force on the cloth.** Narrowing the span the fixed * fabric has to cross raises the local fullness, and the folds deepen because they cannot do anything * else. Nothing here pushes any fabric sideways. */ pull?: number; /** * How far the leading edge is drawn in AT THE HEM. Defaults to `0.12`. * * This is the dial that decides what a panel does BELOW its tieback, and the useful answers span its * whole range. Set it to `0` and the panel flares fully back out to its rod width — the widest * hourglass, which is a heavy curtain with plenty of material in the base. Set it equal to * {@link pull} and the leading edge falls straight from the tie, a vertical drop parallel to the outer * edge, which is what a thin curtain does because it has no material to flare with. Above `pull` it * keeps narrowing, tapering to the floor. * * The default sits a little off zero deliberately. A tieback holds some cloth back permanently, so a * real panel rarely recovers its full width at the floor — and a default of exactly `0` would leave * the hem looking like a fixed consequence of the tie rather than something the caller controls. */ hemPull?: number; /** * How much the OUTER edge follows the leading edge, `0` to `1`. Defaults to `0`. * * At `0` the outer edge is pinned at {@link width} and never moves, which is right for a rod-hung * curtain: that edge is the RETURN, wrapping back to the wall where a bracket holds it. One panel is * then an L — one edge curved, one straight — and the hourglass you see in photographs belongs to the * PAIR, each half contributing one curve. * * At `1` the outer edge draws in by exactly as much as the leading edge, and a single panel becomes a * symmetric hourglass on its own. Between the two it follows partway. This is not a stylistic dial: a * panel with no return is a real thing — a stage curtain cinched at its middle, a portière in a * doorway with no wall to return to, a free-hanging banner tied in the centre — and none of those can * be built with the outer edge pinned. * * The two draws are scaled down together if they would close the panel, so the ratio between them * survives and the cloth stays centred rather than one edge overrunning the other. */ outerPull?: number; /** * Mirror the panel about `x = 0`, for the other half of a pair. Defaults to `false`. * * **Use this rather than rotating or negatively scaling a second copy in the scene.** Turning a panel * through 180° about Y maps `(x, y, z)` to `(−x, y, −z)`, which flips the DEPTH as well as the width — * so one panel's pleats face the room and the other's face the wall. On a heading that is symmetric * about zero, `pencil`, `box` and `knife`, that is invisible — measured, their depth ranges really are * symmetric, so rotating happened to be harmless. On `pinch` it is not: its section runs from −1 to * +1.9, so negating the depth buries its pleats behind the flats and the pair stops matching. * A negative scale would keep the depth but invert the winding instead. * * This reflects only `x` and re-winds the surface to suit, so both halves of a pair present the same * face to the room. */ mirror?: boolean; /** * How the leading edge curves between its three anchors. Defaults to `0.7`. * * `0` runs straight lines from rod to tie to hem, giving a hard V at the tieback. `1` eases into and * out of every anchor, which bows each half. The default leans toward the curve, because hung cloth * bows rather than creasing into a straight line between its anchors. */ slack?: number; /** * Samples across the width. Defaults to `160`. * * Rounded up so each pleat gets a multiple of four, putting a sample on every extremum of the wave. * Tessellation only — the silhouette does not move with it. */ widthSegments?: number; /** Samples down the drop. Defaults to `40`. */ heightSegments?: number; } /** * The heading — how the fullness is taken up where the panel meets the rod. * * - `pinch` — the French pleat. Flat spans lying BACK, with the fullness pinched into tight groups that * stand proud toward the room. The flat is the feature: it is what makes a pinch pleat read as * tailored rather than gathered. * - `pencil` — continuous rounded gathers, very close to a true sinusoid in plan. * - `box` — flat front and back, square in plan, with the folds turned at the corners. * - `knife` — every fold leaning one way. Triangular in plan. */ export declare type CurtainPleat = "pinch" | "pencil" | "box" | "knife"; /** Deforms existing vertices in geometry-local coordinates; never subdivides the mesh. */ export declare interface CurveDeformationOptions { axis?: "X" | "Y" | "Z"; anchor?: "Start" | "Center" | "End"; fit?: "Fit guide" | "Keep axis length"; /** Rotation around the source axis, in radians. */ roll?: number; /** Uniformly scale the guide to this length; defaults to the sampled curve length. */ guideLength?: number; /** Number of curve intervals (default 256). Independent of mesh tessellation. */ samples?: number; normals?: "Jacobian" | "Facet"; /** Local fold detection only; does not certify global non-intersection. */ onInvalid?: "throw" | "report"; } /** * Sample a Three Curve by arc length using getPointAt/getTangentAt, including both endpoints. * Tangent accuracy follows the supplied Curve implementation. * * ```ts * const curve = new CatmullRomCurve3(points, false, "centripetal"); * const path = curvePath(curve, 64); * ``` */ export declare function curvePath(curve: Curve, segments?: number): PathPoint[]; /** * Project an ordered ring along axis to two bounding planes, inserting points where plane ownership changes. * Hit-distance differences are linear along an edge, so crease fraction is f0 / (f0 - f1); avoid parallel axes. * * ```ts * // Two hips meeting at a roof apex: each cap is cut against its two neighbors. * const bound = (mine: Vector3, theirs: Vector3): CutPlane => ({ * point: apex, * normal: mine.clone().sub(theirs).normalize(), * }); * const points = cutEnd(ring, direction, [bound(mine, previous), bound(mine, next)]); * const geometry = cutEndGeometry(points, direction); * ``` */ export declare function cutEnd(ring: Vector3[], axis: Vector3, planes: [CutPlane, CutPlane], { stopAt }?: CutEndOptions): CutPoint[]; /** * Build flat-shaded, nonindexed sides and caps from CutPoints; requires a convex source section. * Each planar end facet is fanned separately; axis is accepted but unused. */ export declare function cutEndGeometry(points: CutPoint[], axis: Vector3): BufferGeometry; export declare interface CutEndOptions { /** * first selects the smaller axis parameter (intersection of halfspaces); last selects the larger (union). * Use finite forward hits from ring points inside both bounds. */ stopAt?: "first" | "last"; } /** * Member-end plane whose normal points into the allowed region. For unit outward member axes, * a shared miter normal is normalize(a_i - a_j); swapping axes reverses it. */ export declare interface CutPlane { point: Vector3; normal: Vector3; } /** Original ring point, projected endpoint, and selected bounding-plane index. */ export declare interface CutPoint { /** Source ring position. */ start: Vector3; /** Projected endpoint. */ end: Vector3; /** `0` or `1` for the plane it met, or `-1` for a point sitting exactly on the crease between them. */ owner: number; } /** * Project a convex ring to two bounds at each end, along ±axis, splitting at both ends’ crease crossings. * Returns a flat-shaded nonindexed solid; selected hits must be finite and lie in the intended direction. */ export declare function cutSegment(ring: Vector3[], axis: Vector3, { start, end }: SegmentBounds, { stopAt }?: CutEndOptions): BufferGeometry; /** * A seamless backdrop: a wall curving into a floor with no visible join. A CYCLORAMA — an infinity cove, * or in a photographer's words simply a SWEEP, after the roll of paper it imitates. * * Stands with its back wall on `z = 0` rising in `+Y`, and its floor running toward `+Z`, centered on X. * A development and presentation aid like {@link GroundGrid}, not scene content. * * **The bend has exactly one control, and that is a property of the shape rather than a simplification.** * The corner is always 90°, so the arc is fully determined by its `radius`. `width`, `height` and `depth` * only say where the flats END; none of them touches what the curve does. * * **Why the join disappears.** The arc's center sits at `(radius, radius)` — one radius in from the wall * and one up from the floor — which is the only place a circle can be tangent to both planes at once. At * tangency the curve leaves each flat traveling in exactly that flat's own direction, so there is no * crease for light to catch. Move the center anywhere else and a corner appears, however smooth the * geometry. * * **Shading: this is the one place `flatShading` is wrong.** Every other low-poly surface in this library * wants to read as intentionally faceted; a cyclorama wants to read as continuous, and faceting IS seeing * the bend. The geometry is therefore INDEXED on purpose, so `computeVertexNormals` averages across each * seam along the profile and the cove shades as one surface. Supply your own material and it must be * smooth, or the whole thing collapses into a fan of bands. * * **Choosing `segments` by measurement.** {@link Cyclorama.sagitta} reports how deep each facet dips * inside the true arc — `r · (1 − cos(θ/2))`. On a `0.7` radius, 3 segments dips 24mm and the banding is * obvious; 12 dips 1.5mm and it is not. Compare it against how close the camera gets rather than guessing. * * @example * ```ts * const backdrop = new Cyclorama({ width: 4, radius: 0.9 }); * scene.add(backdrop); * backdrop.sagitta; // how visible the faceting is, in world units * backdrop.dispose(); * ``` */ export declare class Cyclorama extends Mesh { #private; /** The cove radius actually used, after clamping to `min(height, depth)`. */ readonly radius: number; /** * How far each facet's chord dips inside the true arc, in world units — the thing an eye catches. * Raise `segments` until this is small against the distance the backdrop is seen from. */ readonly sagitta: number; constructor({ width, height, depth, radius, segments, color, material, }?: CycloramaOptions); /** Releases the geometry, and the material when this backdrop made it. */ dispose(): void; } export declare interface CycloramaOptions { /** Extent across, along X. Defaults to `3`. */ width?: number; /** How far the back wall rises. Defaults to `1.8`. */ height?: number; /** How far the floor runs toward the camera. Defaults to `1.8`. */ depth?: number; /** * The cove's radius — **the only control the bend has.** Defaults to `0.7`. * * A cyclorama's corner is always 90°, and a quarter arc is fully determined by its radius, so there is * no span or angle to give. Clamped to `min(height, depth)`, because a curve larger than its own flats * would run past the ends of the sheet; the value used is reported as {@link Cyclorama.radius}. */ radius?: number; /** * How finely the cove is cut. Defaults to `12`. * * **This one is not a style knob.** A cyclorama exists so the bend is not visible, and faceting is the * bend becoming visible — see {@link Cyclorama.sagitta} for how to choose it by measurement rather than * by eye. */ segments?: number; /** Backdrop tint. Defaults to `0xd8d5d0` — a paper gray. */ color?: ColorRepresentation; /** * A material to use instead of the default. * * **Do not give it `flatShading: true`.** The house style everywhere else in this library is faceted, * and here it defeats the object entirely — see the note on shading below. */ material?: Material; } /** * Map angle around Y to u and raw height y to v; no height normalization or seam splitting. * * ``` * const cylinderVertices = [ * [1, 0, 0], // Vertex on the "equator" * [0, 1, 0], // Vertex on the top * [0, -1, 1], // Vertex on the bottom * [-1, 0, 0], // Opposite side of the equator * ]; * * const cylinderUVs = cylindricalUVMapping(cylinderVertices); * ``` */ export declare function cylindricalUVMapping(vertices: [number, number, number][]): [number, number][]; /** * Deterministic crooked broadleaf tree with a sparse, instanced crown. * * One merged low-poly branch skeleton plus an {@link InstancedMesh} of faceted leaf clusters tinted per * instance from {@link DeciduousTreeOptions.leafPalette}. The trunk's deliberate lean supplies the large * silhouette; recursive branching supplies the gnarl. * * Local frame: **grows from the origin**, so the base sits flat on the `y = 0` plane and the tree occupies * `+Y`. That flatness comes from {@link DeciduousTreeOptions.baseRise}; without it the leaning trunk's bottom * face tilts and sinks below the ground. * * Two draw calls regardless of crown size — one for the merged branches, one for every leaf cluster. Both are * exposed as {@link branches} and {@link leaves} rather than left to be dug out of `children`. * * **This factory owns its materials**, unlike the geometry classes. That is faithful to the scene it came * from, where bark color and leaf palette are part of the asset's identity rather than a consumer choice. * Call {@link dispose} to release them. * * @example * ```typescript * const tree = new DeciduousTree({ seed: 0xa711 }); * scene.add(tree); * ``` */ export declare class DeciduousTree extends Group { #private; /** The merged branch skeleton — one draw call however deep the branching goes. */ readonly branches: Mesh; /** Every leaf cluster, tinted per instance. `count` is the cluster total. */ readonly leaves: InstancedMesh; constructor({ seed, trunkRadius, segmentLength, maxDepth, leafDensity, barkColor, leafPalette, leafColors, leafSize, clustersPerPoint, baseRise, }?: DeciduousTreeOptions); /** Release the geometries and materials this factory created. */ dispose(): void; } export declare interface DeciduousTreeOptions { /** Seed for the deterministic stream. Defaults to `0xa711`. Shapes differ from the source scene's. */ seed?: number; /** Trunk radius at the base. Defaults to `0.32`. */ trunkRadius?: number; /** Length of one branch segment before taper. Defaults to `0.66`. */ segmentLength?: number; /** Recursion limit for branching. Defaults to `4`. */ maxDepth?: number; /** Fraction of crown points that receive leaf clusters. Defaults to `0.72`. */ leafDensity?: number; /** Bark color. Defaults to `"#332419"`. */ barkColor?: string; /** * Colors sampled per leaf cluster. Defaults to a set of summer greens. * * **The palette is what makes this tree a season**, and nothing else does. Swapping in rust and ochre * gives an autumn tree; swapping in pale pinks and raising `clustersPerPoint` gives a cherry in blossom. * Both are studies rather than subclasses — see `Studies › Trees` — because a season is ten numbers, not * a different tree. The default stays green so the class is not named for one family and dressed as one * member of it. */ leafPalette?: string[]; /** * Optional per-cluster sampler, overriding leafPalette. Writes a working-space Color. * Index follows visible crown-point order, then cluster order. The seeded color stream * is independent of placement; sampling more random values does not move leaf clusters. * Bark remains controlled by barkColor. Omit to preserve the original palette and seeded output. */ leafColors?: ColorSampler; /** Leaf cluster radius. Defaults to `0.38`. */ leafSize?: number; /** Clusters placed at each crown point. Defaults to `2`. */ clustersPerPoint?: number; /** * Height of the straight vertical rise before the trunk starts leaning. Defaults to `0.35`. * * This is what lets the base sit FLAT. The trunk leans from its first segment, so without a rise the * bottom face is tilted with it and its low edge sinks below `y = 0` by roughly * `trunkRadius × sin(lean)` — measured at `-0.087` on the default seed. One vertical segment makes the * tangent at the base exactly UP, so the face lies in the ground plane. * * A correction to the PATH, not to the geometry: a real trunk rises out of the earth before it does * anything interesting. Same fix, same reasoning as {@link GnarledTreeGeometry}'s `baseRise`. Set `0` to * see the original tilt. */ baseRise?: number; } /** * The stock cemetery: mostly plain rounded and square stones, the occasional cross, and obelisks that * stand out because they are fewer. The rounded family dominates and fans into four tops — a full-width * semicircle, a shouldered one, a gently curved segmental, and a gothic point. * * These are relative weights, not counts: a row draws from them, so the mix holds at any `count`. */ export declare const DEFAULT_HEADSTONE_STYLES: readonly HeadstoneStyle[]; /** Use an open Three.js Curve, including splines. The selected source anchor retains its position and frame. */ export declare function deformAlongCurve(source: BufferGeometry, curve: Curve, options?: CurveDeformationOptions): { diagnostics: { minDet: number; folded: number; chordError: number; selfIntersectionsChecked: false; }; map: (point: Vector3) => Vector3; guide: Vector3[]; length: number; guideLength: number; usedLength: number; extension: number; anchorPoint: Vector3; geometry: BufferGeometry< NormalBufferAttributes, BufferGeometryEventMap>; }; /** * Derive an independent sub-stream seed from a master seed and domain salt. * * XOR the salt before mixing so each subsystem gets its own mulberry32 stream * without sequential `seed + 1` collision risk. Use stable hex constants per * domain (`0x101` books, `0x202` fog, `0x303` windows, …). * * @example * ```ts * const master = 1337; * const bookRng = createRandom(deriveSubSeed(master, 0x101)); * const fogRng = createRandom(deriveSubSeed(master, 0x202)); * ``` */ export declare function deriveSubSeed(masterSeed: number, salt: number): number; /** * Group indices: * 0. Desk surface * 1. Desk legs */ export declare class DeskGeometry extends BufferGeometry { constructor(); } /** * Extruded diamond prism — the card suit. See {@link DiamondShape}. */ export declare class DiamondGeometry extends ExtrudeGeometry { constructor({ depth, curveSegments, ...shapeOptions }?: DiamondGeometryOptions); } export declare interface DiamondGeometryOptions extends DiamondShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; /** Curve resolution of the concave sides — the low-poly knob. Defaults to `16`. */ curveSegments?: number; } /** * Diamond lattice leading — the cames of a leaded light, cut into the opening at both ends. * * Every came SPANS the opening, and both of its ends are cut by the boundary itself rather than stopping * square. That is the whole point: a square-ended bar leaves teeth poking out through the frame, which is * what has always made an arched lattice hard. Here each ring point of the came runs along its own axis to * whichever segment of the outline it meets, with the ring split wherever that choice changes, so the ends * follow the arch exactly as closely as the arch itself is cut. * * **Not "arched" in the name, deliberately.** `arch: "square"` is a flat head, so a rectangular light and * a gothic one are the same geometry with different points — exactly as {@link WindowFrameGeometry} rings * any arch without saying so in its name. Two names would rebuild the split this construction removes. * * Cames CROSS one another and are left to interpenetrate, which is correct rather than lazy: lead came * crosses lead came, and an X-junction has no bisector to share. * * Baked to a single `BufferGeometry` — one draw call for the whole leading. * * Drawn at the ORIGIN — centered on X, sill at `y = 0` — whatever the opening's own `x` and `y` say, so * one lattice can be positioned into many openings and so it lands on a `WindowFrameGeometry` built from * the same description. Material groups: none; pass one material, not an array. * * A leaded light is the obvious use, but nothing here knows that. The same thing is a garden trellis, a * gate infill, or a screen. * * @example * ```ts * const opening = { width: 1.24, height: 1.15, arch: "pointed", archHeight: 0.78 } as const; * * const lattice = new Mesh(new DiamondLatticeGeometry({ opening }), lead); * const frame = new Mesh(new WindowFrameGeometry({ opening }), iron); * ``` */ export declare class DiamondLatticeGeometry extends BufferGeometry { /** How many cames were built. Short offcuts that clip a corner are dropped, so this is not derivable. */ readonly cameCount: number; constructor({ opening, angle, spacing, phase, cameWidth, cameDepth, cameSides, curveSegments, }?: DiamondLatticeGeometryOptions); } export declare interface DiamondLatticeGeometryOptions { /** * The opening the lattice fills. The SAME description a wall is punched with and a * {@link WindowFrameGeometry} is built from, so the three agree by construction. * * **There is no separate rectangular case.** `arch: "square"` is a flat head — an arch-shaped hole with * no curve in it — so a mullioned rectangle and a gothic light are one geometry with different points. */ opening?: WallOpeningOptions; /** * Half the angle between the two came families, in degrees. Defaults to `45`, which is the square * diamond everyone pictures. * * Lower leans the quarries tall, higher leans them wide. The families are symmetric: `+angle` and * `−angle`. */ angle?: number; /** * Perpendicular distance between neighboring cames. Defaults to `0.19`. * * Measured across the cames rather than along an axis, so it means the same thing at any `angle` — * spacing measured on an axis would compress as the lattice leans. */ spacing?: number; /** * Slides the whole grid across the opening, in world units. Defaults to `0`. * * The difference between a quarry centered on the crown and a came running up it. Nothing else moves the * pattern relative to the opening, and it is what decides which cames clip a corner and get dropped. */ phase?: number; /** Width of the came ACROSS the glass — what you see from the front. Defaults to `0.022`. */ cameWidth?: number; /** * Depth of the came THROUGH the glass. Defaults to `cameWidth`, a square section. * * Free to vary because it is the one dimension none of the cutting depends on: a came's end is decided * by casting in the opening's own plane, so a point's depth never reaches the boundary maths. Real lead * is deeper than it is wide, and a flat came reads as painted rather than leaded. */ cameDepth?: number; /** Sides on the came's section — the low-poly knob. `4` is square lead, `12` reads round. Defaults to `4`. */ cameSides?: number; /** * How finely the arch is followed. Defaults to `20`. * * This is also the ceiling on the came ENDS: they are cut against the outline's segments, so a came can * never be finer than the boundary it dies into — and is never rougher. */ curveSegments?: number; } /** * A leaded light: glass, diamond leading, and the frame that carries it. * * **Why these three and not some other bundle.** Leading has to be framed — cames cannot support cut glass * on their own — so this is a unit that exists in the world rather than a convenience grouping. The test * worth applying to any factory: *is the assembly a thing people have a name for?* A leaded light is. * * **A factory exposes what the ASSEMBLY decides, and delegates the rest.** So `cellsX` / `cellsY` are here * and `angle` / `spacing` are not — alignment determines them, and they are reported on the instance * rather than asked for. `cameWidth` is here because the frame is sized from it. `cameSides` is not, * because it has to agree with nothing; reach for {@link DiamondLatticeGeometry} directly for that. * * All three parts are built from ONE `opening`, which is also what you punch the wall with, so nothing has * to be kept in step by hand. Every part is exposed as a field, so any of them can be replaced without * forking this. * * Local frame: centered on X, sill at `y = 0`, facing `+Z` — the anchor the whole trio shares, so the * window drops straight into a wall hole built from the same description. * * @example * ```ts * const opening = { width: 1.24, height: 1.15, arch: "pointed", archHeight: 0.78 } as const; * * const wall = new Mesh(new ExtrudeGeometry(new WallShape({ windows: [opening] }), { depth: 0.3 }), stone); * const light = new DiamondLatticeWindow({ opening, cellsX: 4, cellsY: 4 }); * ``` */ export declare class DiamondLatticeWindow extends Group { /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */ readonly cutout: Path; readonly lattice: Mesh; readonly frame?: Mesh; readonly glass?: Mesh; readonly cellsX: number; readonly cellsY: number; /** The angle the cell counts worked out to, in degrees. An OUTPUT — see `cellsX`. */ readonly angle: number; /** The came spacing the cell counts worked out to. An OUTPUT. */ readonly spacing: number; constructor({ opening, cellsX, cellsY, cameWidth, cameDepth, curveSegments, frame, glass, leadColor, frameColor, glassColor, glassEmissive, glassEmissiveIntensity, }?: DiamondLatticeWindowOptions); /** Release every geometry and material this window owns. */ dispose(): void; } export declare interface DiamondLatticeWindowOptions { /** * The opening. **The same object that punches the wall** — pass one description to both and the hole and * the window cannot drift apart. * * Any arch, including `square`: a flat head is an arch-shaped hole with no curve in it, so a rectangular * light and a gothic one are this one window with different points. */ opening?: WallOpeningOptions; /** * Diamonds across the opening's width. Defaults to `4`. * * Counts rather than an angle, because alignment is the point: with counts, the diamonds' corners land * exactly on the jambs, the sill, and the springing line. Above the springing the head cuts what it * cuts — a curve is not a whole number of anything, and real leaded lights accept that too. */ cellsX?: number; /** Diamonds from the sill up to the springing. Defaults to `4`. */ cellsY?: number; /** * Width of the came across the glass. Defaults to `0.022`. * * An assembly option, not a lattice one: the frame's inner band is sized from it, which is what makes * the leading and the frame read as one piece of work rather than two. */ cameWidth?: number; /** Depth of the came through the glass. Defaults to `cameWidth * 1.4` — real lead is deeper than wide. */ cameDepth?: number; /** How finely the arch is followed. Shared by all three parts so they tessellate identically. Defaults to `24`. */ curveSegments?: number; /** The frame. `false` omits it; an object overrides what the assembly would have chosen. */ frame?: boolean | { inset?: number; outset?: number; depth?: number; }; /** The glass. `false` omits it; `rebate` runs the pane past the opening into a frame's groove. */ glass?: boolean | { rebate?: number; }; /** Lead tint. Defaults to `#0c0f14`. */ leadColor?: ColorRepresentation; /** Frame tint. Defaults to the lead's, because the two are one piece of ironwork. */ frameColor?: ColorRepresentation; /** Glass tint. Defaults to `#6a7d8c`. */ glassColor?: ColorRepresentation; /** Glass emissive, for moonlit or storm backlight. Defaults to off. */ glassEmissive?: ColorRepresentation; /** Defaults to `0`. */ glassEmissiveIntensity?: number; } /** * Diamond profile — the fourth card suit, with gently concave sides. * * Four points (top, right, bottom, left) joined by quadratic curves that bow toward the center. A rhombus * with straight edges reads as flat and kite-like; the inward `)` sweep is what makes it a *card* * diamond. Drop `concavity` to `0` for the plain rhombus. * * Drawn counter-clockwise from the top point, centered on the origin — the family convention shared with * {@link SpadeShape}, {@link HeartShape}, {@link ClubShape}. */ export declare class DiamondShape extends Shape { constructor({ size, width, height, concavity }?: DiamondShapeOptions); } export declare interface DiamondShapeOptions { /** Overall scale factor. Defaults to `1`. */ size?: number; /** Diamond width, point to point across. Defaults to `1.6`. */ width?: number; /** Diamond height, point to point. Defaults to `2.2`. */ height?: number; /** * How far the four sides bow INWARD, as a fraction of the way to the center. Defaults to `0.15`. * * `0` is a plain rhombus with straight `/` sides. Above it the sides pull in toward the middle — the * `)` curve a printed card diamond actually has, which keeps it from reading as a kite. */ concavity?: number; } /** * Movement or orientation in a specific direction. * * Example usages: * * Moving an Object Along a Direction * ``` * const speed = 1; // Movement speed * const direction = Direction.FORWARD; // Choose a direction * * object.position.add(direction.clone().multiplyScalar(speed * deltaTime)); * ``` * * Snapping Positions to a Direction * ``` * const targetPosition = new Vector3(5, 0, 3); * const snapDirection = Direction.UP; // Align upwards * * object.position.copy(targetPosition.clone().add(snapDirection.clone().multiplyScalar(10))); * ``` * * Animating Along a Direction * ``` * const distance = 10; // Total distance to travel * const duration = 2; // Animation duration in seconds * const startPosition = object.position.clone(); * const targetPosition = startPosition.clone().add(Direction.BACKWARD.clone().multiplyScalar(distance)); * * let elapsed = 0; * function animate(deltaTime) { * elapsed += deltaTime; * const t = Math.min(elapsed / duration, 1); // Normalize time to [0, 1] * object.position.lerpVectors(startPosition, targetPosition, t); * } * ``` * * Shader Uniforms * ``` * material.uniforms.uDirection.value = Direction.FORWARD; // Use as light or flow direction * ``` * * Particle Systems * ``` * particles.forEach(particle => { * particle.velocity.add(Direction.FORWARD.clone().multiplyScalar(0.1)); * }); * ``` * * Directional Raycasting * ``` * const rayOrigin = new Vector3(0, 0, 0); * const rayDirection = Direction.FORWARD; * const raycaster = new THREE.Raycaster(rayOrigin, rayDirection); * * // Find intersected objects * const intersects = raycaster.intersectObjects(scene.children); * ``` * * Physics Forces * ``` * const forceDirection = Direction.UP; // Push upwards * const forceMagnitude = 50; * * rigidBody.applyForce(forceDirection.clone().multiplyScalar(forceMagnitude)); * ``` */ export declare const Direction: { UP: Vector3; DOWN: Vector3; LEFT: Vector3; RIGHT: Vector3; FORWARD: Vector3; BACKWARD: Vector3; }; /** Displace positions in place within radius; direction magnitude scales strength. Normals and bounds remain stale. */ export declare const displacementBrush: (geometry: T, position: Vector3, radius: number, strength: number, direction?: Vector3, falloffFn?: (distance: number, radius: number) => number) => void; export declare interface DollyClipOptions extends CameraClipTiming { /** Distance along view axis — positive pulls back, negative pushes in. */ distance: number; ease?: EasingFunction; } /** Which jamb a door hangs from — and therefore where its origin sits. */ export declare type DoorHinge = "left" | "right"; /** A door slab, wood and iron in their own geometry groups. */ export declare type DoorLeaf = Mesh; /** Two leaves under one arch. See {@link createDoubleDoor} for how to swing them. */ export declare interface DoubleDoor extends Group { /** Hung on the left jamb. Opens OUTWARD on a NEGATIVE `rotation.y`. */ left: DoorLeaf; /** Hung on the right jamb. Opens OUTWARD on a POSITIVE `rotation.y` — it mirrors the left. */ right: DoorLeaf; } export declare interface DoubleDoorOptions extends Omit { /** * Width of the whole opening, jamb to jamb — **not** the width of one leaf. Defaults to `2.6`, which * is two doors of the standard `1.3`. * * The arch spans this, and each leaf takes half of it. Which is the thing to watch: **`archHeight` is * measured against this OPENING, not against a leaf.** A semicircle over a `2.6` opening wants * `archHeight: 1.3`, not `0.65` — see {@link ArchedDoorOptions.archHeight}. */ width?: number; } /** * Fine dust drifting through a lit interior — tiny additive specks slowly * settling and wafting, twinkling as they catch the light and all but vanishing * in shadow. This is the thing that sells a light shaft as volumetric. Additive * spheres read the same from any angle, so no billboarding is needed. * * Call {@link DustMotesEffect.update} each frame with elapsed time in seconds. * * @example * ```typescript * const dust = new DustMotesEffect({ * count: 150, * width: 8, * height: 9, * depth: 8, * color: "#aebfe6", * }); * scene.add(dust); * * onFrame((dt) => dust.update(dt)); * ``` */ export declare class DustMotesEffect extends InstancedMesh { private readonly width; private readonly height; private readonly depth; private readonly floorY; private readonly waft; private readonly scaleMin; private readonly scaleMax; private readonly px; private readonly py; private readonly pz; private readonly settle; private readonly twinkle; private readonly phase; private readonly dummy; private clock; constructor(options?: DustMotesEffectOptions); /** * Advance the drift and twinkle. Pass elapsed frame time in seconds. */ update(dt: number): void; /** Release geometry and materials held by the field. */ dispose(): this; private respawn; private writeMatrices; } export declare interface DustMotesEffectOptions { /** Number of dust instances. Defaults to `150`. */ count?: number; /** Horizontal spread (world units). Defaults to `12`. */ width?: number; /** Vertical spawn span (world units). Defaults to `8`. */ height?: number; /** Depth spread (world units). Defaults to `12`. */ depth?: number; /** World Y of the volume floor; motes respawn at the top after settling past it. Defaults to `0`. */ floorY?: number; /** Speck tint. Defaults to `#aebfe6`. */ color?: ColorRepresentation; /** Speck radius (world units). Defaults to `0.02`. */ radius?: number; /** Base material opacity. Defaults to `0.9`. */ opacity?: number; /** Override the default additive speck material. */ material?: Material; /** Minimum settle (fall) speed (units/s). Defaults to `0.1`. */ settleMin?: number; /** Maximum settle (fall) speed (units/s). Defaults to `0.35`. */ settleMax?: number; /** Lateral waft amplitude (units/s). Defaults to `0.08`. */ waft?: number; /** Smallest twinkle scale multiplier. Defaults to `0.6`. */ scaleMin?: number; /** Largest twinkle scale multiplier. Defaults to `1.2`. */ scaleMax?: number; /** Minimum twinkle frequency (rad/s). Defaults to `0.7`. */ twinkleMin?: number; /** Maximum twinkle frequency (rad/s). Defaults to `1.6`. */ twinkleMax?: number; } /** * Easing functions for interpolating values over time. * * Use these functions to create smooth animations and transitions. * All easing functions take a value t between 0 and 1 and return an eased value between 0 and 1. * * @example * ```typescript * import { Easing } from 'three-low-poly'; * * // Ease a value directly * const easedValue = Easing.cubicInOut(0.5); * * // Interpolate between two values over a duration * const startValue = 0; * const endValue = 100; * const t = elapsed / duration; * const easedT = Easing.sineInOut(t); * const currentValue = startValue + (endValue - startValue) * easedT; * ``` */ export declare const Easing: { sineIn: (t: number) => number; sineOut: (t: number) => number; sineInOut: (t: number) => number; quadIn: (t: number) => number; quadOut: (t: number) => number; quadInOut: (t: number) => number; cubicIn: (t: number) => number; cubicOut: (t: number) => number; cubicInOut: (t: number) => number; quartIn: (t: number) => number; quartOut: (t: number) => number; quartInOut: (t: number) => number; quintIn: (t: number) => number; quintOut: (t: number) => number; quintInOut: (t: number) => number; expoIn: (t: number) => number; expoOut: (t: number) => number; expoInOut: (t: number) => number; circIn: (t: number) => number; circOut: (t: number) => number; circInOut: (t: number) => number; linear: (t: number) => number; smoothstep: (t: number) => number; concave: (t: number) => number; convex: (t: number) => number; logarithmic: (t: number) => number; squareRoot: (t: number) => number; inverse: (t: number) => number; gaussian: (t: number) => number; }; /** * Easing function type for interpolating values over time. * @param t - Progress value between 0 and 1 * @returns Eased value between 0 and 1 */ export declare type EasingFunction = (t: number) => number; /** Which pair of faces is worked — the axis the treatment is measured along. */ export declare type EdgeAxis = "x" | "y" | "z"; /** * A box with its edges chamfered, rounded, or coved along one axis. * * **Built as a LOFT, not by rounding edges.** The solid is a stack of cross-sections — each one the base * rectangle pushed inward by however much the edge profile says at that height — with the bands between * them stitched. Three treatments come out of one mechanism, differing only in how the inset falls off: * a straight line, a convex quarter, a concave quarter. * * Two things fall out for free, and they are the reason this construction was worth finding: * * - **No corner logic.** There is none in this file. A corner is only ever where two bands of the loft * meet, and each band brings its own plane, so the four corners of the treatment resolve themselves. * - **Nothing is trimmed.** Edge rounding wants a real trimming capability; lofting between sections * wants nothing but {@link offsetLoop}. * * The inset is a true offset rather than a scale, so a long thin box keeps a constant edge all the way * round instead of a wider one on its long sides. * * Sits on the `y = 0` plane, centered on X and Z — whichever `axis` is worked. Material groups: none; pass * one material, not an array. * * @example * ```ts * // A shelf with a bullnose front edge. * const shelf = new Mesh( * new EdgedBoxGeometry({ width: 1.2, height: 0.04, depth: 0.3, edge: "round", radius: 0.02, axis: "z", ends: "high" }), * oak, * ); * ``` */ export declare class EdgedBoxGeometry extends BufferGeometry { constructor({ width, height, depth, edge, radius, segments, axis, ends, }?: EdgedBoxGeometryOptions); } export declare interface EdgedBoxGeometryOptions { /** Extent on X. Defaults to `1`. */ width?: number; /** Extent on Y. Defaults to `1`. */ height?: number; /** Extent on Z. Defaults to `1`. */ depth?: number; /** How the edge is worked. Defaults to `"chamfer"`. See {@link EdgeStyle}. */ edge?: EdgeStyle; /** * How deep the treatment runs, both inward from the sides and along the axis. Defaults to `0.1`. * * Clamped so the solid can never fold: it will not exceed half the smaller cross-section dimension, nor * half the length when both ends are worked. */ radius?: number; /** * How finely a `round` or `cove` is cut — the low-poly knob. Defaults to `4`. * * `1` collapses either onto its own chord, which is a chamfer. Like `segments` everywhere else here it * changes TESSELLATION only: the solid fills `width × height × depth` exactly at every setting. */ segments?: number; /** * Which pair of faces is worked. Defaults to `"y"` — the top and bottom. * * The dimensions do not rotate with it: `width` is always the extent on X. Only the treatment moves. */ axis?: EdgeAxis; /** Which of that pair. Defaults to `"both"`. `"low"` alone is a plinth; `"both"` is a raised panel. */ ends?: EdgeEnds; } /** Which of the two faces on {@link EdgeAxis} is worked. `low` and `high` are the −/+ ends of that axis. */ export declare type EdgeEnds = "both" | "low" | "high" | "none"; /** * How an edge is worked. * * - `sharp` — left alone. The box, unmodified. * - `chamfer` — a flat splay. One facet, whatever `segments` says. * - `round` — convex, a bullnose. The solid reaches nearly full size at once, then flattens. * - `cove` — concave. It stays pulled in and flares late. * * `round` and `cove` are the same construction with the curve bowed the other way, which is why an inside * and an outside edge are one option here rather than two geometries. */ export declare type EdgeStyle = "sharp" | "chamfer" | "round" | "cove"; /** * Carbonation bubbles rising through a bounded volume — seltzer, soda, or * brewing liquid. Each instance drifts upward at its own speed and respawns * near {@link EffervescenceEffectOptions.baseY} after reaching the top. * * Spawn positions use a square footprint (`width` × `depth`). {@link EffervescenceEffectOptions.spread} * pulls that box inward so round jars do not get occasional corner outliers. * Position and scale the effect to sit inside a jar, flask, or panel viewport. * Call {@link EffervescenceEffect.update} each frame with elapsed time in seconds. * * @example * ```typescript * const fizz = new EffervescenceEffect({ width: 1.2, height: 2.5, count: 30 }); * fizz.position.set(0, 1.2, 0); * scene.add(fizz); * * onFrame((dt) => fizz.update(dt)); * ``` */ export declare class EffervescenceEffect extends InstancedMesh { private readonly width; private readonly height; private readonly depth; private readonly baseY; private readonly spread; private readonly px; private readonly py; private readonly pz; private readonly speed; private readonly dummy; constructor(options?: EffervescenceEffectOptions); /** * Advance bubble positions. Pass elapsed frame time in seconds (e.g. from * `createScene`'s `onFrame` callback). */ update(dt: number): void; /** Release geometry and materials held by the field. */ dispose(): this; private respawn; private writeMatrices; } export declare interface EffervescenceEffectOptions { /** Override bubble geometry. Defaults to a small `SphereGeometry`. */ geometry?: BufferGeometry; /** Override the default bubble material. */ material?: Material; /** Number of bubble instances. Defaults to `24`. */ count?: number; /** Horizontal spread (world units). Defaults to `1.5`. */ width?: number; /** Vertical column height (world units). Defaults to `3`. */ height?: number; /** Depth spread (world units). Defaults to `1.5`. */ depth?: number; /** * Horizontal spawn inset (0–1). Scales width/depth spawn area inward so a * square volume fits round vessels. `1` = full box; `0.88` (default) trims corners. */ spread?: number; /** World Y where bubbles spawn when they recycle. Defaults to `0`. */ baseY?: number; /** Minimum rise speed (units/s). Defaults to `0.35`. */ speedMin?: number; /** Maximum rise speed (units/s). Defaults to `0.85`. */ speedMax?: number; /** Bubble tint when using the default material. Defaults to `0xffffff`. */ color?: ColorRepresentation; /** Default material opacity. Defaults to `0.6`. */ opacity?: number; /** Default material emissive intensity. Defaults to `0`. */ emissiveIntensity?: number; } export declare class EllipticLeafGeometry extends BufferGeometry { constructor(size?: number); } /** * Smooth emissive pulse for fake LEDs — animates `emissiveIntensity` on an existing * mesh material without adding geometry or scene lights. * * Attach it to any mesh whose material has an `emissive` — place the mesh, pass its * material here, call `update(dt)` each frame. For a bank of same-color LEDs, give * each instance a different `speed` so they breathe out of sync. * * Note the effect drives the MATERIAL, not the mesh. A bank of LEDs can therefore * share one geometry, but each needs its own material, or they all pulse as one. * * @example * ```ts * const led = new Mesh( * new SphereGeometry(0.05, 8, 8), * new MeshStandardMaterial({ color: 0xffc7c7, emissive: 0xff0000 }), * ); * scene.add(led); * * const pulse = new EmissivePulseEffect({ * material: led.material, * speed: 1.4, * minIntensity: 0.05, * maxIntensity: 2, * }); * * onFrame((dt) => pulse.update(dt)); * ``` */ export declare class EmissivePulseEffect { speed: number; minIntensity: number; maxIntensity: number; readonly material: EmissivePulseMaterial; private elapsed; constructor({ material, speed, maxIntensity, minIntensity, }: EmissivePulseEffectOptions); /** Advance the pulse by `dt` seconds (elapsed time, not frame index). */ update(dt: number): void; } export declare interface EmissivePulseEffectOptions { /** Material whose `emissiveIntensity` will oscillate. Must have emissive set. */ material: EmissivePulseMaterial; /** * Pulse frequency in radians per second (`abs(sin(elapsed * speed))`). * Defaults to `2`. Primary differentiator when many LEDs share one color. */ speed?: number; /** Lower bound of the pulse. Defaults to `0.2`. */ minIntensity?: number; /** Upper bound of the pulse. Defaults to `0.8`. */ maxIntensity?: number; } export declare type EmissivePulseMaterial = MeshStandardMaterial | MeshPhysicalMaterial | MeshLambertMaterial | MeshPhongMaterial; /** * Erlenmeyer flask — a conical body rising to a straight neck, walled to a real glass thickness. * * A lathe of {@link vesselShell} over {@link erlenmeyerFlaskProfile}. The outer silhouette is exposed as * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a * measurement. Local frame: base on Y=0. */ export declare class ErlenmeyerFlaskGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly bodyRadius: number; readonly height: number; constructor(options?: ErlenmeyerFlaskGeometryOptions); } export declare interface ErlenmeyerFlaskGeometryOptions extends ErlenmeyerFlaskProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `16`. */ radialSegments?: number; } /** * Erlenmeyer flask silhouette — a conical body rising to a straight neck. Base on Y=0; ends at the rim. * * The base is drawn a touch in from full radius with a small chamfer, so the wall turns up rather than * meeting the bottom at a hard rim that catches the light wrongly. */ export declare function erlenmeyerFlaskProfile({ bodyRadius, neckRadius, bodyHeight, neckHeight, }?: ErlenmeyerFlaskProfileOptions): Vector2[]; export declare interface ErlenmeyerFlaskProfileOptions { /** Body (base) radius — the widest point. Defaults to `1`. */ bodyRadius?: number; /** Neck radius. Defaults to `0.3`. */ neckRadius?: number; /** Body height, before the neck. Defaults to `2.5`. */ bodyHeight?: number; /** Neck height. Defaults to `1`. */ neckHeight?: number; } /** Normalized (b − a) × (c − a), facing the viewer of CCW corners; degenerate triangles return a zero vector. */ export declare function faceNormal(a: Vec3, b: Vec3, c: Vec3): Vec3; export declare const Falloff: { linear: (distance: number, radius: number) => number; quadratic: (distance: number, radius: number) => number; squareRoot: (distance: number, radius: number) => number; logarithmic: (distance: number, radius: number) => number; sine: (distance: number, radius: number) => number; exponential: (distance: number, radius: number) => number; cubic: (distance: number, radius: number) => number; gaussian: (distance: number, radius: number) => number; inverse: (distance: number, radius: number) => number; smoothstep: (distance: number, radius: number) => number; }; export declare type FalloffFunction = (distance: number, radius: number) => number; /** A resolved fence run: every value concrete, mutually consistent. */ export declare interface FenceSpan { /** Center-to-center bar spacing. */ pitch: number; /** Number of bars. */ count: number; /** Total run length, always `count * pitch`. */ length: number; } export declare interface FenceSpanOptions { /** Center-to-center picket spacing. Defaults to `0.4`. */ pitch?: number; /** Number of pickets. Defaults to `10`. */ count?: number; /** Target run length. Pickets divide it equally, overriding `pitch`. */ length?: number; /** * Width of a single picket. Optional, but pass it whenever `length` is pinned: it is what stops a * request for more pickets than physically fit from producing an overlapping run. Defaults to `0` * (no limit). */ itemWidth?: number; } /** * A whole graveyard — a grid of rows, aged the same way a single {@link rowOfHeadstones} is. * * **It is the row's instancing, shared across every row.** Call `rowOfHeadstones` once per row and each * call builds its own instanced meshes, so a 10×10 field costs ten rows × a mesh-per-style ≈ eighty draw * calls. This lays every plot up front and instances them together: one mesh per silhouette for the * *entire field*, so a hundred stones — or ten thousand — stay the same handful of draw calls. That is * the whole reason to reach for it over a loop. * * The grid gives the structure a surveyed cemetery has; the per-stone settling ({@link settleStone}) * gives the age that keeps it from reading as a spreadsheet. `density` below `1` thins it to the gappy * scatter of an old churchyard — or of a sparse fill trailing off into the distance. * * The first plot sits at the origin; the field runs out along `+x` (`columns`) and `+z` (`rows`). Its * extent is `(columns − 1) · spacing` by `(rows − 1) · rowSpacing`, so center it with * `field.position.set(-width / 2, 0, -depth / 2)`. * * @example * ```ts * const graveyard = fieldOfHeadstones({ columns: 10, rows: 10, seed: 1337 }); * scene.add(graveyard); * * // A thin, weathered scatter for the distance — still one handful of draw calls at any size. * const distant = fieldOfHeadstones({ columns: 40, rows: 40, density: 0.35, weathering: 0.14 }); * ``` */ export declare function fieldOfHeadstones({ columns, rows, spacing, rowSpacing, density, ...settle }?: HeadstoneFieldOptions): Group; /** * The shared "fill capability" — the optional liquid any vessel can carry. * * Level is geometry; colour, opacity and glow are material. One interface covers both so a filled vessel * is described the same way wherever it is composed (a flask in a stand, a tube in a rack, an example). */ export declare interface FillOptions { /** Fill level, as a fraction of the vessel's height. `0` (or omitted) is empty. */ fill?: number; /** Liquid colour. Defaults to a pale green. */ color?: ColorRepresentation; /** Liquid opacity. Defaults to `0.85`. */ opacity?: number; /** Emissive glow, `0` for none. Defaults to `0`. */ glow?: number; /** Radius inset from the glass wall, so the two surfaces don't z-fight. Defaults to `0.02`. */ inset?: number; } /** * The liquid that fills a vessel to a given fraction of its height — derived from the shell's OWN * silhouette rather than written per vessel. * * One function serves every vessel, and the liquid cannot disagree with the glass it sits in because it is * the same curve. `fill` is a fraction of the vessel's height, so it means the same on every shape. * * `inset` clears the liquid off the glass by ONE uniform gap — a fraction of the widest radius — applied * along the wall's own NORMAL, so the sides, the bottom AND the meniscus all pull in by the same amount and * nothing is coplanar with the glass to z-fight. (A radius-only shrink leaves zero gap at the axis, so the * flat bottom stays on the glass floor and fights it.) The whole silhouette is offset first, THEN cut at the * fill line, so every point keeps a clean normal and the rim never juts where the level meets a shell point. * * Returns `[]` when there is nothing to draw. */ export declare function fillProfile(shell: Vector2[], fill: number, inset?: number): Vector2[]; export declare function findClosestColor(inputColor: number, dataset: number[]): number | null; export declare function findClosestColorChannelWise(inputColor: number, dataset: number[]): number | null; /** * Select the nearest stored vertex using geometry-local distances, then return it in world space. * * `point` must be in the mesh geometry's local coordinate space. This searches the position * attribute only; it does not find the closest point on triangle faces or edges. Under nonuniform * world scaling, the selected vertex need not be the nearest vertex by world-space distance. * An empty position attribute returns the mesh's local origin transformed to world space. * * @example * ```ts * const localPoint = new Vector3(5, 2, 1); * const worldVertex = findClosestPoint(localPoint, targetMesh); * ``` */ export declare function findClosestPoint(point: Vector3, mesh: Mesh): Vector3; /** * A flagstone floor — **individual slabs, not a textured plane.** * * **The grout lines are the point.** A long floor is read down its length, and the gaps between flags give * perspective lines converging toward the far end — depth for free, before any light is placed. A single * plane with a tiled texture cannot do that, and a vertex-colored plane gives tint variation but still no * gaps, because the quads stay flush. Take `gap` to `0` and watch the floor collapse into one slab: the * converging lines vanish and so does the depth. * * Every slab also settles slightly and carries a whisper of yaw, so the surface is not perfectly flat and * the joints are not laser-straight. Both are seeded. * * **One `InstancedMesh`, one draw call, at any size.** Every slab is the same box; only its matrix and its * tint differ. That is the opposite call from {@link PlankFloor} and {@link HardwoodFloor}, whose boards are * each a different shape and therefore merge — identical items instance, differing items merge. * * Centered on the origin, with the slab tops on `y = 0` so anything standing on the floor sits at zero. * * @example * ```ts * const floor = new FlagstoneFloor({ width: 20, length: 26, tile: 1, gap: 0.06 }); * scene.add(floor); * floor.tiles; // slabs laid * ``` */ export declare class FlagstoneFloor extends InstancedMesh { /** Slabs laid. */ readonly tiles: number; /** Slabs across X. */ readonly columns: number; /** Slabs along Z. */ readonly rows: number; constructor({ width, length, tile, gap, thickness, color, tintJitter, colors, heightJitter, seed, roughness, }?: FlagstoneFloorOptions); /** Releases the shared slab geometry and material. */ dispose(): void; } export declare interface FlagstoneFloorOptions { /** Extent across X. Defaults to `20`. */ width?: number; /** Extent along Z. Defaults to `24`. */ length?: number; /** * Nominal tile pitch. Defaults to `1.2`. * * The slab is the pitch MINUS the grout, so this stays the number you reason about and the gap eats into * it rather than adding to it — widen the joint and the floor keeps its coursing. */ tile?: number; /** Gap between slabs — the grout line. Defaults to `0.06`. See the note on why it matters. */ gap?: number; /** Slab thickness. Defaults to `0.12`. */ thickness?: number; /** Base stone tint. Defaults to `#54524d`. */ color?: ColorRepresentation; /** * How far each slab's tint wanders, 0–1. Defaults to `0.12`. * * **Lightness only.** Hue drift per slab reads as STAINED rather than weathered, which is the opposite of * what stone wants — a pumpkin patch wants the hue, a floor does not. */ tintJitter?: number; /** Per-slab sampler overriding color/tintJitter. Row-major index; independent seeded color stream. */ colors?: ColorSampler; /** * How far each slab settles or lifts, in world units. Defaults to `0.012`. * * Small numbers: this is a floor. Past about `0.05` it stops reading as worn and starts reading as broken. */ heightJitter?: number; /** * Deterministic layout seed. Defaults to `1`. * * Stable across rebuilds — the floor is an address, not a reshuffle. Change this and you get a different * floor; change anything else and you get the same floor, altered. */ seed?: number; /** Gloss, for catching lantern light. `1` is matte. Defaults to `0.72`. */ roughness?: number; } /** * Calm flame flicker from detuned sines — drives optional real lights, flame * materials, and {@link GlowHalo} opacity in sync. Time-based (`update(dt)`). * * Use without a `light` for mass candlefields (halo + bloom only). Add a * `light` for hero sconces that must cast on walls and props. */ /** * Flicker factor from a sum of detuned sines — roughly `0.5`–`1.1`, never repeating because the two * frequencies are incommensurate. * * A plain function on purpose: **mass populations want one loop over an array, not N flicker objects each * carrying a clock.** {@link FlameFlickerEffect} is the convenience wrapper for a single flame; anything * driving many flames — a chandelier, a votive rack — should call this directly with a per-item `phase`. * * @param elapsed - Seconds. * @param phase - Per-item offset, so a population doesn't gutter in unison. */ export declare function flameFlicker(elapsed: number, phase?: number): number; export declare class FlameFlickerEffect { readonly seed: number; light?: PointLight; lightIntensity: number; flame?: MeshBasicMaterial; halo?: GlowHalo; haloOpacity: number; private readonly flameColor; private elapsed; constructor({ seed, light, lightIntensity, flame, flameColor, halo, haloOpacity, }?: FlameFlickerEffectOptions); /** * Flicker factor at elapsed time — lazy sum of detuned sines (~0.5–1.1). */ static factor(elapsed: number, seed: number): number; /** Current flicker factor after the last `update`. */ get level(): number; update(dt: number): void; } export declare interface FlameFlickerEffectOptions { /** * Desyncs multiple instances. Defaults to `Math.random() * 100`. */ seed?: number; /** Optional real light — use sparingly; casts on geometry but counts against light limits. */ light?: PointLight; /** Base intensity when `light` is set. Defaults to `4`. */ lightIntensity?: number; /** * Optional flame core material (e.g. `MeshBasicMaterial` on a small sphere). * * TODO: `update()` mutates `flame.color` in place, so several flickers sharing one material will * fight over it and gutter in lockstep. Give each flame its own material, or move the tint off * the shared material. Worth a runtime warning or a doc note at minimum. */ flame?: MeshBasicMaterial; /** Flame tint when `flame` is set. Defaults to `0xffaa44`. */ flameColor?: ColorRepresentation; /** Optional {@link GlowHalo}; opacity scales with the flicker factor. */ halo?: GlowHalo; /** * Halo opacity multiplier at flicker peak. Defaults to `0.75`. * * TODO: this duplicates {@link GlowHalo}'s own `opacity`. Once a flicker drives a halo, whatever * `opacity` the halo was constructed with is overwritten every frame, so two places own one * number. Decide which is authoritative — probably read the halo's own opacity as the peak. */ haloOpacity?: number; } /** Move positions toward dot(vertex, direction) = targetHeight; normalizes the supplied direction in place. * Normals and bounds remain stale. */ export declare const flattenBrush: (geometry: T, position: Vector3, radius: number, targetHeight: number, strength: number, direction?: Vector3, falloffFn?: (distance: number, radius: number) => number) => void; /** * Florence flask — a spherical bulb drawn out into a straight neck, walled to a real glass thickness. * * A lathe of {@link vesselShell} over {@link florenceFlaskProfile}. The outer silhouette is exposed as * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a * measurement. A round-bottom flask cannot stand on its own — see {@link FlorenceFlaskStand}. Local frame: * bulb bottom on Y=0, opening up +Y. */ export declare class FlorenceFlaskGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly bodyRadius: number; readonly height: number; constructor(options?: FlorenceFlaskGeometryOptions); } export declare interface FlorenceFlaskGeometryOptions extends FlorenceFlaskProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `32`. */ radialSegments?: number; } /** * Florence flask silhouette — a sphere opened at the top into a straight neck. * * The arc runs from the south pole to the latitude where the sphere is exactly as wide as the neck * (`asin(neckRadius / bodyRadius)`), so the neck meets the bulb tangentially with no crease. Bulb bottom * on Y=0; ends at the rim. */ export declare function florenceFlaskProfile({ bodyRadius, neckRadius, neckHeight, profileSegments, }?: FlorenceFlaskProfileOptions): Vector2[]; export declare interface FlorenceFlaskProfileOptions { /** Bulb (sphere) radius. Defaults to `1`. */ bodyRadius?: number; /** Neck radius — the straight tube above the bulb. Defaults to `0.2`. */ neckRadius?: number; /** Neck height, above the bulb's shoulder. Defaults to `1.5`. */ neckHeight?: number; /** Arc stations over the bulb — its smoothness. Defaults to `32`. */ profileSegments?: number; } /** * Florence flask resting in a ring stand. * * A round-bottom flask cannot stand on its own; the stand is what makes it a thing that sits on a table. * This composes {@link FlorenceFlaskGeometry} and {@link RingStandGeometry} into a `Group` resting on Y=0. * * **The bulb is seated by radius, not tessellation.** The ring is sized to the bulb, and the bulb settles * until its surface is tangent to the ring tube — in an axial cross-section, a bulb circle of radius `R` * tangent (from inside) to a tube circle of radius `t` at ring radius `ringRadius`: * * ``` * riseAboveRing = √((R + t)² − ringRadius²) * ``` * * The flask is MEASURED (bounding box) rather than assumed, so the seating stays correct if * {@link FlorenceFlaskGeometry} is parameterized later. * * **Glass and metal are SEPARATE meshes, not one merged geometry.** Transparency sorts per object, so a * glass group baked into the opaque stand would sort wrong; the parts must stay separate objects. */ export declare class FlorenceFlaskStand extends Group { constructor({ flask, fill, seat, ringThickness, legs, clearance, radialSegments, glassMaterial, standMaterial, }?: FlorenceFlaskStandOptions); } export declare interface FlorenceFlaskStandOptions { /** Flask geometry — resize the bulb, neck, etc. The ring re-sizes and re-seats to whatever bulb results. */ flask?: FlorenceFlaskGeometryOptions; /** Optional liquid inside the flask — colour, opacity, glow, fill level. */ fill?: FillOptions; /** * Ring radius as a fraction of the bulb radius — how deep the bulb sits. Defaults to `0.55`, which * cradles the lower third. Must be below `1`, or the ring is wider than the bulb and it falls through. */ seat?: number; /** Ring tube thickness. Defaults to `0.03 ×` the bulb radius. */ ringThickness?: number; /** Number of legs. Defaults to `3`. */ legs?: number; /** Gap from the bulb's lowest point to the ground. Defaults to `0.15 ×` the bulb radius. */ clearance?: number; /** Ring circumference segments. Defaults to `24`. */ radialSegments?: number; /** Flask (glass) material. A translucent default is supplied. */ glassMaterial?: MeshStandardMaterial; /** Stand (metal) material. A brushed-metal default is supplied. */ standMaterial?: MeshStandardMaterial; } export declare interface FlythroughClipOptions extends CameraClipTiming { waypoints: Vector3[]; /** Optional look-at points per waypoint; defaults to the current focus. */ lookAt?: Vector3[]; ease?: EasingFunction; } export declare interface FocusTransferClipOptions extends CameraClipTiming { /** New subject to frame. This changes orientation, not optical focus or FOV. */ target: Vector3; } export declare interface FovPulseClipOptions extends CameraClipTiming { /** Peak additive FOV change in degrees. Positive widens; negative narrows. Defaults to 8. */ amplitude?: number; } /** * A **full moon** — a bright unlit disc wrapped in a soft additive haze, for the hazy ring you get * on a humid night. * * Deliberately the full-moon case, not a general moon. The haze is a *filled* additive gradient * sitting in front of the disc, so the moon can only ever read brighter than its surroundings; a * crescent, a new moon, or an eclipse would need an occluding terminator and a halo that follows the * lit limb, which is different machinery rather than another option. * * This is a **sky layer, not a skybox**: it owns the moon and nothing else, so it composes freely * with {@link StarField}, a scene background, or a dome of your own. Nothing here paints * the rest of the sky. * * **Placement** — `azimuth` and `elevation` are horizontal (alt-az) angles in degrees, the way sun * and moon positions are normally given: `0°` azimuth is north, `90°` east, and elevation climbs * from the horizon. The resolved unit vector is exposed as {@link direction} for aiming a * `DirectionalLight` along the same bearing. * * The moon is **viewer-relative — direction without location.** It pins itself to the active camera * every frame (see {@link lockToViewer}), so `scene.add(moon)` is the whole contract: there is no * per-frame call, and no amount of dollying brings the moon closer. `distance` is a render depth, * not a place. If you want a moon that can actually be reached, build geometry instead. * * **Both parts are flat and neither billboards.** The disc is unlit and uniformly colored, so a * sphere would be pixel-identical to a circle while costing an order of magnitude more triangles — * only the silhouette does any work. Both the disc and the haze are oriented once, perpendicular * to {@link direction}: because the moon rides the camera, the world-space line of sight to it is * always `direction`, so a fixed orientation is square-on at every orbit angle, exactly and with * no per-frame call. * * A `Sprite` is the trap here. Sprites align to the camera's view *plane*, not toward the camera's * *position*, so off screen-center the card tilts off the moon axis and dips behind the disc, which * then depth-occludes its own glow. Sprite cut-through scales with the sprite's own size versus its * clearance, not with distance from the camera. * * **Depth** — the disc is a normal depth-tested opaque mesh, so terrain and trees silhouette * against it. The halo is additive and writes no depth, so it never occludes what's in front. * * Uses only standard materials and a {@link createRadialGradientTexture}, so it renders under either * `WebGPURenderer` or `WebGLRenderer`, and constructs with no DOM. * * @example * ```typescript * const moon = new FullMoon({ radius: 14, azimuth: 18, elevation: 1.15 }); * const stars = new StarField({ radius: 480, twinkle: true }); * scene.add(moon, stars); // both pin themselves to the viewer — nothing per frame * * // Rake moonlight in from wherever the moon actually is. * const moonlight = new DirectionalLight(0xc8d8ff, 1.8); * moonlight.position.copy(moon.direction).multiplyScalar(40); * scene.add(moonlight); * ``` * * Call {@link dispose} when removing the effect to free geometry, materials, and the halo texture. */ export declare class FullMoon extends Object3D { /** The moon body. Depth-tested and opaque, so scene geometry silhouettes against it. */ readonly disc: Mesh; /** The additive haze card, or `undefined` when `halo` is `false`. */ readonly halo?: Mesh; /** * Unit direction resolved from `azimuth` / `elevation`, pointing from the viewer toward the * moon. Read-only output, not an input — copy it onto a `DirectionalLight` to rake moonlight * in from wherever the moon actually is. */ readonly direction: Vector3; /** Distance from this object's origin to the disc center. */ readonly distance: number; private readonly haloTexture?; constructor({ radius, azimuth, elevation, distance, color, segments, halo, fog, }?: FullMoonOptions); /** Release GPU resources held by the moon. */ dispose(): void; } export declare interface FullMoonHaloOptions { /** * Halo extent as a multiple of the moon radius, measured at the moon's distance. * Defaults to `6.2`. */ scale?: number; /** Overall halo opacity, scaling the stop alphas. Defaults to `0.72`. */ opacity?: number; /** Radial falloff, core to rim. Any number of stops; defaults to a cool blue-white haze. */ stops?: RadialGradientStop[]; } export declare interface FullMoonOptions { /** Moon disc radius in world units. Defaults to `14`. */ radius?: number; /** * Compass bearing in degrees, following the astronomical horizontal (alt-az) convention: * `0` is north (`-Z`), `90` east (`+X`), `180` south, `270` west — clockwise seen from * above. Defaults to `18`. */ azimuth?: number; /** * Degrees above the horizon, `-90` to `90`. Defaults to `1.15` — a low moon just clear of * the horizon. Negative values sit below it. */ elevation?: number; /** * Distance along the resolved direction. Defaults to `300`, and should sit inside the * camera's far plane. */ distance?: number; /** Disc color. Defaults to `0xd8e3ff`. */ color?: ColorRepresentation; /** * Disc edge count. Defaults to `64` — smooth, because a moon is the canonical round thing * and a chunky one reads as broken rather than stylized. Drop it for a deliberately faceted * moon; the disc is flat, so even a high count costs almost nothing. */ segments?: number; /** Halo settings, or `false` for a bare disc. */ halo?: FullMoonHaloOptions | false; /** * Whether `scene.fog` tints the moon. Defaults to `false`, so the disc stays crisp and reads * as a light source rather than a distant lit sphere. * * Because the moon rides at a fixed distance from the camera, enabling this yields a * *constant* haze wash rather than fog that varies as the viewer moves. */ fog?: boolean; } /** * Extruded gear profile with a center bore. See {@link GearShape} for the tooth period and how the two flats * divide it. * * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, so a rank of gears sharing an * arbor lines up on the plane they turn in rather than each one starting where the last began. * * Material groups: **none** — one material for the whole wheel. * * @example * ```typescript * const gear = new Mesh(new GearGeometry({ teeth: 12, tipWidth: 0.1 }), brass); * const ratchet = new Mesh(new GearGeometry({ teeth: 16, lean: 1, tipWidth: 0 }), steel); * ``` */ export declare class GearGeometry extends ExtrudeGeometry { /** The bore radius actually used, after clamping to fit inside the tooth profile. */ readonly holeRadius: number; constructor({ depth, ...shapeOptions }?: GearGeometryOptions); } export declare interface GearGeometryOptions extends GearShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Gear profile — teeth around a polygonal center bore. Rests with a tooth up. * * One tooth period runs tip, falling flank, valley, rising flank. The two flats * are sized independently and the rest of the period is split between the * flanks, so the same profile spans a blunt trapezoidal gear, a spiked one, and * an asymmetric ratchet wheel. A flat given zero width collapses to a single * point rather than a doubled vertex. */ export declare class GearShape extends Shape { /** The bore radius actually used, after clamping to fit inside the tooth profile. */ readonly holeRadius: number; constructor({ teeth, innerRadius, outerRadius, tipWidth, valleyWidth, lean, holeSides, holeRadius, rotation, holeRotation, }?: GearShapeOptions); } export declare interface GearShapeOptions { /** Number of gear teeth. Defaults to `5`. */ teeth?: number; /** Tooth valley radius. Defaults to `0.5`. */ innerRadius?: number; /** Tooth tip radius. Defaults to `1`. */ outerRadius?: number; /** * Width of the flat at the tooth tip, as a fraction of one tooth period. * `0` brings the tooth to a point. Defaults to `0.25`. */ tipWidth?: number; /** * Width of the flat at the valley floor, as a fraction of one tooth period. * `0` brings the valley to a point. Defaults to `0.25`. */ valleyWidth?: number; /** * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal. At `1` the * rising flank vanishes and the tooth's trailing face drops radially — a * ratchet or escapement wheel rather than a gear. Defaults to `0`. */ lean?: number; /** Number of sides on the center bore. Defaults to `5`. */ holeSides?: number; /** * Center bore radius. Clamped to stay strictly inside the tooth profile — a bore that * reaches the outline would punch through the gear and cannot be triangulated. Set to * `0` to omit the bore. Defaults to `0.25`. */ holeRadius?: number; /** Rotation in radians from the resting state. Defaults to `0`. */ rotation?: number; /** * Rotation of the bore in radians, **relative to the wheel**. Defaults to `0`. * * Only visible on a low {@link GearShapeOptions.holeSides} count: at `4` the bore rests as a diamond, points * at north, south, east and west, and `Math.PI / 4` turns it into a square with flat sides. A round bore has * no orientation to set. * * Relative rather than absolute, so turning the wheel carries the bore with it — the shaft does not slip. */ holeRotation?: number; } /** The four flat arrays a `BufferGeometry` is assembled from. */ export declare interface GeometryBuffers { positions: number[]; normals: number[]; uvs: number[]; indices: number[]; } export declare interface GeometryInspection { /** Owned representative positions in geometry-local coordinates. */ points: Vector3[]; /** Maps each source position-attribute index to a representative point ID. */ vertexToPoint: number[]; /** Welded point IDs in source triangle order, including degenerate triangles. */ triangles: [number, number, number][]; /** Component per source triangle; -1 for excluded degenerate triangles. */ componentOf: number[]; /** Edges with one nondegenerate triangle use, as point-ID pairs. */ boundary: [number, number][]; /** Edges with more than two nondegenerate triangle uses. */ nonManifold: [number, number][]; /** Two-use edges traversed in the same direction by both triangles. */ winding: [number, number][]; /** Point IDs whose vertex link is not one cycle or boundary chain. */ nonManifoldVertices: number[]; /** Source triangle ordinals with repeated welded IDs or negligible normalized area. */ degenerate: number[]; /** All source triangle ordinals in duplicate sets, independent of winding. */ duplicate: number[]; components: GeometryInspectionComponent[]; /** Actual weld distance in source units. */ tolerance: number; selfIntersectionsChecked: false; } export declare interface GeometryInspectionComponent { /** Source triangle ordinals belonging to this edge-connected component. */ triangles: number[]; /** Closed edges and manifold vertex links, with consistent winding and no duplicate faces. */ closed: boolean; /** Algebraic signed volume of the welded mesh, or null when inconsistent or nonfinite. Not certified occupied volume. */ signedVolume: number | null; } export declare interface GeometrySection { /** Normalized copy in geometry-local coordinates. */ plane: Plane; /** Closed contours without repeated endpoints; includes hole contours. */ loops: Vector3[][]; holes: number; /** Enclosed section area, subtracting holes, in source units squared. */ area: number; } /** * **The library's canonical glow falloff** — one cached texture, shared by everything that draws a glow. * * This exists so there is exactly one definition of what a glow looks like. A single {@link GlowHalo} and * a batched field of hundreds must be visually identical, and the only way to guarantee that is for both * to call this rather than each restating the same stops and easing. Duplicating the ramp is how a seam * appears. * * Colorless by design, so the tint lives on the material and one 64 KB texture serves any population and * any color. Never disposed — it is a module-level singleton other halos are still using. * * TODO: settle `smoothstep` vs `linear` here. Because everything now shares this ramp, the choice is a * library-wide aesthetic decision rather than a per-asset one. `smoothstep` softens the rim (it zeroes the * slope at each stop, which kills the Mach band a linear kink produces) but raises core and mid alpha, so * a dense arrangement merges into one mass instead of reading as distinct glows. Jason confirmed `linear` * reproduces his existing look and holds up better in a packed ring. Changing it here changes every glow — * which is the point, and why it needs deciding once rather than exposing an `easing` dial per asset. */ export declare function glowFalloffTexture(): DataTexture; /** * Soft glow card — reads as light without spending a `PointLight`. * * Lights are a **fixed budget**, capped independently of how much geometry you draw, and * exhausting the fragment-uniform space makes materials fail to compile outright rather than * degrade. Halos scale the other way: they are ordinary blended sprites, so hundreds cost hundreds * of cheap quads and no light slots at all. The usual arrangement is many halos plus one * real light per fixture, its intensity driven by the aggregate of the fakes. * * **It billboards itself.** `GlowHalo` *is* a `Sprite`, so it faces the viewer with no per-frame * call to forget, and every instance shares one internal quad geometry. Position and scale it * directly — `halo.position.copy(flame.position)`. * * **Tint is applied once, on the material.** The falloff texture is colorless and shared across * every default halo, so `setColor` is free and a rack of hundreds still holds a single texture. * * **Additive on purpose.** Light adds, and additive keeps overlaps *flat*: as one glow dims the next * brightens at the same rate, so their sum stays constant where they meet. Bright cores can clip at * 1.0 without an HDR target, which reads as a blown-out white center — acceptable, and the consumer's * to solve with tone mapping or bloom if they want to. * * A screen blend (`a + b - ab`) is tempting since it never clips, but its `-ab` term is largest where * two contributions are *equal* — the middle of every overlap — so it carves a shallow dark basin * exactly where glows meet. Measurably worse for a cluster of candles. `material` is public if you * want to try it anyway; screen also needs `premultipliedAlpha`, or the destination factor will refer * to the untinted texel and darken everything behind the card. * * **A card that intersects its fixture gets sliced along the intersection.** That seam is inherent to * representing glare as a world-space quad: real glare is a camera effect and spills *over* whatever * sits in front of the flame, while a quad occupying world space cannot. The levers are physical — * keep `size` modest relative to the fixture, and let a bloom pass (the consumer's choice) carry the * wide spread. `depthWrite` stays off so halos never occlude one another. * * @example * ```typescript * const halo = new GlowHalo({ color: 0xffaa44, size: 0.9 }); * halo.position.set(0, 1.4, 0); * scene.add(halo); * * // Drive it from a flicker, or leave it steady. * halo.setOpacity(0.75 * flicker); * ``` * * @see {@link FlameFlickerEffect} to modulate opacity, and {@link createRadialGradientTexture} * for the falloff itself. */ export declare class GlowHalo extends Sprite { material: SpriteMaterial; constructor({ color, size, opacity, map }?: GlowHaloOptions); /** Set halo opacity (e.g. scaled each frame by {@link FlameFlickerEffect}). */ setOpacity(opacity: number): void; get opacity(): number; /** Retint. Free — the falloff is colorless, so nothing is rebuilt. */ setColor(color: ColorRepresentation): void; /** * Release the material. Textures are never released here — the shared default is still in use by other * halos, and a supplied `map` belongs to whoever built it. */ dispose(): void; } export declare interface GlowHaloOptions { /** * Glow tint, multiplied over the falloff. Defaults to `0xffaa44` — or to white when `map` is * supplied, so a colored texture passes through untouched. * * The default ramp is colorless, so with it this *is* the halo's color. */ color?: ColorRepresentation; /** Card edge length in world units. Defaults to `1.2`. */ size?: number; /** Base opacity before flicker scaling. Defaults to `0.75`. */ opacity?: number; /** * Supply your own falloff texture instead of the shared default — typically one built with * {@link createRadialGradientTexture} and **shared across many halos**, which is what makes a large * population cheap: one texture, N materials. * * The caller owns it. {@link GlowHalo.dispose} will not release a texture it did not create. * * A supplied map may carry its own colors (a blue core inside a warm rim, say), so `color` defaults * to white here — a tint multiplies, and multiply can only darken, never add a hue that isn't there. */ map?: DataTexture; } /** * A gnarled, leafless oak — the kind that belongs in a graveyard. * * One recursive routine grows the skeleton. A branch is not a straight tube: it is a short *walk*, and * at every step the growth direction is nudged by a small random angle (the gnarl) while the radius * tapers. When a branch runs out of steps it forks into thinner, shorter children that walk the same * way, and recursion does the rest. * * Each branch is then given a body by the SWEEP — one continuous tube carried along a curve threaded * through its nodes. The obvious alternative, a chain of tapered frustums, needs a sphere at every * joint to mask the seam where consecutive tubes fail to meet; parallel transport carries one ring * around the bend and there is no seam to hide. Where a child meets its parent the tubes simply * intersect — welding branch surfaces is skinning, and at this polygon budget nobody will see it. * * Local frame: base at Y=0, growing +Y. * * @example * ```ts * const geometry = new GnarledTreeGeometry({ seed: 1337, maxDepth: 4 }); * ``` */ export declare class GnarledTreeGeometry extends BufferGeometry { readonly trunkRadius: number; constructor(options?: GnarledTreeGeometryOptions); /** * Turn a skeleton into a path. A Catmull-Rom curve threads the nodes, which turns the gnarl from a * chain of hard corners into an actual curve — and, crucially, the curve KNOWS ITS OWN TANGENT. We * ask it rather than estimating from the chords. * * The radius is read from the nodes the generator actually walked, not fitted to its endpoints: a * formula would smear the root flare all the way up the trunk. */ private branchPath; } export declare interface GnarledTreeGeometryOptions { /** Radius of the trunk at the collar. Defaults to `0.24`. */ trunkRadius?: number; /** Length of one step of growth. Defaults to `0.5`. */ segmentLength?: number; /** How many times a branch may fork. Defaults to `4`. */ maxDepth?: number; /** How hard each step bends. Defaults to `1`. */ gnarl?: number; /** How much each step narrows. Defaults to `0.86`. */ taper?: number; /** Sides of a branch's cross-section — the low-poly knob. Defaults to `5`. */ sides?: number; /** Stations swept per skeleton step. `1` is faceted; higher smooths the gnarl into a curve. Defaults to `4`. */ smoothing?: number; /** * A straight vertical rise before the trunk starts to gnarl — the root collar. Defaults to `0.35`. * * Without it the trunk bends on its very first step and leaves the ground already leaning, so its * base cannot lie flat. This is a correction to the PATH, not to the geometry: a real trunk rises * vertically out of the earth before it does anything interesting. */ baseRise?: number; /** How much wider the trunk is at the ground than just above it — the root flare. `1` is none. Defaults to `1.5`. */ rootFlare?: number; /** Optional seed for a reproducible tree. Omit for unique per runtime. */ seed?: number; } declare function gradient(stops: readonly ColorGradientStop[]): ColorSampler; /** * Graduated cylinder — a straight bore on a flared base foot, with a rolled rim. * * A lathe of {@link vesselShell} over {@link graduatedCylinderProfile}; the silhouette is exposed as * `.profile` for the fill. Local frame: base on Y=0, opening up +Y. */ export declare class GraduatedCylinderGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: GraduatedCylinderGeometryOptions); } export declare interface GraduatedCylinderGeometryOptions extends GraduatedCylinderProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `24`. */ radialSegments?: number; } /** * Graduated cylinder silhouette — a straight bore rising from a flared base foot. Base on Y=0, ends at the * rim. A straight cylinder, not the Erlenmeyer's cone. */ export declare function graduatedCylinderProfile({ radius, height, footRadius, footHeight, }?: GraduatedCylinderProfileOptions): Vector2[]; export declare interface GraduatedCylinderProfileOptions { /** Body (bore) radius. Defaults to `0.35`. */ radius?: number; /** Overall height. Defaults to `3`. */ height?: number; /** Base-foot radius — the wider skirt for stability. Defaults to `1.5 ×` the body radius. */ footRadius?: number; /** Foot height. Defaults to `0.08 ×` the height. */ footHeight?: number; } /** * Gregorian lattice — upright MULLIONS and level TRANSOMS dividing an opening into rectangular lights. * * The sibling of {@link DiamondLatticeGeometry}, and the same construction underneath: **a lattice type is * only ever a choice of angles.** A diamond is two families at `±45°`; this is two families at `90°` and * `0°`. Both hand their families to the same bar builder, so neither knows what the other is making. * * **No miters here, and none wanted.** Mullion crosses transom, and an X-junction has no bisector to * share — real glazing bars are halved into each other or simply butted, and interpenetration is the * honest model. What the bars DO need is their ends cut to the boundary, which is a different thing: in a * square opening every boundary is perpendicular to the bar meeting it, so a square end is already right * and nothing happens; under an arch the mullions run into a curve, and the ends follow it. * * Bars that would lie ON the boundary — a transom on the sill line, a mullion on a jamb — are dropped by * the same rule that drops offcuts, since their section straddles the edge. The frame occupies those * positions. * * Baked to a single `BufferGeometry` — one draw call for the whole lattice. * * Drawn at the ORIGIN — centered on X, sill at `y = 0` — so it lands on a frame and a pane built from the * same opening. Material groups: none. * * @example * ```ts * const opening = { width: 1.2, height: 1.6, arch: "semicircle" } as const; * * const bars = new Mesh(new GregorianLatticeGeometry({ opening }), painted); * ``` */ export declare class GregorianLatticeGeometry extends BufferGeometry { /** How many bars were built. Offcuts and bars lying on the boundary are dropped, so this is not derivable. */ readonly barCount: number; constructor({ opening, mullionSpacing, transomSpacing, mullionPhase, transomPhase, barWidth, barDepth, barSides, curveSegments, }?: GregorianLatticeGeometryOptions); } export declare interface GregorianLatticeGeometryOptions { /** * The opening the lattice fills. The SAME description a wall is punched with, a * {@link WindowFrameGeometry} rings, and a {@link PaneGeometry} glazes. * * Any arch, including `square`. **A rectangular Gregorian light needs no cutting at all** — every * boundary a bar meets is perpendicular to it, so a square end is already correct. Put the same lattice * under an ARCH and the mullions run into a curve, and the ends have to follow it. Both cases are this * one geometry. */ opening?: WallOpeningOptions; /** Distance between neighboring MULLIONS — the upright bars. Defaults to `0.24`. */ mullionSpacing?: number; /** Distance between neighboring TRANSOMS — the level bars. Defaults to `0.3`. */ transomSpacing?: number; /** * Slides the mullions across the opening. Defaults to `0`, which puts one on the centerline. * * Half a spacing puts a LIGHT on the centerline instead, which is what an even number of lights wants. * {@link GregorianLatticeWindow} works this out from the light counts. */ mullionPhase?: number; /** Slides the transoms up the opening. Defaults to `0`, which puts one on the sill line. */ transomPhase?: number; /** Width of the bar across the glass. Defaults to `0.03`. */ barWidth?: number; /** Depth of the bar through the glass. Defaults to `barWidth`, a square section. */ barDepth?: number; /** Sides on the bar's section — the low-poly knob. `4` is square stock. Defaults to `4`. */ barSides?: number; /** How finely the arch is followed. Defaults to `20`. */ curveSegments?: number; } /** * A Gregorian light: glass, glazing bars, and the frame that carries them. * * The sibling of {@link DiamondLatticeWindow}, assembled the same way and for the same reason — bars have * to be framed, so the three are a unit rather than a convenience grouping. * * **A factory exposes what the ASSEMBLY decides, and delegates the rest.** So `lightsAcross` / `lightsUp` * are here and the bar spacings are not: even divisions determine them, and they are reported on the * instance rather than asked for. `barWidth` is here because the frame is sized from it. `barSides` is * not, because it has to agree with nothing — reach for {@link GregorianLatticeGeometry} for that. * * All three parts are built from ONE `opening`, which is also what you punch the wall with. Every part is * exposed as a field, so any of them can be replaced without forking this. * * Local frame: centered on X, sill at `y = 0`, facing `+Z`. * * @example * ```ts * const opening = { width: 1.2, height: 1.6, arch: "semicircle" } as const; * * const light = new GregorianLatticeWindow({ opening, lightsAcross: 3, lightsUp: 4 }); * ``` */ export declare class GregorianLatticeWindow extends Group { /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */ readonly cutout: Path; readonly bars: Mesh; readonly frame?: Mesh; readonly glass?: Mesh; readonly lightsAcross: number; readonly lightsUp: number; /** The mullion spacing the light counts worked out to. An OUTPUT. */ readonly mullionSpacing: number; /** The transom spacing the light counts worked out to. An OUTPUT. */ readonly transomSpacing: number; constructor({ opening, lightsAcross, lightsUp, barWidth, barDepth, curveSegments, frame, glass, barColor, frameColor, glassColor, glassEmissive, glassEmissiveIntensity, }?: GregorianLatticeWindowOptions); /** Release every geometry and material this window owns. */ dispose(): void; } export declare interface GregorianLatticeWindowOptions { /** * The opening. **The same object that punches the wall** — pass one description to both and the hole and * the window cannot drift apart. Any arch, including `square`. */ opening?: WallOpeningOptions; /** * Lights across the opening's width. Defaults to `3`. * * A LIGHT is one pane; `3` gives two mullions between three lights. Counts rather than a spacing, * because the divisions have to land evenly — the spacing and the phase are worked out from this and * reported back. */ lightsAcross?: number; /** Lights from the sill up to the springing. Defaults to `4`. */ lightsUp?: number; /** * Width of the glazing bar. Defaults to `0.03`. * * An assembly option: the frame's inner band is sized from it, which is what makes the bars and the * frame read as one piece of joinery. */ barWidth?: number; /** Depth of the bar through the glass. Defaults to `barWidth`. */ barDepth?: number; /** How finely the arch is followed, shared by all three parts. Defaults to `24`. */ curveSegments?: number; /** The frame. `false` omits it; an object overrides what the assembly would have chosen. */ frame?: boolean | { inset?: number; outset?: number; depth?: number; }; /** The glass. `false` omits it; `rebate` runs the pane past the opening into a frame's groove. */ glass?: boolean | { rebate?: number; }; /** Bar and frame tint. Defaults to `#5c4033` — painted wood. */ barColor?: ColorRepresentation; /** Frame tint. Defaults to the bar's, because the two are one piece of joinery. */ frameColor?: ColorRepresentation; /** Glass tint. Defaults to `#6a7d8c`. */ glassColor?: ColorRepresentation; /** Glass emissive, for a lit window seen from outside. Defaults to off. */ glassEmissive?: ColorRepresentation; /** Defaults to `0`. */ glassEmissiveIntensity?: number; } /** * Creeping ground mist — soft horizontal cards drifting above the floor. * Interior patches wrap toroidally within `area`; optional perimeter patches * sit on/outside the fence on edges opposite the camera. One texture, one * `update(dt)` — perimeter is placement logic, not a separate effect. */ export declare class GroundFogEffect extends Object3D { private readonly patches; private readonly area; private readonly heightAt; private readonly texture; private elapsed; constructor({ count, area, perimeterCount, plotHalf, terrainHalf, cameraFacing, color, heightAt, }?: GroundFogEffectOptions); update(dt: number): void; dispose(): void; private makeInteriorPatch; private makePerimeterPatch; private makeMesh; } export declare interface GroundFogEffectOptions { /** Interior mist cards scattered across the plot. Defaults to `14`. */ count?: number; /** Half-extent of the interior scatter (world units). Defaults to `16`. */ area?: number; /** * Large cards hugging the plot perimeter — softens terrain cutoffs on the * horizons opposite the camera. Defaults to `0` (interior only). */ perimeterCount?: number; /** Half-extent of the inner bounded plot (fence, wall, scene edge, etc.). Defaults to `12`. */ plotHalf?: number; /** Terrain half-extent; perimeter cards spill outward toward this edge. Defaults to `16`. */ terrainHalf?: number; /** * Horizontal direction from plot center toward the camera. Perimeter patches * concentrate on the opposite edges. Defaults to `{ x: 1, z: 1 }`. */ cameraFacing?: { x: number; z: number; }; /** Mist tint. Defaults to `#9fb0c8`. */ color?: ColorRepresentation; /** * Sample ground height at world (x, z). Defaults to flat `y = 0`. * Portfolio graveyard uses undulating terrain via the same callback. */ heightAt?: (x: number, z: number) => number; } /** * A reference floor — a shadow-receiving plane with a coplanar {@link GridHelper}, ready to * `scene.add()`. A development aid for placing and scaling objects, not scene content. * * **The grid and the plane are exactly coplanar and do not z-fight.** Put a `GridHelper` on a plane at * the same Y and the depth buffer cannot separate them: the two surfaces round to the same depth and * the lines tear and shimmer as the camera moves. The usual workaround is to lift the grid by some * epsilon, which trades one bug for a subtler one — the lines float, visibly so at grazing angles, and * the epsilon has to be retuned every time the scene changes scale. * * The real fix is to bias the DEPTH rather than the position. The plane's material sets * `polygonOffset`, which pushes its fill back in the depth buffer *without moving it in space* — and * polygon offset does not apply to lines, so the grid stays exactly where it is and simply wins the * depth test. Perfectly coplanar, no tearing, no geometric lift, at any scale. * * A {@link Group}, because a `Mesh` and a `GridHelper` cannot merge into one object. Shadow receipt is * configured on the plane, where it belongs. * * @example * ```ts * const floor = new GroundGrid({ size: 16, planeColor: 0x1c2428 }); * scene.add(floor); * // toggle both together: floor.visible = false; * // release GPU resources: floor.dispose(); * ``` */ export declare class GroundGrid extends Group { readonly plane: Mesh; readonly grid: GridHelper; constructor({ size, divisions, planeColor, gridColor, centerColor, y, }?: GroundGridOptions); /** Dispose the plane and grid geometry/material. */ dispose(): void; } export declare interface GroundGridOptions { /** Square extent of the floor and grid, in world units. Defaults to `24`. */ size?: number; /** Grid divisions across `size`. Defaults to `size` (one cell per unit). */ divisions?: number; /** Solid floor tint. Defaults to `0x1a2430`. */ planeColor?: ColorRepresentation; /** Grid line color. Defaults to `0x223344`. */ gridColor?: ColorRepresentation; /** Center cross-line color. Defaults to `0x334455`. */ centerColor?: ColorRepresentation; /** World Y of the floor. Defaults to `0`. */ y?: number; } export declare interface HandheldDriftClipOptions extends CameraClipTiming { /** Positional drift scale in world units. Defaults to 0.04. */ intensity?: number; /** Pitch/yaw drift scale in radians. Defaults to 0.008. */ rotation?: number; /** Base frequency in cycles per playback second. Defaults to 0.6. */ frequency?: number; } /** * Wrought-iron hanging lantern frame — chain, cap, and open octahedron cage * built from edge struts. * * Material groups: `0` mount (chain + cap), `1` cage struts, `2` inner lamp * (solid octahedron). * * Local frame: origin at the chain top (hang point). The cage top vertex * attaches at the cap center, optionally lowered by `cageGap`. */ export declare class HangingLanternGeometry extends BufferGeometry { readonly drop: number; readonly chainWidth: number; readonly capWidth: number; readonly capHeight: number; readonly capDepth: number; readonly capOffset: number; readonly cageRadius: number; readonly cageStretch: number; readonly cageGap: number; readonly cageBarWidth: number; readonly innerScale: number; readonly inner: boolean; /** Y of the cage center in local space (negative, below the hang point). */ readonly cageCenterY: number; constructor({ drop, chainWidth, capWidth, capHeight, capDepth, capOffset, cageRadius, cageStretch, cageGap, cageBarWidth, innerScale, inner, }?: HangingLanternGeometryOptions); } export declare interface HangingLanternGeometryOptions { /** Chain length from the hang point. Defaults to `3`. */ drop?: number; /** Chain link cross-section. Defaults to `0.05`. */ chainWidth?: number; /** Cap width (X). Defaults to `0.18`. */ capWidth?: number; /** Cap height (Y). Defaults to `0.16`. */ capHeight?: number; /** Cap depth (Z). Defaults to `0.18`. */ capDepth?: number; /** Cap center offset below the chain bottom. Defaults to `0.02`. */ capOffset?: number; /** Cage vertex radius before stretch. Defaults to `0.42`. */ cageRadius?: number; /** Vertical stretch on the cage. Defaults to `1.4`. */ cageStretch?: number; /** Extra downward offset below the cap-center cage attach. Defaults to `0`. */ cageGap?: number; /** Cage strut thickness. Defaults to `0.03`. */ cageBarWidth?: number; /** Inner lamp scale relative to the cage (inset to sit inside struts). Defaults to `0.96`. */ innerScale?: number; /** Include the solid inner octahedron lamp volume. Defaults to `true`. */ inner?: boolean; } /** * A hardwood floor of planed boards, laid at any angle and **cut to the room**. Walking surface on * `y = 0`, centered on the origin. * * The laying is {@link layPlankFloor} — the same rows, stagger, starter boards and no-runt rule the rustic * {@link PlankFloor} uses. It never learns that the rows are not square to the room: the boards are laid on * a sheet sized to COVER the room, then each is clipped to the room's outline and the overhang thrown away. * * **Clipped, not mitered.** A board crossing a corner comes back with five or six sides, which no pair of * cut planes on a swept box can express. Clipping handles it, handles every other case with the same code, * and at `rotation: 0` is a no-op — so the general case costs nothing when it is not needed. Measured, the * boards cover the room to within the row gaps at every angle. * * **Baked to a single geometry and a single material** at any size. Every board is a different shape once * cut, and differing items merge where identical ones would instance; per-board color rides a vertex * attribute rather than a material group, which is what keeps it to one draw call. * * A cut board at the wall is not a defect. **A wall is a boundary condition, not the end of the floor** — * a carpenter cuts what the room demands, and the offcuts at a diagonal's corners are what the style looks * like. `minSliverArea` decides only how small a scrap is still worth laying. * * Material groups: none. * * @example * ```ts * const floor = new HardwoodFloor({ width: 6, depth: 4, rotation: Math.PI / 4, seed: 12 }); * scene.add(floor); * floor.boardCount; // laid * floor.clippedCount; // how many met a wall * floor.sliverCount; // how many offcuts were too small to lay * ``` */ export declare class HardwoodFloor extends Group { #private; readonly mesh: Mesh; /** Boards laid. */ readonly boardCount: number; /** Of those, how many were cut by a wall. Zero at `rotation: 0`. */ readonly clippedCount: number; /** Offcuts discarded for being smaller than `minSliverArea`. */ readonly sliverCount: number; /** Rows across the laying sheet. */ readonly rowCount: number; /** The width each board actually got. */ readonly plankWidth: number; /** How close any two neighboring-row joints came. Compare to `minStagger`. */ readonly closestJoint: number; constructor({ width, depth, rotation, plankThickness, minSliverArea, color, colorVariance, colors: colorSampler, material, ...layout }?: HardwoodFloorOptions); /** Releases the merged geometry, and the material when this floor made it. */ dispose(): void; } export declare interface HardwoodFloorOptions extends Omit { /** Room extent along X. Defaults to `5`. */ width?: number; /** Room extent along Z. Defaults to `4`. */ depth?: number; /** * Which way the boards run, in radians. Defaults to `0` — along the room's width. * * `Math.PI / 4` is the classic diagonal. Any angle works: the boards are laid on a sheet sized to cover * the room and then cut to it, so nothing here is a special case. */ rotation?: number; /** Board thickness. Defaults to `0.055`. */ plankThickness?: number; /** * Smallest offcut worth laying, in square units. Defaults to `0.004`. * * Cutting boards to a room leaves scraps, and past some size a scrap is not a board. Where that line * sits is a judgment rather than a calculation — set it to `0` and the corners fill with needles; set it * high and real boards go in the bin, leaving a visible notch at the wall. */ minSliverArea?: number; /** Base timber color. Defaults to `#6b4b2c`. */ color?: string; /** Per-board tint spread in HSL, so no two boards match. Defaults to `0.06`. */ colorVariance?: number; /** * Per-board sampler overriding color and colorVariance. Writes a working-space Color. * Called once per retained board, in layout order after clipping/sliver removal; * index is contiguous from zero. All vertices of that board receive the same color. * The seeded color stream is separate from layout. Omit to preserve the original tint recipe. */ colors?: ColorSampler; /** A material to use instead of the default. **Must set `vertexColors: true`**, or every board goes white. */ material?: Material; } export declare interface HeadstoneFieldOptions extends HeadstoneSettleOptions { /** Plots across each row — the X axis. Defaults to `10`. */ columns?: number; /** Number of rows, front to back — the Z axis. Defaults to `10`. */ rows?: number; /** Plot pitch ACROSS a row — the plot's width. Defaults to `1`. */ spacing?: number; /** * Plot pitch BETWEEN rows — the plot's length. Defaults to `2.2`. * * A grave is longer than it is wide, so a cemetery's rows sit further apart than the stones within a * row. Leave this at the default and the field reads as real surveyed plots rather than a square grid. */ rowSpacing?: number; /** * Fraction of plots that actually hold a stone, `0`–`1`. Defaults to `1` (every plot filled). * * Below `1`, plots are left empty at random — the gaps of an old churchyard where stones were never * cut or have since been lost. It is also what a sparse, distant fill wants: a thin scatter of stones * rather than a solid block. */ density?: number; } export declare interface HeadstoneRowOptions extends HeadstoneSettleOptions { /** Number of plots. Defaults to `8`. */ count?: number; /** * Plot pitch — center to center along the row. Defaults to `1`. * * A cemetery is surveyed on a uniform grid, so the *plot* is what repeats, not the gap. Headstones * vary wildly in width (a cross is 0.4, an obelisk 0.75), so spacing them by a fixed gap would put * their centers at irregular intervals — and a row of graves reads by its plot rhythm. Irregular * centers do not look aged; they look wrong. */ spacing?: number; } /** Everything that ages a stone — shared by a single {@link rowOfHeadstones} and a whole {@link fieldOfHeadstones}. */ export declare interface HeadstoneSettleOptions { /** Optional seed for a reproducible layout. Omit for unique per runtime. */ seed?: number; /** Max lean off vertical, in radians, on both X and Z. Defaults to `0.12` (~7°). */ leanMax?: number; /** Max twist about Y, in radians. Keep it small — a turned stone reads as settled, not knocked over. Defaults to `0.4`. */ twistMax?: number; /** * How the twist is distributed within `±twistMax`, via {@link RandomSource.skewCenter}. Defaults to * `1.6`. * * `1` is uniform — every angle equally likely, so half the stones are dramatically turned. Higher pulls * most stones toward straight while still letting the occasional one reach the full `twistMax`, so a * hard-turned stone reads as the exception it should be, not the rule. */ twistBias?: number; /** * Max *additional* depth a stone settles into the ground, beyond whatever its lean already * demands. Stones only ever sink, never rise. Defaults to `0.08`. * * Leaning is not free: a stone pivots about its base, so tilting lifts one edge of its footing out * of the earth. That much burial is compulsory — it is what the geometry costs. This is the depth * the stone has settled *on top of* it, so the two stay independent and a hard-leaning stone still * sinks as deep as an upright one. */ sinkMax?: number; /** Max lateral drift off the plot center, on X and Z. Defaults to `0.05`. */ driftMax?: number; /** Min uniform scale. Defaults to `0.85`. */ scaleMin?: number; /** Max uniform scale. Defaults to `1.2`. */ scaleMax?: number; /** Base stone tint. Defaults to `#777777`. */ color?: ColorRepresentation; /** Legacy HSL tint spread around the base color. `0` makes them identical. Defaults to `0.09`. */ weathering?: number; /** Overrides color/weathering. Index counts retained stones before grouping by silhouette. * Uses an independent seeded stream. The generated material is white; supplied materials still multiply the tint. */ colors?: ColorSampler; /** Stone material. Omit for a flat-shaded standard material: white with `colors`, otherwise tinted by `color`. */ material?: Material; /** * The palette the row draws from. Defaults to {@link DEFAULT_HEADSTONE_STYLES}. * * Pass your own to reshape the graveyard — `[{ kind: "cross" }]` for a war plot, all-`rounded` with * one `arch` for a uniform churchyard. Weights are relative; omit `weight` for `1`. */ styles?: readonly HeadstoneStyle[]; } /** * One kind of stone in the row's palette. * * The `rounded` family is where the variety lives — it is an arched slab, so it takes the whole * {@link ArchStyle} vocabulary plus a narrower `archWidth` for the shouldered look (an arch sitting *on* * the slab). `cross`, `obelisk` (a tapered monument) and `obeliskHeadstone` (a stepped one) are single * silhouettes. Every style carries a `weight` — its relative frequency in the row. */ export declare type HeadstoneStyle = { kind: "rounded"; arch?: ArchStyle; archWidth?: number; archHeight?: number; weight?: number; } | { kind: "square"; weight?: number; } | { kind: "cross"; weight?: number; } | { kind: "celticCross"; weight?: number; } | { kind: "obelisk"; weight?: number; } | { kind: "obeliskHeadstone"; weight?: number; }; /** * Extruded heart prism. */ export declare class HeartGeometry extends ExtrudeGeometry { constructor({ depth, ...shapeOptions }?: HeartGeometryOptions); } export declare interface HeartGeometryOptions extends HeartShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Heart profile — two bulbous circular lobes sweeping down to a sharp tip. * * **The lobes are real circles, not cubics pretending to be round.** A heart is two discs set side by * side, met at a cleft, with the outer edges sweeping in to a point — so that is exactly how it is drawn * here: two half-circle arcs of radius `width / 4`, joined tip-ward by concave curves. Faking the lobes * with beziers is what leaves them flat and lopsided. * * Because the lobe radius is set by `width` alone, **stretching `height` lengthens the point without * deflating the lobes** — a tall heart stays round on top, which a single width/height scale of a bezier * heart never manages. * * Centered on the origin; `width` / `height` are its real extents. The card suit, sibling to * {@link SpadeShape}, {@link ClubShape}, {@link DiamondShape}. */ export declare class HeartShape extends Shape { constructor({ size, width, height }?: HeartShapeOptions); } export declare interface HeartShapeOptions { /** Overall scale factor. Defaults to `1`. */ size?: number; /** Heart width across the lobes. Defaults to `1.8`. */ width?: number; /** Heart height, lobe tops to tip. Defaults to `1.7`. */ height?: number; } /** Helix about +Y with circular sections in XZ; samples include both endpoints and carry derivatives. */ export declare function helixPath({ radius, height, turns, startAngle, segments, }?: HelixPathOptions): PathPoint[]; export declare interface HelixPathOptions { /** Radial distance from the Y axis. */ radius?: number; /** Total displacement along +Y. */ height?: number; /** Number of turns. */ turns?: number; /** Start angle in radians. */ startAngle?: number; /** Number of helix intervals. */ segments?: number; } export declare interface HewnTimberGeometryOptions { /** Radius at the top. Defaults to `0.5`. */ topRadius?: number; /** * Radius at the bottom. Defaults to `0.55`. * * The taper is what stops a rank of timbers reading as extruded pipe — a real pole is thicker at the * butt. Keep it small; past about `1.2 ×` the top it stops looking hewn and starts looking turned. */ bottomRadius?: number; /** * Facets around the pole. Defaults to `6`. * * This is the low-poly knob. `6` is a coarse split log; `7`–`8` reads finer and more dressed. Even * counts put a flat toward the viewer, odd counts put an edge — which is why `7` looks subtly less * machined than `6` at the same radius. */ radialSegments?: number; /** Rings along its length. Defaults to `3`. More rings let the irregularity vary along the timber. */ heightSegments?: number; /** * Spatial frequency of the surface perturbation, per axis. Defaults to `[17.3, 11.7, 23.1]`. * * Deliberately mutually prime-ish and unequal: equal frequencies produce a visible helical banding * because the three terms come back into phase along the axis. */ frequency?: [number, number, number]; /** How far the perturbation pushes the surface, as a fraction of radius. Defaults to `0.075`. */ amplitude?: number; } export declare interface HexagonalTileCountOptions { width: number; depth: number; height: number; count: number; gap: number; material?: Material; } export declare interface HexagonalTileRadiusOptions { width: number; depth: number; height: number; radius: number; gap: number; material?: Material; } export declare function hexToHsl(hex: number): [number, number, number]; /** * Convert hexadecimal literal numeric color value to RGB array * @param hex */ export declare function hexToRgb(hex: number): [number, number, number]; /** Convert HSL degrees / percentages to a packed 24-bit RGB number (for example, 0xff0000). */ export declare function hslToHex(h: number, s: number, l: number): number; /** Hue wraps to [0, 360); saturation/lightness are clamped to 0–100. Returns fractional RGB bytes. */ export declare function hslToRgb(h: number, s: number, l: number): [number, number, number]; export declare interface ImpactKickClipOptions extends CameraClipTiming { /** Camera-local kick direction: +X right, +Y up, +Z backward. Normalized internally. */ direction?: Vector3; /** Kick amplitude in world units. Defaults to 0.25. */ intensity?: number; /** Number of damped rebound cycles. Defaults to 2. */ oscillations?: number; } /** An oriented triangle sheet. Shared point indices define connectivity, independently of shading. */ export declare interface IndexedSurface { readonly points: readonly Vector3[]; readonly triangles: readonly (readonly [number, number, number])[]; /** Optional per-point UVs. Without them, each skin triangle receives a unit triangle mapping. */ readonly uv?: readonly Vector2[]; } /** Read-only inspection of stored positions and triangles. Does not repair, evaluate morphs/skinning, * test intersections, or interpret shell nesting. Other attributes and material groups are ignored. */ export declare function inspectGeometry(source: BufferGeometry, options?: InspectGeometryOptions): GeometryInspection; export declare interface InspectGeometryOptions { /** Weld distance relative to the bounding-box diagonal. Default 1e-6; range [1e-12, 0.001]. */ tolerance?: number; } /** * Extruded **internal gear** — a ring whose opening is toothed, teeth pointing inward. See * {@link InternalGearShape} for the profile and its three radii. * * This is the ring of a planetary gearset and the mating half of an internal pair. Note that an *externally* * toothed ring — a flywheel starter ring — needs nothing new: it is {@link GearGeometry} with a bore set just * inside the valley radius. "Ring gear" names the form, not the tooth direction. * * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, matching {@link GearGeometry} so * meshing wheels share the plane they turn in. * * Material groups: **none** — one material for the whole ring. * * @example * ```typescript * const ring = new Mesh(new InternalGearGeometry({ teeth: 36 }), steel); * ``` */ export declare class InternalGearGeometry extends ExtrudeGeometry { /** The tip radius actually used. */ readonly tipRadius: number; /** The valley radius actually used. */ readonly valleyRadius: number; /** The rim radius actually used, after clamping outside the toothed opening. */ readonly rimRadius: number; constructor({ depth, ...shapeOptions }?: InternalGearGeometryOptions); } export declare interface InternalGearGeometryOptions extends InternalGearShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Internal gear profile — a plain ring whose **opening is toothed**, teeth pointing inward. * * Where {@link GearShape} makes the teeth its outer contour and cuts a bore, this inverts the roles: the outer * contour is a plain circle and the teeth are the hole. The tooth period is the external gear's, unchanged. * * **Three radii, all absolute from the center.** {@link InternalGearShapeOptions.tipRadius} and * {@link InternalGearShapeOptions.valleyRadius} are the two extremes of the toothing; their order is not * enforced, so a valley inside the tip inverts it. {@link InternalGearShapeOptions.rimRadius} is the third * because the teeth do not define the outer edge here — the opening is a hole, so the ring needs its own * outside dimension. */ export declare class InternalGearShape extends Shape { /** The tip radius actually used. */ readonly tipRadius: number; /** The valley radius actually used. */ readonly valleyRadius: number; /** The rim radius actually used, after clamping outside the toothed opening. */ readonly rimRadius: number; constructor({ teeth, tipRadius, valleyRadius, rimRadius, rimSides, tipWidth, valleyWidth, lean, rotation, }?: InternalGearShapeOptions); } export declare interface InternalGearShapeOptions { /** Number of teeth. Defaults to `36`. */ teeth?: number; /** Radius the tooth tips reach. Defaults to `0.72`. */ tipRadius?: number; /** Radius the valley floors sit at. Defaults to `0.85`. */ valleyRadius?: number; /** Outside radius of the ring. Clamped to stay outside the toothed opening. Defaults to `1`. */ rimRadius?: number; /** Sides on the outer rim. Defaults to `48`. */ rimSides?: number; /** * Width of the flat at the tooth tip, as a fraction of one tooth period. `0` brings the tooth to a point. * Defaults to `0.25`. */ tipWidth?: number; /** * Width of the flat at the valley floor, as a fraction of one tooth period. `0` brings the valley to a point. * Defaults to `0.25`. */ valleyWidth?: number; /** * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal; at `1` the rising flank vanishes. Defaults to * `0`. */ lean?: number; /** Rotation in radians from the resting state. Defaults to `0`. */ rotation?: number; } /** * Sample Vector2(radius, height): radius follows curveFunction over the clamped min/max interval; * height interpolates linearly. segments must be positive. */ export declare function interpolateCurve(curveFunction: (t: number) => number, startRadius: number, endRadius: number, startHeight: number, endHeight: number, segments?: number, min?: number, max?: number): Vector2[]; /** A reusable geometry; lighting and materials belong to the consuming scene. */ export declare class JackOLanternGeometry extends BufferGeometry { readonly type = "JackOLanternGeometry"; constructor(options?: JackOLanternGeometryOptions); } export declare interface JackOLanternGeometryOptions extends JackOLanternRindGeometryOptions, PumpkinStemGeometryOptions, PumpkinAssemblyOptions { } export declare interface JackOLanternRindGeometryOptions { /** Overall radius. Positive; defaults to 1. The rind rests on y = 0. */ rindRadius?: number; /** Vertical radius divided by horizontal radius. Positive; defaults to 0.82. */ rindSquash?: number; /** Integer rib count, 0–16. Defaults to 8. */ rindRibs?: number; /** Radial rib amplitude, 0–0.2. Defaults to 0.075. */ rindRibDepth?: number; /** Fractional concentric inset, strictly between 0 and 1. Default 0.13. * Inner scale = 1 - rindThickness; this is NOT a constant normal offset or a world-unit distance. */ rindThickness?: number; /** Uniform parameter-space subdivisions, 2–4. Default 4; each level quadruples skin triangles. */ rindSubdivisions?: number; /** Scale of the fixed face in longitude/latitude space, 0.1–1.5. Default 1. */ faceScale?: number; } /** Concatenate paths without cloning entries or removing coincident joints. */ export declare function joinPaths(...paths: PathPoint[][]): PathPoint[]; export declare interface LandingBumpClipOptions extends CameraClipTiming { /** Initial dip scale along camera-local -Y, in world units. Defaults to 0.3. */ intensity?: number; } /** * Lay a boarded floor — **where the boards go, not what they are made of.** * * Returns placements, so the same laying rules serve a floor of plain boxes, one of * {@link WeatheredPlankGeometry}, a ceiling, or a deck. The trade knowledge is here; the geometry is not. * * **The mistake that makes a plank floor read as stripes is spanning each board across the whole room.** A * real floor is laid in rows of *several* boards butted end to end, with the end joints in neighboring * rows deliberately kept apart — the flooring trade's own rule, and the same idea as a running bond in * masonry. Board-to-board color and shape variation cannot rescue a floor whose joints all line up, and is * barely needed once they do not. * * Two smaller rules come from the same trade. Each row opens with a **shortened starter board**, so rows do * not all begin their run together. And a row never ends on a **runt** — a remainder shorter than the * minimum is absorbed by the board before it. * * `minStagger` is capped at `(longest − shortest) / 2`. A joint can only be moved by varying its board's * length, so that is the furthest it can travel while still landing clear of an obstruction; asking for * more does not tighten the floor, it makes the search fail more often and the worst joint *worse*. * * @example * ```ts * const { placements, plankWidth } = layPlankFloor({ length: 4, depth: 3, seed: 7 }); * * for (const { start, length, across, sequence } of placements) { * const board = new BoxGeometry(length, thickness, plankWidth); * board.translate(start + length / 2 - 2, -thickness / 2, across); * } * ``` */ export declare function layPlankFloor({ length, depth, plankWidth, gap, minPlankLength, maxPlankLength, minStagger, seed, }?: PlankFloorLayoutOptions): PlankFloorLayout; /** * Low-poly folded leaf — a pointed ellipse with a gently raised midrib so it * catches rim light instead of reading as a flat sliver. Spine vertices sit * slightly above the mirrored rim outline, giving a soft V cross-section under * flat shading. * * Local frame: tip at +Y, base at −Y, fold rises along +Z. */ export declare class LeafGeometry extends BufferGeometry { readonly size: number; readonly lift: number; constructor({ size, lift }?: LeafGeometryOptions); } export declare interface LeafGeometryOptions { /** Overall leaf scale. Defaults to `0.13`. */ size?: number; /** Midrib rise above the rim as a fraction of size. Defaults to `0.22`. */ lift?: number; } /** * Thunderstorm lightning that drives a {@link DirectionalLight} each frame. * * Rather than scheduling timeouts (which need careful teardown), each strike * enqueues two or three decaying intensity spikes a few frames apart — the * characteristic stutter of real lightning. Read {@link LightningEffect.level} * after {@link LightningEffect.update} to sync fog, sky, rain, or emissive * surfaces with the flash. * * @example * ```typescript * const bolt = new DirectionalLight(0xcdd8ff, 0); * bolt.position.set(5, 12, -8); * bolt.target.position.set(0, 0, 0); * scene.add(bolt, bolt.target); * * const storm = new LightningEffect({ light: bolt, peak: 12, minGap: 3, maxGap: 9 }); * * function animate(delta: number) { * storm.update(delta); * const flash = storm.level; // 0..~1.2 * renderer.render(scene, camera); * } * ``` */ export declare class LightningEffect { /** Current flash level, 0 = dark. Read after each {@link LightningEffect.update}. */ level: number; private readonly light; private readonly peak; private readonly minGap; private readonly maxGap; private readonly spikes; private clock; private nextStrike; constructor({ light, peak, minGap, maxGap }: LightningEffectOptions); /** * Advance the strike schedule and update the driven light intensity. Pass * elapsed frame time in seconds. */ update(dt: number): void; /** Force the driven light dark, e.g. when lightning is toggled off. */ quiet(): void; private strike; } export declare interface LightningEffectOptions { /** Directional light driven by lightning flashes. Start at intensity `0`. */ light: DirectionalLight; /** Peak light intensity at full flash. Defaults to `12`. */ peak?: number; /** Minimum seconds between strikes. Defaults to `3`. */ minGap?: number; /** Maximum seconds between strikes. Defaults to `9`. */ maxGap?: number; } /** One stop of a linear ramp. */ export declare interface LinearGradientStop { /** Position along the ramp: `0` at the start (first row), `1` at the end (last row). */ offset: number; color: ColorRepresentation; /** Opacity at this stop, `0`–`1`. Defaults to `1`. */ alpha?: number; } export declare interface LinearGradientTextureOptions { /** The ramp, start to end. Sorted internally, so declaration order doesn't matter. */ stops: LinearGradientStop[]; /** Length of the ramp in texels. Defaults to `128` (a power of two, so mipmaps are exact). */ size?: number; /** * How each pair of stops is interpolated. Defaults to {@link Easing.linear}, matching a canvas gradient. * {@link Easing.smoothstep} brings the slope to zero at each stop, removing the faint Mach band a linear * ramp leaves. */ easing?: EasingFunction; } export declare const LineEquations: { calculateXFromSlopeIntercept: typeof calculateXFromSlopeIntercept; calculateYFromSlopeIntercept: typeof calculateYFromSlopeIntercept; }; /** Sample a straight segment including both endpoints; from and to must differ for a usable tangent. */ export declare function linePath(from: Vector3, to: Vector3, segments?: number): PathPoint[]; /** * The liquid inside a vessel, cut from the vessel's OWN profile (see {@link fillProfile}) and revolved. * * Pure geometry: the liquid's colour, opacity and glow are a material the caller supplies. Because it is * turned from the same curve as the glass, it can never clip through it. Comes back EMPTY (no attributes) * when the vessel is empty, so a caller can always build one and drive `fill` from a control. * * Draw the liquid BEFORE the glass (`liquid.renderOrder < shell.renderOrder`): their centres coincide, so * depth-sorting has nothing to say and the order must be stated. */ export declare class LiquidFillGeometry extends BufferGeometry { readonly fillHeight: number; constructor({ profile, fill, inset, radialSegments }: LiquidFillGeometryOptions); } export declare interface LiquidFillGeometryOptions { /** The vessel profile to fill — take it from a vessel geometry's `profile`. */ profile: Vector2[]; /** Fill level, as a fraction of the vessel's height. `0` is empty. Defaults to `0`. */ fill?: number; /** Radius inset so the liquid wall isn't coplanar with the glass. Defaults to `0.02`. */ inset?: number; /** Circumference segments — match the vessel's for a clean surface. Defaults to `32`. */ radialSegments?: number; } /** * Pin a sky layer to the viewer, so it holds a **direction but never a location** — it can never * be approached, dollied toward, or placed behind anything. Call once at construction; the layer * then needs nothing per frame, so consumers only ever `scene.add(layer)`. * * This follows camera translation only; it does not rotate the layer to face the camera. * * Each renderable's `onBeforeRender` re-snaps the layer to the active camera. The renderer invokes * that hook before it derives the object's model-view matrix, so the move lands in the same frame. * Working on the container rather than in a shader is what makes this safe for `InstancedMesh`: * per-instance matrices are untouched, so instanced layers need no special handling. * * Notes: * - **This claims `onBeforeRender` on every renderable passed in.** Assigning your own handler to one * of them silently breaks the lock — the layer stops tracking the camera, with no error. Wrap or * chain the existing handler rather than replacing it. Nothing needs unsubscribing, though: the * renderer only invokes the hook while the object is being drawn, so removing the layer from the * scene stops it, and dropping the reference collects it. `dispose()` has nothing to undo. * - `frustumCulled` is disabled on every renderable. Culling runs *before* `onBeforeRender`, so a * layer judged against a stale position could be culled and then never get the chance to correct * itself. * - The camera's *world* position is used and converted back into the layer's parent space, so a * transformed parent or a parented camera still resolves correctly. * - The layer sits at the origin until the first render, since nothing has supplied a camera yet. * Read world positions off a sky layer only after a frame has been drawn. * * @example * ```typescript * class Sun extends Object3D { * constructor() { * super(); * const disc = new Mesh(geometry, material); * this.add(disc); * lockToViewer(this, [disc]); * } * } * ``` */ export declare function lockToViewer(layer: Object3D, renderables: Object3D[]): void; /** * Skin closed rings with equal point counts and corresponding indices; inputs remain unchanged. * Fewer than two rings returns empty geometry; unequal point counts throw. * * ```ts * // Transition from a square section to a circle. * const loops = correspondLoops([squareOutline, circleOutline]); * const rings = loops.map((loop, i) => loop.map((p) => new Vector3(p.x, i * 2, p.y))); * const geometry = loft(alignRings(rings)); * ``` */ export declare function loft(rings: Vector3[][], { cap, closed }?: LoftOptions): BufferGeometry; export declare interface LoftOptions { /** Triangulate each end after projection onto its Newell-normal plane. */ cap?: boolean; /** Stitch the final ring to the first and omit caps; do not repeat the first ring. */ closed?: boolean; } /** Where a door hangs in this mausoleum, in the mausoleum's own coordinates. */ export declare interface MausoleumDoorway { /** Width of the opening. A door should be built slightly narrower, for clearance. */ width: number; /** Height of the opening's straight sides, to the springing. */ height: number; /** Rise of the arch. Equal to `width / 2` — a perfect semicircle. */ archHeight: number; /** Centerline of the opening. */ x: number; /** The sill. A door's `y = 0` sits here. */ y: number; /** * The hinge plane — the wall's OUTER face. A door's `z = 0` (its front face, where the straps are * bolted and the pin stands) sits here. * * Not set back into the reveal. Strap hinges are mounted on the face you can reach, so the pin is * flush with the facade and the slab hangs behind it, filling the reveal. Sink the hinge plane into * the opening instead and the ironwork disappears into the jamb's shadow — which is exactly what a * real door does not do. */ z: number; } /** * A mausoleum — four stone walls, a peaked roof, and an arched doorway you can walk through. * * **The building is a SHELL, not a block.** Each wall is a slab with real thickness, so the interior is * genuine space with genuine inward-facing surfaces: open the doors and you look into a room, not at a * backface. That is the whole reason the walls cost four boxes instead of one. * * **The doorway is carved out of the front wall's OUTLINE, not punched through it as a hole.** A void * that reaches the floor is not a hole — `ExtrudeGeometry` would run a side wall along its bottom edge * and hand you a face lying across the threshold. Drawing the wall *around* the opening means that face * never exists, and the notch's side walls become the reveals: the jambs and the arch soffit. See * {@link WallShape}, which owns this distinction. * * The doorway's dimensions are published on {@link MausoleumGeometry.doorway} rather than left for a * caller to rediscover — hang a door by asking the building where its hinges go, the same way a fence * run asks a post how wide it is. * * Group indices: * 0. Base * 1. Building — walls and pillars * 2. Roof * 3. Interior — the floor and ceiling of the room inside * * @example * ```ts * const mausoleum = new Mausoleum(); * const { width, height, archHeight, x, y, z } = mausoleum.geometry.doorway; * * const doors = createDoubleDoor({ width: width - 0.04, height: height - 0.02, archHeight: archHeight - 0.02 }); * doors.position.set(x, y, z); * mausoleum.add(doors); * ``` */ export declare class MausoleumGeometry extends BufferGeometry { /** Where a door hangs. See {@link MausoleumDoorway}. */ readonly doorway: MausoleumDoorway; constructor(); } /** * Cache cumulative chord distances for at least two points. Closed-path sampling requires positive total length. * * ```ts * const plan = measurePath(footprint, { closed: true }); * plan.length; // the perimeter * ``` */ export declare function measurePath(points: Vector3[], { closed }?: MeasurePathOptions): PathMeasure; export declare interface MeasurePathOptions { /** Include the closing segment from last point to first. */ closed?: boolean; } /** * Bisecting cut normals for separate members sharing a polyline; duplicate corners are removed. * Use matching sections/roll and widenSeatCuts for mirrored members; widening is 1 / cos φ. * * ```typescript * // A picture frame as four separate sticks, each mitered at both ends. * const cuts = miterCuts(corners, { closed: true }); * * const sides = corners.map((from, i) => { * const to = corners[(i + 1) % corners.length]; * return sweep( * rectProfile(faceWidth, depth), * miterFrames(linePath(from, to, 1), { * startCut: cuts[i], * endCut: cuts[(i + 1) % corners.length], * widenSeatCuts: true, * }), * ); * }); * ``` */ export declare function miterCuts(corners: Vector3[], { closed }?: MiterCutsOptions): Vector3[]; export declare interface MiterCutsOptions { /** Treat the corners as a closed loop, so the last corner joins back to the first. Do not repeat the * start point. */ closed?: boolean; } /** * Frame polyline corners on normalize(incoming + outgoing), using positions rather than supplied tangents. * Widen along the lean axis by 1 / cos φ (√2 at a 90° corner); matched sections share each corner ring. * * ```typescript * // A mitered square frame, swept as one closed loop. * const corners = [a, b, c, d].map((position) => ({ position, tangent: new Vector3() })); * const rail = sweep(rectProfile(0.03, 0.02), miterFrames(corners, { closed: true }), { closed: true }); * ``` * * ```typescript * // A raked post seat-cut flat at both ends, so it sits flush on horizontal plates. * const up = new Vector3(0, 1, 0); * const post = sweep(circleProfile(0.015, 4), miterFrames(linePath(foot, head, 2), { startCut: up, endCut: up })); * ``` */ export declare function miterFrames(path: PathPoint[], { reference, closed, startCut, endCut, widenSeatCuts, miterLimit, }?: MiterFramesOptions): Station[]; export declare interface MiterFramesOptions { /** Reference for the initial perpendicular frame; must yield a nonzero seed. */ reference?: Vector3; /** * Treat the path as a closed loop — the last point joins back to the first, and both get mitered. * Do not repeat the start point. */ closed?: boolean; /** First endpoint cut normal, oriented to the path; ignored on a closed path. */ startCut?: Vector3; /** Last endpoint cut normal, oriented to the path; ignored on a closed path. */ endCut?: Vector3; /** * false preserves the cut-plane footprint; true preserves section width through widening by 1 / cos φ. * Without widening, section-width loss is 1 − cos φ (about 1.1% at 8.5°); internal corners always widen. */ widenSeatCuts?: boolean; /** * Clamp widening 1 / cos φ to miterLimit; Infinity removes the bound. * Clamping shortens the miter without adding bevel topology. */ miterLimit?: number; } /** * For unit axes pointing away from joint, normal = normalize(a - b), with (a - b) · a = 1 - a · b > 0. * A closing joint requires mirrored section and roll as well as axes; distinct axes are required. */ export declare function miterPlane(joint: Vector3, a: Vector3, b: Vector3): CutPlane; declare function mix(samplers: readonly ColorSampler[], weights?: readonly number[]): ColorSampler; /** Which side of the run the molding stands on. */ export declare type MoldingFacing = "inward" | "outward"; /** * Molding run along a wall line — crown at the ceiling, base at the floor. * * This is a sweep of a {@link moldingProfile} along the corner line, and the only interesting part is * what happens where two walls meet: the run is framed with {@link miterFrames}, so every corner is cut * on the plane bisecting it and the two lengths share one ring. The joint closes exactly, at any angle, * for any section — **the miter never sees the profile, because the corner is a property of the path.** * * A carpenter *copes* an inside corner rather than mitering it, but that is a tolerance trick for walls * that are not truly square. These walls are square, so the miter is exact. * * Real crown molding cut on a saw needs a COMPOUND miter — two settings, because the stock lies tilted * against the fence. That is an artifact of cutting flat: in world space the corner is a single vertical * plane through the bisector, which is what this builds and why nothing extra is needed for it. * * **A short run between two CONCAVE corners has a floor.** Both miters carry material inward along the * segment between them, so a face narrower than `2 · projection · tan(turn / 2)` — 2× the projection at * right angles — has its two ends overlapping. Nothing is wrong when that happens: both miters are exact, * and the request simply does not fit, the way molding too deep for a narrow alcove does not fit on a real * wall. A CONVEX pair has no such limit, because there the miters spread apart instead — molding wraps a * chimney breast at any width. * * An open run gets a square cut at each end, which is a length dying into a doorway. A closed run has no * ends at all, and no caps. * * No origin of its own: it is drawn where its `points` are, so a run built from a room's footprint lands * in that room. Material groups: none — pass one material, not an array. * * @example * ```ts * const room = [ * new Vector3(-2, 2.4, -1.5), * new Vector3(2, 2.4, -1.5), * new Vector3(2, 2.4, 1.5), * new Vector3(-2, 2.4, 1.5), * ]; * * const cornice = new Mesh(new MoldingGeometry({ points: room, closed: true, style: "ogee" }), plaster); * ``` */ export declare class MoldingGeometry extends BufferGeometry { constructor({ points, closed, style, profile, drop, projection, segments, run, facing, }: MoldingGeometryOptions); } export declare interface MoldingGeometryOptions { /** * The CORNER LINE the molding follows — where wall meets ceiling for a crown, wall meets floor for a * base. One point per corner, in order. Two points is a single length; three is one corner; a whole * room is the footprint with `closed`. * * Lift a plan straight into one: `footprint.map((p) => new Vector3(p.x, ceilingY, p.y))`. */ points: Vector3[]; /** Close the run back onto its first point — a room, rather than a wall. Defaults to `false`. */ closed?: boolean; /** Which section. Defaults to `"cove"`. See {@link MoldingStyle}. */ style?: MoldingStyle; /** * A section of your own, overriding `style`. Any closed profile works — the corners never see it. * * Author it in the same corner axes {@link moldingProfile} uses: `x` runs along the wall, `y` out from * it, with the corner at the origin. */ profile?: Vec2[]; /** How far the molding runs along the wall. Defaults to `0.09`. */ drop?: number; /** How far it stands out from the wall. Defaults to `0.09`. */ projection?: number; /** How finely the section's face is cut — the low-poly knob. Defaults to `6`. */ segments?: number; /** * Which corner this is. Defaults to `"crown"`. * * - `"crown"` — the corner line is at the CEILING and the molding hangs down from it. * - `"base"` — the corner line is at the FLOOR and the molding stands up from it. A baseboard, or a * plinth. The identical section, flipped. * * Both take the same profile, because a molding's two backs do not care which surface is which. */ run?: MoldingRun; /** * Which side of the run the molding stands on. Defaults to `"inward"` — a room, seen from inside. * * Honored **regardless of how the points are wound**: the run is measured against its own center and * reversed if it came out facing the wrong way. A winding rule the caller has to remember is a rule * that silently produces molding facing into the wall. * * A perfectly straight run has no inside, so nothing is flipped there — reverse the points, or swap * this, if it lands on the wrong face. */ facing?: MoldingFacing; } /** * Closed CCW corner profile in (normal, binormal) coordinates, with backs meeting at (0, 0). * The x extent is drop; the y extent is projection. * * ``` * ceiling * ────┬──────────────────► projection (the profile's `y`, and the sweep's binormal) * │╲ * wall │ ╲___ * │ ╲ * ▼ ╵ * drop (the profile's `x`, and the sweep's normal) * ``` * * ```ts * const cornice = sweep(moldingProfile({ style: "ogee", drop: 0.12, projection: 0.09 }), stations, { * closed: true, * }); * ``` */ export declare function moldingProfile({ style, drop, projection, segments, }?: MoldingProfileOptions): Vec2[]; export declare interface MoldingProfileOptions { /** Exposed contour between the wall and ceiling or floor backs. */ style?: MoldingStyle; /** Distance along the wall from the corner. */ drop?: number; /** Distance along the ceiling or floor from the wall. */ projection?: number; /** Curve subdivisions; endpoints remain at drop and projection. Chamfer, fillet and step use fixed polygons. */ segments?: number; } /** Which corner the molding sits in, and therefore which way its face runs. */ export declare type MoldingRun = "crown" | "base"; /** * Solid-backed corner-section styles: cove/scotia hollows, ovolo convex quarter, ogee/cyma S-curves, * chamfer splay, fillet rectangle, and step polygon. */ export declare type MoldingStyle = "cove" | "ovolo" | "chamfer" | "ogee" | "cyma" | "scotia" | "fillet" | "step"; /** * Mortar — the thick-walled bowl a {@link PestleGeometry} grinds in. * * A lathe of {@link vesselShell} over the bowl silhouette: the profile climbs the outside, rolls over the * rim, and comes back DOWN a real inner wall to an inner floor above the base, closing a solid shell. So * the interior normals face inward and the material can be single-sided — `DoubleSide` is no longer * load-bearing. The outer silhouette is exposed as `.profile`. * * Local frame: base on Y=0, centered on X/Z. * * TODO: a `mortarAndPestle()` factory is the home for the assembled pair — seating the pestle head against * the bowl's interior is arithmetic against both profiles, which is a factory's job, not either geometry's. */ export declare class MortarGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor({ radius, height, baseRadius, wallThickness, radialSegments, }?: MortarGeometryOptions); } export declare interface MortarGeometryOptions { /** Outer radius at the widest point. Defaults to `1.4`. */ radius?: number; /** Overall height, base to rim. Defaults to `1.8`. */ height?: number; /** Base (foot) radius. Defaults to `1`. */ baseRadius?: number; /** Wall thickness — a mortar is thick-walled. Defaults to `0.45`. */ wallThickness?: number; /** Circumference segments — the low-poly knob. Defaults to `16`. */ radialSegments?: number; } /** * Mossy rock — dodecahedron body with a smaller, flatter moss shell (group 1). * * Material groups: `0` rock, `1` moss. * * Local frame: centered on the rock body. */ export declare class MossyRockGeometry extends BufferGeometry { readonly radius: number; readonly detail: number; constructor({ radius, detail, mossScaleXZ, mossScaleY, mossOffsetY, }?: MossyRockGeometryOptions); } export declare interface MossyRockGeometryOptions { /** Rock dodecahedron radius. Defaults to `1`. */ radius?: number; /** Dodecahedron detail level. Defaults to `0`. */ detail?: number; /** Moss horizontal scale relative to the rock. Defaults to `0.9`. */ mossScaleXZ?: number; /** Moss vertical scale relative to the rock. Defaults to `0.5`. */ mossScaleY?: number; /** Moss center offset above the rock origin. Defaults to `0.3`. */ mossOffsetY?: number; } /** * Fast seeded PRNG — portfolio Gotham/Water parity. * Returns a closure yielding floats in [0, 1). */ export declare function mulberry32(seed: number): RandomStream; /** Perturb positions in place with independent random components weighted by normalized direction. * Normals and bounds remain stale. */ export declare const noiseBrush: (geometry: T, position: Vector3, radius: number, strength: number, direction?: Vector3, falloffFn?: (distance: number, radius: number) => number) => void; export declare function normalizedTime(runtime: ClipRuntime, ease: EasingFunction): number; /** Divide byte channels by 255; this is normalization, not sRGB-to-linear conversion. */ export declare function normalizeRgb(r: number, g: number, b: number): [number, number, number]; /** * Normalize UVs using supplied bounds; each axis must have nonzero extent. * * ``` * const planarMapping = (vertex: [number, number, number]) => [vertex[0], vertex[1]]; * const uvs = vertices.map(mappingFunction); * const { minBounds, maxBounds } = calculateUVBounds(uvs); * const normalizedUVs = normalizeUVBatch(uvs, minBounds, maxBounds); * ``` */ export declare function normalizeUV(uv: [number, number], minU: number, maxU: number, minV: number, maxV: number): [number, number]; /** Normalize each UV using common nonzero-extent bounds. */ export declare function normalizeUVBatch(uvs: [number, number][], minBounds: [number, number], maxBounds: [number, number]): [number, number][]; /** * Obelisk — a tapered four-sided shaft rising to a pyramidion. The tall Victorian monument that stands * over a family plot. * * Three rings of vertices, and that is the whole model: * * ```text * apex 1 vertex * / \ * / \ 4 CAP TRIANGLES * /________\ * | | shoulder — 4 vertices * | | * | | 4 SIDE QUADS (trapezoids: the taper) * | | * |__________| base — 4 vertices * ``` * * Fourteen triangles, counting the closed foot. The cap faces are triangles because they close to a single point — a quad would * need the apex twice. * * The side UVs INSET at the top by exactly the ratio the geometry tapers. A triangle interpolates UVs * linearly, so the four (position → uv) pairs must lie on ONE affine map, or the quad's two triangles * solve for different maps and the texture creases visibly along the diagonal. Stretching a trapezoid * to fill a 0–1 square is a projective transform, which triangles cannot represent. * * Distinct from {@link ObeliskHeadstoneGeometry}, which is a *stepped* stack of boxes. This one is a * single tapered shaft. * * Local frame: base on Y=0, centered on X/Z. * * @example * ```ts * const geometry = new ObeliskGeometry({ shaftHeight: 2.6 }); * ``` */ export declare class ObeliskGeometry extends BufferGeometry { readonly totalHeight: number; readonly baseWidth: number; constructor({ baseWidth, topWidth, shaftHeight, capHeight, }?: ObeliskGeometryOptions); } export declare interface ObeliskGeometryOptions { /** Width of the shaft at its foot. Defaults to `0.5`. */ baseWidth?: number; /** Width of the shaft at the shoulder, where the cap begins — the taper. Defaults to `0.34`. */ topWidth?: number; /** Height of the shaft, below the cap. Defaults to `2.2`. */ shaftHeight?: number; /** Height of the pyramidion. Defaults to `0.45`. */ capHeight?: number; } /** * Tiered obelisk headstone with pyramid cap. * * Local frame: base on Y=0, centered on X/Z. */ export declare class ObeliskHeadstoneGeometry extends BufferGeometry { readonly totalHeight: number; readonly baseWidth: number; constructor({ totalHeight, baseWidth }?: ObeliskHeadstoneGeometryOptions); } export declare interface ObeliskHeadstoneGeometryOptions { /** Total monument height. Defaults to `1.75`. */ totalHeight?: number; /** Base platform width. Defaults to `0.75`. */ baseWidth?: number; } /** * Offset a CCW loop: positive distance expands, negative contracts; repeated closing points are removed. * Miters beyond miterLimit × |distance| bevel; right-angle miter ratio is √2 ≈ 1.41. No global intersection repair. * * ```ts * const outline = openingOutline(opening).getPoints(48); * const outer = offsetLoop(outline, 0.06); // the frame's outer edge, out on the wall * const inner = offsetLoop(outline, -0.03); // its inner edge, biting into the aperture * ``` */ export declare function offsetLoop(points: Vector2[], distance: number, miterLimit?: number): Vector2[]; /** * A window, as a hole. Wound CLOCKWISE — the reverse of the wall's outline, which is what tells the * triangulator this is a void rather than another island of material. */ export declare function openingCutout(opening: WallOpeningOptions, wallWidth?: number): Path; /** * An opening's outline as a filled {@link Shape} — the SAME curve the wall punches out of itself. * * This is the piece everything else in a window hangs off. The hole, the pane of glass that fills it, and * the frame ringing it are not three shapes that happen to line up; they are one shape used three ways. * Cut them from anywhere else and they will drift the moment somebody changes an arch. * * Wound counter-clockwise, and closed — up one jamb, over the arch, down the other, and back along the * SILL. A window has a bottom edge (a doorway does not, which is why a doorway can never be a hole). * * @example * ```ts * const opening = { width: 0.8, height: 1, arch: "ogee" } as const; * * const wall = new WallShape({ width: 6, height: 4, windows: [{ ...opening, x: -2, y: 1.5 }] }); * const glass = new ShapeGeometry(openingOutline(opening)); // fits, by construction * ``` */ export declare function openingOutline(opening: WallOpeningOptions): Shape; export declare interface OrbitClipOptions extends CameraClipTiming { target: Vector3; /** Distance from target. Interpolates from the current distance when specified. */ radius?: number; /** Elevation above target Y in radians. Defaults to current camera elevation. */ elevation?: number; /** Revolutions over the clip. Defaults to `1`. */ revolutions?: number; } /** * The glass: a flat pane filling an opening. * * The third of the trio that share one `opening` description, and the one that was missing — a wall can * be punched, {@link WindowFrameGeometry} can ring the hole, {@link DiamondLatticeGeometry} can lead it, * and until now nothing could glaze it. `ArchedSlabGeometry` is a solid with depth; this is a surface. * * **A plane, not a solid.** Glass at this scale is a surface: giving it thickness doubles its triangles, * buys nothing a low-poly scene can see, and introduces two coincident faces to z-fight. Give it a * double-sided material and be done. * * Follows ANY arch, including `square` — a flat head is an arch-shaped hole with no curve in it — so * there is no separate rectangular pane, and the name does not pretend otherwise. * * Drawn at the ORIGIN — centered on X, sill at `y = 0`, lying in the XY plane at `z = 0` — regardless of * where the opening sits in its wall, so one pane can be positioned into many openings and so it lands on * a frame and a lattice built from the same description. Material groups: none. * * @example * ```ts * const opening = { width: 1.24, height: 1.15, arch: "pointed", archHeight: 0.78 } as const; * * const glass = new Mesh(new PaneGeometry({ opening }), glazing); * const leading = new Mesh(new DiamondLatticeGeometry({ opening }), lead); * const frame = new Mesh(new WindowFrameGeometry({ opening }), iron); * ``` */ export declare class PaneGeometry extends ShapeGeometry { constructor({ opening, rebate, curveSegments, miterLimit, }?: PaneGeometryOptions); } export declare interface PaneGeometryOptions { /** * The opening the pane glazes. The SAME description a wall is punched with, a * {@link WindowFrameGeometry} rings, and a {@link DiamondLatticeGeometry} fills — so the four agree by * construction rather than by keeping numbers in step. */ opening?: WallOpeningOptions; /** * How far the pane runs PAST the opening, into the frame's rebate. Defaults to `0`. * * A real pane is oversize, not undersize: its edge is hidden in the groove that holds it, and the * visible opening is the frame. `0` fills the opening exactly, which is what a leaded light does, since * there the came holds the glass rather than a rebate. A NEGATIVE value pulls the pane in and leaves a * deliberate reveal — rarely what you want, because it reads as glass that does not fit. */ rebate?: number; /** How finely the arch is followed — the low-poly knob. Defaults to `24`. */ curveSegments?: number; /** * How far a corner's offset may reach before it bevels, as a multiple of `rebate`. Defaults to `4`, * the SVG default. Only consulted when `rebate` is non-zero. * * It matters when the pane is glazed inside a JAMB: the lining's inner edge is offset with a tight * limit so a sharp ogee or pointed head blunts rather than growing a needle, and the glass has to be * offset the same way or it will spike where the lining does not. Pass the same value the lining used — * {@link WindowFrameGeometry} uses `2` for its inner aperture. */ miterLimit?: number; } /** * A four-panel door, built the way a joiner builds one: **frame and panel**. * * Two STILES run the full height, and the RAILS — top, lock, bottom — butt into them, with a MUNTIN * butting between the rails to split each row in two. That is a T-junction at every joint, and it is * deliberate rather than a simplification: the hinges screw into the stile and the whole leaf hangs off * it, so the stile has to be one continuous member. Mitering those corners would trade the door's * strongest member for four end-grain joints. (Mitered frames are a real style, but a cabinet-door one — * they cannot carry a door's weight, and a miter cannot join unequal stock, so the deep bottom rail that * gives a door its stance would be impossible.) * * The panels FLOAT. Each one is cut oversize and runs into a groove in the surrounding members, never * glued, so it can move with the season without splitting the frame. A raised panel is a flat field with * a bevel sloping down to a thin tongue — and its four bevels meet at the corners in a 45° hip, which * comes free because the surface is lofted between two loops rather than swept along one. * * With `molding` on, an ovolo section wraps each opening as one closed **mitered** loop. That is the * only miter on the door, and it is the one a joiner cuts too. * * Stands on the `y = 0` plane, centered on X, with its faces at `±thickness / 2`. To hang it, move the * origin onto the hinge stile first — `geometry.translate(width / 2, 0, 0)` puts it on the left edge, so * rotating the mesh about Y swings the door. * * Material groups: none. A door is one piece of joinery in one material, so this is a single geometry * with a single group — pass one material, not an array. * * @example * ```ts * const door = new Mesh(new PanelDoorGeometry({ molding: true }), paint); * ``` */ export declare class PanelDoorGeometry extends BufferGeometry { /** * Height of the lock rail's center, in world units — where a knob, a latch, or a letter plate mounts. * * Reported rather than assumed, because it follows `lockRailPosition` and the door's height. */ readonly lockRailY: number; constructor({ width, height, thickness, stileWidth, topRail, lockRail, bottomRail, lockRailPosition, muntinWidth, panel, panelThickness, bevelWidth, tongueThickness, grooveDepth, molding, moldingWidth, moldingHeight, moldingSegments, }?: PanelDoorGeometryOptions); } export declare interface PanelDoorGeometryOptions { /** Width of the door leaf. Defaults to `0.813` — a 32 inch door. */ width?: number; /** Height of the leaf. Defaults to `2.032` — 80 inches, the standard door height. */ height?: number; /** Thickness of the leaf. Defaults to `0.045`. */ thickness?: number; /** * Width of each stile — the two vertical members. Defaults to `0.115`. * * The stiles run the full height and everything else lands on them, so this also sets how far the * panels are held in from the door's edges. */ stileWidth?: number; /** Height of the top rail. Defaults to `0.115`, matching the stiles. */ topRail?: number; /** * Height of the lock rail — the middle one, named for the lockset it carries. Defaults to `0.2`. * * Deeper than the others because it is bored through for a latch, and because it is the rail a hand * meets. */ lockRail?: number; /** * Height of the bottom rail. Defaults to `0.235`. * * Traditionally the deepest member: it is the one that gets kicked, and a taller rail reads as a base * the door stands on rather than a border around it. */ bottomRail?: number; /** * Height of the lock rail's CENTER, as a fraction of the door's height. Defaults to `0.44`. * * A fraction rather than a distance, deliberately. Given in world units it would stay put while the * door grew around it, so resizing would quietly change the door's character instead of scaling it — * a tall door would end up with a lock rail down by its knees. As a fraction the proportions hold, and * {@link PanelDoorGeometry.lockRailY} reports where it actually landed. */ lockRailPosition?: number; /** Width of the muntin — the short vertical divider between the panels. Defaults to `0.1`. */ muntinWidth?: number; /** * How the panels are worked. Defaults to `"raised"`. * * - `"raised"` — a flat FIELD in the middle, a BEVEL sloping down to a thin edge. The classical panel, * and what casts the shadow line that makes a paneled door read as paneled. * - `"flat"` — a plain board of `panelThickness` throughout. The Shaker door. */ panel?: "raised" | "flat"; /** Thickness of the panel at its field. Defaults to `0.018`. */ panelThickness?: number; /** Width of the bevel around a raised panel — the slope from field to tongue. Defaults to `0.055`. */ bevelWidth?: number; /** * Thickness of the panel's TONGUE, the thinned edge that sits in the frame's groove. Defaults to * `0.008`. Ignored by a flat panel, which is one thickness throughout. */ tongueThickness?: number; /** * How far the panel runs into the frame's groove on every side. Defaults to `0.012`. * * A panel is never cut to its opening — a panel the size of the opening falls out of it. It is cut * oversize and held in a groove, loose, so it can move with the season without splitting the frame. */ grooveDepth?: number; /** Add planted molding around each panel, on both faces. Defaults to `false`. */ molding?: boolean; /** How far the molding lies across the frame, measured out from the opening's edge. Defaults to `0.022`. */ moldingWidth?: number; /** How far the molding stands proud of the door's face. Defaults to `0.012`. */ moldingHeight?: number; /** * How finely the molding's quarter-round is cut — the low-poly knob. Defaults to `4`. * * `1` is a plain chamfer, `12` reads as turned. */ moldingSegments?: number; } export declare const ParametricCurve: { cubic: (t: number, p0: number, p1: number, p2: number, p3: number) => number; damped: (t: number, damping?: number) => number; exponential: (t: number, base?: number, factor?: number) => number; logarithmic: (t: number, base?: number, factor?: number) => number; parabolic: (t: number, a?: number, b?: number, c?: number) => number; quadratic: (t: number, p0: number, p1: number, p2: number) => number; sigmoid: (t: number, a?: number) => number; sinusoidal: (t: number) => number; }; export declare const ParametricCurveUtils: { createCubicCurvePoints: (start: Vector2, control1: Vector2, control2: Vector2, end: Vector2, segments?: number) => Vector2[]; createDampedCurvePoints: (start: Vector2, end: Vector2, damping: number, segments?: number) => Vector2[]; createExponentialCurvePoints: (start: Vector2, end: Vector2, base: number, factor: number, segments?: number) => Vector2[]; createLogarithmicCurvePoints: (start: Vector2, end: Vector2, base: number, factor: number, segments?: number) => Vector2[]; createParabolicCurvePoints: (start: Vector2, end: Vector2, a: number, b: number, c: number, segments?: number) => Vector2[]; createQuadraticCurvePoints: (start: Vector2, control: Vector2, end: Vector2, segments?: number) => Vector2[]; createSigmoidCurvePoints: (start: Vector2, end: Vector2, a: number, segments?: number) => Vector2[]; }; /** * Numeric color utilities: RGB channels use 0–255, HSL uses degrees / percentages. * These functions do not perform sRGB transfer-function conversion. normalizeRgb * changes scale only. Use Three Color with explicit SRGBColorSpace when rendering * byte RGB values. Distance helpers are numeric RGB metrics, not perceptual Delta E. */ /** * Convert hex color code color string to RGB array */ export declare function parseHexCode(hex: string): [number, number, number]; export declare interface PassThroughClipOptions extends CameraClipTiming { /** Point to travel through. Choose a clear path above or beside solid geometry. */ target: Vector3; /** Distance to continue beyond the point, in world units. Defaults to 5. */ beyond?: number; } /** Cached polyline distances; points remain caller-owned and must stay fixed while the measure is used. */ export declare interface PathMeasure { /** The vertices, in order. Not copied: the caller still owns them. */ points: Vector3[]; /** Whether the last vertex joins back to the first. */ closed: boolean; /** Cumulative vertex distances plus a final total: distances[i + 1] - distances[i] is segment i length. */ distances: number[]; /** Total arc length. The perimeter, for a closed run. */ length: number; } /** Position and tangent for frame generation. Transport framing requires finite nonzero tangents. */ export declare interface PathPoint { position: Vector3; tangent: Vector3; /** Per-station section scale; overrides the sweep scale callback, including when zero. */ scale?: number; } export declare interface PathRepeat { /** Item centers, as distances along the path. Feed each to `slicePath` or `pointAtDistance`. */ centers: number[]; /** Actual pitch; corners reports total spanned length / step count, an average across segments. */ pitch: number; /** Unallocated distance under pitch anchoring; zero under corners anchoring. */ slack: number; /** Whether every vertex ended up with an item on it. */ anchored: boolean; } export declare interface PendulumClipOptions extends CameraClipTiming { target: Vector3; /** End distance from target. Defaults to the captured camera distance. */ distance?: number; /** Peak azimuth swing in radians — keep small for Ken Burns mood (e.g. `0.12`). */ azimuthAmplitude?: number; /** Slow back-and-forth cycles over the clip. Defaults to `2`. */ oscillations?: number; ease?: EasingFunction; } /** * Pestle — the grinding tool that works a {@link MortarGeometry}. * * The **head is the wide end and it is at the bottom**, because that is how a pestle rests when it is * not in your hand: standing on the part that does the work. A pestle used in anger sits head-down in * the bowl, so this frame is also the one an assembly wants to place from. * * Local frame: head at Y=0, grip at `+height`, centered on X and Z. * * The assembled pair — a pestle seated and leaning in the bowl — belongs to a future * `mortarAndPestle()` factory, not to either geometry. See the TODO on {@link MortarGeometry}. * * @example * ```ts * const geometry = new PestleGeometry({ height: 1.5, headRadius: 0.3 }); * const pestle = new Mesh(geometry, stoneMaterial); * scene.add(pestle); * ``` */ export declare class PestleGeometry extends BufferGeometry { readonly height: number; readonly headRadius: number; readonly gripRadius: number; constructor({ height, headRadius, gripRadius, radialSegments, }?: PestleGeometryOptions); } export declare interface PestleGeometryOptions { /** Overall length, head to grip. Defaults to `1.5`. */ height?: number; /** Radius of the grinding head — the fat end that meets the mortar. Defaults to `0.3`. */ headRadius?: number; /** Radius of the grip — the end you hold. Defaults to `0.2`. */ gripRadius?: number; /** Sides around the shaft. `6` reads as hand-cut stone. Defaults to `8`. */ radialSegments?: number; } /** * Soft, slow-drifting petals (or leaves) falling through a bounded volume — * cherry-blossom float rather than stiff tumble. Each instance drifts downward * with gentle horizontal wander and a light sinusoidal flutter. * * Call {@link PetalDriftEffect.update} each frame with elapsed time in seconds. * * @example * ```typescript * const petals = new PetalDriftEffect({ * count: 80, * color: [0xffd6f0, 0xfff0f8, 0xf8c8e0], * flutter: 0.3, * }); * scene.add(petals); * * onFrame((dt) => petals.update(dt)); * ``` */ export declare class PetalDriftEffect extends InstancedMesh { private readonly source; private readonly width; private readonly height; private readonly depth; private readonly floorY; private readonly flutter; private readonly px; private readonly py; private readonly pz; private readonly fallSpeed; private readonly driftX; private readonly driftZ; private readonly rotX; private readonly rotY; private readonly rotZ; private readonly phase; private readonly dummy; private clock; constructor(options?: PetalDriftEffectOptions); /** * Advance petal positions and flutter. Pass elapsed frame time in seconds. */ update(dt: number): void; /** Release geometry and materials held by the field. */ dispose(): this; private respawn; private writeMatrices; } export declare interface PetalDriftEffectOptions { /** Optional seed for initial state and respawns; reproduce motion with the same update steps. */ seed?: number; /** Per-petal working-space color overriding color. Sampled once at construction; retained on respawn. */ colors?: ColorSampler; /** Override petal geometry. Defaults to {@link EllipticLeafGeometry}. */ geometry?: BufferGeometry; /** Override the default petal material. */ material?: Material; /** Number of petal instances. Defaults to `120`. */ count?: number; /** Horizontal spread (world units). Defaults to `16`. */ width?: number; /** Vertical spawn span (world units). Defaults to `8`. */ height?: number; /** Depth spread (world units). Defaults to `16`. */ depth?: number; /** World Y where petals respawn after drifting below the floor. Defaults to `0`. */ floorY?: number; /** Minimum fall speed (units/s). Defaults to `0.12`. */ fallSpeedMin?: number; /** Maximum fall speed (units/s). Defaults to `0.28`. */ fallSpeedMax?: number; /** Minimum horizontal drift speed (units/s). Defaults to `0.04`. */ driftMin?: number; /** Maximum horizontal drift speed (units/s). Defaults to `0.14`. */ driftMax?: number; /** * Flutter strength (radians). Subtle rotation sway as each petal falls. * Defaults to `0.35`. */ flutter?: number; /** Single petal color or palette; multiple entries pick a random color per petal. */ color?: ColorRepresentation | ColorRepresentation[]; } declare function pick(colors: readonly ColorRepresentation[], weights?: readonly number[]): ColorSampler; /** * Pipette — a thin tube tapering through a cone to a point at the base, with a rolled rim. * * A lathe of {@link vesselShell} over {@link pipetteProfile}; the silhouette is exposed as `.profile` for * the fill. Local frame: tip on Y=0, opening up +Y. */ export declare class PipetteGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: PipetteGeometryOptions); } export declare interface PipetteGeometryOptions extends PipetteProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `16`. */ radialSegments?: number; } /** * Pipette silhouette — a very thin tube tapering through a cone to a point at the base. Tip on Y=0, ends at * the rim. Like a test tube, but with a conical instead of spherical base. */ export declare function pipetteProfile({ radius, height, tipLength }?: PipetteProfileOptions): Vector2[]; export declare interface PipetteProfileOptions { /** Tube radius. Defaults to `0.1`. */ radius?: number; /** Overall height. Defaults to `3`. */ height?: number; /** Length of the tapering cone tip at the base. Defaults to `0.22 ×` the height. */ tipLength?: number; } /** * Project by dropping the named coordinate; output remains in source units. * * ``` * const vertices = [ * [-1, -1, 1], * [1, -1, 1], * [-1, 1, 1], * [1, 1, 1], * ]; * * const uvs = planarUVMapping(vertices, 'z'); // Project onto the Z-axis * ``` */ export declare function planarUVMapping(vertices: [number, number, number][], axis: 'x' | 'y' | 'z'): [number, number][]; export declare interface PlaneGeometryCut extends GeometrySection { planeIndex: number; /** Reserved by input plane order, even if an earlier cap disappears. */ capMaterialIndex: number; } /** * A boarded floor, **laid rather than tiled**. Walking surface on `y = 0`, boards running along X, centered * on the origin. * * The laying is {@link layPlankFloor} — rows of boards butted end to end, joints staggered from the row * alongside, a shortened starter board, and no runt at the end of a run. Read that for the reasoning; this * factory only decides what the boards are *made of*. * * **Baked to a single geometry and a single material**, whatever the floor's size. Every board is its own * {@link WeatheredPlankGeometry} with its own seed, so no two repeat — and differing items merge where * identical ones would instance. Per-board color rides a **vertex attribute** rather than a material * group, which is what keeps it to one draw call: a palette would otherwise cost one group, and one draw * call, per tint. The board's whole shell gets one color, so it reads as a board rather than a gradient. * * **Rotation is deliberately absent.** These boards are deformed individually and butt end-grain to * end-grain; laying them diagonally would need every perimeter board cut to the room, which is a different * construction rather than an option on this one. * * Material groups: none. * * @example * ```ts * const floor = new PlankFloor({ length: 6, depth: 4, seed: 12 }); * scene.add(floor); * floor.plankCount; // how many boards it took * floor.closestJoint; // how close two neighboring joints came — compare to minStagger * ``` */ export declare class PlankFloor extends Group { #private; readonly mesh: Mesh; /** Boards laid. */ readonly plankCount: number; /** Rows across the floor's depth. */ readonly rowCount: number; /** The width each board actually got, after the rows were fitted to `depth`. */ readonly plankWidth: number; /** How close any two neighboring-row joints came. Compare to `minStagger`. */ readonly closestJoint: number; constructor({ plankThickness, plankEdgeRoughness, plankEndSkew, plankBow, color, colorVariance, tints, colors: colorSampler, material, ...layout }?: PlankFloorOptions); /** Releases the merged geometry, and the material when this floor made it. */ dispose(): void; } export declare interface PlankFloorLayout { placements: PlankPlacement[]; rows: number; /** The width each board actually got, after the rows were fitted to `depth`. */ plankWidth: number; /** * The closest any two neighboring-row joints came — **what the floor actually got**, against what * `minStagger` asked for. Two things hold it down: the request is capped at `(longest − shortest) / 2`, * since a joint can only be moved by varying its board's length, and the per-board search is bounded, so * even inside the cap it lands short. Worth reading; a floor that came out at half its target is a floor * whose board range is too narrow for the room. */ closestJoint: number; } export declare interface PlankFloorLayoutOptions { /** Extent along the direction the boards run. Defaults to `2.5`. */ length?: number; /** Extent across the boards. Defaults to `2.5`. */ depth?: number; /** Board width. Rows are fitted to `depth`, so the width actually laid is reported back. Defaults to `0.2`. */ plankWidth?: number; /** Gap between rows. Defaults to `0.012`. */ gap?: number; /** * Shortest board laid, in world units. Defaults to `0.5`. * * **Absolute, not a fraction of the floor**, because a board is milled at a real size: a larger room takes * *more* boards, not longer ones. Given either way round, and clamped to the floor. */ minPlankLength?: number; /** Longest board laid, in world units. Defaults to `1.4`. */ maxPlankLength?: number; /** * How far an end joint should stand clear of the joints in the row beside it, in world units. Defaults * to `0.35`. **The single rule that separates a laid floor from a set of stripes.** * * A TARGET, not a guarantee. Each board's length is chosen from a bounded search, so the clearance * actually achieved is reported back as {@link PlankFloorLayout.closestJoint} — expect roughly two * thirds of what is asked for, and read the result rather than assuming it. */ minStagger?: number; /** Defaults to `0x51ab`. */ seed?: number; } export declare interface PlankFloorOptions extends PlankFloorLayoutOptions { /** Board thickness. Defaults to `0.055`. */ plankThickness?: number; /** * Edge wander per board, as a fraction of its width. Defaults to `0.05`. * * Most of the floor's character lives here: raised, the boards' edges break up and the run stops * repeating; too high and it turns cartoonish, which is a style of its own; low, and the floor reads as * planed and refined. */ plankEdgeRoughness?: number; /** * End skew per board, as a fraction of its width — how far a board's cut ends lean off square. Defaults * to `0.06`. The other half of the character, and what makes the butt joints read as sawn rather than * machined. */ plankEndSkew?: number; /** * Broad bow per board, as a fraction of its thickness. Defaults to `0.12`. Nailed flooring cannot bow * much, so this stays low; it mostly catches the light. */ plankBow?: number; /** Base timber color. Defaults to `#6b4b2c`. Ignored when `tints` is given. */ color?: string; /** * Per-board tint spread in HSL, so no two boards match. Defaults to `0.06`. * * Sampled ± about {@link PlankFloorOptions.color}, hue a third as far as saturation and lightness — * timber from one delivery varies in depth far more than in hue. */ colorVariance?: number; /** * Deal boards from a fixed palette instead of varying them continuously. * * Costs nothing extra — the tint still rides the same vertex attribute — and reads as a delivery of * mixed timber rather than a run of one board dyed slightly differently each time. */ tints?: string[]; /** * Per-board working-space color sampler. Overrides tints, color, and colorVariance. * Called once per board in layout order, with a contiguous index starting at zero. * Uses a seeded stream independent of layout and plank geometry; all vertices of a * board receive the same tint. Omit to preserve the original palette/HSL behavior. */ colors?: ColorSampler; /** A material to use instead of the default. **Must set `vertexColors: true`**, or every board goes white. */ material?: Material; } /** One board, as a position and a size. No geometry — what to build it from is the caller's business. */ export declare interface PlankPlacement { /** Distance from the run's start to this board's near end. */ start: number; length: number; /** Which row, counting from the near edge. */ row: number; /** The row's center line, measured across the floor from its middle. */ across: number; /** Laying order. Use it to derive a per-board seed and tint, so no two boards repeat. */ sequence: number; } /** Return a point at distance; closed paths wrap (-0.1 equals length - 0.1), open paths clamp. */ export declare function pointAtDistance({ points, closed, distances, length }: PathMeasure, distance: number): Vector3; /** * Map angle around Y to u and XZ radius to v, in source units. * * const discVertices = [ * [1, 0, 0], // Vertex at (1, 0, 0) * [0, 0, 1], // Vertex at (0, 0, 1) * [-1, 0, 0], // Vertex at (-1, 0, 0) * [0, 0, -1], // Vertex at (0, 0, -1) * ]; * * const polarUVs = polarUVMapping(discVertices); */ export declare function polarUVMapping(vertices: [number, number, number][]): [number, number][]; /** * Extruded regular n-gon prism. */ export declare class PolygonGeometry extends ExtrudeGeometry { readonly sides: number; readonly radius: number; readonly depth: number; constructor({ sides, radius, depth, ...shapeOptions }?: PolygonGeometryOptions); } export declare interface PolygonGeometryOptions extends PolygonShapeOptions { /** Extrusion depth. Defaults to `0.01`. */ depth?: number; } /** * Regular n-gon profile. * * Rests with a corner pointing up. Rotate by `Math.PI / sides` for a flat top. */ export declare class PolygonShape extends Shape { constructor({ sides, radius, rotation }?: PolygonShapeOptions); } export declare interface PolygonShapeOptions { /** Number of sides. Defaults to `6`. */ sides?: number; /** Circumradius — center to corner. Defaults to `1`. */ radius?: number; /** Rotation in radians from the resting state. Defaults to `0`. */ rotation?: number; } /** * Stoppered potion bottle — glass shell, a fitted cork, and an optional bright fill. * * The same spatial factory as {@link ApothecaryJar}: transparent glass, so shell, cork and liquid are * separate meshes, and the cork is fitted and sealed at any depth by {@link createCorkStopper}. Rests on * Y=0. */ export declare class PotionBottle extends Group { constructor({ bottle, fill, cork, corkDepth, glassMaterial, corkMaterial }?: PotionBottleOptions); } /** * Potion bottle — a small, bulbous glass bottle with a narrow neck and a rolled rim, corked by * {@link PotionBottle}. * * A lathe of {@link vesselShell} over {@link potionBottleProfile}; the silhouette is exposed as `.profile` * for the fill and for seating a cork. Local frame: base on Y=0, opening up +Y. */ export declare class PotionBottleGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: PotionBottleGeometryOptions); } export declare interface PotionBottleGeometryOptions extends PotionBottleProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `20`. */ radialSegments?: number; } export declare interface PotionBottleOptions { /** Bottle geometry — resize the body, neck, etc. The cork re-sizes and re-seats to the resulting rim. */ bottle?: PotionBottleGeometryOptions; /** Optional liquid inside the bottle — colour, opacity, glow, fill level. */ fill?: FillOptions; /** Cork shape — vertical cap height (`upperHeight`), plug depth (`lowerHeight`), tip radius. */ cork?: CorkGeometryOptions; /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */ corkDepth?: number; /** Bottle (glass) material. A translucent default is supplied. */ glassMaterial?: MeshStandardMaterial; /** Cork material. A cork-brown default is supplied. */ corkMaterial?: MeshStandardMaterial; } /** * Potion bottle silhouette — a small, bulbous body drawn in to a narrow neck (a perfume-bottle shape). * Base on Y=0, ends at the rim. */ export declare function potionBottleProfile({ radius, baseRadius, neckRadius, height, }?: PotionBottleProfileOptions): Vector2[]; export declare interface PotionBottleProfileOptions { /** Widest body radius. Defaults to `1`. */ radius?: number; /** Base (foot) radius. Defaults to `0.7 ×` the body radius. */ baseRadius?: number; /** Neck (mouth) radius — where the cork seats. Defaults to `0.4 ×` the body radius. */ neckRadius?: number; /** Overall height. Defaults to `2.6`. */ height?: number; } /** End extension and cut normal for a rectangular XZ member. */ export declare interface PrismEnd { /** Outward extension from this endpoint, along the member run. */ reach?: number; /** Cut-plane normal in XZ plan; omitted normal gives a square end. */ wall?: Vector2; } /** One stone standing proud. Where and how big — what it is made of is the caller's business. */ export declare interface ProudStone { /** Center on the surface, from its lower-left corner. */ x: number; y: number; /** Along the course — the stretcher face. */ length: number; /** Up — the course. */ height: number; /** How far it stands out of the surface. */ depth: number; /** Roll, radians. */ tilt: number; } export declare interface ProudStoneOptions { /** Extent of the surface being decorated. Defaults to `3.2`. */ width?: number; /** Defaults to `2.6`. */ height?: number; /** * The course grid the stones must land on. Defaults to `0.26`. * * Not decoration — a proud stone has to sit ON a stone rather than across a joint, so it needs the same * grid the wall was built to. Give it the wall's own numbers. */ courseHeight?: number; /** A whole stone's length, as a multiple of the course. Defaults to `2.2`. */ stoneAspect?: number; /** How far alternate courses start along a stone. Defaults to `0.5`. */ bondOffset?: number; /** * Chance a cell carries a proud stone. Defaults to `0.14`. * * A chance PER CELL, not a count — the grid stays regular and the result does not clump the way sampling * positions at random would. */ density?: number; /** * Length range, as multiples of a whole stone. Defaults to `0.72`–`1.12`. * * **The brick/stone dial.** Collapse a range and every proud stone is the same unit that has popped, * which is brick; open it and each came from its own mold, which is stone. */ lengthMin?: number; lengthMax?: number; /** Height range, as multiples of the course. Defaults to `0.8`–`0.92`. */ heightMin?: number; heightMax?: number; /** How far it stands out of the surface, in world units. Defaults to `0.024`–`0.056`. */ depthMin?: number; depthMax?: number; /** Max roll, radians. Defaults to `0.025`. */ tilt?: number; /** * Rectangles nothing may be placed across — an opening, a quoin, a doorway. * * **This is what lets the scatter stay a surface operation.** Its only two wall-aware rules were "not on * a quoin" and "not across a slit", and both are COMPOSITION rather than masonry. Handed in, the scatter * composes with anything without ever learning what it is composing with. */ exclusions?: SurfaceRect[]; /** Defaults to `0x2c1a`. */ seed?: number; } export declare interface ProudStoneScatter { placements: ProudStone[]; /** Cells considered. `placements.length / candidates` is the density actually achieved. */ candidates: number; /** Cells skipped for landing on an exclusion. */ excluded: number; } export declare interface PullAwayClipOptions extends CameraClipTiming { /** Retreat along the starting view axis, in world units. */ distance: number; /** Additional rise along world Y. Defaults to 0. */ height?: number; } export declare interface PumpkinAssemblyOptions { /** * Extra depth the stem base is buried past its natural seat on the rind, for a * rooted look. `0` rests the stem's footprint exactly on the surface. An * assembly-tier option: it belongs to neither part, only to how they join. * * Subject-prefixed (`stem…`) so the vocabulary stays unambiguous as the unit * gains its own lean/twist/sink at the placement tier above. */ stemSink?: number; /** * Tilt (pitch) of the stem away from vertical, in radians, pivoting about its * seated base. `0` stands straight up. */ stemLean?: number; /** * Yaw of the stem about the vertical axis, in radians. On its own it is * invisible for an axisymmetric stem; combined with `stemLean` it yaws the tilt * into a chosen compass direction. */ stemTwist?: number; } /** A cohesive single-instance geometry, analogous to Three.js built-in geometries. */ export declare class PumpkinGeometry extends BufferGeometry { readonly type = "PumpkinGeometry"; constructor(options?: PumpkinGeometryOptions); } export declare interface PumpkinGeometryOptions extends PumpkinRindGeometryOptions, PumpkinStemGeometryOptions, PumpkinAssemblyOptions { } export declare class PumpkinPatch extends Group { #private; readonly rindInstances: InstancedMesh; readonly stemInstances: InstancedMesh; constructor({ rows, columns, spacing, seed, scaleMin, scaleMax, stemLeanMax, stemSinkMax, leanMax, twistMax, sinkMax, driftMax, colorVariance, rindColors, }?: PumpkinPatchOptions); /** Release both instanced geometries and the owned materials. */ dispose(): void; } /** * The instancing half of the pattern: a field of pumpkins — rows × columns, * potentially thousands — batched into exactly two draw calls, one rind * InstancedMesh and one stem InstancedMesh. * * This is why the geometry stayed separable. Because rind and stem are distinct * batches, each can carry its own per-instance data — `setColorAt` gives every * pumpkin its own rind tint, which a single merged geometry could never do. * Organic variety, not tuning knobs: `lean`/`twist`/`sink`/`drift` are the * seeded *max* ranges from the shared placement vocabulary, sampled per instance. * * The stem batch reuses `pumpkinStemMatrix` — the exact positioning the * single-instance merge bakes — composed here into each instance's world matrix. * Positioning computed once; only the mechanics (bake vs. instance) differ. */ export declare interface PumpkinPatchOptions { rows?: number; columns?: number; spacing?: number; seed?: number; scaleMin?: number; scaleMax?: number; /** Max stem tilt off vertical, radians. Sampled ±value. */ stemLeanMax?: number; /** Max extra stem burial into the rind past its seat. Sampled 0..value. */ stemSinkMax?: number; /** Max whole-pumpkin tilt off vertical, radians. Sampled ±value — keep subtle. */ leanMax?: number; /** Max whole-pumpkin yaw, radians. Sampled ±value. */ twistMax?: number; /** Max depth the whole pumpkin beds into the ground. Sampled 0..value. */ sinkMax?: number; /** Max XZ wander off the grid point. Sampled ±value. */ driftMax?: number; /** Per-instance rind tint spread in HSL, for a non-repeating field. */ colorVariance?: number; /** Per-rind sampler overriding colorVariance. Index is row-major; seeded color draws do not alter placement. */ rindColors?: ColorSampler; } export declare interface PumpkinRindGeometryOptions { rindRadius?: number; rindWidthSegments?: number; rindHeightSegments?: number; rindRibs?: number; rindRibDepth?: number; rindSquash?: number; } export declare interface PumpkinStemGeometryOptions { stemTopRadius?: number; stemBottomRadius?: number; stemHeight?: number; stemSegments?: number; } /** * The stem's placement relative to the rind, as a single transform: seat the * base where the footprint rests on the rind (buried by `sink`), then lean and * twist it about that seated pivot. * * Positioning expressed once, so both mechanics can consume it — the * single-instance merge bakes it into the stem's vertices via `applyMatrix4`, * while the instanced patch composes it into each per-instance matrix. Applied * to a point the order is translate · Ry(twist) · Rz(lean): lean tips the stem, * twist yaws the tipped stem, and the translate lifts the pivot onto the rind. */ export declare function pumpkinStemMatrix({ rindRadius, rindSquash, stemBottomRadius, stemSink, stemLean, stemTwist, }?: PumpkinGeometryOptions): Matrix4; /** * Append a rectangular beam spanning two XZ points, between heights y0 and y1. * End normals shear the corners along the run to meet an angled surface. * Parallel cuts fall back to square ends; other shears are limited to twice the width. * Appends six quads; degenerate spans, nonpositive widths, and inverted heights add nothing. */ export declare function pushMiteredPrism(buffers: GeometryBuffers, from: Vector2, to: Vector2, width: number, y0: number, y1: number, near?: PrismEnd, far?: PrismEnd): void; /** * Append four face-local vertices and triangles (0,1,2), (0,2,3). Corners must face outward by winding. * An omitted normal uses the first three corners; supplied normals are copied unchanged. * * ```ts * const buffers = createGeometryBuffers(); * * pushQuad( * buffers, * [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]], // CCW seen from +Y * [0, 1, 0], * UNIT_QUAD_UV, * ); * * const geometry = toBufferGeometry(buffers); * ``` */ export declare function pushQuad(buffers: GeometryBuffers, corners: [Vec3, Vec3, Vec3, Vec3], normal: Vec3 | undefined, cornerUvs?: [Vec2, Vec2, Vec2, Vec2]): void; /** * Append three face-local vertices and one triangle, counter-clockwise as seen from the outward side. * An omitted normal is derived from winding; supplied normals are copied unchanged. * * ```ts * // One face of a pyramid cap: two shoulder corners rising to the apex. * pushTriangle( * buffers, * [shoulderA, shoulderB, apex], * undefined, // slanted face — let the winding derive the normal * [[0, 0], [1, 0], [0.5, 1]], // apex sits at the top-center of the texture * ); * ``` */ export declare function pushTriangle(buffers: GeometryBuffers, corners: [Vec3, Vec3, Vec3], normal?: Vec3, cornerUvs?: [Vec2, Vec2, Vec2]): void; /** * How the two returns vary from course to course. Every pattern in the catalog is a rule for two * numbers, which is why one construction covers them all. * * - `"straight"` — equal returns, every course the same. Reads as a plain stacked column. * - `"alternating"` — the long face swaps walls each course. This is TOOTHING: it reads as though the two * walls are bonded into one another rather than merely meeting, which is the classic quoin. * - `"staggered"` — one leg varies and the other holds. A softer step that keeps a clean line on one wall. */ export declare type QuoinPattern = "straight" | "alternating" | "staggered"; /** * The dressed stones at a building's external corner. * * **A quoin is not an L-shaped block.** It is a rectangular stone laid so it shows a LONG face on one wall * and a SHORT end on the other, and every pattern in the catalog is just a rule for those two returns * per course. That is why one construction covers `straight`, `alternating` and `staggered` — nothing * differs but two numbers. * * **The origin is the corner LINE**, where the two walls' center planes cross — not the stack's own outer * corner. So placing it is one line: put it where the walls meet, and `wallThickness` and `proud` carry it * out to where a quoin actually sits. The stack runs UP from `y = 0` and its returns run along `−X` and * `−Z`, so the outside corner it dresses faces `+X +Z`. * * ```ts * const quoins = new Mesh(new QuoinStackGeometry({ height: 2.8, wallThickness: 0.34 }), stone); * quoins.position.set(cornerX, 0, cornerZ); // where the two walls cross * ``` * * Per-course tint rides a **vertex attribute**, so the whole stack is one geometry and one draw call and * still varies stone to stone. Give it a material with `vertexColors: true`, or every quoin comes out white. * * Material groups: none. */ export declare class QuoinStackGeometry extends BufferGeometry { /** Quoins laid. */ readonly quoinCount: number; /** Courses the stack was divided into — fitted to `height`. */ readonly courseCount: number; /** The course height actually used. */ readonly courseHeight: number; constructor({ height, courseHeight, pattern, longLeg, shortLeg, everyOther, phase, wallThickness, proud, color, colors, colorVariance, alternateTint, seed, }?: QuoinStackGeometryOptions); } export declare interface QuoinStackGeometryOptions { /** How tall the stack runs. Defaults to `2.8`. */ height?: number; /** * Target course height. Defaults to `0.26`. * * Fitted to `height`, so it never leaves a sliver at the top. Give it the wall's own course height and * the quoins line up with the coursing. */ courseHeight?: number; /** See {@link QuoinPattern}. Defaults to `"alternating"`. */ pattern?: QuoinPattern; /** The longer return. Defaults to `0.44`. */ longLeg?: number; /** The shorter return. Defaults to `0.22`. Ignored by `"straight"`, which uses `longLeg` for both. */ shortLeg?: number; /** * Lay a quoin on every other course, leaving the wall showing between — "teeth of a comb". Defaults to * `false`. The pattern still advances per quoin LAID, so gapping and alternating compose rather than * canceling. */ everyOther?: boolean; /** * Which phase the pattern starts on, `0` or `1`. Defaults to `0`. * * Two corners of one building want opposite phases, or the pattern mirrors instead of continuing round. */ phase?: number; /** * The wall's thickness — what the stack is standing at the corner of. Defaults to `0.34`. * * Together with `proud` this places the stack's outer corner. See the note on the origin below. */ wallThickness?: number; /** * How far the stack stands out of BOTH wall faces. Defaults to `0.032`. * * Most of why a corner reads as dressed rather than merely turned. On a 340mm wall: under 0.02 is a * shadow line, 0.02–0.045 is clearly proud, past that is RUSTICATED. **Not optional at 0** — flush would * land the quoin's end exactly coplanar with the other wall's face, and two coplanar surfaces fight. */ proud?: number; /** Base stone tint. Defaults to `#d6ccb6` — dressed limestone, paler than the wall it turns. */ color?: string; /** Per-quoin tint spread in HSL. Defaults to `0.025`. A delivery of dressed stone is fairly uniform. */ colorVariance?: number; /** Per-stone sampler; overrides color/colorVariance/alternateTint. Index counts laid stones, excluding mortar; seeded color draws do not alter geometry. */ colors?: ColorSampler; /** * Shade alternate courses light and dark. Defaults to `false`. * * **Only correct because ONE stack owns the corner.** A real corner is built by two walls contributing * alternate courses; were this two stacks, each would need a UNIFORM tint opposite its neighbor, since * both alternating in step gives light, light, dark, dark. Ownership decides the rule. */ alternateTint?: boolean; /** Defaults to `0x2c1a`. */ seed?: number; } /** * Extruded **rack** — the straight member of a rack and pinion. See {@link RackShape} for the profile. * * To mesh with a pinion, size the bar so its derived {@link pitch} lands on the pinion's: a run of `n` teeth * against a `pinionTeeth` pinion of pitch radius `r` wants `length = n × 2π × r / pinionTeeth + inset × 2`. * * Local frame: **rests on `y = 0`** with teeth pointing up, running along `+X` from the origin and extruded * across `+Z`. Ground contact, like the rest of the library — no translate needed to lay it on a surface. * * Material groups: **none** — one material for the whole rack. * * @example * ```typescript * // A 24-tooth rack cut to mesh with a 20-tooth pinion of pitch radius 0.8. * const length = (24 * 2 * Math.PI * 0.8) / 20; * const rack = new Mesh(new RackGeometry({ teeth: 24, length }), steel); * ``` */ export declare class RackGeometry extends ExtrudeGeometry { /** Overall length of the bar, after clamping. */ readonly length: number; /** Tooth period, center to center — `(length − inset × 2) / teeth`. */ readonly pitch: number; /** Height the tooth tips reach, after clamping. */ readonly tipHeight: number; /** Height the valley floors sit at, after clamping. */ readonly valleyHeight: number; /** Tip flat as a fraction of the period, after clamping. */ readonly tipWidth: number; /** Valley flat as a fraction of the period, after clamping. */ readonly valleyWidth: number; constructor({ depth, ...shapeOptions }?: RackGeometryOptions); } export declare interface RackGeometryOptions extends RackShapeOptions { /** Extrusion depth — the rack's thickness across its run. Defaults to `0.25`. */ depth?: number; } /** * Rack profile — the straight counterpart of a gear, as in rack and pinion. * * **A rack is a gear of infinite radius.** The teeth no longer converge on a center, so they stand parallel and * the period advances along a line rather than around a circle. That is why the tooth fractions are identical to * {@link GearShape}'s — tip, falling flank, valley, rising flank, with the two flats sized independently and the * flanks taking the remainder — and why there is no polar arithmetic here at all. * * **{@link pitch} is an output, not an input** — `(length − inset × 2) / teeth`. Size the bar, then choose how * finely to divide it: teeth subdivide a fixed run instead of extending it, so every tooth is whole by * construction and adding teeth never moves the ends. A circular gear divides its circumference the same way, * `2π × outerRadius / teeth`, though it does not publish the result. * * **{@link RackShapeOptions.tipHeight} and {@link RackShapeOptions.valleyHeight} are absolute**, both measured * from the underside, exactly as the circular gears measure both their radii from the center. That completes a * grid with the tooth flats — `tipWidth`/`tipHeight`, `valleyWidth`/`valleyHeight` — and their order is not * enforced: put the valley above the tip and the teeth invert into channels. * * Rests with its underside on `y = 0`, teeth pointing up, running along `+X` from the origin. */ export declare class RackShape extends Shape { /** Overall length of the bar, after clamping. */ readonly length: number; /** Tooth period, center to center — `(length − inset × 2) / teeth`. */ readonly pitch: number; /** Height the tooth tips reach, after clamping. */ readonly tipHeight: number; /** Height the valley floors sit at, after clamping. */ readonly valleyHeight: number; /** Tip flat as a fraction of the period, after clamping. */ readonly tipWidth: number; /** Valley flat as a fraction of the period, after clamping. */ readonly valleyWidth: number; constructor({ length, teeth, tipHeight, valleyHeight, inset, tipWidth, valleyWidth, lean, }?: RackShapeOptions); } export declare interface RackShapeOptions { /** Overall length of the bar, end to end. Defaults to `3`. */ length?: number; /** Number of teeth. Defaults to `12`. */ teeth?: number; /** * Height the tooth tips reach, measured from the underside. Defaults to `0.38`. * * Absolute, like the radii on the circular gears — and paired with * {@link RackShapeOptions.valleyHeight} the same way {@link RackShapeOptions.tipWidth} is paired with * {@link RackShapeOptions.valleyWidth}. */ tipHeight?: number; /** * Height the valley floors sit at, measured from the underside. Defaults to `0.2`. * * Absolute from the same datum as {@link RackShapeOptions.tipHeight}, so the two are directly comparable. * * **Their order is not enforced.** Set the valley above the tip and the teeth invert into channels cut down * into the bar — a legitimate shape, and the caller's business. */ valleyHeight?: number; /** * Flat carved out of **each** end before the toothed run begins. Defaults to `0`. * * Taken out of {@link RackShapeOptions.length}, never added to it: the bar measures `length` whatever this is * set to, and the teeth crowd into what is left. * * **Any nonzero inset destroys tileability** — hence the default of `0`. At `0` each end carries exactly half * a valley, so two racks butted end to end form a seam valley identical to an interior one and a pinion rolls * across the join without a hitch. An inset adds `inset × 2` to that seam and the gap becomes visible. Use it * for a standalone bar that wants plain material at its ends, not for a run. */ inset?: number; /** * Width of the flat at the tooth tip, as a fraction of one period. `0` brings the tooth to a point. Defaults * to `0.25`. */ tipWidth?: number; /** * Width of the flat at the valley floor, as a fraction of one period. `0` brings the valley to a point. * Defaults to `0.25`. */ valleyWidth?: number; /** * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal; at `1` the rising flank vanishes and the tooth's * trailing face drops vertically — a linear ratchet. Defaults to `0`. */ lean?: number; } /** One stop of a radial falloff. */ export declare interface RadialGradientStop { /** Distance from the core: `0` at the center, `1` at the rim. */ offset: number; color: ColorRepresentation; /** Opacity at this stop, `0`–`1`. Defaults to `1`. */ alpha?: number; } export declare interface RadialGradientTextureOptions { /** Falloff from core to rim. Sorted internally, so declaration order doesn't matter. */ stops: RadialGradientStop[]; /** * Edge length in texels. Defaults to `128`, which is a power of two (so mipmaps are exact) and * ample for a smooth ramp: bilinear filtering interpolates *between* texels, so upsampling a * gradient loses nothing perceptible. Banding in a glow comes from the 8-bit framebuffer, not * from texture size, so raising this rarely helps. */ size?: number; /** * How each pair of stops is interpolated. Defaults to {@link Easing.linear}, which matches a * canvas gradient exactly. * * Linear interpolation leaves a **slope discontinuity** at every stop — brightness stays * continuous, but its rate of change kinks. Human vision exaggerates precisely those kinks * (Mach banding), so a linear ramp reads as having a faint ring at the rim and a hard edge where * two glows overlap. {@link Easing.smoothstep} brings the derivative to zero at each stop, which * removes the ring and softens the outer edge without changing overall brightness. */ easing?: EasingFunction; } /** * Calculate the radius to achieve a spherical cap height. * R = r / (1 - cos(thetaLength)) */ export declare const radiusFromCapHeight: (height: number, thetaLength: number) => number; /** * Calculate the radius to achieve a spherical cap width. * R = w / (2 * sin(thetaLength)) */ export declare const radiusFromCapWidth: (width: number, thetaLength: number) => number; /** * Misty rainfall as instanced vertical streaks. * * Each streak is a thin, gradient-textured quad animated through a bounded * volume. By default streaks fall straight down with no rotation. Optional * {@link RainEffectOptions.windDirection} / {@link RainEffectOptions.windStrength} * tilt streaks **and** drift them horizontally together, so motion matches the * visual angle. Streak materials use `DoubleSide` so thin quads stay visible * from any camera angle. Scene fog dissolves distant streaks when the material's * `fog` flag is enabled. * * **`intensity`** (0–1) scales how many instances draw, how fast they fall, and * their opacity — useful for storm ramps or lightning flashes. * * @example * ```typescript * const rain = new RainEffect({ area: 12, height: 16, intensity: 0.4 }); * scene.add(rain); * scene.fog = new Fog(0x0a0a12, 4, 28); * * function animate(delta: number) { * rain.update(delta); * renderer.render(scene, camera); * } * ``` */ export declare class RainEffect extends InstancedMesh { /** Rainfall strength (0–1). Adjust at runtime for storm variation. */ intensity: number; private readonly maxCount; private readonly area; private readonly height; private readonly groundY; private readonly baseOpacity; private readonly windDirection; private readonly windStrength; private readonly fallDirection; private readonly streakOrientation; private readonly sx; private readonly sz; private readonly topY; private readonly len; private readonly speed; private readonly streakTexture?; private readonly dummy; private clock; constructor(options?: RainEffectOptions); /** * Advance streak positions and refresh instance transforms. Pass elapsed frame * time in seconds (e.g. from `createScene`'s `onFrame` callback). */ update(dt: number): void; /** Release geometry, materials, and the procedural streak texture. */ dispose(): this; private applyIntensity; private updateFallDirection; private writeMatrices; } export declare interface RainEffectOptions { /** Override the streak quad geometry. Defaults to a thin `PlaneGeometry`. */ geometry?: BufferGeometry; /** Override the default streak material. */ material?: Material; /** Maximum number of streak instances. Defaults to `1400`. */ count?: number; /** Horizontal half-extent of the rainfall area (square centered on the origin). Defaults to `26`. */ area?: number; /** Vertical span above `groundY`. Defaults to `22`. */ height?: number; /** World Y where streaks recycle. Defaults to `0`. */ groundY?: number; /** Streak quad width. Defaults to `0.009`. */ width?: number; /** Streak color. Defaults to `#aebfd6`. */ color?: ColorRepresentation; /** Base material opacity at full intensity. Defaults to `0.16`. */ opacity?: number; /** Minimum streak length. Defaults to `0.18`. */ lengthMin?: number; /** Maximum streak length. Defaults to `0.42`. */ lengthMax?: number; /** Minimum fall speed (units/s). Defaults to `11`. */ speedMin?: number; /** Maximum fall speed (units/s). Defaults to `19`. */ speedMax?: number; /** * Horizontal compass direction the wind blows (radians, 0 = +X, π/2 = +Z). * Only used when {@link RainEffectOptions.windStrength} is greater than zero. * Defaults to `0`. */ windDirection?: number; /** * How much rain tilts and drifts from vertical, as `tan(angleFromVertical)`. * `0` = straight down (default). `0.15` ≈ 8.5° lean with matching horizontal drift. */ windStrength?: number; /** * Rainfall strength (0–1). Scales visible instance count, fall speed, and opacity. * Defaults to `0.5`. */ intensity?: number; } /** * Grouped exports — same function API, namespace import like {@link Easing}. * * @example * ```ts * import { Random } from "three-low-poly"; * const rng = Random.create(deriveSubSeed(1337, 0x101)); * ``` */ export declare const Random: { readonly create: typeof createRandom; readonly mulberry32: typeof mulberry32; readonly splitmix32: typeof splitmix32; readonly deriveSubSeed: typeof deriveSubSeed; readonly range: typeof randomRange; readonly pick: typeof randomPick; readonly weighted: typeof randomWeighted; }; /** * Prepare reusable color samplers, then supply a RandomSource when sampling. * Colors/arrays/weights are copied at construction (custom function closures remain caller-owned). * No global color-management settings or random sources are changed. * Hex/string colors use Three's normal conversion; Color inputs are already working-space values. * Configure the working color space before constructing samplers. Results can be passed directly * to setColorAt or copied to vertex colors. Sampling allocates no Color objects. * * @example * ```ts * const sample = RandomColor.between("#493729", "#93714f"); * const target = new Color(); * const context = { index: 0, random: createRandom(1337) }; * sample(target, context); * mesh.setColorAt(context.index, target); * ``` */ export declare const RandomColor: { /** Independently sample neighboring hues and absolute S/L ranges in sRGB; output is working-space Color. */ readonly analogous: typeof analogous; /** One configured color; consumes no randomness. */ readonly constant: typeof constant; /** * Exact palette selection. Optional finite weights are relative probabilities; * negative weights contribute zero, all-zero weights fall back to uniform selection. * Empty selections and mismatched/non-finite weights throw at construction. */ readonly pick: typeof pick; /** Component-wise working-RGB interpolation; normally Linear-sRGB, not perceptually uniform. */ readonly between: typeof between; /** * Sample an ordered working-RGB path at a uniform t. Segment probability is proportional * to its width. Requires at least two strictly increasing stops, starting at 0 and ending at 1. */ readonly gradient: typeof gradient; /** * Select a sampler, then invoke it with the same context. Does not interpolate between families. * Weight validation and fallback are identical to pick. The selected sampler consumes its own draws. */ readonly mix: typeof mix; }; /** * Generates a random number between `min` and `max`. */ export declare function randomFloat(min?: number, max?: number, source?: RandomSource): number; /** * Generates a random integer between `min` and `max`. */ export declare function randomInteger(min?: number, max?: number, source?: RandomSource): number; /** Pick from a non-empty array using any stream — website `pick()` parity. */ export declare function randomPick(stream: RandomStream, arr: readonly T[]): T; /** Float in [min, max) from any stream — website `range()` parity. */ export declare function randomRange(stream: RandomStream, min: number, max: number): number; /** * Random number in `[min, max]` skewed toward the CENTER. See {@link RandomSource.skewCenter}. * * `exponent` is the bias strength — **higher clusters tighter** to the middle; `1` is uniform, `< 1` * reverses (toward the edges). Defaults to `2`. */ export declare function randomSkewCenter(exponent?: number, min?: number, max?: number, source?: RandomSource): number; /** * Random number in `[min, max]` skewed toward `max`. See {@link RandomSource.skewMax}. * * `exponent` is the bias strength — **higher skews harder** toward `max`; `1` is uniform, `< 1` reverses. * Defaults to `2`. */ export declare function randomSkewMax(exponent?: number, min?: number, max?: number, source?: RandomSource): number; /** * Random number in `[min, max]` skewed toward `min`. See {@link RandomSource.skewMin}. * * `exponent` is the bias strength — **higher skews harder** toward `min`; `1` is uniform, `< 1` reverses. * Defaults to `2`. */ export declare function randomSkewMin(exponent?: number, min?: number, max?: number, source?: RandomSource): number; /** * Random source — a stream plus distribution helpers. * Returned by {@link createRandom}; also accepted by {@link RandomNumberUtils}. */ export declare interface RandomSource { /** `true` when backed by {@link mulberry32}; `false` when using `Math.random()`. */ readonly seeded: boolean; /** Next float in [0, 1). */ next(): number; /** Float in [min, max). */ float(min?: number, max?: number): number; /** Integer in [min, max] (inclusive). */ int(min?: number, max?: number): number; /** Uniform element from a non-empty array. */ pick(arr: readonly T[]): T; /** * Element from a non-empty array, chosen with probability proportional to `weights[i]`. * * The weighted sibling of {@link pick}. A palette of styles that appear at different rates — a * cemetery that is mostly plain stones with the occasional monument — is a weighted draw, not a * uniform one. Negative weights count as zero; if every weight is zero it falls back to uniform. */ weighted(arr: readonly T[], weights: readonly number[]): T; /** `true` with given probability (default 0.5). */ boolean(probability?: number): boolean; /** * Skew toward `max`. **`exponent` is the bias strength — higher pulls harder;** `1` is uniform, and * `< 1` reverses (piling toward `min` instead). Defaults to `2`. Mirrors {@link randomSkewMax}. */ skewMax(exponent?: number, min?: number, max?: number): number; /** * Skew toward `min` — the mirror of {@link skewMax}. **Higher `exponent` = stronger bias;** `1` is * uniform, `< 1` reverses. Defaults to `2`. Mirrors {@link randomSkewMin}. */ skewMin(exponent?: number, min?: number, max?: number): number; /** * Skew toward the CENTER of the range — the symmetric sibling of {@link skewMax} / {@link skewMin}. * * Small deviations from the middle are common, large ones rare, both directions equally likely: a * gentle jitter with the occasional outlier. Same convention as its siblings — **higher `exponent` = * stronger** pull to the center; `1` is uniform, `< 1` reverses (piling toward the edges). Defaults to * `2`. The full range is still reached, just seldom. */ skewCenter(exponent?: number, min?: number, max?: number): number; } /** * Randomness for procedural generation — unseeded by default, reproducible on demand. * * --- * * ### Layer 1 — stream primitive (portfolio parity) * * - {@link mulberry32} — fast seeded PRNG; returns a `() => number` closure yielding * floats in `[0, 1)`. Same algorithm as Gotham/Water on the portfolio site. * * ### Seed mixing * * - {@link splitmix32} — mixer only, **not** a stream. Maps one 32-bit value to another. * - {@link deriveSubSeed} — `splitmix32(masterSeed ^ salt)`. Fan one user-facing master * seed into independent sub-streams per subsystem. Use stable hex salts per domain * (`0x101` books, `0x202` fog, `0x303` windows, …) instead of `seed + n` offsets. * * ### Layer 2 — library ergonomics * * - {@link createRandom} — **no seed** → wraps `Math.random()`, unique every runtime * (showcase default). **With seed** → {@link mulberry32} stream, same seed ⇒ same sequence. * - Returns a {@link RandomSource}: `next`, `float`, `int`, `pick`, `boolean`, `skewMax`, `skewMin`. * - {@link Random} namespace — grouped exports, same API as standalone functions (like {@link Easing}). * * ### Layer 3 — {@link RandomNumberUtils} * * Existing helpers (`randomFloat`, `randomSkewMax`, …) accept an optional * {@link RandomSource} as their last argument. Omit it for unseeded default behavior. * * --- * * @example Unique runtime (default) * ```ts * const rng = createRandom(); * rng.float(0, 10); // different every page load * ``` * * @example Reproducible layout with sub-seeds * ```ts * const master = 1337; * const books = createRandom(deriveSubSeed(master, 0x101)); * const fog = createRandom(deriveSubSeed(master, 0x202)); * ``` * * @example Namespace import * ```ts * const rng = Random.create(deriveSubSeed(1337, 0x101)); * ``` */ /** Callable stream returning floats in [0, 1). */ export declare type RandomStream = () => number; /** Deletes input UVs/normals, then returns a welded geometry displaced by axis × randomScale per vertex. * Recomputes output normals; the input attribute deletion is a side effect. */ export declare function randomTransformVertices(geometry: T, axis?: Vector3, minScale?: number, maxScale?: number, random?: () => number): T; /** Weighted pick from a non-empty array using any stream — `weights[i]` is the relative chance of `arr[i]`. */ export declare function randomWeighted(stream: RandomStream, arr: readonly T[], weights: readonly number[]): T; export declare interface RecoilClipOptions extends CameraClipTiming { /** Peak backward displacement along camera-local +Z. Defaults to 0.15 world units. */ distance?: number; /** Peak upward pitch in radians. Defaults to 0.06. */ pitch?: number; } /** Centered CCW rectangle: thickness along station normal, width along binormal. */ export declare function rectProfile(width: number, thickness: number): Vec2[]; /** * Return item-center distances and spacing metadata. Use pointAtDistance for placement or * slicePath(center ± width / 2) for an item spanning multiple segments. * * ```ts * const plan = measurePath(footprint, { closed: true }); * const { centers } = repeatAlongPath(plan, { pitch: 1.2 }); * * // A battlement: the interval, swept. Corner merlons need no special case — their slice comes back * // with the corner in it, and `miterFrames` cuts it. * const merlons = centers.map((c) => * sweep( * section, * miterFrames( * slicePath(plan, c - 0.4, c + 0.4).map((position) => ({ position, tangent: new Vector3() })), * { reference: new Vector3(0, 1, 0) }, * ), * ), * ); * * // A balustrade: the center, populated. * for (const c of centers) baluster.position.copy(pointAtDistance(plan, c)); * ``` */ export declare function repeatAlongPath({ points, closed, distances, length }: PathMeasure, { pitch, anchor }: RepeatAlongPathOptions): PathRepeat; export declare interface RepeatAlongPathOptions { /** Requested center-to-center distance, greater than zero. */ pitch: number; /** How spacing handles a remainder along the measured path. */ anchor?: RepeatAnchor; } /** corners adjusts spacing per segment to place an item at each vertex; pitch keeps fixed spacing from the start. */ export declare type RepeatAnchor = "corners" | "pitch"; /** * Return count samples of a closed outline without a repeated endpoint. Empty input or count ≤ 0 returns []. * Angular sampling assumes a star-shaped outline about its centroid; index upsampling repeats vertices. * * ```ts * const circle = resampleLoop(squareOutline, 32); // arc length: 32 evenly spaced points * const corners = resampleLoop(squareOutline, 32, "index"); // 4 distinct points, 28 collapsed edges * ``` */ export declare function resampleLoop(loop: Vector2[], count: number, method?: ResampleMethod): Vector2[]; /** * arclength samples perimeter distance; index selects loop[floor(i * n / count)] and can duplicate vertices. * angular takes nearest centroid-ray hits, falling back to the first point when a ray misses. */ export declare type ResampleMethod = "arclength" | "index" | "angular"; /** * Resolve a fence run from any two of `pitch`, `count`, and `length`. * * The three are bound by `length = count * pitch`, so pinning two solves the third: * * - `count` alone — the run is as long as it needs to be. * - `length` alone — pickets divide it equally; `pitch` is recomputed to land them on the span. * - both — `pitch` is whatever divides `length` into `count` pickets. * * A run spans `[0, length]` with pickets inset a half-pitch from each end. That inset is what puts a * run's end pickets a half-gap clear of the posts it sits between. * * Pinning `count` *and* `length` leaves only the gap to absorb the difference, and the gap has a * floor — pickets may touch, but they cannot overlap. Pass `itemWidth` and an impossible request * (more pickets than physically fit) yields on the count rather than producing intersecting * geometry. Read the returned `count` rather than assuming you got the one you asked for. * * @example * ```ts * resolveFenceSpan({ length: 4.2, pitch: 0.4 }); * // → { count: 11, pitch: 0.3818…, length: 4.2 } * * // 20 planks of 0.35 need 7.0 of plank alone — they cannot fit in 6. * resolveFenceSpan({ length: 6, count: 20, itemWidth: 0.35 }); * // → { count: 17, … } the count yielded; nothing overlaps * ``` */ export declare function resolveFenceSpan({ pitch, count, length, itemWidth }?: FenceSpanOptions): FenceSpan; /** Reverse entry order and clone/negate tangents; position vectors remain shared. */ export declare function reversePath(path: PathPoint[]): PathPoint[]; export declare function rgbToHex(r: number, g: number, b: number): number; /** * Converts RGB bytes to HSL degrees / percentages without rounding. Channels clamp to 0–255. * * Example usage: * ``` * const rgbColor = { r: 255, g: 0, b: 0 }; // Red * const hslColor = rgbToHsl(rgbColor.r, rgbColor.g, rgbColor.b); * console.log(hslColor); // Output: [0, 100, 50] * ``` */ export declare function rgbToHsl(r: number, g: number, b: number): [number, number, number]; export declare interface RimUVOptions { /** Omit for the existing 0–1 loop fit; positive value uses source boundary distance per repeat. */ unitsPerRepeat?: number; /** Texture-coordinate offset, applied independently to each boundary loop. */ offset?: Vector2; } /** * Ring stand — a torus ring on radial legs, for a round-bottom vessel to rest in. * * Local frame: legs on Y=0, ring at Y=height. */ export declare class RingStandGeometry extends BufferGeometry { readonly radius: number; readonly height: number; readonly count: number; constructor({ radius, height, count, thickness, radialSegments, }?: RingStandGeometryOptions); } export declare interface RingStandGeometryOptions { /** Ring radius. Defaults to `0.3`. */ radius?: number; /** Leg height. Defaults to `0.4`. */ height?: number; /** Number of legs. Defaults to `3`. */ count?: number; /** Ring tube thickness. Defaults to `0.03`. */ thickness?: number; /** Circumference segments. Defaults to `16`. */ radialSegments?: number; } /** * Low-poly rock — sphere with randomized vertex offsets, then centered. */ export declare class RockGeometry extends BufferGeometry { readonly radius: number; readonly widthSegments: number; readonly heightSegments: number; constructor({ seed, radius, widthSegments, heightSegments }?: RockGeometryOptions); } export declare interface RockGeometryOptions { /** Optional seed for repeatable vertex offsets. */ seed?: number; /** Base sphere radius before vertex noise. Defaults to `1`. */ radius?: number; /** Horizontal segments. Defaults to `4`. */ widthSegments?: number; /** Vertical segments. Defaults to `4`. */ heightSegments?: number; } export declare interface RockScatterBounds { /** Scatter extent along X (centered on origin). Defaults to `4`. */ width?: number; /** Scatter extent along Z (centered on origin). Defaults to `4`. */ depth?: number; /** Max random Y offset above the ground plane. Defaults to `0`. */ heightJitter?: number; } export declare interface RockScatterPlacementOptions extends RockScatterBounds { /** Number of instances. Defaults to `5`. */ count?: number; /** Min per-axis instance scale. Defaults to `0.8`. */ scaleMin?: number; /** Max per-axis instance scale. Defaults to `1.2`. */ scaleMax?: number; /** Optional seed for reproducible scatter. Omit for unique runtime. */ seed?: number; } /** Return cloned ring points with the start index shifted cyclically; offset must be an integer. */ export declare function rotateRing(ring: Vector3[], offset: number): Vector3[]; /** * A round-topped headstone — the classic one. * * **It is an {@link ArchedSlabGeometry} with a headstone's defaults, and nothing else.** It used to be a * box welded to half a cylinder: the same silhouette arrived at the hard way, with no `curveSegments` * knob, no arch styles, and a smooth-shaded cap sitting on a faceted body. * * Being the slab means it inherits the whole arch vocabulary for free — including the SHOULDERS the slab * was designed for in the first place (`archWidth` narrower than `width`), which is the shape you see in * every real cemetery and which this, of all things, could not previously make. * * Defaults reproduce the original silhouette: `0.6` wide, `0.2` deep, `1.0` tall overall — a `0.7` body * under a `0.3` cap, which is exactly `width / 2` and therefore a true semicircle. * * Base at `y = 0`, centered on X and Z. * * @example * ```ts * const classic = new RoundedHeadstoneGeometry(); * const shouldered = new RoundedHeadstoneGeometry({ width: 0.7, archWidth: 0.45 }); * const ogee = new RoundedHeadstoneGeometry({ arch: "ogee", archHeight: 0.45 }); * ``` */ export declare class RoundedHeadstoneGeometry extends ArchedSlabGeometry { constructor({ width, height, archHeight, depth, arch, curveSegments, ...rest }?: RoundedHeadstoneGeometryOptions); } export declare interface RoundedHeadstoneGeometryOptions extends ArchedSlabGeometryOptions { } /** * Row of books by count — the row is however long the books turn out. * * This is the **partially-filled shelf**, and it is the more common one: a real bookshelf is almost * never packed wall to wall. Ask for twelve books, get a run you then position on a shelf with space * beside it. Pinning `count` is safe here precisely *because* nothing else is pinned — the books * keep their natural thicknesses and the length simply falls out. * * Reach for {@link rowOfBooksByLength} instead when the shelf is the fixed thing and you want it * full. * * Local frame: the row starts at Z=0 and grows along +Z. Read the row's bounding box to place it — * its length is not knowable in advance. * * @example * ```ts * // Twelve books; the row is as long as they happen to be. * const row = rowOfBooksByCount({ coverMaterial, pagesMaterial, count: 12, seed: 1337 }); * scene.add(row); * * // Same count, different seed -> a different length. That is the point. * // seed 1337 -> 12 books spanning 2.00 * // seed 7 -> 12 books spanning 1.87 * ``` */ export declare function rowOfBooksByCount({ coverMaterial, pagesMaterial, count, scaleXMin, scaleXMax, scaleYMin, scaleYMax, scaleZMin, scaleZMax, seed, }: RowOfBooksByCountOptions): InstancedMesh; export declare interface RowOfBooksByCountOptions extends BookScaleOptions { /** Number of books. The row is as long as they turn out. Defaults to `10`. */ count?: number; } /** * Pack a shelf of a given length with plausibly-sized books. * * **Book count is an output, not an input.** Books touch — there is no gap to absorb slack — so * the only variable left to solve is thickness, and thickness has a physical floor (`scaleZMin`). * Pinning both `length` and `count` would drive the solver straight through that floor and produce * paper-thin books, so `count` is deliberately not accepted here. Ask for a shelf; get however many * books fit. * * Packing stops once the space left is thinner than the thinnest legal book, leaving a small gap at * the end — the way a real shelf does. A final book is trimmed to close the gap only when trimming * still leaves it above `scaleZMin`. * * This is the **shelf packed full**, wall to wall. For a partially-filled shelf — the more common * look — use {@link rowOfBooksByCount} and position the row within the shelf. * * Local frame: the row starts at Z=0 and grows along +Z. * * @example * ```ts * // Fill a 6-unit shelf. You do not say how many books; you find out. * const shelf = rowOfBooksByLength({ coverMaterial, pagesMaterial, length: 6, seed: 1337 }); * scene.add(shelf); * * shelf.count; // 34 — an output. A different seed gives a different number. * ``` */ export declare function rowOfBooksByLength({ coverMaterial, pagesMaterial, length, scaleXMin, scaleXMax, scaleYMin, scaleYMax, scaleZMin, scaleZMax, seed, }: RowOfBooksByLengthOptions): InstancedMesh; export declare interface RowOfBooksByLengthOptions extends BookScaleOptions { /** * Shelf length to pack, in world units along Z. Defaults to `10`. * * Book count is an *output* of packing, never an input — see {@link rowOfBooksByLength}. */ length?: number; } /** * Row of books from scales you supply yourself — the escape hatch beneath * {@link rowOfBooksByCount} and {@link rowOfBooksByLength}, for when you want to choose every * book's size rather than have one drawn for you. * * Both of the other row factories are thin wrappers over this: they only differ in how they build * the `scales` array. * * Local frame: the row starts at Z=0 and grows along +Z. * * @example * ```ts * const source = createRandom(1337); * const scales = [ * new Vector3(0.5, 0.9, 0.3), // z is the thickness * new Vector3(0.5, 0.7, 0.2), * new Vector3(0.6, 0.8, 0.4), * ]; * const row = rowOfBooksByScales({ coverMaterial, pagesMaterial, scales, source }); * ``` */ export declare function rowOfBooksByScales({ coverMaterial, pagesMaterial, scales, source, }: RowOfBooksByScalesOptions): InstancedMesh; export declare interface RowOfBooksByScalesOptions { coverMaterial: T; pagesMaterial: T; /** One scale per book. `z` is the thickness, and thickness is what fills the shelf. */ scales: Vector3[]; /** Shared stream for shelf jitter — must be the same source that built `scales`. */ source: RandomSource; } /** * A row of headstones that has been standing for a hundred years. * * Perfectly upright, perfectly aligned stones read as *brand new* — which is exactly wrong for a * graveyard. Age is the point here, so each stone is drawn from a random silhouette, then settled by * {@link settleStone}: it leans, twists a little, sinks, drifts off its plot, and weathers to its own * shade of gray. * * Returns a {@link Group} of {@link InstancedMesh}es — one per silhouette used. The first plot sits at * the origin and the row runs out along `+x`; position the group to place it. For many rows at once, see * {@link fieldOfHeadstones}, which shares its instancing so the whole field stays a handful of draw * calls. Dispose each child's geometry and the shared material when removing it. * * @example * ```ts * const row = rowOfHeadstones({ count: 8, spacing: 1, seed: 1337 }); * scene.add(row); * * // A newer plot: upright, evenly set, barely weathered. * const fresh = rowOfHeadstones({ count: 8, leanMax: 0.01, sinkMax: 0, weathering: 0.02 }); * ``` */ export declare function rowOfHeadstones({ count, spacing, ...settle }?: HeadstoneRowOptions): Group; /** * A straight run of rough split-rail country fence, centered on local X with * its feet on y=0. Short runs can be rotated and joined to trace a boundary. */ export declare class RusticFence extends Group { #private; constructor({ sections, sectionLength, railCount, postHeight, postThickness, railThickness, seed, colors, }?: RusticFenceOptions); dispose(): void; } export declare interface RusticFenceOptions { /** Number of bays between posts. */ sections?: number; sectionLength?: number; railCount?: 2 | 3; postHeight?: number; postThickness?: number; railThickness?: number; seed?: number; /** Overrides the repeating timber palette. Index counts posts first, then rails by section. * Each distinct sampled color needs a material in this Mesh-based factory. */ colors?: ColorSampler; } /** * Scatter instanced boulders through a horizontal bounds region — a "created layer" of the * noise-lumped {@link BoulderGeometry}. Unlike {@link scatterRocks}, which rotates a single * shared shape, this generates several distinct boulder geometries (see `variants`) and * distributes instances across them, so the group reads as unique rocks while staying to a * few draw calls. * * **Scatter, not field.** Placement is stochastic — a `count` distributed pseudo-randomly * within the bounds — so the name states the operation rather than one of its uses. A * "field" in this library means a laid-out grid (see {@link fieldOfHeadstones}, which walks * rows and columns at fixed spacing), and this is not that. The same scatter serves a * boulder field, stones set proud of a wall, rubble along a path, or rocks in a streambed; * naming it for any one of them would narrow it and misdescribe how it places. * * Returns a {@link Group} of {@link InstancedMesh}es (one per variant), each sharing the * stone material. Pass a `seed` to make a scatter reproducible. Dispose each child's * geometry and the shared material when removing it. * * @example * ```ts * const boulders = scatterBoulders({ count: 24, width: 12, depth: 12, seed: 1337 }); * scene.add(boulders); * ``` */ export declare function scatterBoulders({ count, width, depth, heightJitter, scaleMin, scaleMax, seed, radius, detail, noiseHeight, noiseScale, octaves, persistence, variants, material, color, colors, }?: ScatterBouldersOptions): Group; export declare interface ScatterBouldersOptions extends RockScatterPlacementOptions { /** Base radius for each boulder geometry. Defaults to `1`. */ radius?: number; /** Icosphere subdivision per boulder. Defaults to `2`. */ detail?: number; /** Radial relief amplitude per boulder. Defaults to `0.35`. */ noiseHeight?: number; /** Noise frequency per boulder. Defaults to `1.6`. */ noiseScale?: number; /** fbm octaves per boulder. Defaults to `3`. */ octaves?: number; /** fbm gain per octave. Defaults to `0.5`. */ persistence?: number; /** * Distinct boulder geometries generated and distributed across the field. Each is * a real, unique lumped shape (unlike rotating one shared mesh); instances round-robin * across them, so the field stays batch-friendly (one draw call per variant). * Defaults to `4`. */ variants?: number; /** Override the default stone material (shared across all instances). */ material?: Material; /** Stone tint when `material` is omitted. Defaults to `#6f6f6f`. */ color?: ColorRepresentation; /** Per-boulder color overriding color. Index follows scatter order before variant batching. Custom material colors still multiply the tint. */ colors?: ColorSampler; } /** * Scatter instanced mossy rocks inside a horizontal bounds region. * * Uses two material groups (rock + moss) on a shared {@link InstancedMesh}, so the moss can be * tinted and made translucent independently of the stone beneath it. * * @example * ```ts * const rocks = scatterMossyRocks({ count: 12, width: 8, depth: 8, seed: 1337 }); * scene.add(rocks); * ``` */ export declare function scatterMossyRocks({ count, width, depth, heightJitter, scaleMin, scaleMax, seed, radius, detail, mossScaleXZ, mossScaleY, mossOffsetY, rockMaterial, mossMaterial, rockColor, mossColor, mossOpacity, tints, }?: ScatterMossyRocksOptions): InstancedMesh; export declare interface ScatterMossyRocksOptions extends RockScatterPlacementOptions { /** Per-instance multiplier applied to BOTH stone and moss materials. White is neutral; use gray endpoints for brightness variation. */ tints?: ColorSampler; /** Dodecahedron radius for each instance geometry. Defaults to `1`. */ radius?: number; detail?: number; mossScaleXZ?: number; mossScaleY?: number; mossOffsetY?: number; rockMaterial?: Material; mossMaterial?: Material; /** Rock tint when `rockMaterial` is omitted. Defaults to `#808080`. */ rockColor?: ColorRepresentation; /** Moss tint when `mossMaterial` is omitted. Defaults to `#4b8b3b`. */ mossColor?: ColorRepresentation; /** Moss opacity when `mossMaterial` is omitted. Defaults to `0.8`. */ mossOpacity?: number; } /** * Where stones stand proud of a surface. * * **Takes a rectangle, not a wall.** A width, a height and a course grid is everything it needs, so the * same call decorates a wall, a pier, a chimney, a plinth or an arched slab. Returns placements rather * than geometry, so the caller decides how far each block sinks and what it is made of. * * **The block must be half-embedded, not sat on the face.** Build each one deeper than its `depth` and * push it out by exactly `depth`, leaving the rest buried: * * ```ts * const solid = new BoxGeometry(length, height, stoneWidth); * solid.rotateZ(tilt); * solid.translate(x, y, surfaceThickness / 2 + depth - stoneWidth / 2); * ``` * * Sunk further than it stands out, so the join at its foot is inside solid material rather than on it and * no two faces land coplanar. Flush-backed, it becomes a sticker: same silhouette, wrong shadow. * * Every dimension is a MULTIPLIER on the course rather than an absolute, so one set of numbers reads the * same on a garden wall and a bell tower. * * **Distinct from {@link StoneWall}'s own `proudChance`, and both are right.** The wall MOVES stones it * already has; this ADDS blocks to a surface. Use the wall's when the face is built from real stones, and * this when it is a slab you cannot take apart. * * @example * ```ts * const { placements } = scatterProudStones({ * width: 3.2, * height: 2.6, * density: 0.14, * exclusions: [{ x: 1.1, y: 0.8, width: 0.9, height: 1.4 }], // a window * }); * ``` */ export declare function scatterProudStones({ width, height, courseHeight, stoneAspect, bondOffset, density, lengthMin, lengthMax, heightMin, heightMax, depthMin, depthMax, tilt, exclusions, seed, }?: ProudStoneOptions): ProudStoneScatter; /** * Scatter instanced rocks inside a horizontal bounds region. * * @example * ```ts * const rocks = scatterRocks({ count: 12, width: 8, depth: 8, seed: 1337 }); * scene.add(rocks); * ``` */ export declare function scatterRocks({ count, width, depth, heightJitter, scaleMin, scaleMax, seed, radius, widthSegments, heightSegments, material, color, colors, }?: ScatterRocksOptions): InstancedMesh; export declare interface ScatterRocksOptions extends RockScatterPlacementOptions { /** Per-instance color; overrides color with a white generated material. Custom materials still multiply it. */ colors?: ColorSampler; /** Base sphere radius for each instance geometry. Defaults to `1`. */ radius?: number; widthSegments?: number; heightSegments?: number; material?: Material; /** Stone tint when `material` is omitted. Defaults to `#808080`. */ color?: ColorRepresentation; } /** * Extract an independent cross-section of a closed triangle mesh. Source and plane are unchanged. * Uses capped slicing internally and disposes its temporary solids. Tangent/coplanar contact follows * sliceGeometry's rules; this is not an arbitrary surface-intersection operation. */ export declare function sectionGeometry(source: BufferGeometry, plane: Plane, options?: Pick): GeometrySection; /** Which planes bound each end of a segment cut at both ends. */ export declare interface SegmentBounds { start: [CutPlane, CutPlane]; end: [CutPlane, CutPlane]; } /** * Set a random interval that will call the callback function with a random delay between minDelay and maxDelay. * * Example usage: * ``` * const clearRandomInterval = setRandomInterval(() => { * console.log('Random interval executed!'); * }, 500, 1500); // Random delay between 500ms and 1500ms * ``` */ export declare function setRandomInterval(callback: () => void, minDelay: number, maxDelay: number): () => void; /** * Set a random timeout that will call the callback function with a random delay between minDelay and maxDelay. * * Example usage: * ``` * setRandomTimeout(() => { * console.log(`Callback ${i} executed!`); * }, 100, 500); // Random timeout between 100ms and 500ms * ``` */ export declare function setRandomTimeout(callback: () => void, minDelay: number, maxDelay: number): ReturnType; export declare interface SliceCapUVOptions { /** Units keeps planar distances; fit maps the combined cap bounds to 0–1. Default units. */ mode?: "units" | "fit"; /** Positive source units per repeat in units mode. Default 1. */ unitsPerRepeat?: number; /** Rotation in the cap plane, in radians. Default 0. */ rotation?: number; offset?: Vector2; } /** * Split a closed, consistently outward-wound triangle mesh in local coordinates. * Positive means plane.normal.dot(point) + plane.constant >= 0. Caps face out of each half. * Supports indexed/nonindexed positions, normals, UVs and material groups. Other attributes, * morph data and partial draw ranges are rejected rather than silently discarded. * * Input and plane are not mutated. Owned output is nonindexed; callers dispose both geometries. * Contours include holes and separate islands, without a repeated closing point. Coplanar exterior * faces stay with the solid behind their outward normal; tangent cuts do not create duplicate caps. * * Uses a relative geometric tolerance, not exact predicates. Touching/branching contours and detected * topology failures throw. Global self-intersections are not checked; successful output is not a * general solid-validity certificate. Keep small features near the origin for Float32 precision. */ export declare function sliceGeometry(source: BufferGeometry, cuttingPlane: Plane, { cap, tolerance, capUV }?: SliceGeometryOptions): SliceGeometryResult; export declare interface SliceGeometryOptions { /** Only new caps; both halves share the same mapping. Existing UVs are interpolated unchanged. */ capUV?: SliceCapUVOptions; /** Seal both cut surfaces. Defaults to true. */ cap?: boolean; /** Relative to the source bounding-box diagonal. Default 1e-7; range (0, 0.001]. */ tolerance?: number; } export declare interface SliceGeometryResult { /** Owned geometry on the positive side; empty when no solid remains there. */ positive: BufferGeometry; /** Owned geometry on the negative side. */ negative: BufferGeometry; /** Cut contours in input coordinates, without repeated endpoints. */ loops: Vector3[][]; /** Number of odd-depth hole contours. */ holes: number; /** New cap area on one half, in source units squared. Zero if caps are disabled. */ capArea: number; /** One greater than the largest input material index (1 when the input is ungrouped). */ capMaterialIndex: number; diagnostics: { /** Source triangles below the area tolerance, including collapsed primitive poles. */ discardedDegenerateTriangles: number; selfIntersectionsChecked: false; }; } /** * Return endpoints and crossed vertices in distance order; to must exceed from. * Closed ranges can cross the seam; each source vertex is included at most once, even over multiple laps. * * ```ts * // One merlon, wherever it happens to land. * const merlon = sweep(section, miterFrames( * slicePath(plan, center - width / 2, center + width / 2).map((p) => ({ position: p, tangent: new Vector3() })), * { reference: new Vector3(0, 1, 0) }, * )); * ``` */ export declare function slicePath(measure: PathMeasure, from: number, to: number): Vector3[]; /** * A rising, wrapping curl — the stylized steam trailing a chimney, or the smoke off a snuffed candle. * * This is the library's first path that LEAVES ITS PLANE. The arch and the scroll are both flat, so * their frames only ever had to survive straight runs and changing curvature. A curl has **torsion**: * it twists out of any plane you could draw through it, which is precisely the case where a Frenet * frame spins the cross-section like a corkscrew for no reason at all. Parallel transport carries the * section along without ever spinning it, and this is the shape where you can see the difference. * * The tangent is analytic, not estimated from the chords. For `r(t)·(cos θ, ·, sin θ)` climbing in Y: * * ```text * dx/dt = r'·cos θ − r·sin θ·θ' * dy/dt = height * dz/dt = r'·sin θ + r·cos θ·θ' * ``` * * Local frame: root at the origin, rising +Y. * * @example * ```ts * const geometry = new SmokeCurlGeometry({ turns: 2, taper: 0.02 }); * ``` */ export declare class SmokeCurlGeometry extends BufferGeometry { readonly height: number; constructor({ swirl, height, turns, radius, taper, segments, sides, }?: SmokeCurlGeometryOptions); } export declare interface SmokeCurlGeometryOptions { /** How far the curl swings out from its axis by the top. Defaults to `0.7`. */ swirl?: number; /** How high it rises. Defaults to `3`. */ height?: number; /** How many times it wraps around as it climbs. Defaults to `1.25`. */ turns?: number; /** Thickness at the root. Defaults to `0.22`. */ radius?: number; /** Thickness at the tip, as a fraction of the root. Drive it toward 0 and the trail dissolves to a point. Defaults to `0.04`. */ taper?: number; /** Stations along the path — the smoothness of the curl. Defaults to `80`. */ segments?: number; /** Sides of the cross-section. `4` gives a hard-edged ribbon, `16` a round wisp. Defaults to `8`. */ sides?: number; } /** Average positions in place within radius; sequential updates make results vertex-order dependent. * Normals and bounds remain stale; neighbor search is O(n²). */ export declare const smoothBrush: (geometry: T, position: Vector3, radius: number, strength: number) => void; /** * Extruded spade prism. */ export declare class SpadeGeometry extends ExtrudeGeometry { constructor({ depth, ...shapeOptions }?: SpadeGeometryOptions); } export declare interface SpadeGeometryOptions extends SpadeShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Spade profile — a heart inverted onto a stem, drawn counter-clockwise from the point. * * The card suit, and the terminal a smith forges onto the end of a strap hinge. Same outline, two * traditions. */ export declare class SpadeShape extends Shape { constructor({ size, width, height, stemWidth, stemDepth, stemConcavity, }?: SpadeShapeOptions); } export declare interface SpadeShapeOptions { /** Overall scale factor. Defaults to `1`. */ size?: number; /** Spade width across the lobes. Defaults to `1.9`. */ width?: number; /** Height of the point above the origin. Defaults to `1`. */ height?: number; /** Stem width. Defaults to `0.6`. */ stemWidth?: number; /** Depth of the stem below the origin. Defaults to `0.75`. */ stemDepth?: number; /** * How far the stem's sides bow INWARD, as a fraction of the way to the centerline. Defaults to `0.18`. * * `0` is a straight trapezoid; higher pinches the waist and flares the foot — the concave sweep of a * printed spade. Same idea as {@link DiamondShapeOptions.concavity}, applied to the stem. */ stemConcavity?: number; } /** * Convert spherical coordinates to Cartesian coordinates. * @param {number} radius - The radius of the sphere. * @param {number} theta - The azimuthal angle in radians (from the x-axis in the x-y plane). * @param {number} phi - The polar angle in radians (from the positive z-axis). * @returns {{x: number, y: number, z: number}} The Cartesian coordinates. */ export declare const sphericalToCartesian: (radius: number, theta: number, phi: number) => { x: number; y: number; z: number; }; /** * Map directions about the origin to spherical UVs around Y; zero-length positions produce undefined coordinates. * * ``` * const sphereVertices = [ * [1, 0, 0], * [0, 1, 0], * [0, 0, 1], * ]; * * const sphereUVs = sphericalUVMapping(sphereVertices); * ``` */ export declare function sphericalUVMapping(vertices: [number, number, number][]): [number, number][]; /** Move positions radially from the target; inward reverses the displacement. Normals and bounds remain stale. */ export declare const spikeBrush: (geometry: T, position: Vector3, radius: number, strength: number, inward?: boolean, falloffFn?: (distance: number, radius: number) => number) => void; export declare interface SpiralClipOptions extends CameraClipTiming { /** Ground point to look down at (typically scene center, `y = 0`). */ target: Vector3; /** @deprecated Use endRadius. Starting radius is always captured from the camera. */ radius?: number; /** Optional wider radius at the end — pulls back as you rise. Defaults to the captured radius. */ endRadius?: number; /** Total vertical rise over the clip. */ height: number; revolutions: number; ease?: EasingFunction; } /** Logarithmic spiral r = r₀·e^(−kθ) in XY. Tangents carry derivative direction, with the positive r factor omitted. */ export declare function spiralPath({ startRadius, turns, tightness, segments, }?: SpiralPathOptions): PathPoint[]; export declare interface SpiralPathOptions { /** Radius at the first point. */ startRadius?: number; /** Number of turns. */ turns?: number; /** Exponential decay coefficient; zero gives a circle. */ tightness?: number; /** Number of spiral intervals. */ segments?: number; } /** * Turret-style spiral staircase — trapezoidal treads between an inner newel radius * and an outer wall radius, ascending counter-clockwise when viewed from above. * * Each step is a four-sided tread (no pinched center point). Step angle is * derived from tread depth at the mid-radius so treads meet without overlapping. */ export declare class SpiralStaircaseGeometry extends BufferGeometry { readonly innerRadius: number; readonly width: number; readonly outerRadius: number; readonly treadDepth: number; readonly riserHeight: number; readonly stepCount: number; readonly startAngle: number; readonly stepAngle: number; readonly totalHeight: number; readonly totalTurn: number; constructor({ innerRadius, width, treadDepth, riserHeight, stepCount, startAngle, stepAngle: stepAngleOption, }?: SpiralStaircaseGeometryOptions); } export declare interface SpiralStaircaseGeometryOptions { /** Newel / center-hole radius (inner edge of every tread). Defaults to `0.45`. */ innerRadius?: number; /** Radial tread width (outer − inner radius). Defaults to `1.95`. */ width?: number; /** Arc run per step at the walking line (mid-radius). Defaults to `0.45`. */ treadDepth?: number; /** Vertical rise per step (riser). Defaults to `0.2`. */ riserHeight?: number; /** Number of steps. Defaults to `20`. */ stepCount?: number; /** Spiral start angle in radians (+X = 0, CCW). Defaults to `0`. */ startAngle?: number; /** Override step angle (radians). When omitted, derived from `treadDepth`. */ stepAngle?: number; } /** * Seed mixer — maps one 32-bit value to another well-distributed value. * Use with {@link deriveSubSeed}, not as a drop-in stream replacement for * {@link mulberry32}. */ export declare function splitmix32(seed: number): number; export declare class SquareHeadstoneGeometry extends BufferGeometry { constructor(width?: number, height?: number, depth?: number); } /** * Stack of books on the floor — each book lays flat on its cover; count sets stack height. * Each layer is laid flat (+90° X), then given a small in-plane spin (world Y) * around the book's geometric center — not the spine corner. * * Local frame: bottom of the stack at Y=0, centered on X/Z. * * @example * ```ts * const stack = stackOfBooks({ * coverMaterial, * pagesMaterial, * count: 8, * yawMax: 0.6, * seed: 1337, * }); * scene.add(stack); * ``` */ export declare function stackOfBooks({ coverMaterial, pagesMaterial, count, scaleXMin, scaleXMax, scaleYMin, scaleYMax, scaleZMin, scaleZMax, yawMax, offsetMax, seed, }: StackOfBooksOptions): InstancedMesh; export declare interface StackOfBooksOptions { coverMaterial: T; pagesMaterial: T; /** Number of books in the stack. Defaults to `6`. */ count?: number; scaleXMin?: number; scaleXMax?: number; scaleYMin?: number; scaleYMax?: number; scaleZMin?: number; scaleZMax?: number; /** * Max in-plane spin (world Y, radians) once the book is laid flat — a lazy * turn on the floor, not a tilt. Defaults to `0.55` (~31°). */ yawMax?: number; /** Max horizontal drift per layer on X/Z. Defaults to `0.06`. */ offsetMax?: number; /** Optional seed for reproducible layout. Omit for unique runtime. */ seed?: number; } /** * Straight run staircase — open risers and treads (no side stringers yet). * * Local frame: centered on width, rises along +Y, runs along +Z. Each step * emits a front riser (+Z) and a top tread (+Y). UVs are normalized per face * (0–1) so materials can tile per step. */ export declare class StaircaseGeometry extends BufferGeometry { readonly width: number; readonly riserHeight: number; readonly treadDepth: number; readonly stepCount: number; readonly topTread: boolean; readonly totalHeight: number; /** Run from the foot to the last surface — one tread shorter when a landing tops the flight. */ readonly totalDepth: number; constructor({ width, riserHeight, treadDepth, stepCount, topTread, }?: StaircaseGeometryOptions); } export declare interface StaircaseGeometryOptions { /** Stair width (tread left–right extent). Defaults to `2`. */ width?: number; /** Vertical rise per step (riser). Defaults to `0.3`. */ riserHeight?: number; /** Horizontal run per step (tread depth). Defaults to `0.5`. */ treadDepth?: number; /** Number of steps — counted as risers, the way a stair is actually measured. Defaults to `10`. */ stepCount?: number; /** * Emit the tread at the very top. Defaults to `true`. * * Set `false` when the flight climbs to a landing or a floor, because that surface *is* the top * tread — the last riser lifts you onto it. Emitting one anyway leaves a tread lying coplanar with * the landing: you would climb the last riser, arrive on a step, and then walk *forward* rather * than up. It also silently deepens the landing by one tread. * * So a flight of 5 steps into a landing is 5 risers and 4 treads; the landing is the fifth. */ topTread?: boolean; } export declare interface StaircaseOptions { /** Number of flights. Landings sit between them, so a run has `flights - 1` landings. Defaults to `2`. */ flights?: number; /** Steps in each flight. Defaults to `5`. */ stepsPerFlight?: number; /** Stair width — the tread's left-right extent. Defaults to `2`. */ width?: number; /** Vertical rise per step. Defaults to `0.3`. */ riserHeight?: number; /** Horizontal run per step. Defaults to `0.5`. */ treadDepth?: number; /** Landing depth along the direction of travel. Defaults to `width` — a square landing. */ landingSize?: number; /** * Degrees the run turns at each landing. Defaults to `90`. * * - `90` / `-90` — a quarter turn. Four of them wrap a stairwell, so the fifth flight climbs * directly above the first. The sign picks which way it winds. * - `0` — a straight run, broken by flat landings. * - `180` / `-180` — a switchback. The next flight reverses, so it must be displaced sideways by a * full stair width or it would climb back through the flight below it. The landing widens to * span both. Two of them stack the run vertically: the third flight sits above the first. The * sign picks which side the run steps to. */ turn?: number; /** * Gap between the two flights of a switchback — the open well down the middle of the stair. * Ignored unless `turn` is ±180. Defaults to `0`, flights shoulder to shoulder. */ well?: number; /** Material. Omit to build a flat-shaded standard material from `color`. */ material?: Material; /** Tint when `material` is omitted. Defaults to `#9a9a9a`. */ color?: ColorRepresentation; } export declare interface StarBurstShapeOptions extends BurstGeometryOptions { /** Number of burst points. Defaults to `4` — a diffraction-spike star. */ points?: number; /** Extrusion depth (`orientation: "radial"` only — screen-aligned stars are flat). Defaults to `0.05`. */ depth?: number; } /** * Procedural star field distributed on a spherical shell — intended as an infinite sky dome. * * The shell pins itself to the active camera every frame (see {@link lockToViewer}), so * `scene.add(stars)` is the whole contract — the field is unreachable no matter how far the * viewer travels, and there is no per-frame placement call. {@link update} remains necessary * only for `twinkle`. * * **Orientation** decides how stars are drawn *and* how they are sized — the two travel together, * because screen-aligned stars are naturally measured in screen space and real geometry in world * space: * * - `points` — screen-aligned via `PointsNodeMaterial`, with per-star position, size, and rotation * supplied as instanced attributes. The field stays visually fixed as the camera orbits. Flat by * construction: only the geometry's XY profile is used. Sized by `pixelSizeMin` / `pixelSizeMax` * in logical pixels, with **no distance term at all**. Requires `WebGPURenderer`. * - `radial` — instanced 3D meshes rotated to face the shell center, drawn `DoubleSide` so stars * stay visible from inside the shell. Sized by `sizeMin` / `sizeMax` as angular extents (radians * at unit distance), scaled by each star's distance from the origin so stars look similar * regardless of shell depth. That conversion assumes the viewer sits at the shell's center, which * {@link lockToViewer} guarantees. Uses only standard materials, so it runs on either renderer. * * Both render as a single instanced draw call, so the geometry you pass is a matter of looks rather * than cost. * * @example * ```typescript * const stars = new StarField({ * count: 2500, * radius: 480, * rotationJitter: 0, // every burst locked vertical on screen * twinkle: true, * }); * * scene.add(stars); // pins itself to the viewer — no placement call needed * * function animate() { * stars.update(); // only for twinkle; a no-op when twinkle is false * renderer.render(scene, camera); * } * ``` * * Call {@link dispose} when removing the effect to free geometry and materials. */ export declare class StarField extends Object3D { private readonly source; private readonly colorSource; readonly orientation: StarFieldOrientation; private readonly field; private readonly twinkle; private readonly baseScales?; private readonly twinklePhases?; /** Billboard scale attribute, rewritten each frame while twinkling. */ private scaleAttribute?; private readonly dummy; constructor(options?: StarFieldOptions); get mesh(): InstancedMesh; get geometry(): BufferGeometry; get material(): Material | Material[]; /** Release GPU resources held by the field. */ dispose(): void; /** * Animate twinkling. No-op when `twinkle` is `false`. * * Each star pulses on its own phase offset so the field twinkles out of sync. Billboards * rewrite the instanced scale attribute; radial stars rebuild each instance matrix. * Pass elapsed time in seconds (defaults to `performance.now()`). */ update(elapsed?: number): void; /** * EXPERIMENTAL — screen-aligned stars sized in **screen pixels** instead of world units. * * `PointsNodeMaterial` extends `SpriteNodeMaterial`, aligning the geometry's XY to the view plane * the same way, but scaling that offset by a pixel size and dividing by the viewport. A star is * therefore N pixels wherever it sits in the shell — no angular-to-world conversion, and no * dependence on where the viewer is. * * The geometry is normalized so its XY profile radius is `1`, which makes `pixelSize` mean an * honest pixel radius rather than a multiple of whatever the burst happened to measure. * * Note the dispatch in `PointsNodeMaterial.setupVertex`: the pixel path runs for objects that are * **not** `isPoints`, so an `InstancedMesh` is precisely what selects it. */ private createPointsField; /** Full 3D stars rotated to face the shell center. */ private createRadialField; } export declare interface StarFieldOptions { /** Optional seed for placement, twinkle phases, and independent color sampling. */ seed?: number; /** Per-star working-space color overriding color. Index follows star creation order. */ colors?: ColorSampler; /** * How each star faces the viewer. This also decides which size options apply. * * - `points` (default) — screen-aligned, so the field holds its orientation as the camera * orbits. Only the geometry's **XY profile** is drawn; any Z extent is ignored. Sized with * `pixelSizeMin` / `pixelSizeMax`. **Requires `WebGPURenderer`** (node material). * - `radial` — full 3D geometry rotated to face the shell center. Depth is real here, and stars * shear as the camera moves, the way any world-space mesh does. Sized with `sizeMin` / * `sizeMax` as angular extents. Uses only standard materials, so it runs on either renderer. */ orientation?: StarFieldOrientation; /** Star shape used to build the default {@link BurstGeometry}. */ burst?: StarBurstShapeOptions; /** Replace the star geometry entirely. Billboards use its XY profile; radial uses all of it. */ geometry?: BufferGeometry; /** * Override the default field material. In `points` mode this must be a `PointsNodeMaterial` — * per-star position, size, and rotation are assigned onto it as node inputs. */ material?: Material; /** Number of stars. Defaults to `1500`. */ count?: number; /** Shell radius when `minRadius` / `maxRadius` are omitted. Defaults to `500`. */ radius?: number; /** Inner shell radius. Defaults to `radius`. */ minRadius?: number; /** Outer shell radius. Defaults to `radius`. */ maxRadius?: number; /** * Minimum angular size (radians at 1 unit distance). Scaled by each star's shell distance * so apparent size stays consistent. Defaults to `0.008`. */ sizeMin?: number; /** Maximum angular size. Defaults to `0.025`. */ sizeMax?: number; /** * Star radius in logical (CSS) pixels — **`points` only**. Defaults to `4` / `14`. * * Screen-aligned stars are naturally sized in screen space, so there is no distance term at all: * a star is the same size wherever it sits in the shell, and nothing depends on the viewer being * at the shell's center. The trade against angular sizing is that pixels are absolute, so stars * occupy a smaller fraction of a larger display. */ pixelSizeMin?: number; /** Star radius in logical pixels, maximum — **`points` only**. Defaults to `14`. */ pixelSizeMax?: number; /** Single color or palette; multiple entries pick a random color per star. */ color?: ColorRepresentation | ColorRepresentation[]; /** * Whether `scene.fog` tints the stars. Defaults to `false` — the shell sits far enough out * that any usable fog density saturates and flattens the whole field to fog color. * * Ignored when you supply your own `material`; set the flag on that material instead. */ fog?: boolean; /** Enable pulsing brightness; call {@link StarField.update} each frame when `true`. */ twinkle?: boolean; /** * Base star rotation, in radians. Defaults to `0`. * * Measured in screen space for `points` and world space for `radial` — the same knob means * different things, because a screen-aligned star re-aligns every frame and a radial star * does not. */ rotation?: number; /** * Random rotation spread added per star, in radians. Defaults to `Math.PI * 2`. * * `0` aligns every star — with `points` that yields a coherent diffraction-spike field that * stays locked as the camera orbits. `2π` is fully random. */ rotationJitter?: number; } /** How each star is turned to face the viewer. */ export declare type StarFieldOrientation = "points" | "radial"; /** * Extruded star prism. */ export declare class StarGeometry extends ExtrudeGeometry { constructor({ depth, ...shapeOptions }?: StarGeometryOptions); } export declare interface StarGeometryOptions extends StarShapeOptions { /** Extrusion depth. Defaults to `0.25`. */ depth?: number; } /** * Star profile — radial points joined by straight edges. * * Rests with a point up. */ export declare class StarShape extends Shape { constructor({ points, innerRadius, outerRadius, rotation }?: StarShapeOptions); } export declare interface StarShapeOptions { /** Number of star points. Defaults to `5`. */ points?: number; /** Inner vertex radius. Defaults to `0.5`. */ innerRadius?: number; /** Outer vertex radius. Defaults to `1`. */ outerRadius?: number; /** Rotation in radians from the resting state. Defaults to `0`. */ rotation?: number; } /** * Profile placement basis: position + normal * px + binormal * py, scaled by scale. * Miter stations can carry nonunit normal/binormal vectors. */ export declare interface Station { position: Vector3; tangent: Vector3; normal: Vector3; binormal: Vector3; scale?: number; } /** * Stone fence post — wide base, column, and cap. * * The stepped profile is the whole point, and the whole difficulty: a fence run meeting this post * must clear the widest step at bar height while its rails reach the narrower column. Use * {@link widthAt} and {@link maxWidthBetween} to size the run rather than hardcoding the steps. * * Local frame: base on Y=0. * * @example * ```ts * const geometry = new StoneFencePostGeometry({ height: 2.6 }); * const post = new Mesh(geometry, stoneMaterial); * scene.add(post); * ``` */ export declare class StoneFencePostGeometry extends BufferGeometry { /** Column height, excluding base and cap. */ readonly height: number; readonly columnWidth: number; readonly baseWidth: number; readonly baseHeight: number; readonly capWidth: number; readonly capHeight: number; /** Overall height, base and cap included. */ get totalHeight(): number; constructor({ height, columnWidth, baseWidth, baseHeight, capWidth, capHeight, }?: StoneFencePostGeometryOptions); /** * Post width at height `y` — what a fence run asks to size itself against. * * Zero above and below the post. The profile steps between base, column, and cap. * * This is the face-to-face width, which is what a run meeting the post square-on needs. A run * approaching a corner diagonally would face the wider diagonal instead — worth revisiting when * fences follow arbitrary paths. */ widthAt(y: number): number; /** * Widest the post gets between two heights — what bars must clear to avoid burying themselves * in the stonework. */ maxWidthBetween(y0: number, y1: number): number; } export declare interface StoneFencePostGeometryOptions { /** Main column height, excluding base and cap. Defaults to `2.25`. */ height?: number; /** Column width and depth. Defaults to `1`. */ columnWidth?: number; /** Base width and depth. Defaults to `1.2`. */ baseWidth?: number; /** Base height. Defaults to `0.5`. */ baseHeight?: number; /** Cap width and depth. Defaults to `1.4`. */ capWidth?: number; /** Cap height. Defaults to `0.3`. */ capHeight?: number; } /** * A coursed stone wall — **ASHLAR**: squared, dressed stone laid in level courses. Centered on X, foot on * `y = 0`, faces on ±Z. * * The wall is built stone by stone rather than as a slab with lines drawn on it, and three rules make it * read as masonry: * * - **A RUNNING BOND.** Alternate courses start part-way along a stone, so no vertical joint (a PERPEND) * runs through. `bondOffset: 0` gives a stack bond, which is not a bond at all. * - **No course ever gives up.** A stone that would strand an uncuttable remainder takes the remainder * instead, so every course reaches the edge and no sliver appears. The same rule {@link layPlankFloor} * lays floors by — it belongs to LAYING, not to floors. * - **The joint comes OUT of the stone.** Stone is cut to suit a course, so widening the mortar does not * move the coursing. * * **Three axes of variance, and they are not interchangeable.** `lengthVariance` and `depthVariance` are * per STONE; `courseVariance` is per COURSE, because a course that is not level is not a course. Their * ceilings are set low deliberately: variance compounds and ceilings do not, so a wall with this many * controls needs each one reined in. * * `settle` and `tilt` are **displacement**, not size — where a stone ended up rather than how big it is — * and are a stylized, decrepit read rather than masonry truth. Both default to nothing. * * **One geometry, one material, one draw call** at any size. Every stone differs, so they merge; the tint * rides a vertex attribute rather than a material group, which is what keeps it to a single call. * * @example * ```ts * const wall = new StoneWall({ width: 6, height: 4, seed: 12 }); * scene.add(wall); * wall.stoneCount; // stones laid * wall.closerCount; // how many were cut short to finish a course * ``` */ export declare class StoneWall extends Group { #private; readonly mesh: Mesh; /** Stones laid. */ readonly stoneCount: number; /** Courses laid — fitted to `height`, so not necessarily `height / courseHeight`. */ readonly courseCount: number; /** The course height actually used. */ readonly courseHeight: number; /** Stones cut short to finish a course. */ readonly closerCount: number; /** Stones that came out standing proud. */ readonly proudCount: number; constructor({ width, height, thickness, courseHeight, stoneAspect, joint, bondOffset, shortestStone, courseVariance, lengthVariance, mortar, mortarRecess, mortarColor, settle, tilt, depthVariance, proudChance, proudDepth, color, colors, colorVariance, seed, material, }?: StoneWallOptions); /** Releases the merged geometry, and the material when this wall made it. */ dispose(): void; } export declare interface StoneWallOptions { /** Extent along X. Defaults to `3.2`. */ width?: number; /** Extent along Y, from the ground up. Defaults to `3`. */ height?: number; /** Extent along Z. Defaults to `0.34`. */ thickness?: number; /** * Target course height. Defaults to `0.26`. * * Courses are fitted to `height`, so this is a target and never leaves a sliver at the top. The number * actually laid is reported as {@link StoneWall.courseHeight}. */ courseHeight?: number; /** A whole stone's length, as a multiple of the course. Defaults to `2.2`. */ stoneAspect?: number; /** * The mortar line. Defaults to `0.012`. * * **Taken OUT of the stone**, so the coursing keeps its pitch as the joint widens. That is the mason's * convention — stone is cut to suit a course. Brick does the opposite, adding the joint to the pitch, * because a brick arrives at a fixed size. */ joint?: number; /** * How far alternate courses start along a stone. Defaults to `0.5` — a RUNNING BOND, so no vertical * joint runs through. `0` is a STACK BOND: real, but nothing is bonded to anything. */ bondOffset?: number; /** * The shortest stone worth cutting, as a fraction of a whole one. Defaults to `0.45`. * * **The only reason a course ends on anything but a whole stone.** Before laying, a stone that would * strand an uncuttable remainder takes the remainder instead — so every course reaches the edge and no * sliver is ever left. Below about `1 − lengthVariance` this governs only the closers; above it, it * starts clipping every stone's low-side variance. */ shortestStone?: number; /** * How much course heights differ from one another. Defaults to `0`. * * Per COURSE, never within one — a course that is not level is not a course. `0` is ASHLAR; above it is * RANDOM COURSED. The courses are jittered and then normalized, so they still sum to `height` exactly. */ courseVariance?: number; /** How much stone lengths differ, as a fraction of a whole stone. Defaults to `0.22`. */ lengthVariance?: number; /** * Bed the stones in a mortar core. Defaults to `true`. * * Without it the joints are holes — at a hairline they read as shadow, but open the joint and you see * daylight through the wall. `false` is a DRY STONE wall, which is a real thing and wants tight joints. */ mortar?: boolean; /** * How far the core sits BEHIND the stonework, on EVERY axis. Defaults to `0.014`. * * Recessed rather than flush: a joint filled level with the face has no shadow and reads as a painted * line. Raked back, it reads as a joint. It insets from the wall's ends and head as well as its faces, * because the stones themselves stop `joint / 2` short of the nominal extent — a core built to full size * would stand proud of the stonework there and ring the wall with a pale edge. */ mortarRecess?: number; /** Mortar tint. Defaults to `#b8b2a6`. */ mortarColor?: string; /** * How far each stone strays from its bed, in world units. Defaults to `0`. * * Displacement, not size. Together with {@link StoneWallOptions.tilt} this takes a wall from newly built * to long-standing — a stylized read rather than masonry truth, which is why both default to nothing. */ settle?: number; /** * Max roll per stone, radians, about its own center. Defaults to `0`. * * Past about `0.038` a stone's corner reaches through the mortar recess, which is the decrepit look and * is allowed. Note only the Z component stays in the wall's plane; X and Y tip the stone out of it and * are what drive it into the core. */ tilt?: number; /** How far each stone sits in or out of the face, in world units. Defaults to `0.006`. */ depthVariance?: number; /** Chance a stone stands notably PROUD. Defaults to `0.12`. */ proudChance?: number; /** How far a proud stone stands out. Defaults to `0.03`. */ proudDepth?: number; /** Base stone tint. Defaults to `#6a6560`. */ color?: string; /** Per-stone tint spread in HSL. Defaults to `0.07` — mostly lightness, barely any hue. */ colorVariance?: number; /** Per-stone sampler; overrides color/colorVariance. Index counts laid stones, excluding mortar; seeded color draws do not alter geometry. */ colors?: ColorSampler; /** Defaults to `0x2c1a`. */ seed?: number; /** A material to use instead of the default. **Must set `vertexColors: true`**, or every stone goes white. */ material?: Material; } /** * A wrought strap hinge — wide at the pin, drawn out to a point, with its sides bowing inward. * * Drawn counter-clockwise from the pin edge, which is the straight one: the strap hangs off the door's * hinge side and reaches across its face. * * Local frame: the pin edge on X=0, centered on Y, reaching +X. */ export declare class StrapHingeShape extends Shape { constructor({ length, width, sweep }?: StrapHingeShapeOptions); } export declare interface StrapHingeShapeOptions { /** How far the strap reaches across the door, from the pin to the tip. Defaults to `0.85`. */ length?: number; /** Width at the pin, where the strap is widest. Defaults to `0.22`. */ width?: number; /** * How far the strap's edges bow INWARD, as a fraction of the half-width. Defaults to `0.28`. * * At `0.5` the sides are straight and you get a dull triangle. Below that they curve in, and it * starts to look forged — a smith DRAWS the metal out, and the taper of drawn metal is a curve, not * a chamfer. This one number is most of the strap's character. */ sweep?: number; } /** * Convert an open rectangular grid to an owned indexed sheet, grid[v][u]. No seam welding, * periodic wrapping, or collapsed rows are inferred. Degenerate faces are rejected by thickenSurface. */ export declare function surfaceFromGrid(grid: readonly (readonly Vector3[])[], { flip }?: { flip?: boolean; }): IndexedSurface; /** * Skin rectangular grid[v][u] without wrapping, using whole-sheet UVs. * Fewer than two rows or columns returns empty geometry; otherwise ragged rows throw. * * ```ts * // Any f(u, v). Here, a hanging sheet. * const grid = Array.from({ length: rows + 1 }, (_, j) => * Array.from({ length: columns + 1 }, (_, i) => surfacePoint(i / columns, j / rows)), * ); * const geometry = surfaceGrid(grid); * ``` */ export declare function surfaceGrid(grid: Vector3[][], { flip }?: SurfaceGridOptions): BufferGeometry; export declare interface SurfaceGridOptions { /** Reverse winding and normals; u along +X and v along +Y ordinarily faces +Z. */ flip?: boolean; } /** * Closed CCW profile with one flat back from (0, 0) to (height, 0); projection is the outward y extent. * * ``` * CORNER (moldingProfile) SURFACE (this) * ceiling ╭──╮ * ────┬────────► projection ────┴──┴────► projection * │╲ ▲ * wall │ ╲___ │ one back, flat on the wall * ▼ │ * drop height * ``` * * ```ts * // A chair rail: an astragal, run along a wall at chair height. * const rail = new MoldingGeometry({ * points: wallLine(0.9), * profile: surfaceProfile({ style: "astragal", height: 0.07, projection: 0.028 }), * run: "base", * facing: "outward", * }); * ``` */ export declare function surfaceProfile({ style, height, projection, segments, reeds, }?: SurfaceProfileOptions): Vec2[]; export declare interface SurfaceProfileOptions { /** Exposed contour projecting from the flat back. */ style?: SurfaceStyle; /** Extent along the supporting surface. */ height?: number; /** Projection from the supporting surface. */ projection?: number; /** Curve subdivision count. */ segments?: number; /** Bead count for reed; ignored by other styles. */ reeds?: number; } /** A rectangle on the surface, from its lower-left corner. Used to keep stones off things. */ export declare interface SurfaceRect { x: number; y: number; width: number; height: number; } /** * Single-surface profiles: fillet band, bead half-round, astragal stepped bead, reed repeated beads, * ovolo quarter, ogee S-curve, and lip overhang with an undercut throat. */ export declare type SurfaceStyle = "fillet" | "bead" | "astragal" | "reed" | "ovolo" | "ogee" | "lip"; /** * A swag — cloth hung in a curve between two pins, cinched to a knot at each end. * * One continuous surface over `(u, v)`: `u` across the span, `v` down the fold tiers. Three terms — * the macro sag hanging each tier, the micro fold rippling down them, and the cinch `E(u)` collapsing * both to zero at the horns. * * ``` * x(u,v) = u · (span/2 − taper·(1 − v)) * y(u,v) = −(topSag + (sag − topSag)·v^sagPower) · E(u) * z(u,v) = (bulge·v + foldDepth·v·sin(2π·folds·v)) · E(u) * ``` * * A vertical cut through the middle is a stack of waves — the S you see edge-on in any velvet valance, * and the profile this surface is a loft of. It is a LOFT rather than a sweep precisely because that * profile's amplitude changes across the span; carried unchanged it would be a sweep, and it would stop * looking like cloth. * * **Origin is the pin line**, at `y = 0`, with the cloth hanging to negative Y — the same convention as * {@link CascadeGeometry}. * * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`. * * @example * ```ts * const swag = new Mesh( * new SwagGeometry({ span: 2, sag: 0.85, folds: 3.5 }), * new MeshStandardMaterial({ color: 0x1f5b45, roughness: 0.95, side: DoubleSide, flatShading: true }), * ); * ``` */ export declare class SwagGeometry extends BufferGeometry { constructor({ span, sag, topSag, sagPower, folds, foldDepth, bulge, taper, sagCurve, widthSegments, heightSegments, }?: SwagGeometryOptions); } export declare interface SwagGeometryOptions { /** Distance between the two pins. Defaults to `2`. */ span?: number; /** How far the LOWEST tier falls below the pins. Defaults to `0.85`. */ sag?: number; /** * How far the HIGHEST tier falls below the pins. Defaults to `0` — flat against the board. * * Zero is the usual answer, because the top of a swag is stapled to a straight piece of timber. Lift * it and the first visible fold hangs on its own, which is what a swag mounted on a pole or a rod does * rather than on a board. The pins stay at `y = 0` either way: the cinch takes every tier to zero at * `u = ±1`, so this deepens the middle of the top tier without moving where it is fixed. * * Clamped to {@link sag}, since a top fold hanging below the bottom one is not a swag. */ topSag?: number; /** * How the tiers distribute down the sag. Defaults to `1.2`. * * Above 1 they bunch toward the hem instead of stacking evenly, which is what stops a swag reading as * a set of concentric arcs at equal spacing. Cloth does not distribute itself linearly. */ sagPower?: number; /** Fold cycles down the tier stack. Defaults to `3.5`. Fractional values are legitimate. */ folds?: number; /** Depth of the fold ripple. Defaults to `0.12`. */ foldDepth?: number; /** * How far the lower tiers push forward. Defaults to `0.1`. * * Cloth has mass and the deeper folds hang out over the ones above them, which is what turns a flat * scallop into the nested crescent a real swag makes. */ bulge?: number; /** How much narrower the upper tiers are. Defaults to `0.16`, because a higher fold spans less. */ taper?: number; /** The tier envelope. Defaults to `"catenary"`. See {@link SwagSagCurve}. */ sagCurve?: SwagSagCurve; /** Samples across the span. Defaults to `90`. Tessellation only — it never moves the silhouette. */ widthSegments?: number; /** Samples down the tiers. Defaults to `110`. Carries the fold ripple, so it wants to be generous. */ heightSegments?: number; } /** * The shape each tier hangs in. * * - `catenary` — `a·cosh(x/a)`, what a uniform hanging chain actually does. * - `parabola` — `1 − u²`, the approximation procedural code reaches for. At the sags a swag tier uses * it is genuinely close; the two differ by about 5% of the sag at a deep setting. */ export declare type SwagSagCurve = "catenary" | "parabola"; /** * Sweep a closed CCW profile in each station’s (normal, binormal) basis. * Use at least two stations; tight curvature and collapsed scales can create invalid geometry. * * ```ts * // A wrought iron tube arching over a gate — swap the profile for a rectangle and it is masonry. * const path = joinPaths( * linePath(new Vector3(-2, 0, 0), new Vector3(-2, 2, 0), 2), * transformPath(arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 }), translate), * linePath(new Vector3(2, 2, 0), new Vector3(2, 0, 0), 2), * ); * * const geometry = sweep(circleProfile(0.08, 8), transportFrames(path)); * ``` */ export declare function sweep(profile: Vec2[], stations: Station[], { scale, cap, closed }?: SweepOptions): BufferGeometry; export declare interface SweepOptions { /** Section scale at t ∈ [0, 1] by station index; a station scale takes precedence, including zero. */ scale?: (t: number) => number; /** Triangulate open-end profiles; a failed ear-clipping result falls back to a fan. */ cap?: boolean; /** * Stitch the last ring to the first and omit caps; the start station must not be repeated. * Spatial closed loops can retain parallel-transport twist (holonomy); no seam correction is applied. */ closed?: boolean; } /** * Rounded terrain cap — a circular disc bulged into a gentle dome, then broken up * with coherent fbm noise so it reads as rolling terrain rather than a smooth lens. * The relief tapers to a clean circular rim seated on the Y=0 plane. * * Displacement is baked into real vertices (no shader), so shadows, raycasts, and * any physics collider derived from the mesh match exactly what's drawn — and it * renders identically on WebGL and WebGPU/TSL. Pair with a `flatShading` material * for a faceted low-poly look; the coherent noise keeps neighboring vertices moving * together, so faces never tear. * * Local frame: base on Y=0, peak toward +Y, centered on the origin. */ export declare class TerrainMoundGeometry extends BufferGeometry { readonly radius: number; readonly height: number; constructor({ radius, height, radialSegments, angularSegments, noiseHeight, noiseScale, octaves, persistence, rim, seed, }?: TerrainMoundGeometryOptions); } export declare interface TerrainMoundGeometryOptions { /** Footprint radius (world units). Defaults to `8`. */ radius?: number; /** Dome peak height at the center. Defaults to `1.2`. */ height?: number; /** Concentric rings from center to rim. Defaults to `40`. */ radialSegments?: number; /** Segments around the circumference. Defaults to `64`. */ angularSegments?: number; /** Amplitude of the terrain relief added on top of the dome. Defaults to `0.5`. */ noiseHeight?: number; /** Noise frequency — higher packs more, smaller bumps into the footprint. Defaults to `0.35`. */ noiseScale?: number; /** fbm octaves (detail layers). Defaults to `4`. */ octaves?: number; /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */ persistence?: number; /** Normalized radius (0–1) where the rim begins fading relief to a flat edge. Defaults to `0.82`. */ rim?: number; /** Seed for reproducible terrain. Defaults to `1`. */ seed?: number; } /** * Rectangular terrain patch — a flat grid displaced on Y by the shared coherent fbm * sampler ({@link fbm2}). The rectangular counterpart to {@link TerrainMoundGeometry}: * same noise strategy, grid layout instead of a radial disc. * * A pure heightfield (Y is single-valued per XZ) so faces can never fold, and it's * baked into real vertices — shadows, raycasts, and physics colliders match what's * drawn, on WebGL and WebGPU/TSL alike. Pair with a `flatShading` material for a * faceted low-poly look. Leave `edgeFalloff` at `0` for a tileable field; raise it to * seat the edges flat at Y=0. * * Local frame: base grid on Y=0, relief toward ±Y, centered on the origin. */ export declare class TerrainPlaneGeometry extends BufferGeometry { readonly width: number; readonly depth: number; constructor({ width, depth, widthSegments, depthSegments, noiseHeight, noiseScale, octaves, persistence, edgeFalloff, seed, }?: TerrainPlaneGeometryOptions); } export declare interface TerrainPlaneGeometryOptions { /** Extent along X (world units). Defaults to `16`. */ width?: number; /** Extent along Z (world units). Defaults to `16`. */ depth?: number; /** Grid segments along X. Defaults to `48`. */ widthSegments?: number; /** Grid segments along Z. Defaults to `48`. */ depthSegments?: number; /** Amplitude of the terrain relief (world units, ±). Defaults to `0.8`. */ noiseHeight?: number; /** Noise frequency — higher packs more, smaller features into the footprint. Defaults to `0.35`. */ noiseScale?: number; /** fbm octaves (detail layers). Defaults to `4`. */ octaves?: number; /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */ persistence?: number; /** * Border band (0–1 fraction of the half-extent) over which relief fades to a flat * edge at Y=0. `0` leaves a raw, seamless heightfield (tileable); higher values * seat the slab like a contained diorama patch. Defaults to `0`. */ edgeFalloff?: number; /** Seed for reproducible terrain. Defaults to `1`. */ seed?: number; } /** * Group indices * 0: Base * 1: Coil */ export declare class TeslaCoilGeometry extends BufferGeometry { constructor(); } /** * Test tube — a cylinder closed by a hemisphere as ONE curve, walled to a real glass thickness. * * A lathe of {@link vesselShell} over {@link testTubeProfile}. The outer silhouette is exposed as * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a * measurement. Building the silhouette as a single curve rather than a merged cylinder + hemisphere avoids * the shading crease at the join. Local frame: rounded bottom on Y=0, rim up +Y. */ export declare class TestTubeGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: TestTubeGeometryOptions); } export declare interface TestTubeGeometryOptions extends TestTubeProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `32`. */ radialSegments?: number; } /** * Test tube silhouette — a cylinder closed by a hemisphere, as ONE curve. * * A single profile rather than a cylinder merged with half a sphere: a merge leaves two rings of vertices * at the join with different normals, so the seam shades as a crease on a tube meant to read as * continuous. The hemisphere's centre sits one radius up, so the tube rests on Y=0; ends at the rim. */ export declare function testTubeProfile({ radius, height, profileSegments }?: TestTubeProfileOptions): Vector2[]; export declare interface TestTubeProfileOptions { /** Tube radius. Defaults to `0.2`. */ radius?: number; /** Overall height, rounded bottom to rim. Defaults to `3`. */ height?: number; /** Arc stations over the rounded bottom. Defaults to `16`. */ profileSegments?: number; } /** * A rack of test tubes — a row or grid, each seated by its rounded bottom on the base and held through the * top plate. Nothing floats. * * A spatial factory: it sizes a two-plate frame (base + top plate on corner posts) to whatever tube it is * given, lays the tubes out on a pitch, and rests the whole `Group` on Y=0. Everything is proportional to * the tube radius, so one set of numbers holds across tube sizes. * * The frame is ONE merged opaque mesh; the tubes are SEPARATE glass meshes sharing a single geometry — * glass sorts per object, so it cannot be baked into the opaque frame. */ export declare class TestTubeRack extends Group { constructor({ columns, rows, tube, fill, gap, rise, glassMaterial, rackMaterial }?: TestTubeRackOptions); } export declare interface TestTubeRackOptions { /** Tubes per row. Defaults to `6`. */ columns?: number; /** Rows of tubes. Defaults to `1`. */ rows?: number; /** Tube geometry — its radius and height size the whole rack. */ tube?: TestTubeGeometryOptions; /** Optional liquid in every tube — colour, opacity, glow, fill level. */ fill?: FillOptions; /** Gap between neighbouring tubes, added to the diameter for the pitch. Defaults to `0.9 ×` the tube radius. */ gap?: number; /** * Height of the top plate — how high up the tube the rack holds it — as a fraction of the tube height. * Defaults to `0.55`. Clamped to `[0.1, 0.9]` so the tube always seats on the base and protrudes above. */ rise?: number; /** Glass material for the tubes. A translucent default is supplied. */ glassMaterial?: MeshStandardMaterial; /** Frame material for the rack. A wood default is supplied. */ rackMaterial?: MeshStandardMaterial; } /** * Calculate the thetaLength to achieve a specific hole radius in a sphere. * thetaLength = asin(w / (2 * R)) * * Returns the thetaLength in radians. * * Example usage: * ``` * const sphereRadius = 5; // Radius of the sphere * const holeRadius = 1; // Desired radius of the hole at the top * const thetaLength = thetaLengthForRadius(sphereRadius, holeRadius); * ``` */ export declare const thetaLengthForRadius: (sphereRadius: number, holeRadius: number) => number; /** * Add thickness to an explicitly connected, consistently oriented, manifold open sheet. * Disconnected open components and holes are supported. Closed components, bow-tie vertices, * unused points, degenerate/duplicate faces and inconsistent winding are rejected. * * Connectivity comes from point indices, never position welding. Offsets are computed on that * topology, then rendering corners are duplicated for flat normals and UV seams. Front/back UVs * retain the source map; rim UVs run 0–1 around each boundary loop and from back (0) to front (1). * * Normal and crease offsets may self-intersect or consume narrow features. Output diagnostics * detect local inversions and degeneracy after Float32 conversion, not global intersections. * Keep coordinates near the origin when small features would otherwise lose Float32 precision. * * @example * const { geometry, diagnostics } = thickenSurface(surfaceFromGrid(grid), { * thickness: 0.08, placement: "centered", offset: "normal", * }); * const mesh = new Mesh(geometry, [frontMaterial, backMaterial, rimMaterial]); */ export declare function thickenSurface(surface: IndexedSurface, { thickness, placement, offset, onInvalid, rimUV }: ThickenSurfaceOptions): ThickenSurfaceResult; export declare interface ThickenSurfaceOptions { /** Mapping of new rim walls only. Front/back retain caller UVs. */ rimUV?: RimUVOptions; /** Positive distance in the input coordinate system. */ thickness: number; /** Defaults to centered. Front follows input winding; back faces the opposite way. */ placement?: "front" | "centered" | "back"; /** * Normal uses angle-weighted unit vertex normals. Crease compensation fits incident face-plane * distances along that normal, and is approximate on general meshes. A vector is normalized and * used as a fixed extrusion direction; it must point into every source face's front hemisphere. */ offset?: "normal" | "crease-compensated" | Vector3; /** * Throw on detected output inversions, collapsed triangles or nonpositive component volumes * (default). Report returns that geometry for inspection. Invalid input always throws. * Neither mode detects global self-intersections. */ onInvalid?: "throw" | "report"; } export declare interface ThickenSurfaceResult { /** Owned nonindexed geometry. Groups 0, 1, 2 are front, back, and rim; caller disposes it. */ geometry: BufferGeometry; diagnostics: ThicknessDiagnostics; } export declare interface ThicknessDiagnostics { /** Source boundary edges, including hole boundaries. */ boundaryEdges: number; /** Front/back output triangles facing against their corresponding source orientation. */ invertedFaces: number; /** Collapsed or numerically degenerate output triangles, including walls. */ degenerateFaces: number; /** Closed output components with zero or negative signed volume. */ nonPositiveVolumeComponents: number; /** Sum of component signed volumes, in input units cubed. */ signedVolume: number; /** Maximum corresponding-vertex displacement error projected on an incident source normal. */ maxFaceThicknessError: number; /** Explicitly not a solid-validity certificate. */ selfIntersectionsChecked: false; } /** * Copy arrays into an indexed BufferGeometry with position, normal and UV attributes and a bounding sphere. * * ```ts * const buffers = createGeometryBuffers(); * pushQuad(buffers, corners, [0, 1, 0]); * const geometry = toBufferGeometry(buffers); * ``` */ export declare function toBufferGeometry(buffers: GeometryBuffers): BufferGeometry; /** * Append an arch to an existing Path whose current point is at from. Jambs remain caller-owned. * * ```ts * // A door's silhouette: up the right side, over the top, down the left. * const shape = new Shape(); * shape.moveTo(-hw, 0); * shape.lineTo(hw, 0); * shape.lineTo(hw, height); * traceArch(shape, { style: "semicircle", y: height, halfSpan: hw, from: "right", to: "left" }); * shape.closePath(); * ``` * * ```ts * // Half an arch — one leaf of a double door, split at the crown. * traceArch(shape, { style: "ogee", y: h, halfSpan: hw, rise, from: "crown", to: "left" }); * ``` */ export declare function traceArch(path: Path, options: ArchProfileOptions): void; /** Return transformed position/tangent copies. Tangents use transformDirection; scale metadata is unchanged. */ export declare function transformPath(path: PathPoint[], matrix: Matrix4): PathPoint[]; /** * Parallel-transport frames from path tangents, removing adjacent coincident positions. * Provide a nonempty path with nonzero tangents and a reference that yields a nonzero perpendicular seed. * * ```ts * const stations = transportFrames(arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 })); * const geometry = sweep(circleProfile(0.08, 8), stations); * ``` */ export declare function transportFrames(path: PathPoint[], reference?: Vector3): Station[]; export declare interface TriangulatedRegion { /** Owned points in the same coordinate system as the input. */ points: Vector2[]; /** Counter-clockwise triangles referencing points. */ triangles: [number, number, number][]; /** Counter-clockwise outer boundary, without a repeated closing index. */ outline: number[]; /** Clockwise hole boundaries, in input hole order, without repeated closing indices. */ holes: number[][]; } /** * Triangulate a planar region with holes, refine its triangles and retain corresponding boundary * loops. Map these points onto a curved surface, or use the loops to attach thickness or frames. * Input loops are cloned. Either winding and an optional repeated closing point are accepted. * * Loops must be simple, nondegenerate and disjoint, with holes strictly inside the outline and no * nested holes. Redundant collinear corners are rejected. This is region meshing, not a boolean * operation or a general constrained-Delaunay solver; the bounded improvement pass is heuristic. * Coordinates are normalized internally so geometric tolerances are independent of model scale. */ export declare function triangulateRegion(contour: readonly Vector2[], cutouts?: readonly (readonly Vector2[])[], { subdivisions, improveTriangles }?: TriangulateRegionOptions): TriangulatedRegion; export declare interface TriangulateRegionOptions { /** Uniform midpoint subdivisions, 0–5. Triangle count grows by four per level. Default 0. */ subdivisions?: number; /** Improve interior edges with up to 40 Delaunay-flip passes. Boundary edges stay fixed. Default true. */ improveTriangles?: boolean; } /** Rotate positions in place about a unit direction through the target; strength is radians before falloff. * Normals and bounds remain stale. */ export declare const twistBrush: (geometry: T, position: Vector3, radius: number, strength: number, direction?: Vector3, falloffFn?: (distance: number, radius: number) => number) => void; /** Per-corner UV order: (0,0), (0,1), (1,1), (1,0). */ export declare const UNIT_QUAD_UV: [Vec2, Vec2, Vec2, Vec2]; /** * Vase — a silhouette revolved around an axis. * * A vase is a LATHE, not a sweep: it revolves a profile rather than carrying a cross-section along a * path. Which is easy to say, and still misses the thing that actually makes pottery hard: * * **A pot's silhouette is not a mathematical function.** * * *Swelling at the foot, pinched at the waist, flaring at the lip* does not come out of a parabola, a * sine, or an easing curve — those are single-inflection shapes, and a pot has three or four. Reaching * for a formula is the mistake, because no formula has the shape in it. * * What a pot's profile actually is: **a handful of control points with a spline through them.** Which * is precisely what the Utah teapot is — a few hundred hand-placed control points and an evaluator. * You do not compute a pot's curve; you AUTHOR it, and then you tessellate it. Small data, plus a * generating function. * * So the radii ARE the design. Raise the second and the bulge sits low; raise the fourth instead and * it climbs to the shoulder; pinch the middle for an hourglass. One geometry covers all three, because * it is not committed to any curve family. * * Local frame: foot on Y=0, opening up +Y. * * @example * ```ts * const geometry = new VaseGeometry({ radii: [0.4, 1, 0.7, 0.35, 0.5], height: 2.4 }); * ``` */ export declare class VaseGeometry extends LatheGeometry { readonly height: number; constructor({ radii, height, profileSegments, radialSegments, bands, }?: VaseGeometryOptions); } export declare interface VaseGeometryOptions { /** * The silhouette, as radii from the foot to the lip. The spline passes THROUGH these, so they behave * like handles you drag rather than weights you nudge. Defaults to `[0.55, 0.95, 0.8, 0.5, 0.62]` — * a swelling belly, a slight waist, and a flared lip. * * Any number of points is accepted; they are spaced evenly up `height`. Two gives a cone. */ radii?: number[]; /** Overall height. Defaults to `2.4`. */ height?: number; /** How finely the silhouette is sampled — the smoothness of the curve. Defaults to `40`. */ profileSegments?: number; /** How many times the silhouette is revolved — the low-poly knob. `6` gives a faceted, hand-thrown pot. Defaults to `32`. */ radialSegments?: number; /** * Horizontal bands, as ascending fractions of `height`, where the material index steps up. Defaults to * none — a single group. * * `[0.1, 0.9]` yields three groups: material `0` below a tenth of the height, `1` between, `2` above — * a contrasting foot and lip against the body. Supply one material per band plus one; repeats are fine. */ bands?: number[]; } /** 2D coordinate tuple, used for profiles and UVs. */ export declare type Vec2 = [number, number]; /** 3D coordinate tuple. */ export declare type Vec3 = [number, number, number]; /** * Thicken a vessel silhouette into the profile actually lathed. * * With `thickness > 0` the profile winds up the outside, rolls over the rim, and comes back down a full * inner wall to a closed inner bottom — real glass under a single-sided material. With `thickness = 0` it * is the bare silhouette with a decorative rolled rim. */ export declare function vesselShell(silhouette: Vector2[], { thickness, rim, roundedRim }?: VesselShellOptions): Vector2[]; export declare interface VesselShellOptions { /** * Wall thickness, in world units. `> 0` builds a full double wall (up the outside, rolled over the rim, * down a full inner wall, closed at the bottom) so the vessel reads solid under a single-sided material — * right for OPAQUE vessels (a mortar). `0` (the default) leaves a single surface with just a rounded * rolled rim — right for TRANSPARENT glass, where a double wall only multiplies the layers to sort. For * glass, fake the wall with a fill gap instead ({@link fillProfile}'s `inset`). */ thickness?: number; /** Rolled-rim bead thickness, as a fraction of the rim radius — used only when `thickness` is `0`. Defaults to `0.1`. */ rim?: number; /** * Round the double wall's rim over a bead (a rolled lip). When `false`, the outer and inner walls meet * the rim with a flat edge — right for a plain thick rim like a stone mortar. Only affects `thickness > 0`. * Defaults to `true`. */ roundedRim?: boolean; } /** * A tiered rack of votive candles — tens or hundreds of them, in **four draw calls**. * * Whatever the population, the rack draws: one merged iron frame, one wax batch, one flame batch, one * halo batch. Raising the count multiplies triangles, not draws. * * **Per-instance variety without per-instance objects.** Candle heights vary, some cups are empty, some * candles are spent, and every flame flickers on its own phase — all of it carried in instance matrices * and instanced attributes rather than in separate `Mesh`es. * * The **presence cascade** (`density` → `litFraction`) is why the three batches have three different * counts: every present candle gets wax, only lit ones get a flame and a halo. Absence cannot be a * per-instance value, so the batches are sized to the survivors. * * **Per-candle flicker with one shared material** works because the halo blending is additive: folding * the flicker factor into per-instance *color* is mathematically identical to scaling opacity, so a * single material serves N independently guttering halos. * * > **Requires `WebGPURenderer`.** Screen-aligned instancing needs a node material * > (`SpriteNodeMaterial`) to build each halo's quad in the vertex shader. A single {@link GlowHalo} is * > renderer-agnostic; a *batch* of them is not. * * @example * ```typescript * const rack = new VotiveRack({ seed: 7, rows: 5, columns: 12, intensity: 1.4 }); * scene.add(rack); * * function animate(elapsed: number) { * rack.update(elapsed); * renderer.render(scene, camera); * } * ``` * * Call {@link dispose} when removing the rack. */ export declare class VotiveRack extends Group { #private; /** Every present candle. */ readonly waxInstances: InstancedMesh; /** Only the lit ones. */ readonly flameInstances: InstancedMesh; /** Only the lit ones — screen-aligned, one draw call. */ readonly haloInstances: InstancedMesh; /** The rack's single light, when `intensity > 0`. */ readonly light?: PointLight; constructor({ seed, rows, columns, width, rowRise, rowDepth, baseHeight, density, litFraction, candleHeightMin, candleHeightMax, color, waxColor, ironColor, glowSize, glowOpacity, intensity, haloMap, ironMaterial, waxMaterial, }?: VotiveRackOptions); /** Present candles, lit or spent. */ get candleCount(): number; /** Lit candles — the flame and halo batch size. */ get litCount(): number; /** `elapsed` in seconds — the same clock any other flame in the scene advances on. */ update(elapsed: number): void; /** Release geometries and materials. The halo ramp is a shared singleton and is deliberately kept. */ dispose(): void; } export declare interface VotiveRackOptions { /** Seed for a reproducible layout. Omit for a different rack every run. */ seed?: number; /** Shelves, bottom to top. Defaults to `4`. */ rows?: number; /** Cups per shelf. Defaults to `8`. */ columns?: number; /** Overall width of the frame. Defaults to `2.2`. */ width?: number; /** Vertical rise per shelf. Defaults to `0.28`. */ rowRise?: number; /** * Depth offset per shelf, so upper rows sit back. Defaults to `0.18`. * * The lowest shelf is the nearest to `+Z` and each row above steps away — stadium seating, so no row hides * behind the one in front. The rack stays centered on `z = 0` whatever the row count. */ rowDepth?: number; /** Height of the lowest shelf. Defaults to `0.55`. */ baseHeight?: number; /** * Fraction of cups holding a candle at all. Defaults to `0.9` — a rack in use has gaps. * * This is the first step of a **presence cascade**: a cup may be empty; a candle may be spent; only * what survives both is lit. Absence is the one thing a per-instance value cannot express, so it is * expressed by not allocating the instance. */ density?: number; /** Fraction of *present* candles that are lit. Defaults to `0.72`. */ litFraction?: number; /** Shortest candle. Defaults to `0.055`. */ candleHeightMin?: number; /** Tallest candle. Defaults to `0.185`. */ candleHeightMax?: number; /** Flame and glow tint. Defaults to `0xffb347`. */ color?: ColorRepresentation; /** Wax color. Defaults to `0xd9cdb2`. */ waxColor?: ColorRepresentation; /** Iron color. Defaults to `0x2b2622`. */ ironColor?: ColorRepresentation; /** Halo card size in world units. Defaults to `0.48`. */ glowSize?: number; /** Halo opacity at flicker peak. Defaults to `0.42`. */ glowOpacity?: number; /** * Intensity of the rack's single {@link PointLight}. Defaults to `0`, which omits the light entirely — * the flames are unlit emissive geometry and the halos are additive, so the rack reads as a light * source while costing no light budget at all. * * Lights are a fixed budget capped independently of geometry; a hundred votives must never mean a * hundred lights. When set, one light serves the whole rack and its intensity follows the *mean* of * every fake flame, so it brightens when many happen to flare rather than tracking any one. */ intensity?: number; /** * Supply a halo falloff instead of the library's canonical ramp — build one with * {@link createRadialGradientTexture} if you want different stops or easing. * * Deliberately a *texture* rather than stops-and-easing options: one ramp shared by the whole rack is * what keeps a large population cheap, and a per-asset easing dial would let a rack drift visually * away from a single {@link GlowHalo} standing beside it. The caller owns and disposes what it passes. */ haloMap?: DataTexture; /** Override the iron material. */ ironMaterial?: Material; /** Override the wax material. */ waxMaterial?: Material; } /** A surface's normal in plan, from the direction it runs. */ export declare function wallNormal(tangent: Vector2): Vector2; /** An opening in a wall — a doorway or a window. The same description; a different way in. */ export declare interface WallOpeningOptions { /** Width of the opening. Defaults to `1.2`. */ width?: number; /** Height of the straight sides, up to where the arch springs. Defaults to `1.4`. */ height?: number; /** * Rise of the arch above the springing. Defaults to half the width — a semicircle. * * A radius, not an angle. Some styles override it: `square` has none, `semicircle` forces it. */ archHeight?: number; /** Which arch tops the opening. Defaults to `semicircle`. See {@link ArchStyle}. */ arch?: ArchStyle; /** Where the opening sits across the wall. Defaults to `0` — centered. */ x?: number; /** **Windows only.** Height of the sill above the wall's base. A doorway's sill IS the floor. */ y?: number; } /** The crown of an opening, measured from the wall's base. Useful for checking it clears the wall. */ export declare function wallOpeningTop(opening: WallOpeningOptions, wallWidth?: number): number; /** * Wall-mounted oil-lamp sconce — iron mount, cap, and bowl framing an emissive * glass chimney. * * Material groups: `0` mount (plate + bracket), `1` iron frame (cap + bowl), * `2` glass chimney. * * Local frame: faces +X from a −X wall; lamp center at * `(bodyOffsetX, chimneyCenterY, 0)`. */ export declare class WallSconceGeometry extends BufferGeometry { readonly bodyOffsetX: number; readonly chimneyCenterY: number; readonly innerScale: number; readonly inner: boolean; readonly lightCenterX: number; readonly lightCenterY: number; readonly lightCenterZ: number; constructor({ plateThickness, plateHeight, plateDepth, plateOffsetX, bracketLength, bracketHeight, bracketDepth, bracketOffsetX, bracketOffsetY, bodyOffsetX, chimneyHeight, chimneyTopRadius, chimneyBottomRadius, chimneyCenterY, capRadius, capHeight, capCenterY, bowlTopRadius, bowlBottomRadius, bowlHeight, bowlCenterY, radialSegments, innerScale, inner, }?: WallSconceGeometryOptions); } export declare interface WallSconceGeometryOptions { /** Wall-plate thickness (X). Defaults to `0.05`. */ plateThickness?: number; /** Wall-plate height (Y). Defaults to `0.22`. */ plateHeight?: number; /** Wall-plate depth (Z). Defaults to `0.28`. */ plateDepth?: number; /** Wall-plate center X (negative = into the wall). Defaults to `-0.055`. */ plateOffsetX?: number; /** Bracket length into the room (X). Defaults to `0.1`. */ bracketLength?: number; /** Bracket height (Y). Defaults to `0.05`. */ bracketHeight?: number; /** Bracket depth (Z). Defaults to `0.07`. */ bracketDepth?: number; /** Bracket center X. Defaults to `-0.005`. */ bracketOffsetX?: number; /** Bracket center Y. Defaults to `0.1`. */ bracketOffsetY?: number; /** Chimney / lamp body center X. Defaults to `0.06`. */ bodyOffsetX?: number; /** Chimney height. Defaults to `0.3`. */ chimneyHeight?: number; /** Chimney top radius. Defaults to `0.1`. */ chimneyTopRadius?: number; /** Chimney bottom radius. Defaults to `0.105`. */ chimneyBottomRadius?: number; /** Chimney center Y. Defaults to `-0.05`. */ chimneyCenterY?: number; /** Cap radius. Defaults to `0.115`. */ capRadius?: number; /** Cap height. Defaults to `0.05`. */ capHeight?: number; /** Cap center Y. Defaults to `0.12`. */ capCenterY?: number; /** Bowl top radius. Defaults to `0.09`. */ bowlTopRadius?: number; /** Bowl bottom radius. Defaults to `0.11`. */ bowlBottomRadius?: number; /** Bowl height. Defaults to `0.05`. */ bowlHeight?: number; /** Bowl center Y. Defaults to `-0.22`. */ bowlCenterY?: number; /** Radial segments on cylinders. Defaults to `8`. */ radialSegments?: number; /** Glass chimney scale relative to the frame opening. Defaults to `0.96`. */ innerScale?: number; /** Include the emissive glass chimney. Defaults to `true`. */ inner?: boolean; } /** * A wall, with a doorway carved out of it and windows punched through it — and those are **not the same * operation**, which is the entire reason this shape exists. * * ``` * ______________________ A WINDOW is strictly interior: the wall * | ___ | completely surrounds it. That is a HOLE. * | | | ______ | * | |___| / \ | A DOORWAY reaches the floor. It touches the * | | | | boundary, so it is NOT a hole — it is a notch * |__________| |__| in the wall's own OUTLINE. * ``` * * **Why a doorway cannot be a hole.** `Shape.holes` promises the triangulator a void it can enclose, and * it breaks in two ways when the void touches an edge. The triangulator bridges each hole to the outer * contour, and with the contours coincident that seam is degenerate — it will fill straight across your * threshold. Worse, and unavoidably: `ExtrudeGeometry` builds side walls along **every contour, holes * included**. A doorway-as-hole has a bottom segment lying in the sill, so extruding it produces a * horizontal face spanning the doorway at floor level. You cannot triangulate your way out of that one. * You asked for an edge there, so you got a face. * * Drawing the doorway into the outline means **there is no edge across the threshold at all**, so the * face never exists to be removed. And the notch's side walls become the REVEALS — the jamb faces and * the arch soffit — which is what a real doorway has and what you would otherwise have to fake. * * The rule generalizes: **an interior void is a hole; a void that touches the boundary belongs to the * outline.** It is the same rule that makes an arched door's arch part of its silhouette rather than * something cut out of a rectangle. * * Doorways and windows are described identically and topped by any {@link ArchStyle} — the difference is * only which way in they take. A door built from the same `width` / `height` / `archHeight` / `arch` as * the doorway will match it exactly, since both draw the same arc; give the opening a hair more for * clearance. * * @example * ```ts * const wall = new WallShape({ * width: 6, * height: 4, * doorway: { width: 1.3, arch: "semicircle" }, * windows: [ * { width: 0.7, height: 0.9, arch: "ogee", x: -2, y: 1.6 }, * { width: 0.7, height: 0.9, arch: "ogee", x: 2, y: 1.6 }, * ], * }); * * const geometry = new ExtrudeGeometry(wall, { depth: 0.3, bevelEnabled: false }); * ``` */ export declare class WallShape extends Shape { constructor({ width, height, doorway, windows, holes }?: WallShapeOptions); } export declare interface WallShapeOptions { /** Width of the wall. Defaults to `4`. */ width?: number; /** Height of the wall. Defaults to `3`. */ height?: number; /** An opening that reaches the floor. Carved into the OUTLINE. Omit for a solid wall. */ doorway?: WallOpeningOptions; /** Openings that float clear of every edge. Punched as HOLES. */ windows?: WallOpeningOptions[]; /** Raw holes, for shapes this class does not describe. Appended to {@link windows}. */ holes?: Path[]; } /** * A single rough-sawn board centered at the origin, with its long axis on X. * The geometry owns only the board's shape; gaps and installation variation * belong to whichever wall, floor, or roof assembly places it. */ export declare class WeatheredPlankGeometry extends BoxGeometry { constructor({ length, width, thickness, seed, roughness, bow, endSkew, }?: WeatheredPlankGeometryOptions); } export declare interface WeatheredPlankGeometryOptions { /** Long axis, authored along local X. */ length?: number; width?: number; thickness?: number; seed?: number; /** Maximum edge wander as a fraction of width. */ roughness?: number; /** Maximum broad bow as a fraction of thickness. */ bow?: number; /** Maximum end skew as a fraction of width. */ endSkew?: number; } /** A window: glass, the frame ringing it, the jamb lining the reveal, and the sill under it. */ export declare interface WindowAssembly extends Group { /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */ readonly cutout: Path; /** The pane. Flat, and `DoubleSide`, so it survives being looked at from behind. */ glass?: Mesh; /** The decorative ring on the wall's face. */ frame?: Mesh; /** The lining of the reveal, running the wall's full depth. */ jamb?: Mesh; /** The slab under it. */ sill?: Mesh; /** * Release every geometry and material this window owns. * * Materials may be SHARED — the frame, jamb and sill are one timber by default — so each is disposed * once rather than once per part. */ dispose(): void; } /** * The frame around a window: a flat RING that follows the opening all the way around — up the jambs, over * the arch, and closed along the sill. * * **A glazing bead and a picture-frame casing are the same geometry.** One bites into the aperture, the * other spills out onto the wall, and both are just two offsets of one outline. So there is one class, * with a signed `inset` and `outset`, rather than two that would drift apart. * * The ring's inner boundary is strictly interior to its outer one, so here `Shape.holes` is exactly * right — unlike a doorway, which touches the floor and must be notched into the outline instead. Same * rule, opposite answer, which is the whole reason the rule is worth stating. * * **It rings ANY arch**, because it offsets the CURVE rather than the opening's parameters: an offset * ellipse is not an ellipse and an offset ogee is not an ogee, so a frame built by shrinking `width` and * `archHeight` would pinch and swell around the arch instead of holding its width. See {@link offsetLoop}. * * Drawn at the ORIGIN — centered on X, sill at `y = 0` — regardless of where the opening sits in its * wall, so one frame can be positioned into many openings. Extrudes into `+z`. * * @example * ```ts * const opening = { width: 0.8, height: 1, arch: "ogee" } as const; * * const bead = new WindowFrameGeometry({ opening, inset: 0.04, outset: 0 }); // holds the glass * const casing = new WindowFrameGeometry({ opening, inset: 0.02, outset: 0.1 }); // sits on the wall * ``` */ export declare class WindowFrameGeometry extends ExtrudeGeometry { constructor({ opening, inset, outset, depth, curveSegments, }: WindowFrameGeometryOptions); } export declare interface WindowFrameGeometryOptions { /** The opening this frame rings. The SAME description the wall was punched with. */ opening: WallOpeningOptions; /** * How far the frame's inner edge bites INTO the aperture. Defaults to `0.03`. * * This is what holds the glass, and what you actually see: the thin line of wood or iron running all * the way around the pane, arch included. */ inset?: number; /** * How far the frame's outer edge sits OUT on the wall, past the opening. Defaults to `0.06`. * * `0` gives a frame that fills the aperture and stops — a glazing bead, flush with the reveal. Anything * more and it becomes a casing, lying on the wall's face like a picture frame. */ outset?: number; /** How far the frame stands out of the wall. Defaults to `0.05`. */ depth?: number; /** How finely the arch is followed — the low-poly knob. Defaults to `48`. */ curveSegments?: number; } /** The crown of a window's opening, above its sill. Handy for checking it clears the wall above. */ export declare function windowHeight(opening: WallOpeningOptions): number; export declare interface WindowJambOptions { /** * How far the jamb lining bites into the aperture from the wall's cut edge — the visible board width. * Defaults to `0.05`. */ width?: number; } export declare interface WindowOptions extends Omit { /** The opening this window fills — the SAME description the wall was punched with. */ opening: WallOpeningOptions; /** Omit for a frameless aperture. */ frame?: boolean; /** A sill under it. Pass `true` for the defaults, or an object to size it. Omit for none. */ sill?: boolean | WindowSillOptions; /** Omit to leave the aperture empty — a broken window. */ glass?: boolean; /** * The jamb — the lining of the reveal, running the full depth of the wall. Pass `true` for the * defaults, an object to size it. Omit for a bare opening. * * The decorative {@link WindowOptions.frame} is a shallow ring on the wall's FACE; the jamb is the * same ring turned 90° INTO the wall — deep and flush — so it lines the hole instead of the surface. * With a jamb, the glass drops to the wall's mid-depth and sits inside it, rather than clinging to the * front where it leaves the hole open behind. Needs {@link WindowOptions.wallThickness} to know how * deep to run. */ jamb?: boolean | WindowJambOptions; /** * Thickness of the wall the window is set into — the depth the {@link WindowOptions.jamb} spans and the * span the glass centers in. Defaults to `0.3`. Ignored without a jamb. */ wallThickness?: number; /** Frame material. Omit to build a flat-shaded standard material from `frameColor`. */ frameMaterial?: Material; /** Frame tint when `frameMaterial` is omitted. Defaults to `#4a3b2a` — wood. */ frameColor?: ColorRepresentation; /** Glass material. Omit to build a translucent one from `glassColor`. */ glassMaterial?: Material; /** Glass tint when `glassMaterial` is omitted. Defaults to `#9fb6c4`. */ glassColor?: ColorRepresentation; /** Glass opacity when `glassMaterial` is omitted. Defaults to `0.35`. */ glassOpacity?: number; } export declare interface WindowSillOptions { /** * How far the sill juts out of the wall. Defaults to `0.09`. * * The overhang is most of why a window reads as real — a flush sill reads as a sticker. */ jut?: number; /** Thickness of the slab. Defaults to `0.04`. */ thickness?: number; /** * How far the sill runs PAST the opening on each side — its horns. Defaults to `0.05`. * * Real sills overhang their jambs. Square them off at the opening and the window looks cut out rather * than built in. */ horn?: number; /** * How far the sill's top face sits ABOVE the opening's sill line. Defaults to `0`. * * At `0` the top face lands exactly on the sill line, which is where the glass starts — so the frame's * inner edge, which bites `inset` into the aperture, stands proud of it and the sill reads as sunk. * Setting this to the same value as `inset` brings the two flush, and it is what a real sill does * anyway: the glass sits in a rebate cut into the sill rather than balancing on its surface. */ rise?: number; } /** * Corked wine bottle — glass shell, a long wine cork, and an optional fill. * * The same spatial factory as {@link ApothecaryJar} and {@link PotionBottle}: transparent glass, so shell, * cork and liquid are separate meshes, and the cork is fitted and sealed by {@link createCorkStopper}. The * default cork is longer here — a tall vertical body over a deep plug, the way a wine cork actually is. * Rests on Y=0. */ export declare class WineBottle extends Group { constructor({ bottle, fill, cork, corkDepth, glassMaterial, corkMaterial }?: WineBottleOptions); } /** * Wine bottle — a straight body, rounded shoulder, and long neck, as glass with a rolled rim; corked by * {@link WineBottle}. * * A lathe of {@link vesselShell} over {@link wineBottleProfile}; the silhouette is exposed as `.profile` * for the fill and for seating a cork. Local frame: base on Y=0, opening up +Y. */ export declare class WineBottleGeometry extends LatheGeometry { readonly profile: Vector2[]; readonly radius: number; readonly height: number; constructor(options?: WineBottleGeometryOptions); } export declare interface WineBottleGeometryOptions extends WineBottleProfileOptions, VesselShellOptions { /** Circumference segments — the low-poly knob. Defaults to `20`. */ radialSegments?: number; } export declare interface WineBottleOptions { /** Bottle geometry — resize the body, neck, shoulder, etc. The cork re-sizes and re-seats to the rim. */ bottle?: WineBottleGeometryOptions; /** Optional liquid inside the bottle — colour, opacity, glow, fill level. */ fill?: FillOptions; /** Cork shape. Defaults to a long wine cork — a tall vertical body over a deep plug. */ cork?: CorkGeometryOptions; /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */ corkDepth?: number; /** Bottle (glass) material. A green-glass default is supplied. */ glassMaterial?: MeshStandardMaterial; /** Cork material. A cork-brown default is supplied. */ corkMaterial?: MeshStandardMaterial; } /** * Wine bottle silhouette — a straight cylindrical body, a shoulder, and a long neck. Base on Y=0, ends at * the rim. * * The shoulder is a quarter-ellipse sampled at `shoulderSegments` points: `1` collapses it to one straight * facet (a hard Bordeaux shoulder), more rounds it (a Burgundy/Champagne slope). That is the whole trick to * a lathe — roundness is point count, since the segments between points are straight. */ export declare function wineBottleProfile({ radius, neckRadius, height, neckHeight, shoulderHeight, shoulderSegments, }?: WineBottleProfileOptions): Vector2[]; export declare interface WineBottleProfileOptions { /** Body radius. Defaults to `0.5`. */ radius?: number; /** Neck (mouth) radius — where the cork seats. Defaults to `0.18`. */ neckRadius?: number; /** Overall height. Defaults to `3`. */ height?: number; /** Straight neck height. Defaults to `0.9`. */ neckHeight?: number; /** Shoulder height — the curve from body to neck, the bottle's classical tell. Defaults to `0.5`. */ shoulderHeight?: number; /** Points sampling the shoulder curve. `1` is a single straight line (a hard `/`); more rounds it. Defaults to `6`. */ shoulderSegments?: number; } /** * Will-o'-the-wisps drifting through a bounded volume — eerie green orbs that * bob around spawn points with a pulsing point light. Ported from the portfolio * graveyard scene. * * @example * ```ts * const wisps = new WispEffect({ count: 3 }); * scene.add(wisps); * onFrame((dt) => wisps.update(dt)); * ``` */ export declare class WispEffect extends Object3D { private readonly wisps; private readonly orbGeometry; private readonly orbMaterial; private readonly halfWidth; private readonly depth; private readonly heightMin; private readonly heightMax; private readonly driftX; private readonly driftY; private readonly driftZ; private readonly speedMin; private readonly speedMax; private readonly castLight; private readonly lightDistance; private readonly lightDecay; private readonly lightIntensity; private readonly lightPulseAmplitude; private readonly lightPulseSpeed; private elapsed; constructor({ count, width, depth, heightMin, heightMax, color, orbRadius, driftX, driftY, driftZ, speedMin, speedMax, castLight, lightDistance, lightDecay, lightIntensity, lightPulseAmplitude, lightPulseSpeed, }?: WispEffectOptions); update(dt: number): void; dispose(): void; } export declare interface WispEffectOptions { /** Number of drifting wisps. Defaults to `3`. */ count?: number; /** Horizontal spawn extent (world units, centered on the effect). Defaults to `16`. */ width?: number; /** Depth spawn maximum (world units, from z = 0). Defaults to `8`. */ depth?: number; /** Minimum spawn height. Defaults to `1`. */ heightMin?: number; /** Maximum spawn height. Defaults to `2`. */ heightMax?: number; /** Wisp tint. Defaults to `0x6dffb0` (portfolio graveyard). */ color?: ColorRepresentation; /** Orb radius. Defaults to `0.08`. */ orbRadius?: number; /** Horizontal drift radius (X). Defaults to `1.6`. */ driftX?: number; /** Vertical drift radius (Y). Defaults to `0.3`. */ driftY?: number; /** Depth drift radius (Z). Defaults to `1.6`. */ driftZ?: number; /** Minimum motion speed multiplier. Defaults to `0.3`. */ speedMin?: number; /** Maximum motion speed multiplier. Defaults to `0.7`. */ speedMax?: number; /** * Attach a {@link PointLight} per wisp (keep `count` low). * Defaults to `true` to match the portfolio graveyard. */ castLight?: boolean; /** Point light distance when `castLight`. Defaults to `6`. */ lightDistance?: number; /** Point light decay when `castLight`. Defaults to `2`. */ lightDecay?: number; /** * Light intensity = `lightIntensity + sin(t * lightPulseSpeed) * lightPulseAmplitude`. * Defaults to `2.5`. */ lightIntensity?: number; /** Defaults to `1.2`. */ lightPulseAmplitude?: number; /** Defaults to `3`. */ lightPulseSpeed?: number; } export declare interface WobbleClipOptions extends CameraClipTiming { /** Peak positional shake in world units. */ intensity: number; ease?: EasingFunction; } export declare interface WoodPicketFenceOptions extends WoodPicketGeometryOptions { /** * Clear air between adjacent pickets. Defaults to `0.18`. * * A plank has real width, so the gap and the center-to-center pitch are different numbers: * `pitch = width + gap`. The gap is the one worth exposing — plank width is a lumber constant * (a 1×4 is 3.5" whatever you do), so the gap is where the design actually lives. Zero gap would * mean planks planed seamlessly together, which is a wall, not a fence. */ gap?: number; /** Number of pickets. The run is as long as they turn out. */ count?: number; /** Target run length. Pickets divide it equally, adjusting the gap to land them on the span. */ length?: number; /** Stringer (horizontal rail) height. Defaults to `0.12`. */ railHeight?: number; /** Stringer thickness. Defaults to `0.04`. */ railThickness?: number; /** Height of the lower stringer's center. Defaults to `0.25`. */ lowerRailY?: number; /** Height of the upper stringer's center. Defaults to `height - 0.25`. */ upperRailY?: number; /** How far the stringers run past each end of the picket span, to reach a post. Defaults to `0`. */ railOverhang?: number; /** Wood material. Omit to build a flat-shaded standard material from `color`. */ material?: Material; /** Wood tint when `material` is omitted. Defaults to `#e8e4da`. */ color?: ColorRepresentation; } /** * Wooden fence picket — a plank with a cut top, the white-picket-fence silhouette. * * Built as an extruded profile, so the top style lives in the outline rather than in the mesh. * * **The board is the input; the top is cut out of it.** `height` is the whole plank and `width` the whole face; * {@link WoodPicketGeometryOptions.tipDrop} and {@link WoodPicketGeometryOptions.tipInset} are the two halves of * one corner cut, taken *out of* those bounds. Neither can move the silhouette, and "how tall is this picket" * never means adding two numbers. * * **Flat, dog-ear and pointed are one continuum, not three styles** — two numbers slide between them: * * | style | condition | * |---|---| * | flat top | `tipDrop: 0` | * | dog ear | `tipInset === tipDrop` — a 45° cut, whatever the board | * | pointed | `tipInset: width / 2` — the flanks meet, no flat left | * | blunt / steep point | vary `tipDrop` at that inset | * | chevron | a negative `tipDrop` | * * A *gothic* top is **not** on this dial and never can be: its ornamental neck is a curve, and these two * parameters only ever generate straight chamfers. That would be a different profile, the way * {@link ArchProfile} keeps a style union over genuinely different curve families. * * Unlike a fence post, a picket publishes no width profile — it is infill, not structure. Nothing * attaches to it, so nothing needs to ask how wide it is at a given height. * * Local frame: base at Y=0, centered on X and Z. * * @example * ```ts * // A four-foot fence of 1x4 stock with standard dog ears — equal cuts, so 45 degrees. * const geometry = new WoodPicketGeometry({ width: 3.5, height: 48, tipInset: 0.5, tipDrop: 0.5 }); * geometry.tipFlat; // 2.5 — the flat left across the top * ``` */ export declare class WoodPicketGeometry extends ExtrudeGeometry { readonly width: number; /** Overall height of the plank, tip included. */ readonly height: number; /** Depth of the cut from the tip down. Negative when inverted into a chevron. */ readonly tipDrop: number; /** Depth of the cut in from each side, after clamping to `width / 2`. */ readonly tipInset: number; readonly thickness: number; /** Height of the shoulder, where the cut begins — `height − tipDrop`. */ readonly shoulderHeight: number; /** Flat left across the top — `width − tipInset × 2`. Zero once the flanks meet at a point. */ readonly tipFlat: number; constructor({ width, height, tipDrop, tipInset, thickness, }?: WoodPicketGeometryOptions); /** * Height of the plank's highest point. * * The same as {@link height} for any upright picket. They differ only when the cut is inverted (a negative * {@link tipDrop}), where the shoulder is the top and the chevron is notched below it. */ get totalHeight(): number; /** * Angle of the cut flank from horizontal, in radians — `atan2(tipDrop, tipInset)`. * * An output of the two cuts, never a third dial. `Math.PI / 4` exactly when they are equal, which is the * dog-ear the trade assumes by default. */ get cutAngle(): number; } export declare interface WoodPicketGeometryOptions { /** Plank width — the board's face. A 1×4 is `3.5`, a 1×6 is `5.5`, in inches. Defaults to `0.35`. */ width?: number; /** * Overall height of the plank — the board, tip included. Defaults to `1.38`. * * The board you would buy, and the height a fence is quoted at: *"a four-foot fence"* means the highest point * sits at 48in. The top is cut **out of** this, so it is knowable before any cutting happens. See * `docs/option-parameter-conventions.md`. */ height?: number; /** * Depth of the top cut, measured **down from the tip**. Defaults to `0.175`. `0` gives a flat-topped plank. * * Subtractive: taken out of {@link WoodPicketGeometryOptions.height}, never added to it, so the plank measures * `height` whatever this is set to and {@link WoodPicketGeometry.shoulderHeight} falls out as the difference. * * Sized by **itself** rather than by where the shoulder lands, because the cut is what should survive a change * of board length — a six-foot picket and a four-foot picket carry the same two-inch ear. * * Negative values invert the cut into a chevron notched out of the top. A legitimate shape, deliberately * unguarded, and one you ask for by sign rather than reach by accident. */ tipDrop?: number; /** * Depth of the top cut, measured **in from each side**. Defaults to `0.175` — half the default width, so the * stock picket comes to a point. `0` gives a flat-topped plank. * * The other half of the same cut, and subtractive in the same way — taken out of * {@link WoodPicketGeometryOptions.width}, per side, so the flat left between the two is * `width − tipInset × 2` and is published as {@link WoodPicketGeometry.tipFlat}. * * **Equal to {@link WoodPicketGeometryOptions.tipDrop} is a 45° cut — the trade's dog-ear.** Equal cuts on * perpendicular axes, so the angle needs no solving: *"1-inch dog ears"* is `tipInset: 1, tipDrop: 1` and * stays that on any board width. Reaching `width / 2` brings the flanks together and the top to a point, * after which `tipDrop` alone decides blunt versus steep. * * Clamped to `width / 2`: beyond it the two chamfers cross and the outline folds through itself. */ tipInset?: number; /** Plank thickness — the board's Z depth. Untouched by the top cut. Defaults to `0.04`. */ thickness?: number; } /** * Wooden fence post — a square shaft under an overhanging cap board. * * Its stepped profile is why {@link widthAt} exists: a run's stringers meet the shaft, but its * pickets must clear whatever is widest at picket height. * * Local frame: base at Y=0. * * @example * ```ts * const geometry = new WoodPostGeometry({ height: 1.5 }); * const post = new Mesh(geometry, woodMaterial); * scene.add(post); * ``` */ export declare class WoodPostGeometry extends BufferGeometry { /** Shaft height, excluding the cap. */ readonly height: number; readonly width: number; readonly capWidth: number; readonly capHeight: number; constructor({ width, height, capWidth, capHeight, }?: WoodPostGeometryOptions); /** Overall height, cap included. */ get totalHeight(): number; /** * Post width at height `y` — what a fence run asks to size itself against. Zero above and below * the post; steps out at the cap. */ widthAt(y: number): number; /** Widest the post gets between two heights — what pickets must clear. */ maxWidthBetween(y0: number, y1: number): number; } export declare interface WoodPostGeometryOptions { /** Post width and depth — a square section. Defaults to `0.12`. */ width?: number; /** Shaft height, excluding the cap. Defaults to `1.5`. */ height?: number; /** Cap width and depth. Overhangs the shaft. Defaults to `0.18`. */ capWidth?: number; /** Cap height. `0` gives a bare post. Defaults to `0.05`. */ capHeight?: number; } export declare interface WroughtIronFenceOptions extends WroughtIronPicketGeometryOptions { /** * Clear air between adjacent pickets. Defaults to `0.3`. * * This is how fences are actually specced — and regulated: the gap, not the center-to-center * pitch, is what a building code constrains (a child's head mustn't pass through). Picket width * is a stock tubing size, so the gap is where the design lives. `pitch = width + gap`. */ gap?: number; /** Number of pickets. The run is as long as they turn out. */ count?: number; /** Target run length. Pickets divide it equally, adjusting the gap to land them on the span. */ length?: number; /** Rail height. Defaults to `0.1`. */ railHeight?: number; /** Rail thickness. Defaults to `0.05`. */ railThickness?: number; /** Height of the lower rail's center. Defaults to `railHeight / 2`, resting on the ground. */ lowerRailY?: number; /** Height of the upper rail's center. Defaults to `height - railHeight / 2`, tucked under the finials. */ upperRailY?: number; /** * How far the rails run past each end of the picket span, to embed into a supporting post. * Defaults to `0` (rails flush with the run). * * Pickets and rails want opposite things at a post: pickets must *clear* it or they end up buried * in the stonework, while rails must *reach* it or they float unattached. A post whose profile * changes with height — a wide base, a narrower column, a wide cap — cannot satisfy both with one * extent. Size `length` to clear the post's widest part at picket height, then let `railOverhang` * carry the rails back in to meet the column. */ railOverhang?: number; /** Iron material. Omit to build a flat-shaded standard material from `color`. */ material?: Material; /** Iron tint when `material` is omitted. Defaults to `#2b2b2b`. */ color?: ColorRepresentation; } /** * Wrought-iron fence picket — a vertical bar topped with a spear-point finial. * * A picket is *infill*: many of them, evenly spaced between rails. It publishes no width profile, * because nothing attaches to it — that is a post's job. * * Local frame: base at Y=0. * * @example * ```ts * const geometry = new WroughtIronPicketGeometry({ height: 2, finialHeight: 0.3 }); * const picket = new Mesh(geometry, ironMaterial); * scene.add(picket); * ``` */ export declare class WroughtIronPicketGeometry extends BufferGeometry { /** Picket height, excluding the finial. */ readonly height: number; readonly radius: number; readonly finialHeight: number; constructor({ height, radius, finialHeight, finialRadius, finialScaleZ, radialSegments, }?: WroughtIronPicketGeometryOptions); /** * Nominal picket width — the diameter across, matching how tubing is specced (a ½" square tube is * ½" nominal). This is what a fence measures its gap against. */ get width(): number; /** Overall height, finial included. */ get totalHeight(): number; } export declare interface WroughtIronPicketGeometryOptions { /** Picket height, excluding the finial. Defaults to `2`. */ height?: number; /** Picket radius. Defaults to `0.05`. */ radius?: number; /** Finial height. Defaults to `0.3`. */ finialHeight?: number; /** Finial base radius. Defaults to `0.075`. */ finialRadius?: number; /** Finial depth scale on Z — flattens the spear point. Defaults to `1`. */ finialScaleZ?: number; /** * Circumference segments. Defaults to `8` (round). * * Drop to `4` for square tubing, the way most wrought iron is actually made. The ring starts at a * half-segment offset so the flats face the viewer — without it, four segments would present a * corner and read as a diamond. */ radialSegments?: number; } /** * Wrought-iron fence post — a slim shaft under a ball finial, standing a little proud of the * pickets it supports. The post to a {@link WroughtIronPicketGeometry}'s infill, and the right * weight for a small fenced plot where a stone pier would be far too heavy. * * Local frame: base at Y=0. * * @example * ```ts * const geometry = new WroughtIronPostGeometry({ height: 1.25 }); * const post = new Mesh(geometry, ironMaterial); * scene.add(post); * ``` */ export declare class WroughtIronPostGeometry extends BufferGeometry { /** Shaft height, excluding the ball. */ readonly height: number; readonly radius: number; readonly ballRadius: number; /** Y of the ball's center. */ readonly ballCenterY: number; constructor({ height, radius, ballRadius, ballOffset, radialSegments, ballWidthSegments, ballHeightSegments, }?: WroughtIronPostGeometryOptions); /** Overall height, ball included. */ get totalHeight(): number; /** * Post width at height `y` — what a fence run asks to size itself against. * * Zero above and below the post. Through the ball this is the sphere's chord, so it tapers * rather than stepping. * * Widths are circumscribed, not face-to-face: a low-poly shaft is a hexagon, so its true width * varies with the angle you approach from. Reporting the widest case keeps a run clear of the * post no matter how it meets it. */ widthAt(y: number): number; /** * Widest the post gets between two heights — what pickets must clear to avoid burying themselves * in the post. */ maxWidthBetween(y0: number, y1: number): number; } export declare interface WroughtIronPostGeometryOptions { /** Shaft height, excluding the ball. Defaults to `1.1`. */ height?: number; /** Shaft radius. Defaults to `0.06`. */ radius?: number; /** Ball finial radius. Defaults to `0.1`. */ ballRadius?: number; /** * How far the ball's center sits above the shaft top — it settles onto the shaft rather than * balancing on it. Defaults to `ballRadius * 0.6`. */ ballOffset?: number; /** Shaft circumference segments. Defaults to `6`. */ radialSegments?: number; /** Ball circumference segments. Defaults to `8`. */ ballWidthSegments?: number; /** Ball vertical segments. Defaults to `6`. */ ballHeightSegments?: number; } /** * A wrought iron scroll — a flat bar drawn out and curled into a spiral. * * The path is a LOGARITHMIC spiral, `r = r₀·e^(−kθ)`, which is what a real scroll follows. An * Archimedean spiral (constant spacing) reads as mechanical; a logarithmic one tightens as it winds, * the way hot iron actually curls under a scroll jig. * * And the bar TAPERS as it curls, because a smith draws the metal out toward the tip. That per-station * taper is the thing that makes a scroll read as *forged* rather than bent from pipe — and it is the * one thing Three's own sweep cannot do at all. * * Local frame: the scroll lies in the XY plane, winding inward from `startRadius`. * * @example * ```ts * const scroll = new WroughtIronScrollGeometry({ turns: 1.6, taper: 0.35 }); * ``` */ export declare class WroughtIronScrollGeometry extends BufferGeometry { readonly startRadius: number; readonly turns: number; constructor({ startRadius, turns, tightness, barWidth, barThickness, taper, segments, }?: WroughtIronScrollGeometryOptions); } export declare interface WroughtIronScrollGeometryOptions { /** Radius at the open end, before the bar winds in. Defaults to `1.4`. */ startRadius?: number; /** How many turns it makes. Defaults to `1.6`. */ turns?: number; /** How tightly it winds. Higher closes the curl faster. Defaults to `0.22`. */ tightness?: number; /** Bar width — the wide face, lying in the plane of the scroll. Defaults to `0.16`. */ barWidth?: number; /** Bar thickness, out of the plane. Defaults to `0.05`. */ barThickness?: number; /** How far the bar draws down by the curl. `1` is no taper. Defaults to `0.45`. */ taper?: number; /** Smoothness of the spiral — the low-poly knob. Defaults to `96`. */ segments?: number; } export declare interface ZoomClipOptions extends CameraClipTiming { target: Vector3; /** Narrower FOV at the end of the clip (e.g. `35` from `75`). */ endFov: number; ease?: EasingFunction; } export { }