{"version":3,"file":"three-low-poly.mjs","names":[],"sources":["../src/animators/cameraClip.ts","../src/animators/CameraPlayback.ts","../src/constants/Easing.ts","../src/animators/cameraClips.ts","../src/constants/Axis.ts","../src/constants/BoxSide.ts","../src/constants/ColorPalette.ts","../src/constants/Direction.ts","../src/constants/Falloff.ts","../src/constants/ParametricCurve.ts","../src/utils/Random.ts","../src/utils/RandomNumberUtils.ts","../src/effects/DustMotesEffect.ts","../src/effects/EffervescenceEffect.ts","../src/effects/EmissivePulseEffect.ts","../src/effects/FlameFlickerEffect.ts","../src/textures/radialGradient.ts","../src/effects/GlowHalo.ts","../src/effects/GroundFogEffect.ts","../src/effects/LightningEffect.ts","../src/geometry/foliage/EllipticLeafGeometry.ts","../src/utils/RandomColor.ts","../src/effects/PetalDriftEffect.ts","../src/textures/linearGradient.ts","../src/effects/RainEffect.ts","../src/effects/WispEffect.ts","../src/modeling/profiles/Profiles.ts","../src/modeling/paths/LinePath.ts","../src/modeling/profiles/ArchProfile.ts","../src/shapes/WallShape.ts","../src/modeling/surfaces/MiterFrames.ts","../src/modeling/mesh/GeometryBuffers.ts","../src/geometry/architecture/latticeBars.ts","../src/geometry/architecture/DiamondLatticeGeometry.ts","../src/modeling/profiles/OffsetLoop.ts","../src/geometry/architecture/PaneGeometry.ts","../src/geometry/architecture/WindowFrameGeometry.ts","../src/factory/architecture/DiamondLatticeWindow.ts","../src/shapes/ArchedSlabShape.ts","../src/shapes/ClubShape.ts","../src/shapes/SpadeShape.ts","../src/shapes/StrapHingeShape.ts","../src/factory/architecture/DoorFactory.ts","../src/geometry/architecture/GregorianLatticeGeometry.ts","../src/factory/architecture/GregorianLatticeWindow.ts","../src/geometry/architecture/staircaseQuad.ts","../src/geometry/architecture/StaircaseGeometry.ts","../src/factory/architecture/StaircaseFactory.ts","../src/factory/architecture/WindowFactory.ts","../src/geometry/books/BookGeometry.ts","../src/factory/books/BookFactory.ts","../src/geometry/cemetery/CelticCrossHeadstoneGeometry.ts","../src/geometry/cemetery/CrossHeadstoneGeometry.ts","../src/geometry/cemetery/ObeliskGeometry.ts","../src/geometry/cemetery/ObeliskHeadstoneGeometry.ts","../src/geometry/shapes/ArchedSlabGeometry.ts","../src/geometry/cemetery/RoundedHeadstoneGeometry.ts","../src/geometry/cemetery/SquareHeadstoneGeometry.ts","../src/factory/cemetery/HeadstoneFactory.ts","../src/geometry/fence/WoodPicketGeometry.ts","../src/geometry/fence/WroughtIronPicketGeometry.ts","../src/factory/fence/FenceFactory.ts","../src/geometry/timber/HewnTimberGeometry.ts","../src/factory/fence/RusticFence.ts","../src/factory/floors/FlagstoneFloor.ts","../src/factory/floors/PlankFloorLayout.ts","../src/factory/floors/HardwoodFloor.ts","../src/shapes/PolygonShape.ts","../src/geometry/shapes/PolygonGeometry.ts","../src/factory/floors/HexagonalTileFactory.ts","../src/geometry/timber/WeatheredPlankGeometry.ts","../src/factory/floors/PlankFloor.ts","../src/geometry/flora/PumpkinGeometry.ts","../src/factory/flora/PumpkinPatch.ts","../src/factory/lighting/VotiveRack.ts","../src/factory/masonry/ProudStones.ts","../src/factory/masonry/StoneWall.ts","../src/utils/CoherentNoise.ts","../src/geometry/rocks/BoulderGeometry.ts","../src/factory/rocks/BoulderFactory.ts","../src/geometry/rocks/MossyRockGeometry.ts","../src/modeling/mesh/VertexUtils.ts","../src/geometry/rocks/RockGeometry.ts","../src/factory/rocks/RockFactory.ts","../src/geometry/vessels/vesselProfiles.ts","../src/geometry/vessels/FlorenceFlaskGeometry.ts","../src/geometry/science/RingStandGeometry.ts","../src/geometry/vessels/LiquidFillGeometry.ts","../src/factory/vessels/liquidFill.ts","../src/factory/science/FlorenceFlaskStand.ts","../src/geometry/vessels/TestTubeGeometry.ts","../src/factory/science/TestTubeRack.ts","../src/factory/trees/AppleTree.ts","../src/factory/trees/DeciduousTree.ts","../src/geometry/vessels/ApothecaryJarGeometry.ts","../src/geometry/vessels/CorkGeometry.ts","../src/factory/vessels/corkStopper.ts","../src/factory/vessels/ApothecaryJar.ts","../src/geometry/vessels/PotionBottleGeometry.ts","../src/factory/vessels/PotionBottle.ts","../src/geometry/vessels/WineBottleGeometry.ts","../src/factory/vessels/WineBottle.ts","../src/modeling/surfaces/Sweep.ts","../src/geometry/architecture/ArchGeometry.ts","../src/modeling/profiles/MoldingProfiles.ts","../src/geometry/architecture/MoldingGeometry.ts","../src/geometry/architecture/PanelDoorGeometry.ts","../src/geometry/architecture/SpiralStaircaseGeometry.ts","../src/geometry/atmosphere/SmokeCurlGeometry.ts","../src/geometry/cemetery/MausoleumGeometry.ts","../src/geometry/fence/StoneFencePostGeometry.ts","../src/geometry/fence/WoodPostGeometry.ts","../src/geometry/fence/WroughtIronPostGeometry.ts","../src/modeling/paths/SpiralPath.ts","../src/geometry/fence/WroughtIronScrollGeometry.ts","../src/modeling/mesh/TriangulateRegion.ts","../src/geometry/flora/JackOLanternGeometry.ts","../src/geometry/foliage/LeafGeometry.ts","../src/geometry/furniture/BookshelfGeometry.ts","../src/geometry/furniture/DeskGeometry.ts","../src/shapes/GearShape.ts","../src/geometry/gears/BevelGearGeometry.ts","../src/shapes/CrossedWheelShape.ts","../src/geometry/gears/CrossedWheelGeometry.ts","../src/geometry/gears/GearGeometry.ts","../src/shapes/InternalGearShape.ts","../src/geometry/gears/InternalGearGeometry.ts","../src/shapes/RackShape.ts","../src/geometry/gears/RackGeometry.ts","../src/geometry/lighting/CoachLanternGeometry.ts","../src/geometry/lighting/barBetween.ts","../src/geometry/lighting/HangingLanternGeometry.ts","../src/geometry/lighting/WallSconceGeometry.ts","../src/geometry/masonry/QuoinStackGeometry.ts","../src/geometry/primitives/EdgedBoxGeometry.ts","../src/geometry/science/MortarGeometry.ts","../src/geometry/science/PestleGeometry.ts","../src/geometry/science/TeslaCoilGeometry.ts","../src/geometry/shapes/AnnulusGeometry.ts","../src/shapes/BurstShape.ts","../src/geometry/shapes/BurstGeometry.ts","../src/geometry/shapes/ClubGeometry.ts","../src/shapes/DiamondShape.ts","../src/geometry/shapes/DiamondGeometry.ts","../src/shapes/HeartShape.ts","../src/geometry/shapes/HeartGeometry.ts","../src/geometry/shapes/SpadeGeometry.ts","../src/shapes/StarShape.ts","../src/geometry/shapes/StarGeometry.ts","../src/geometry/skeleton/BoneGeometry.ts","../src/geometry/terrain/TerrainMoundGeometry.ts","../src/geometry/terrain/TerrainPlaneGeometry.ts","../src/modeling/surfaces/SurfaceGrid.ts","../src/geometry/textile/pleatWave.ts","../src/geometry/textile/CascadeGeometry.ts","../src/geometry/textile/CurtainPanelGeometry.ts","../src/geometry/textile/SwagGeometry.ts","../src/geometry/trees/ClearingTreeGeometry.ts","../src/geometry/trees/GnarledTreeGeometry.ts","../src/geometry/vessels/BeakerGeometry.ts","../src/geometry/vessels/ErlenmeyerFlaskGeometry.ts","../src/geometry/vessels/GraduatedCylinderGeometry.ts","../src/geometry/vessels/PipetteGeometry.ts","../src/geometry/vessels/VaseGeometry.ts","../src/helpers/Cyclorama.ts","../src/helpers/GroundGrid.ts","../src/modeling/deformation/BendGeometry.ts","../src/modeling/mesh/BevelConvexGeometry.ts","../src/modeling/mesh/InspectGeometry.ts","../src/modeling/mesh/SliceGeometry.ts","../src/modeling/mesh/ChamferConvexGeometry.ts","../src/modeling/mesh/MiteredPrism.ts","../src/modeling/mesh/PlaneWorkflows.ts","../src/modeling/mesh/BooleanGeometry.ts","../src/modeling/mesh/UVUtils.ts","../src/modeling/profiles/InterpolateCurve.ts","../src/modeling/profiles/ParametricCurveUtils.ts","../src/utils/SphericalGeometryUtils.ts","../src/modeling/profiles/SphericalCurve.ts","../src/modeling/profiles/SurfaceProfiles.ts","../src/modeling/paths/ArcPath.ts","../src/modeling/paths/CurvePath.ts","../src/modeling/paths/HelixPath.ts","../src/modeling/paths/PathMeasure.ts","../src/modeling/paths/PathUtils.ts","../src/modeling/paths/RepeatAlongPath.ts","../src/modeling/surfaces/Correspondence.ts","../src/modeling/surfaces/EndCut.ts","../src/modeling/surfaces/Loft.ts","../src/modeling/surfaces/ThickenSurface.ts","../src/modeling/brushes/DisplacementBrush.ts","../src/modeling/brushes/FlattenBrush.ts","../src/modeling/brushes/NoiseBrush.ts","../src/modeling/brushes/SmoothBrush.ts","../src/modeling/brushes/SpikeBrush.ts","../src/modeling/brushes/TwistBrush.ts","../src/sky/LockToViewer.ts","../src/sky/FullMoon.ts","../src/sky/StarField.ts","../src/textures/checkerboard.ts","../src/utils/AlignToEdge.ts","../src/utils/AlignToRow.ts","../src/utils/AlignToSurface.ts","../src/utils/Center.ts","../src/utils/ColorUtils.ts","../src/utils/FindClosestPoint.ts","../src/utils/LineEquations.ts","../src/utils/RandomTimer.ts"],"sourcesContent":["import { PerspectiveCamera, Quaternion, Vector3 } from \"three\";\nimport type { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\nimport type { EasingFunction } from \"../constants/Easing\";\n\nexport type ClipPhase = \"running\" | \"complete\";\n\n/** A camera view captured explicitly for later restoration. */\nexport interface CameraSnapshot {\n  position: Vector3;\n  quaternion: Quaternion;\n  fov: number;\n  target: Vector3;\n}\n\n/** Per-frame mutable state shared with the active clip. */\nexport interface ClipRuntime {\n  camera: PerspectiveCamera;\n  controls: OrbitControls | null;\n  /** Seconds elapsed in the active clip. */\n  elapsed: number;\n  /** Clip duration in seconds. */\n  duration: number;\n  /** Look-at point the clip is focused on — synced to OrbitControls on complete. */\n  focus: Vector3;\n}\n\nexport interface CameraClip {\n  /** Effects are temporary offsets evaluated after the movement/transition. */\n  readonly kind?: \"movement\" | \"transition\" | \"effect\";\n  readonly label: string;\n  readonly duration: number;\n  start(runtime: ClipRuntime): void;\n  /** Evaluate the current time, including the endpoint. Playback owns completion.\n   * Legacy phase returns are accepted but no longer control the clock. */\n  update(runtime: ClipRuntime, dt: number): ClipPhase | void;\n  /** Cleanup on interruption. Must preserve the base camera pose. */\n  cancel?(runtime: ClipRuntime): void;\n}\n\nexport interface CameraClipTiming {\n  /** Duration in seconds. */\n  duration: number;\n  /** Progress easing for normalized time `t` in [0, 1]. Defaults to smoothstep. */\n  ease?: EasingFunction;\n}\n\nexport function captureSnapshot(camera: PerspectiveCamera, controls?: OrbitControls): CameraSnapshot {\n  const target = controls?.target.clone() ?? new Vector3();\n  if (!controls) {\n    const dir = new Vector3();\n    camera.getWorldDirection(dir);\n    target.copy(camera.position).add(dir);\n  }\n  return {\n    position: camera.position.clone(),\n    quaternion: camera.quaternion.clone(),\n    fov: camera.fov,\n    target,\n  };\n}\n\nexport function applySnapshot(camera: PerspectiveCamera, controls: OrbitControls | undefined, snapshot: CameraSnapshot): void {\n  camera.position.copy(snapshot.position);\n  camera.quaternion.copy(snapshot.quaternion);\n  camera.fov = snapshot.fov;\n  camera.updateProjectionMatrix();\n  if (controls) {\n    controls.target.copy(snapshot.target);\n  }\n}\n\nexport function normalizedTime(runtime: ClipRuntime, ease: EasingFunction): number {\n  const t = Math.min(1, runtime.elapsed / runtime.duration);\n  return t === 0 || t === 1 ? t : ease(t);\n}\n","import { PerspectiveCamera, Vector3 } from \"three\";\nimport type { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\nimport { applySnapshot, captureSnapshot, type CameraClip, type CameraSnapshot, type ClipRuntime } from \"./cameraClip\";\n\ninterface Run {\n  clip: CameraClip;\n  elapsed: number;\n}\n\n/**\n * Delta-time camera playback: one movement/transition plus one temporary effect.\n * Play captures the current pose; stop preserves it. Only reset restores a saved view.\n *\n * Optional OrbitControls integration disables input and synchronizes the target.\n * The host must also skip controls.update() while isPlaying: disabling input alone\n * does not stop controls or other external systems from writing to the camera.\n * Clips contain per-run state; do not share a clip between simultaneous players.\n *\n * @example\n * ```ts\n * const playback = new CameraPlayback(camera, controls);\n * playback.timeScale = 0.5; // half speed; changing speed never restarts a clip\n * playback.play(createOrbitClip({ target: new Vector3(), duration: 10 }));\n * // In your existing render loop (dt is seconds):\n * if (!playback.isPlaying) controls.update();\n * playback.update(dt);\n * // playback.stop() keeps the view; playback.reset() restores the saved view.\n * ```\n */\nexport class CameraPlayback {\n  private speed = 1;\n  private active: Run | null = null;\n  private effect: Run | null = null;\n  private readonly focus = new Vector3();\n  private rest: CameraSnapshot;\n  private base: CameraSnapshot | null = null;\n  private controlsEnabled: boolean | null = null;\n\n  constructor(\n    private readonly camera: PerspectiveCamera,\n    private readonly controls?: OrbitControls,\n  ) {\n    this.rest = captureSnapshot(camera, controls);\n    this.focus.copy(this.rest.target);\n  }\n\n  /** Playback clock multiplier: 1 = normal, 0.5 = half speed, 2 = double.\n   * Zero freezes the current frame while retaining camera ownership.\n   * Applies to both movement and effects; may change during a run.\n   */\n  get timeScale(): number {\n    return this.speed;\n  }\n  set timeScale(value: number) {\n    if (!Number.isFinite(value) || value < 0) throw new RangeError(\"Camera timeScale must be finite and nonnegative\");\n    this.speed = value;\n  }\n\n  get isPlaying(): boolean {\n    return this.active !== null || this.effect !== null;\n  }\n  get isMoving(): boolean {\n    return this.active !== null;\n  }\n  get isEffectPlaying(): boolean {\n    return this.effect !== null;\n  }\n\n  /** Capture an explicit restore point. Neither play nor stop restores it. */\n  setRest(): void {\n    this.rest = captureSnapshot(this.camera, this.controls);\n  }\n  setRestSnapshot(snapshot: CameraSnapshot): void {\n    this.rest = {\n      position: snapshot.position.clone(),\n      quaternion: snapshot.quaternion.clone(),\n      fov: snapshot.fov,\n      target: snapshot.target.clone(),\n    };\n  }\n\n  /** Start a movement/transition at the current base pose, or trigger an effect. */\n  play(clip: CameraClip): void {\n    if (!Number.isFinite(clip.duration) || clip.duration <= 0)\n      throw new RangeError(\"Camera clip duration must be finite and positive\");\n    this.removeEffectPose();\n    if (!this.isPlaying) {\n      this.focus.copy(captureSnapshot(this.camera, this.controls).target);\n      if (this.controls) {\n        this.controlsEnabled = this.controls.enabled;\n        this.controls.enabled = false;\n      }\n    }\n    const run = { clip, elapsed: 0 };\n    if (clip.kind === \"effect\") {\n      if (this.effect) this.effect.clip.cancel?.(this.runtime(this.effect));\n      this.effect = run;\n    } else {\n      if (this.active) this.active.clip.cancel?.(this.runtime(this.active));\n      this.active = run;\n    }\n    clip.start(this.runtime(run));\n  }\n\n  /** Stop all playback, preserving the base pose and removing temporary offsets. */\n  stop(): void {\n    this.removeEffectPose();\n    if (this.active) this.active.clip.cancel?.(this.runtime(this.active));\n    if (this.effect) this.effect.clip.cancel?.(this.runtime(this.effect));\n    this.active = this.effect = null;\n    this.releaseControls();\n  }\n\n  /** Explicitly restore the saved view. Intended for repeatable example setup. */\n  reset(): void {\n    this.stop();\n    applySnapshot(this.camera, this.controls, this.rest);\n    this.focus.copy(this.rest.target);\n  }\n\n  /** Advance in seconds. The controller owns completion, independent of easing. */\n  update(dt: number): void {\n    if (!Number.isFinite(dt) || dt < 0) throw new RangeError(\"Camera playback delta must be finite and nonnegative\");\n    if (!this.isPlaying || this.speed === 0) return;\n    dt *= this.speed;\n    this.removeEffectPose();\n    if (this.active && this.advance(this.active, dt)) this.active = null;\n    if (this.effect) {\n      this.base = captureSnapshot(this.camera);\n      if (this.advance(this.effect, dt)) {\n        this.effect = null;\n        this.removeEffectPose();\n      }\n    }\n    if (!this.isPlaying) this.releaseControls();\n  }\n\n  dispose(): void {\n    this.stop();\n  }\n\n  private advance(run: Run, dt: number): boolean {\n    run.elapsed = Math.min(run.clip.duration, run.elapsed + dt);\n    run.clip.update(this.runtime(run), dt);\n    return run.elapsed >= run.clip.duration;\n  }\n  private removeEffectPose(): void {\n    if (!this.base) return;\n    applySnapshot(this.camera, undefined, this.base);\n    this.base = null;\n  }\n  private releaseControls(): void {\n    if (this.controls && this.controlsEnabled !== null) {\n      // Preserve the actual view when stopping during a look-direction blend.\n      const distance = Math.max(1e-6, this.camera.position.distanceTo(this.focus));\n      this.camera.getWorldDirection(this.focus);\n      this.focus.multiplyScalar(distance).add(this.camera.position);\n      this.controls.target.copy(this.focus);\n      this.controls.enabled = this.controlsEnabled;\n      this.controlsEnabled = null;\n    }\n  }\n  private runtime(run: Run): ClipRuntime {\n    return {\n      camera: this.camera,\n      controls: this.controls ?? null,\n      elapsed: run.elapsed,\n      duration: run.clip.duration,\n      focus: this.focus,\n    };\n  }\n}\n","/**\n * Easing function type for interpolating values over time.\n * @param t - Progress value between 0 and 1\n * @returns Eased value between 0 and 1\n */\nexport type EasingFunction = (t: number) => number;\n\n/**\n * Easing functions for interpolating values over time.\n *\n * Use these functions to create smooth animations and transitions.\n * All easing functions take a value t between 0 and 1 and return an eased value between 0 and 1.\n *\n * @example\n * ```typescript\n * import { Easing } from 'three-low-poly';\n *\n * // Ease a value directly\n * const easedValue = Easing.cubicInOut(0.5);\n *\n * // Interpolate between two values over a duration\n * const startValue = 0;\n * const endValue = 100;\n * const t = elapsed / duration;\n * const easedT = Easing.sineInOut(t);\n * const currentValue = startValue + (endValue - startValue) * easedT;\n * ```\n */\nexport const Easing = {\n  // Sine\n  sineIn: (t: number) => 1 - Math.cos((t * Math.PI) / 2),\n  sineOut: (t: number) => Math.sin((t * Math.PI) / 2),\n  sineInOut: (t: number) => -0.5 * (Math.cos(Math.PI * t) - 1),\n\n  // Quadratic\n  quadIn: (t: number) => t * t,\n  quadOut: (t: number) => 1 - Math.pow(1 - t, 2),\n  quadInOut: (t: number) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),\n\n  // Cubic\n  cubicIn: (t: number) => t * t * t,\n  cubicOut: (t: number) => 1 - Math.pow(1 - t, 3),\n  cubicInOut: (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),\n\n  // Quartic\n  quartIn: (t: number) => t * t * t * t,\n  quartOut: (t: number) => 1 - Math.pow(1 - t, 4),\n  quartInOut: (t: number) => (t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2),\n\n  // Quintic\n  quintIn: (t: number) => t * t * t * t * t,\n  quintOut: (t: number) => 1 - Math.pow(1 - t, 5),\n  quintInOut: (t: number) => (t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2),\n\n  // Exponential\n  expoIn: (t: number) => (t === 0 ? 0 : Math.pow(2, 10 * t - 10)),\n  expoOut: (t: number) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)),\n  expoInOut: (t: number) => {\n    if (t === 0) return 0;\n    if (t === 1) return 1;\n    return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;\n  },\n\n  // Circular\n  circIn: (t: number) => 1 - Math.sqrt(1 - Math.pow(t, 2)),\n  circOut: (t: number) => Math.sqrt(1 - Math.pow(t - 1, 2)),\n  circInOut: (t: number) =>\n    t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2,\n\n  // Special\n  linear: (t: number) => t,\n  smoothstep: (t: number) => t * t * (3 - 2 * t),\n  concave: (t: number) => 1 - Math.pow(1 - t, 0.3),\n  convex: (t: number) => Math.pow(t, 0.3),\n  // Shift the logarithm to start at zero and normalize its value at t = 1.\n  logarithmic: (t: number) => Math.log1p(t) / Math.LN2,\n  squareRoot: (t: number) => Math.sqrt(t),\n  inverse: (t: number) => 1 - t,\n  gaussian: (t: number) => {\n    const sigma = 0.5;\n    return Math.exp(-Math.pow(t - 0.5, 2) / (2 * sigma));\n  },\n};\n","import { MathUtils, Quaternion, Vector3 } from \"three\";\nimport { Easing, type EasingFunction } from \"../constants/Easing\";\nimport { normalizedTime, type CameraClip, type CameraClipTiming, type ClipRuntime } from \"./cameraClip\";\n\nconst tmp = new Vector3();\nconst aimRotation = new Quaternion();\n\n// Ease into a new look direction during the first quarter of a clip.\nfunction aim(runtime: ClipRuntime, start: Quaternion): void {\n  runtime.camera.lookAt(runtime.focus);\n  const blend = Easing.smoothstep(Math.min(1, (runtime.elapsed / runtime.duration) * 4));\n  aimRotation.copy(runtime.camera.quaternion);\n  runtime.camera.quaternion.slerpQuaternions(start, aimRotation, blend);\n  // Keep the camera's up convention through the blend, avoiding incidental roll\n  // that an orbit controller would otherwise remove at handoff.\n  runtime.camera.getWorldDirection(tmp);\n  runtime.camera.lookAt(tmp.add(runtime.camera.position));\n}\n\nexport interface OrbitClipOptions extends CameraClipTiming {\n  target: Vector3;\n  /** Distance from target. Interpolates from the current distance when specified. */\n  radius?: number;\n  /** Elevation above target Y in radians. Defaults to current camera elevation. */\n  elevation?: number;\n  /** Revolutions over the clip. Defaults to `1`. */\n  revolutions?: number;\n}\n\n/**\n * Circle the scene — showcase reel orbit.\n *\n * @example\n * ```ts\n * playback.play(createOrbitClip({\n *   target: new Vector3(0, 0.5, 0),\n *   revolutions: 1,\n *   duration: 10, // seconds; use ease: Easing.linear for steady angular speed\n * }));\n * ```\n */\nexport function createOrbitClip(options: OrbitClipOptions): CameraClip {\n  const { target, duration, ease = Easing.smoothstep, revolutions = 1 } = options;\n  const startAzimuth = { value: 0 };\n  const radius = { value: 0 };\n  const elevation = { value: options.elevation ?? 0.4 };\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Orbit\",\n    kind: \"movement\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      runtime.focus.copy(target);\n      tmp.subVectors(runtime.camera.position, target);\n      radius.value = tmp.length();\n      elevation.value = Math.atan2(tmp.y, Math.hypot(tmp.x, tmp.z));\n      startAzimuth.value = Math.atan2(tmp.z, tmp.x);\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n\n      const azimuth = startAzimuth.value + t * Math.PI * 2 * revolutions;\n      const r = MathUtils.lerp(radius.value, options.radius ?? radius.value, t);\n      const e = MathUtils.lerp(elevation.value, options.elevation ?? elevation.value, t);\n      const horiz = r * Math.cos(e);\n      runtime.camera.position.set(\n        target.x + horiz * Math.cos(azimuth),\n        target.y + r * Math.sin(e),\n        target.z + horiz * Math.sin(azimuth),\n      );\n      aim(runtime, startRotation);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface PendulumClipOptions extends CameraClipTiming {\n  target: Vector3;\n  /** End distance from target. Defaults to the captured camera distance. */\n  distance?: number;\n  /** Peak azimuth swing in radians — keep small for Ken Burns mood (e.g. `0.12`). */\n  azimuthAmplitude?: number;\n  /** Slow back-and-forth cycles over the clip. Defaults to `2`. */\n  oscillations?: number;\n  ease?: EasingFunction;\n}\n\n/**\n * Atmospheric focus drift — slow Ken Burns sway while locked on a subject.\n * Not a full orbit; subtle back-and-forth for mood and screen capture.\n *\n * @example\n * ```ts\n * playback.play(createPendulumClip({\n *   target: new Vector3(0, 0.5, 0),\n *   azimuthAmplitude: 0.12, oscillations: 2, duration: 24,\n * }));\n * ```\n */\nexport function createPendulumClip(options: PendulumClipOptions): CameraClip {\n  const { target, duration, azimuthAmplitude = 0.14, oscillations = 2, ease = Easing.linear } = options;\n  const baseAzimuth = { value: 0 };\n  const distance = { value: options.distance ?? 8 };\n  const elevation = { value: 0.35 };\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Pendulum\",\n    kind: \"movement\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      runtime.focus.copy(target);\n      tmp.subVectors(runtime.camera.position, target);\n      distance.value = tmp.length();\n      elevation.value = Math.atan2(tmp.y, Math.hypot(tmp.x, tmp.z));\n      baseAzimuth.value = Math.atan2(tmp.z, tmp.x);\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n\n      const sway = Math.sin(t * Math.PI * 2 * oscillations) * azimuthAmplitude;\n      const azimuth = baseAzimuth.value + sway;\n      const r = MathUtils.lerp(distance.value, options.distance ?? distance.value, t);\n      const horiz = r * Math.cos(elevation.value);\n      runtime.camera.position.set(\n        target.x + horiz * Math.cos(azimuth),\n        target.y + r * Math.sin(elevation.value),\n        target.z + horiz * Math.sin(azimuth),\n      );\n      aim(runtime, startRotation);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface FlythroughClipOptions extends CameraClipTiming {\n  waypoints: Vector3[];\n  /** Optional look-at points per waypoint; defaults to the current focus. */\n  lookAt?: Vector3[];\n  ease?: EasingFunction;\n}\n\n/**\n * Tour from the current view through destination waypoints; eases at each waypoint.\n *\n * @example\n * ```ts\n * // Start at the current camera position, then visit these destinations.\n * playback.play(createFlythroughClip({\n *   waypoints: [new Vector3(4, 3, 0), new Vector3(-5, 2.5, -4), new Vector3(0, 2, 5)],\n *   duration: 12,\n * }));\n * ```\n * @example\n * ```ts\n * // lookAt[i] is the subject to frame when arriving at waypoints[i].\n * // Focus interpolates between subjects; omit lookAt to retain the starting focus.\n * playback.play(createFlythroughClip({\n *   waypoints: [new Vector3(4, 3, 0), new Vector3(-5, 2.5, -4), new Vector3(0, 2, 5)],\n *   lookAt: [new Vector3(0, 0.5, 0), new Vector3(-3, 0.75, -2.5), new Vector3(1.5, 0.5, 2)],\n *   duration: 12,\n * }));\n * ```\n */\nexport function createFlythroughClip(options: FlythroughClipOptions): CameraClip {\n  const { duration, ease = Easing.cubicInOut } = options;\n  let waypoints = options.waypoints.map((p) => p.clone());\n  if (waypoints.length < 2) throw new Error(\"Flythrough clip requires at least two waypoints\");\n\n  let lookAt: Vector3[] = [];\n  let segments = 0;\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Flythrough\",\n    kind: \"movement\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      waypoints = [runtime.camera.position.clone(), ...options.waypoints.map((p) => p.clone())];\n      lookAt = [runtime.focus.clone(), ...options.waypoints.map((_, i) => options.lookAt?.[i]?.clone() ?? runtime.focus.clone())];\n      segments = waypoints.length - 1;\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, Easing.linear);\n      if (t >= 1) {\n        runtime.camera.position.copy(waypoints[waypoints.length - 1]!);\n        runtime.focus.copy(lookAt[lookAt.length - 1] ?? waypoints[waypoints.length - 1]!);\n        aim(runtime, startRotation);\n        return \"complete\";\n      }\n\n      const scaled = t * segments;\n      const seg = Math.min(segments - 1, Math.floor(scaled));\n      const localT = ease(scaled - seg);\n\n      runtime.camera.position.lerpVectors(waypoints[seg]!, waypoints[seg + 1]!, localT);\n      const lookFrom = lookAt[seg] ?? waypoints[seg + 1]!;\n      const lookTo = lookAt[seg + 1] ?? waypoints[seg + 1]!;\n      runtime.focus.lerpVectors(lookFrom, lookTo, localT);\n      aim(runtime, startRotation);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface DollyClipOptions extends CameraClipTiming {\n  /** Distance along view axis — positive pulls back, negative pushes in. */\n  distance: number;\n  ease?: EasingFunction;\n}\n\n/**\n * Dolly in or out along the current view direction.\n *\n * @example\n * ```ts\n * playback.play(createDollyClip({ distance: -3, duration: 4 })); // dolly in\n * // Use distance: 3 to dolly out. Position changes; FOV stays the same.\n * ```\n */\nexport function createDollyClip(options: DollyClipOptions): CameraClip {\n  const { distance, duration, ease = Easing.cubicInOut } = options;\n  const startPos = new Vector3();\n  const endPos = new Vector3();\n  const viewDir = new Vector3();\n  const startFocus = new Vector3();\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Dolly\",\n    kind: \"movement\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      startPos.copy(runtime.camera.position);\n      startFocus.copy(runtime.focus);\n      runtime.camera.getWorldDirection(viewDir);\n      endPos.copy(startPos).addScaledVector(viewDir, -distance);\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      if (t >= 1) {\n        runtime.camera.position.copy(endPos);\n        runtime.focus.copy(startFocus).addScaledVector(viewDir, -distance);\n        return \"complete\";\n      }\n      runtime.camera.position.lerpVectors(startPos, endPos, t);\n      runtime.focus.copy(startFocus).addScaledVector(viewDir, -distance * t);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface SpiralClipOptions extends CameraClipTiming {\n  /** Ground point to look down at (typically scene center, `y = 0`). */\n  target: Vector3;\n  /** @deprecated Use endRadius. Starting radius is always captured from the camera. */\n  radius?: number;\n  /** Optional wider radius at the end — pulls back as you rise. Defaults to the captured radius. */\n  endRadius?: number;\n  /** Total vertical rise over the clip. */\n  height: number;\n  revolutions: number;\n  ease?: EasingFunction;\n}\n\n/**\n * Scene-transition spiral — orbit upward while looking down at the scene.\n * Camera rises and optionally widens its orbit; `lookAt` stays on the ground\n * target so the view pitches into a bird's-eye survey (not a horizontal orbit).\n *\n * @example\n * ```ts\n * playback.play(createSpiralClip({\n *   target: new Vector3(0, 0.5, 0), height: 28, endRadius: 12,\n *   revolutions: 1, duration: 12,\n * }));\n * ```\n */\nexport function createSpiralClip(options: SpiralClipOptions): CameraClip {\n  const { target, radius, endRadius, height, revolutions, duration, ease = Easing.smoothstep } = options;\n  const startRadius = { value: 0 };\n  const startY = { value: 0 };\n  const startAzimuth = { value: 0 };\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Spiral\",\n    kind: \"transition\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      runtime.focus.copy(target);\n      startRadius.value = Math.hypot(runtime.camera.position.x - target.x, runtime.camera.position.z - target.z);\n      startY.value = runtime.camera.position.y;\n      tmp.subVectors(runtime.camera.position, target);\n      startAzimuth.value = Math.atan2(tmp.z, tmp.x);\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n\n      const angle = startAzimuth.value + t * Math.PI * 2 * revolutions;\n      const orbitRadius = MathUtils.lerp(startRadius.value, endRadius ?? radius ?? startRadius.value, t);\n      runtime.camera.position.set(\n        target.x + orbitRadius * Math.cos(angle),\n        startY.value + height * t,\n        target.z + orbitRadius * Math.sin(angle),\n      );\n      aim(runtime, startRotation);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface ZoomClipOptions extends CameraClipTiming {\n  target: Vector3;\n  /** Narrower FOV at the end of the clip (e.g. `35` from `75`). */\n  endFov: number;\n  ease?: EasingFunction;\n}\n\n/**\n * Focus punch — smooth FOV narrow toward a target. Keeps the current FOV on stop and the end FOV on complete.\n *\n * @example\n * ```ts\n * playback.play(createZoomClip({ target: new Vector3(0, 0.5, 0), endFov: 35, duration: 3 }));\n * // A larger endFov zooms out. FOV changes; position stays the same.\n * ```\n */\nexport function createZoomClip(options: ZoomClipOptions): CameraClip {\n  const { target, endFov, duration, ease = Easing.cubicInOut } = options;\n  const startFov = { value: 75 };\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Zoom\",\n    kind: \"movement\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      runtime.focus.copy(target);\n      startFov.value = runtime.camera.fov;\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      runtime.camera.fov = MathUtils.lerp(startFov.value, endFov, Math.min(1, t));\n      runtime.camera.updateProjectionMatrix();\n      aim(runtime, startRotation);\n      return t >= 1 ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface WobbleClipOptions extends CameraClipTiming {\n  /** Peak positional shake in world units. */\n  intensity: number;\n  ease?: EasingFunction;\n}\n\n/**\n * Impact wobble — short head-shake / recovery shake (gameplay feedback).\n * Decaying sinusoidal offset, not random noise. For showcase orbit rigs use\n * {@link createPendulumClip} instead.\n *\n * @example\n * ```ts\n * playback.play(createOrbitClip({ target: new Vector3(), duration: 10 }));\n * playback.play(createWobbleClip({ intensity: 0.15, duration: 0.8 }));\n * // Wobble layers over the orbit, then removes only its temporary offset.\n * ```\n */\nexport function createWobbleClip(options: WobbleClipOptions): CameraClip {\n  const { intensity, duration, ease = Easing.linear } = options;\n\n  const seed = { value: 0 };\n\n  const startRotation = new Quaternion();\n  return {\n    label: \"Wobble\",\n    kind: \"effect\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n\n      seed.value = runtime.camera.position.x * 17.3 + runtime.camera.position.z * 9.1;\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      if (t >= 1) {\n        return \"complete\";\n      }\n\n      const decay = 1 - t;\n      const w = runtime.elapsed * 28 + seed.value;\n      tmp\n        .set(\n          Math.sin(w * 1.7) * intensity * decay,\n          Math.sin(w * 2.3) * intensity * 0.6 * decay,\n          Math.cos(w * 1.9) * intensity * decay,\n        )\n        .applyQuaternion(runtime.camera.quaternion);\n      runtime.camera.position.add(tmp);\n      return runtime.elapsed >= runtime.duration ? \"complete\" : \"running\";\n    },\n  };\n}\n\nexport interface CraneRevealClipOptions extends CameraClipTiming {\n  /** Subject to reveal as the camera rises. */\n  target: Vector3;\n  /** Vertical displacement in world units. Positive rises; negative descends. */\n  height: number;\n}\n\n/**\n * Rise from the current view while gradually framing a subject; keep the final view.\n * Position rises along world Y. The starting look direction blends toward the\n * subject over the full duration, so the reveal remains gradual.\n *\n * @example\n * ```ts\n * playback.play(createCraneRevealClip({\n *   target: new Vector3(0, 0.5, 0), height: 6, duration: 5,\n * }));\n * ```\n */\nexport function createCraneRevealClip(options: CraneRevealClipOptions): CameraClip {\n  const { target, height, duration, ease = Easing.smoothstep } = options;\n  const origin = new Vector3();\n  const initialDirection = new Vector3();\n  const startFocus = new Vector3();\n  const subject = target.clone();\n  return {\n    label: \"Crane Reveal\",\n    kind: \"transition\",\n    duration,\n    start(runtime) {\n      origin.copy(runtime.camera.position);\n      runtime.camera.getWorldDirection(initialDirection);\n      // Preserve the initial view axis, even if the requested subject is elsewhere.\n      initialDirection.multiplyScalar(Math.max(1, origin.distanceTo(subject)));\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      runtime.camera.position.copy(origin);\n      runtime.camera.position.y += height * t;\n      startFocus.copy(runtime.camera.position).add(initialDirection);\n      runtime.focus.lerpVectors(startFocus, subject, t);\n      runtime.camera.lookAt(runtime.focus);\n    },\n  };\n}\n\nexport interface ImpactKickClipOptions extends CameraClipTiming {\n  /** Camera-local kick direction: +X right, +Y up, +Z backward. Normalized internally. */\n  direction?: Vector3;\n  /** Kick amplitude in world units. Defaults to 0.25. */\n  intensity?: number;\n  /** Number of damped rebound cycles. Defaults to 2. */\n  oscillations?: number;\n}\n\n/**\n * Directional impact with a damped rebound, layered over the current movement.\n * Starts and ends with zero displacement. Retriggering replaces the active effect.\n *\n * @example\n * ```ts\n * playback.play(createImpactKickClip({\n *   direction: new Vector3(-1, 0.3, 0), intensity: 0.3, duration: 0.7,\n * }));\n * ```\n */\nexport function createImpactKickClip(options: ImpactKickClipOptions): CameraClip {\n  const { duration, intensity = 0.25, oscillations = 2, ease = Easing.linear } = options;\n  const direction = (options.direction ?? new Vector3(0, 0, 1)).clone().normalize();\n  const offset = new Vector3();\n  return {\n    label: \"Impact Kick\",\n    kind: \"effect\",\n    duration,\n    start() {},\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      const envelope = t === 0 || t === 1 ? 0 : Math.sin(t * Math.PI * 2 * oscillations) * Math.exp(-4 * t) * (1 - t);\n      offset\n        .copy(direction)\n        .multiplyScalar(intensity * envelope)\n        .applyQuaternion(runtime.camera.quaternion);\n      runtime.camera.position.add(offset);\n    },\n  };\n}\n\nexport interface FovPulseClipOptions extends CameraClipTiming {\n  /** Peak additive FOV change in degrees. Positive widens; negative narrows. Defaults to 8. */\n  amplitude?: number;\n}\n\n/**\n * Brief lens pulse relative to the current animated FOV, with zero offset at each end.\n * Safe to layer over Zoom: recovery follows its changing base FOV, not a saved value.\n * The resulting FOV is clamped to [1, 179] degrees.\n *\n * @example\n * ```ts\n * playback.play(createZoomClip({ target: new Vector3(), endFov: 35, duration: 3 }));\n * playback.play(createFovPulseClip({ amplitude: 8, duration: 0.8 }));\n * ```\n */\nexport function createFovPulseClip(options: FovPulseClipOptions): CameraClip {\n  const { duration, amplitude = 8, ease = Easing.linear } = options;\n  return {\n    label: \"FOV Pulse\",\n    kind: \"effect\",\n    duration,\n    start() {},\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      const envelope = t === 0 || t === 1 ? 0 : Math.sin(Math.PI * t) ** 2;\n      runtime.camera.fov = MathUtils.clamp(runtime.camera.fov + amplitude * envelope, 1, 179);\n      runtime.camera.updateProjectionMatrix();\n    },\n  };\n}\n\nexport interface PullAwayClipOptions extends CameraClipTiming {\n  /** Retreat along the starting view axis, in world units. */\n  distance: number;\n  /** Additional rise along world Y. Defaults to 0. */\n  height?: number;\n}\n\n/**\n * Retreat from the current view with optional ascent, keeping the starting orientation.\n * The focus moves with the camera, so resuming manual controls preserves the view.\n *\n * @example\n * ```ts\n * playback.play(createPullAwayClip({ distance: 12, height: 4, duration: 6 }));\n * ```\n */\nexport function createPullAwayClip(options: PullAwayClipOptions): CameraClip {\n  const { distance, height = 0, duration, ease = Easing.smoothstep } = options;\n  const origin = new Vector3();\n  const focus = new Vector3();\n  const displacement = new Vector3();\n  return {\n    label: \"Pull-away\",\n    kind: \"transition\",\n    duration,\n    start(runtime) {\n      origin.copy(runtime.camera.position);\n      focus.copy(runtime.focus);\n      runtime.camera.getWorldDirection(displacement);\n      displacement.multiplyScalar(-distance);\n      displacement.y += height;\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      runtime.camera.position.copy(origin).addScaledVector(displacement, t);\n      runtime.focus.copy(focus).addScaledVector(displacement, t);\n    },\n  };\n}\n\nexport interface FocusTransferClipOptions extends CameraClipTiming {\n  /** New subject to frame. This changes orientation, not optical focus or FOV. */\n  target: Vector3;\n}\n\n/**\n * Turn smoothly toward another subject while holding the camera position and FOV.\n * Uses a rotation blend so even a subject behind the camera has a defined turn.\n *\n * @example\n * ```ts\n * playback.play(createFocusTransferClip({ target: new Vector3(3, 0.5, -4), duration: 3 }));\n * ```\n */\nexport function createFocusTransferClip(options: FocusTransferClipOptions): CameraClip {\n  const { target, duration, ease = Easing.smoothstep } = options;\n  const startRotation = new Quaternion();\n  const endRotation = new Quaternion();\n  const direction = new Vector3();\n  let distance = 1;\n  return {\n    label: \"Focus Transfer\",\n    kind: \"transition\",\n    duration,\n    start(runtime) {\n      startRotation.copy(runtime.camera.quaternion);\n      distance = runtime.camera.position.distanceTo(target);\n      if (distance < 1e-6) {\n        distance = 1;\n        endRotation.copy(startRotation);\n      } else {\n        runtime.camera.lookAt(target);\n        endRotation.copy(runtime.camera.quaternion);\n        runtime.camera.quaternion.copy(startRotation);\n      }\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      runtime.camera.quaternion.slerpQuaternions(startRotation, endRotation, t);\n      runtime.camera.getWorldDirection(direction);\n      runtime.focus.copy(runtime.camera.position).addScaledVector(direction, distance);\n      // Match the camera's up convention throughout, including controls handoff.\n      runtime.camera.lookAt(runtime.focus);\n    },\n  };\n}\n\nexport interface PassThroughClipOptions extends CameraClipTiming {\n  /** Point to travel through. Choose a clear path above or beside solid geometry. */\n  target: Vector3;\n  /** Distance to continue beyond the point, in world units. Defaults to 5. */\n  beyond?: number;\n}\n\n/**\n * Travel through a point and continue beyond it, retaining the starting orientation.\n * The camera never turns back toward the point after passing it. Collision handling,\n * fades, and switching scenes belong to the host; completion keeps the endpoint.\n * If already at the target, travel along the current view direction instead.\n *\n * @example\n * ```ts\n * playback.play(createPassThroughClip({ target: new Vector3(0, 2.5, 0), beyond: 6, duration: 5 }));\n * // The host can switch scenes once playback.isMoving becomes false.\n * ```\n */\nexport function createPassThroughClip(options: PassThroughClipOptions): CameraClip {\n  const { target, beyond = 5, duration, ease = Easing.linear } = options;\n  const origin = new Vector3();\n  const focus = new Vector3();\n  const displacement = new Vector3();\n  return {\n    label: \"Pass-through\",\n    kind: \"transition\",\n    duration,\n    start(runtime) {\n      origin.copy(runtime.camera.position);\n      focus.copy(runtime.focus);\n      displacement.subVectors(target, origin);\n      const distance = displacement.length();\n      if (distance < 1e-6) runtime.camera.getWorldDirection(displacement);\n      else displacement.divideScalar(distance);\n      displacement.multiplyScalar(distance + beyond);\n    },\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      runtime.camera.position.copy(origin).addScaledVector(displacement, t);\n      runtime.focus.copy(focus).addScaledVector(displacement, t);\n    },\n  };\n}\n\nexport interface RecoilClipOptions extends CameraClipTiming {\n  /** Peak backward displacement along camera-local +Z. Defaults to 0.15 world units. */\n  distance?: number;\n  /** Peak upward pitch in radians. Defaults to 0.06. */\n  pitch?: number;\n}\n\n/**\n * Quick backward and upward kick, followed by smooth recovery to the animated base pose.\n *\n * @example\n * ```ts\n * playback.play(createRecoilClip({ distance: 0.2, pitch: 0.08, duration: 0.45 }));\n * ```\n */\nexport function createRecoilClip(options: RecoilClipOptions): CameraClip {\n  const { distance = 0.15, pitch = 0.06, duration, ease = Easing.linear } = options;\n  const offset = new Vector3();\n  return {\n    label: \"Recoil\",\n    kind: \"effect\",\n    duration,\n    start() {},\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      const envelope = t < 0.2 ? Easing.smoothstep(t / 0.2) : 1 - Easing.smoothstep((t - 0.2) / 0.8);\n      offset.set(0, 0, distance * envelope).applyQuaternion(runtime.camera.quaternion);\n      runtime.camera.position.add(offset);\n      runtime.camera.rotateX(pitch * envelope);\n    },\n  };\n}\n\nexport interface LandingBumpClipOptions extends CameraClipTiming {\n  /** Initial dip scale along camera-local -Y, in world units. Defaults to 0.3. */\n  intensity?: number;\n}\n\n/**\n * Vertical landing dip followed by a damped rebound. The offset follows camera-local Y.\n *\n * @example\n * ```ts\n * playback.play(createLandingBumpClip({ intensity: 0.4, duration: 0.8 }));\n * ```\n */\nexport function createLandingBumpClip(options: LandingBumpClipOptions): CameraClip {\n  const { intensity = 0.3, duration, ease = Easing.linear } = options;\n  const offset = new Vector3();\n  return {\n    label: \"Landing Bump\",\n    kind: \"effect\",\n    duration,\n    start() {},\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      const envelope = t === 0 || t === 1 ? 0 : -Math.sin(t * Math.PI * 3) * Math.exp(-3 * t) * (1 - t);\n      offset.set(0, intensity * envelope, 0).applyQuaternion(runtime.camera.quaternion);\n      runtime.camera.position.add(offset);\n    },\n  };\n}\n\nexport interface HandheldDriftClipOptions extends CameraClipTiming {\n  /** Positional drift scale in world units. Defaults to 0.04. */\n  intensity?: number;\n  /** Pitch/yaw drift scale in radians. Defaults to 0.008. */\n  rotation?: number;\n  /** Base frequency in cycles per playback second. Defaults to 0.6. */\n  frequency?: number;\n}\n\n/**\n * Gentle deterministic handheld motion, fading in and out over a finite duration.\n * Layers camera-local translation and pitch/yaw over the moving base without accumulating drift.\n * Frequency follows playback time, so timeScale slows the entire effect coherently.\n *\n * @example\n * ```ts\n * playback.play(createOrbitClip({ target: new Vector3(), duration: 20 }));\n * playback.play(createHandheldDriftClip({ intensity: 0.04, rotation: 0.008, duration: 20 }));\n * ```\n */\nexport function createHandheldDriftClip(options: HandheldDriftClipOptions): CameraClip {\n  const { intensity = 0.04, rotation = 0.008, frequency = 0.6, duration, ease = Easing.linear } = options;\n  const offset = new Vector3();\n  return {\n    label: \"Handheld Drift\",\n    kind: \"effect\",\n    duration,\n    start() {},\n    update(runtime) {\n      const t = normalizedTime(runtime, ease);\n      const envelope = Easing.smoothstep(Math.min(1, t / 0.15)) * Easing.smoothstep(Math.min(1, (1 - t) / 0.15));\n      const phase = runtime.elapsed * Math.PI * 2 * frequency;\n      offset\n        .set(Math.sin(phase), Math.sin(phase * 0.73) * 0.6, Math.sin(phase * 1.17) * 0.3)\n        .multiplyScalar(intensity * envelope)\n        .applyQuaternion(runtime.camera.quaternion);\n      runtime.camera.position.add(offset);\n      runtime.camera.rotateX(Math.sin(phase * 0.83) * rotation * envelope);\n      runtime.camera.rotateY(Math.sin(phase * 0.61) * rotation * envelope);\n    },\n  };\n}\n","import { Vector3 } from \"three\";\n\n/**\n * Geometric and mathematical role of the vectors as axes in 3D space.\n *\n * Example usages:\n *\n * Defining a geometry's up vector:\n * ```\n * const upVector = Axis.Z; // Set the Z-axis as the \"up\" direction\n * object.up.copy(upVector);\n * ```\n *\n * Rotating or Orienting an Object\n * ```\n * const axis = Axis.XY; // Diagonal upward\n * object.lookAt(object.position.clone().add(axis));\n * ```\n *\n * Rotate around axis:\n * ```\n * const angle = Math.PI / 4; // 45-degree rotation\n * const rotationAxis = Axis.Y; // Rotate around the Y-axis\n *\n * object.rotateOnAxis(rotationAxis, angle);\n * ```\n *\n * Scaling along an axis:\n * ```\n * const scalingAxis = Axis.XZ; // Scale equally along X and Z\n * const scaleFactor = 2;\n *\n * object.scale.multiply(scalingAxis.clone().multiplyScalar(scaleFactor));\n * ```\n *\n * Aligning a Camera\n * ```\n * const cameraAxis = Axis.XZ; // Align the camera diagonally on the XZ plane\n *\n * camera.lookAt(camera.position.clone().add(cameraAxis));\n * ```\n *\n * Directional lighting\n * ```\n * const lightDirection = Axis.YZ; // Diagonal light along Y and Z axes\n * directionalLight.position.copy(lightDirection.clone().multiplyScalar(10));\n * ```\n *\n * Aligning an Object\n * ```\n * const axis = Axis.X; // Align object to the X-axis\n * object.lookAt(object.position.clone().add(axis));\n * ```\n *\n * Spawning objects along an axis\n * ```\n * const spawnAxis = Axis.XZ; // Arrange objects diagonally on XZ\n * const count = 10;\n * const spacing = 5;\n *\n * for (let i = 0; i < count; i++) {\n *   const position = spawnAxis.clone().multiplyScalar(i * spacing);\n *   const newObject = object.clone();\n *   newObject.position.copy(position);\n *   scene.add(newObject);\n * }\n * ```\n *\n * Vector projections\n * ```\n * const vector = new Vector3(3, 5, 7);\n * const projectionAxis = Axis.Z; // Project the vector onto the Z-axis\n *\n * const projection = vector.clone().projectOnVector(projectionAxis);\n * ```\n *\n * Plane definitions\n * ```\n * const normal = Axis.Y; // Y-axis is the normal for a horizontal plane\n * const distanceFromOrigin = 5;\n *\n * const plane = new THREE.Plane(normal, distanceFromOrigin);\n * ```\n *\n * Plane intersections\n * ```\n * const planeNormal = Axis.Y; // Define a plane normal (horizontal plane)\n * const plane = new THREE.Plane(planeNormal);\n *\n * const rayDirection = Axis.Z; // Ray pointing along Z-axis\n * const rayOrigin = new Vector3(0, 5, 0);\n * const ray = new THREE.Ray(rayOrigin, rayDirection);\n *\n * // Find intersection point\n * const intersectionPoint = new Vector3();\n * plane.intersectLine(new THREE.Line3(rayOrigin, rayOrigin.clone().add(rayDirection)), intersectionPoint);\n * ```\n *\n * Defining bounds\n * ```\n * const boundsAxis = Axis.XY; // Restrict an object’s movement within XY bounds\n * const maxBounds = 10;\n *\n * object.position.clamp(\n *   new Vector3(-maxBounds, -maxBounds, -Infinity),\n *   new Vector3(maxBounds, maxBounds, Infinity)\n * );\n * ```\n *\n * Procedural Geometry\n * ```\n * const vertex = new Vector3(0, 0, 0);\n * const axis = Axis.XZ; // Diagonal axis on XZ plane\n *\n * const offset = axis.clone().multiplyScalar(5);\n * vertex.add(offset); // Move vertex in axis\n * geometry.vertices.push(vertex);\n * ```\n */\nexport const Axis = {\n  X: new Vector3(1, 0, 0),\n  Y: new Vector3(0, 1, 0),\n  Z: new Vector3(0, 0, 1),\n  XY: new Vector3(1, 1, 0).normalize(),\n  XZ: new Vector3(1, 0, 1).normalize(),\n  YZ: new Vector3(0, 1, 1).normalize(),\n  XYZ: new Vector3(1, 1, 1).normalize(),\n};\n","export const BoxSide = {\n  LEFT: \"left\",\n  RIGHT: \"right\",\n  TOP: \"top\",\n  BOTTOM: \"bottom\",\n  FRONT: \"front\",\n  BACK: \"back\",\n} as const;\n\nexport type BoxSide = (typeof BoxSide)[keyof typeof BoxSide];\n","export const ColorPalette = {\n  // Red\n  CADMIUM_RED: 0xe60026,      // Cadmium Red or Bright Red. Vivid, bold red shade with warm undertones.\n  CARDINAL_RED: 0xc61416,     // Bright Crimson or Cardinal Red. It’s a vivid, bold red shade with warm undertones.\n  CHERRY_RED: 0xd82424,       // Bold red with slightly warm undertones.\n  CRIMSON: 0xc62828,          // Crimson Red or Firebrick Red. It’s a bold, deep red with a slightly cool undertone.\n  ALIZARIN_CRIMSON: 0xb22222, // Deep, rich red with cool undertones.\n  DARK_RED: 0x8b0000,         // Dark Red. It’s a deep, rich red shade resembling the color of garnets or dried blood.\n  RUST: 0xc2452d,             // Red clay or Brick Red. It’s a warm, earthy red-orange shade, often associated with weathered metal, autumn leaves, or natural clay tones.\n\n  // Orange\n  CORAL_ORANGE: 0xff5733,     // Bold red-orange hue.\n  TANGERINE: 0xff5500,        // Tangerine or Bright Orange-Red. It’s a vibrant, warm shade of orange with red undertones with a  tropical feel.\n  ORANGE_PEEL: 0xff6600,      // Orange Peel or Bright Orange. It’s a vivid, energetic orange.\n  CADMIUM_ORANGE: 0xff7f24,   // warm orange with strong red undertones, giving it a slightly richer and darker appearance.\n  ORANGE: 0xffa500,           // Bright, vibrant hue associated with sunsets, citrus fruits, and autumn tones.\n\n  // Yellow\n  AMBER: 0xffaa00,            // Bright Orange. It’s a warm, vibrant shade of orange with golden-yellow undertones, associated with autumn hues.\n  CADMIUM_YELLOW: 0xffd700,   // Bright golden yellow with warm undertones.\n  GOLD: 0xffd700,             // Rich, vibrant yellow with warm undertones, resembling the color of metallic gold.\n  YELLOW: 0xffff00,           // Bright, pure yellow shade.\n\n  // Green\n  LIME_GREEN: 0xa4c400,       // Lime Green or Bright Chartreuse. It’s a vivid, energetic yellow-green shade\n  SPRING_GREEN: 0x88cc55,     // Fresh, vibrant appearance, resembling new leaves or spring foliage\n  MOSS_GREEN: 0x88aa33,       // Medium-light shade of green with yellow undertones, often seen in natural foliage.\n  FERN_GREEN: 0x4b8b3b,       // It's a natural, medium-dark green with slightly muted tones, resembling the lush greenery of ferns or foliage.\n  FOREST_GREEN: 0x228b22,     // Rich, deep green shade, often associated with dense woodland and nature.\n  SAP_GREEN: 0x507d2a,        // Natural green with a muted tone.\n  OLIVE_DRAB: 0x556b2f,       // Muted earthy green with brownish undertones.\n  VIRIDIAN_GREEN: 0x00a86b,   // Vibrant cool green.\n  MINT_GREEN: 0x88ccaa,       // Mint Green or Light Jade. It’s a soft, pastel green with subtle blue undertones.\n  AQUAMARINE: 0x00ffaa,       // Aquamarine or Mint Green. It’s a bright, vibrant turquoise shade that blends green and blue.\n\n  // Blue\n  PHTHALO_BLUE: 0x000f89,     // Phthalo Blue or Bright Navy.  Intense, rich blue with a slightly greenish cool undertone.\n  AETHER_BLUE: 0x3498db,      // Bright and vivid shade of blue, sitting between the airy Sky Blue and rich Cerulean.\n  SKY_BLUE: 0x88ccff,         // Sky Blue or Baby Blue. It’s a light, cheerful shade of blue with a slightly soft and pastel tone.\n  CERULEAN_BLUE: 0x5dade2,    // Cerulean Blue or Sky Blue. It’s a bright, vivid shade of blue with a slightly cool undertone.\n  AZURE: 0x0077ff,            // Azure Blue or Bright Blue. It’s a vibrant, rich shade of blue with a cool undertone.\n  OCEAN_BLUE: 0x0077be,       // Ocean Blue or Azure Blue. It’s a vibrant medium blue shade with a slight teal undertone.\n  ROYAL_BLUE: 0x5555ff,       // Royal Blue or Bright Indigo. It’s a vibrant medium blue shade with a slightly cool and saturated tone.\n  MIDNIGHT_BLUE: 0x000033,    // A very deep, dark blue shade that evokes the feeling of a starless night sky or deep ocean depths, with subtle blue undertones.\n  ULTRAMARINE_BLUE: 0x3f51b5, // Deep, rich blue with a slightly cool undertone.\n\n  // Purple\n  COBALT_VIOLET: 0x6a0dad,    // Soft, purple tone.\n  DEEP_VIOLET: 0xaa00ff,      // Electric Purple or Deep Violet. It’s a bright, vibrant shade of purple with a strong blue undertone.\n  CORAL_PINK: 0xff3366,       // Bright Rose or Coral Pink. It’s a vivid, bold pinkish-red shade with warm undertones.\n  VIVID_MAGENTA: 0xff22ff,    // It's an intense, electric shade of pink with equal parts red and blue.\n  MAGENTA: 0xff00ff,          // Bright, vivid hue that is a mix of equal parts red and blue, sometimes called Fuchsia.\n  HOT_PINK: 0xff00aa,         // Hot Pink or Fuchsia Rose. It’s a vivid and bold pink with a magenta undertone.\n  PINK_SHERBET: 0xff99cc,     // Pink Sherbet or Light Pink. It’s a soft, playful pink shade with a hint of warmth.\n  SOFT_PINK: 0xffc7c7,        // Blush Pink or Soft Pink. It’s a pale, delicate pink with warm undertones.\n\n  // Brown\n  BURNT_SIENNA: 0xe97451,     // warm, earthy brown with reddish undertones.\n  BURNT_UMBER: 0x8a3324,      // Dark brown with red undertones.\n  SIENNA: 0x8b5a2b,           // Dark Goldenrod Brown. It’s a warm, earthy brown with reddish and golden undertones, often associated with natural clay or autumn leaves.\n  SADDLE_BROWN: 0x8b4513,     // Rich, earthy brown with reddish undertones, often associated with leather, wood, or other natural materials.\n  COFFEE_BROWN: 0x5c4033,     // Rich, earthy tone with hints of red and gray, resembling the color of natural wood or dark leather.\n  DARK_UMBER: 0x4b3621,       // Coffee Brown or Dark Umber. It’s a deep, rich brown with warm, earthy undertones, often resembling dark wood or roasted coffee beans.\n  RAW_UMBER: 0x826644,        // Cool, muted brown.\n  YELLOW_OCHRE: 0xc5a16f,     // Warm, muted yellow-brown\n  RAW_SIENNA: 0xd2b48c,       // Light, golden brown.\n  TAUPE: 0x8b7d7b,            // Rosy Brown or Taupe Gray. It’s a muted, earthy tone with a mix of brown and gray.\n\n  // Gray\n  ONYX: 0x030303,             // Extremely deep, almost black shade with a neutral tone.\n  CARBON: 0x2e2e2e,           // Deep, neutral gray.\n  CHARCOAL: 0x333333,         // Rich, neutral gray.\n  SLATE_GRAY: 0x404040,       // Medium-dark shade of gray\n  ASH_GRAY: 0x444444,         // Ash Gray or Dark Slate Gray.\n  GRAPHITE: 0x505050,         // Medium-dark, neutral Graphite Gray or Smoky Gray shadowy muted depth.\n  STEEL_GRAY: 0x555555,       // Medium Gray or Steel Gray.\n  DIM_GRAY: 0x696969,         // Medium-dark neutral gray with a muted, balanced tone\n  IRON: 0x777777,             // It’s a medium gray neutral.\n  GRAY: 0x808080,             // It is a neutral, medium-gray tone that sits exactly halfway between black and white.\n  STONE: 0x888888,            // It’s a medium-light neutral.\n  SILVER: 0xaaaaaa,           // Light Gray. It’s a soft, pale gray shade with a slightly metallic undertone.\n  LIGHT_GRAY: 0xcccccc,       // Light, neutral Gray, sits between medium grays and white.\n  PALE_GRAY: 0xeeeeee,        // Very Light Gray or Soft Gray. It’s a pale, almost white shade of gray.\n  WHITE_SMOKE: 0xf5f5f5,      // White Smoke or Light Gray. It’s a soft, pale gray shade with a hint of warmth.\n  TITANIUM_WHITE: 0xffffff,   // Pure white, the brightest color possible.\n };\n","import { Vector3 } from \"three\";\n\n/**\n * Movement or orientation in a specific direction.\n *\n * Example usages:\n *\n * Moving an Object Along a Direction\n * ```\n * const speed = 1; // Movement speed\n * const direction = Direction.FORWARD; // Choose a direction\n *\n * object.position.add(direction.clone().multiplyScalar(speed * deltaTime));\n * ```\n *\n * Snapping Positions to a Direction\n * ```\n * const targetPosition = new Vector3(5, 0, 3);\n * const snapDirection = Direction.UP; // Align upwards\n *\n * object.position.copy(targetPosition.clone().add(snapDirection.clone().multiplyScalar(10)));\n * ```\n *\n * Animating Along a Direction\n * ```\n * const distance = 10; // Total distance to travel\n * const duration = 2; // Animation duration in seconds\n * const startPosition = object.position.clone();\n * const targetPosition = startPosition.clone().add(Direction.BACKWARD.clone().multiplyScalar(distance));\n *\n * let elapsed = 0;\n * function animate(deltaTime) {\n *   elapsed += deltaTime;\n *   const t = Math.min(elapsed / duration, 1); // Normalize time to [0, 1]\n *   object.position.lerpVectors(startPosition, targetPosition, t);\n * }\n * ```\n *\n * Shader Uniforms\n * ```\n * material.uniforms.uDirection.value = Direction.FORWARD; // Use as light or flow direction\n * ```\n *\n * Particle Systems\n * ```\n * particles.forEach(particle => {\n *   particle.velocity.add(Direction.FORWARD.clone().multiplyScalar(0.1));\n * });\n * ```\n *\n * Directional Raycasting\n * ```\n * const rayOrigin = new Vector3(0, 0, 0);\n * const rayDirection = Direction.FORWARD;\n * const raycaster = new THREE.Raycaster(rayOrigin, rayDirection);\n *\n * // Find intersected objects\n * const intersects = raycaster.intersectObjects(scene.children);\n * ```\n *\n * Physics Forces\n * ```\n * const forceDirection = Direction.UP; // Push upwards\n * const forceMagnitude = 50;\n *\n * rigidBody.applyForce(forceDirection.clone().multiplyScalar(forceMagnitude));\n * ```\n */\nexport const Direction = {\n  UP: new Vector3(0, 1, 0),\n  DOWN: new Vector3(0, -1, 0),\n  LEFT: new Vector3(-1, 0, 0),\n  RIGHT: new Vector3(1, 0, 0),\n  FORWARD: new Vector3(0, 0, 1),\n  BACKWARD: new Vector3(0, 0, -1),\n};\n","export type FalloffFunction = (distance: number, radius: number) => number;\n\nexport const Falloff = {\n  linear: (distance: number, radius: number): number => 1 - distance / radius,\n  quadratic: (distance: number, radius: number): number => Math.pow(1 - distance / radius, 2),\n  squareRoot: (distance: number, radius: number): number => Math.pow(1 - distance / radius, 0.5),\n  logarithmic: (distance: number, radius: number): number => Math.log(1 + (radius - distance)) / Math.log(1 + radius),\n  sine: (distance: number, radius: number): number => Math.cos(((distance / radius) * Math.PI) / 2),\n  exponential: (distance: number, radius: number): number => Math.exp(-distance / radius),\n  cubic: (distance: number, radius: number): number => Math.pow(1 - distance / radius, 3),\n  gaussian: (distance: number, radius: number): number => Math.exp(-Math.pow(distance, 2) / (2 * Math.pow(radius / 3, 2))),\n  inverse: (distance: number, radius: number): number => radius / (radius + distance),\n  smoothstep: (distance: number, radius: number): number => {\n    const t = Math.max(0, Math.min(1, 1 - distance / radius));\n    return t * t * (3 - 2 * t);\n  },\n};\n","export const ParametricCurve = {\n  cubic: (t: number, p0: number, p1: number, p2: number, p3: number): number => {\n    const clampedT = Math.max(0, Math.min(1, t));\n    const oneMinusT = 1 - clampedT;\n    return (\n      oneMinusT * oneMinusT * oneMinusT * p0 +\n      3 * oneMinusT * oneMinusT * clampedT * p1 +\n      3 * oneMinusT * clampedT * clampedT * p2 +\n      clampedT * clampedT * clampedT * p3\n    );\n  },\n  damped: (t: number, damping: number = 1): number => {\n    const clampedDamping = Math.max(0.001, damping); // Avoid zero or negative damping\n    return (1 - Math.exp(-clampedDamping * t)) / (1 - Math.exp(-clampedDamping));\n  },\n  exponential: (t: number, base: number = 1, factor: number = 1): number => {\n    const clampedT = Math.max(0, Math.min(1, t));\n    return base * Math.pow(clampedT, factor);\n  },\n  logarithmic: (t: number, base: number = 1, factor: number = 1): number => {\n    const clampedT = Math.max(0.001, Math.min(1, t)); // Avoid log(0)\n    return base * Math.log(factor * clampedT + 1);\n  },\n  parabolic: (t: number, a: number = 1, b: number = 0, c: number = 0): number => {\n    const clampedT = Math.max(0, Math.min(1, t));\n    return a * clampedT * clampedT + b * clampedT + c;\n  },\n  quadratic: (t: number, p0: number, p1: number, p2: number): number => {\n    const clampedT = Math.max(0, Math.min(1, t));\n    const oneMinusT = 1 - clampedT;\n    return oneMinusT * oneMinusT * p0 + 2 * oneMinusT * clampedT * p1 + clampedT * clampedT * p2;\n  },\n  sigmoid: (t: number, a: number = 10): number => {\n    const clampedT = Math.max(0, Math.min(1, t));\n    return 1 / (1 + Math.exp(-a * (clampedT - 0.5)));\n  },\n  sinusoidal: (t: number): number => {\n    return Math.sin(t * Math.PI * 0.5);\n  },\n};\n","/**\n * Randomness for procedural generation — unseeded by default, reproducible on demand.\n *\n * ---\n *\n * ### Layer 1 — stream primitive (portfolio parity)\n *\n * - {@link mulberry32} — fast seeded PRNG; returns a `() => number` closure yielding\n *   floats in `[0, 1)`. Same algorithm as Gotham/Water on the portfolio site.\n *\n * ### Seed mixing\n *\n * - {@link splitmix32} — mixer only, **not** a stream. Maps one 32-bit value to another.\n * - {@link deriveSubSeed} — `splitmix32(masterSeed ^ salt)`. Fan one user-facing master\n *   seed into independent sub-streams per subsystem. Use stable hex salts per domain\n *   (`0x101` books, `0x202` fog, `0x303` windows, …) instead of `seed + n` offsets.\n *\n * ### Layer 2 — library ergonomics\n *\n * - {@link createRandom} — **no seed** → wraps `Math.random()`, unique every runtime\n *   (showcase default). **With seed** → {@link mulberry32} stream, same seed ⇒ same sequence.\n * - Returns a {@link RandomSource}: `next`, `float`, `int`, `pick`, `boolean`, `skewMax`, `skewMin`.\n * - {@link Random} namespace — grouped exports, same API as standalone functions (like {@link Easing}).\n *\n * ### Layer 3 — {@link RandomNumberUtils}\n *\n * Existing helpers (`randomFloat`, `randomSkewMax`, …) accept an optional\n * {@link RandomSource} as their last argument. Omit it for unseeded default behavior.\n *\n * ---\n *\n * @example Unique runtime (default)\n * ```ts\n * const rng = createRandom();\n * rng.float(0, 10); // different every page load\n * ```\n *\n * @example Reproducible layout with sub-seeds\n * ```ts\n * const master = 1337;\n * const books = createRandom(deriveSubSeed(master, 0x101));\n * const fog = createRandom(deriveSubSeed(master, 0x202));\n * ```\n *\n * @example Namespace import\n * ```ts\n * const rng = Random.create(deriveSubSeed(1337, 0x101));\n * ```\n */\n\n/** Callable stream returning floats in [0, 1). */\nexport type RandomStream = () => number;\n\n/**\n * Random source — a stream plus distribution helpers.\n * Returned by {@link createRandom}; also accepted by {@link RandomNumberUtils}.\n */\nexport interface RandomSource {\n  /** `true` when backed by {@link mulberry32}; `false` when using `Math.random()`. */\n  readonly seeded: boolean;\n  /** Next float in [0, 1). */\n  next(): number;\n  /** Float in [min, max). */\n  float(min?: number, max?: number): number;\n  /** Integer in [min, max] (inclusive). */\n  int(min?: number, max?: number): number;\n  /** Uniform element from a non-empty array. */\n  pick<T>(arr: readonly T[]): T;\n  /**\n   * Element from a non-empty array, chosen with probability proportional to `weights[i]`.\n   *\n   * The weighted sibling of {@link pick}. A palette of styles that appear at different rates — a\n   * cemetery that is mostly plain stones with the occasional monument — is a weighted draw, not a\n   * uniform one. Negative weights count as zero; if every weight is zero it falls back to uniform.\n   */\n  weighted<T>(arr: readonly T[], weights: readonly number[]): T;\n  /** `true` with given probability (default 0.5). */\n  boolean(probability?: number): boolean;\n  /**\n   * Skew toward `max`. **`exponent` is the bias strength — higher pulls harder;** `1` is uniform, and\n   * `< 1` reverses (piling toward `min` instead). Defaults to `2`. Mirrors {@link randomSkewMax}.\n   */\n  skewMax(exponent?: number, min?: number, max?: number): number;\n  /**\n   * Skew toward `min` — the mirror of {@link skewMax}. **Higher `exponent` = stronger bias;** `1` is\n   * uniform, `< 1` reverses. Defaults to `2`. Mirrors {@link randomSkewMin}.\n   */\n  skewMin(exponent?: number, min?: number, max?: number): number;\n  /**\n   * Skew toward the CENTER of the range — the symmetric sibling of {@link skewMax} / {@link skewMin}.\n   *\n   * Small deviations from the middle are common, large ones rare, both directions equally likely: a\n   * gentle jitter with the occasional outlier. Same convention as its siblings — **higher `exponent` =\n   * stronger** pull to the center; `1` is uniform, `< 1` reverses (piling toward the edges). Defaults to\n   * `2`. The full range is still reached, just seldom.\n   */\n  skewCenter(exponent?: number, min?: number, max?: number): number;\n}\n\n/**\n * Fast seeded PRNG — portfolio Gotham/Water parity.\n * Returns a closure yielding floats in [0, 1).\n */\nexport function mulberry32(seed: number): RandomStream {\n  let a = seed >>> 0;\n  return () => {\n    a |= 0;\n    a = (a + 0x6d2b79f5) | 0;\n    let t = Math.imul(a ^ (a >>> 15), 1 | a);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\n/**\n * Seed mixer — maps one 32-bit value to another well-distributed value.\n * Use with {@link deriveSubSeed}, not as a drop-in stream replacement for\n * {@link mulberry32}.\n */\nexport function splitmix32(seed: number): number {\n  let z = (seed >>> 0) + 0x9e3779b9;\n  z = Math.imul(z ^ (z >>> 16), 0x85ebca6b);\n  z ^= z >>> 13;\n  z = Math.imul(z ^ (z >>> 16), 0xc2b2ae35);\n  return (z ^ (z >>> 16)) >>> 0;\n}\n\n/**\n * Derive an independent sub-stream seed from a master seed and domain salt.\n *\n * XOR the salt before mixing so each subsystem gets its own mulberry32 stream\n * without sequential `seed + 1` collision risk. Use stable hex constants per\n * domain (`0x101` books, `0x202` fog, `0x303` windows, …).\n *\n * @example\n * ```ts\n * const master = 1337;\n * const bookRng = createRandom(deriveSubSeed(master, 0x101));\n * const fogRng = createRandom(deriveSubSeed(master, 0x202));\n * ```\n */\nexport function deriveSubSeed(masterSeed: number, salt: number): number {\n  return splitmix32((masterSeed >>> 0) ^ (salt >>> 0));\n}\n\nfunction buildSource(stream: RandomStream, seeded: boolean): RandomSource {\n  return {\n    seeded,\n    next: stream,\n    float(min = 0, max = 1) {\n      return min + (max - min) * stream();\n    },\n    int(min = 0, max = 1) {\n      return Math.floor(this.float(min, max + 1));\n    },\n    pick<T>(arr: readonly T[]): T {\n      if (!arr.length) throw new Error(\"RandomSource.pick() requires a non-empty array\");\n      return arr[this.int(0, arr.length - 1)]!;\n    },\n    weighted<T>(arr: readonly T[], weights: readonly number[]): T {\n      if (!arr.length) throw new Error(\"RandomSource.weighted() requires a non-empty array\");\n      if (weights.length !== arr.length) {\n        throw new Error(\"RandomSource.weighted() needs one weight per element\");\n      }\n      let total = 0;\n      for (const w of weights) total += Math.max(0, w);\n      if (total <= 0) return arr[this.int(0, arr.length - 1)]!; // all zero — no signal, so uniform\n\n      let r = stream() * total;\n      for (let i = 0; i < arr.length; i++) {\n        r -= Math.max(0, weights[i]!);\n        if (r < 0) return arr[i]!;\n      }\n      return arr[arr.length - 1]!; // rounding slop only\n    },\n    boolean(probability = 0.5) {\n      return stream() < probability;\n    },\n    skewMax(exponent = 2, min = 0, max = 1) {\n      // Higher `exponent` = stronger. `pow(u, 1/exp)` with exp > 1 raises `u` toward 1 → toward `max`.\n      return min + (max - min) * Math.pow(stream(), 1 / exponent);\n    },\n    skewMin(exponent = 2, min = 0, max = 1) {\n      // The mirror of skewMax: flip the RESULT, not the input, so it piles toward `min`.\n      return min + (max - min) * (1 - Math.pow(stream(), 1 / exponent));\n    },\n    skewCenter(exponent = 2, min = 0, max = 1) {\n      // Draw once in [-1, 1), then shrink the MAGNITUDE toward 0 while keeping the sign, so the result\n      // clusters at the midpoint. Higher `exponent` pulls harder; `1` is uniform (same value as `float`).\n      const center = (min + max) / 2;\n      const half = (max - min) / 2;\n      const t = stream() * 2 - 1;\n      return center + half * Math.sign(t) * Math.pow(Math.abs(t), exponent);\n    },\n  };\n}\n\n/**\n * Create a random source.\n *\n * - **No seed** — wraps `Math.random()`. Unique every runtime; default for examples.\n * - **With seed** — {@link mulberry32} stream. Same seed ⇒ same sequence.\n */\nexport function createRandom(seed?: number): RandomSource {\n  if (seed === undefined) return buildSource(Math.random, false);\n  return buildSource(mulberry32(seed >>> 0), true);\n}\n\n/** Float in [min, max) from any stream — website `range()` parity. */\nexport function randomRange(stream: RandomStream, min: number, max: number): number {\n  return min + (max - min) * stream();\n}\n\n/** Pick from a non-empty array using any stream — website `pick()` parity. */\nexport function randomPick<T>(stream: RandomStream, arr: readonly T[]): T {\n  if (!arr.length) throw new Error(\"randomPick() requires a non-empty array\");\n  return arr[Math.floor(stream() * arr.length)]!;\n}\n\n/** Weighted pick from a non-empty array using any stream — `weights[i]` is the relative chance of `arr[i]`. */\nexport function randomWeighted<T>(stream: RandomStream, arr: readonly T[], weights: readonly number[]): T {\n  if (!arr.length) throw new Error(\"randomWeighted() requires a non-empty array\");\n  if (weights.length !== arr.length) throw new Error(\"randomWeighted() needs one weight per element\");\n  let total = 0;\n  for (const w of weights) total += Math.max(0, w);\n  if (total <= 0) return arr[Math.floor(stream() * arr.length)]!;\n  let r = stream() * total;\n  for (let i = 0; i < arr.length; i++) {\n    r -= Math.max(0, weights[i]!);\n    if (r < 0) return arr[i]!;\n  }\n  return arr[arr.length - 1]!;\n}\n\n/**\n * Grouped exports — same function API, namespace import like {@link Easing}.\n *\n * @example\n * ```ts\n * import { Random } from \"three-low-poly\";\n * const rng = Random.create(deriveSubSeed(1337, 0x101));\n * ```\n */\nexport const Random = {\n  create: createRandom,\n  mulberry32,\n  splitmix32,\n  deriveSubSeed,\n  range: randomRange,\n  pick: randomPick,\n  weighted: randomWeighted,\n} as const;","import { createRandom, type RandomSource } from \"./Random\";\n\n/** Unseeded default — unique each runtime. Pass a {@link RandomSource} to override. */\nconst defaultSource = createRandom();\n\n/**\n * Generates a random number between `min` and `max`.\n */\nexport function randomFloat(min = 0, max = 1, source: RandomSource = defaultSource) {\n  return source.float(min, max);\n}\n\n/**\n * Generates a random integer between `min` and `max`.\n */\nexport function randomInteger(min = 0, max = 1, source: RandomSource = defaultSource) {\n  return source.int(min, max);\n}\n\n/**\n * Random number in `[min, max]` skewed toward `max`. See {@link RandomSource.skewMax}.\n *\n * `exponent` is the bias strength — **higher skews harder** toward `max`; `1` is uniform, `< 1` reverses.\n * Defaults to `2`.\n */\nexport function randomSkewMax(exponent = 2, min = 0, max = 1, source: RandomSource = defaultSource) {\n  return source.skewMax(exponent, min, max);\n}\n\n/**\n * Random number in `[min, max]` skewed toward `min`. See {@link RandomSource.skewMin}.\n *\n * `exponent` is the bias strength — **higher skews harder** toward `min`; `1` is uniform, `< 1` reverses.\n * Defaults to `2`.\n */\nexport function randomSkewMin(exponent = 2, min = 0, max = 1, source: RandomSource = defaultSource) {\n  return source.skewMin(exponent, min, max);\n}\n\n/**\n * Random number in `[min, max]` skewed toward the CENTER. See {@link RandomSource.skewCenter}.\n *\n * `exponent` is the bias strength — **higher clusters tighter** to the middle; `1` is uniform, `< 1`\n * reverses (toward the edges). Defaults to `2`.\n */\nexport function randomSkewCenter(exponent = 2, min = 0, max = 1, source: RandomSource = defaultSource) {\n  return source.skewCenter(exponent, min, max);\n}\n","import {\n  AdditiveBlending,\n  Color,\n  ColorRepresentation,\n  DynamicDrawUsage,\n  InstancedMesh,\n  Material,\n  MeshBasicMaterial,\n  Object3D,\n  SphereGeometry,\n} from \"three\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\nexport interface DustMotesEffectOptions {\n  /** Number of dust instances. Defaults to `150`. */\n  count?: number;\n  /** Horizontal spread (world units). Defaults to `12`. */\n  width?: number;\n  /** Vertical spawn span (world units). Defaults to `8`. */\n  height?: number;\n  /** Depth spread (world units). Defaults to `12`. */\n  depth?: number;\n  /** World Y of the volume floor; motes respawn at the top after settling past it. Defaults to `0`. */\n  floorY?: number;\n  /** Speck tint. Defaults to `#aebfe6`. */\n  color?: ColorRepresentation;\n  /** Speck radius (world units). Defaults to `0.02`. */\n  radius?: number;\n  /** Base material opacity. Defaults to `0.9`. */\n  opacity?: number;\n  /** Override the default additive speck material. */\n  material?: Material;\n  /** Minimum settle (fall) speed (units/s). Defaults to `0.1`. */\n  settleMin?: number;\n  /** Maximum settle (fall) speed (units/s). Defaults to `0.35`. */\n  settleMax?: number;\n  /** Lateral waft amplitude (units/s). Defaults to `0.08`. */\n  waft?: number;\n  /** Smallest twinkle scale multiplier. Defaults to `0.6`. */\n  scaleMin?: number;\n  /** Largest twinkle scale multiplier. Defaults to `1.2`. */\n  scaleMax?: number;\n  /** Minimum twinkle frequency (rad/s). Defaults to `0.7`. */\n  twinkleMin?: number;\n  /** Maximum twinkle frequency (rad/s). Defaults to `1.6`. */\n  twinkleMax?: number;\n}\n\n/**\n * Fine dust drifting through a lit interior — tiny additive specks slowly\n * settling and wafting, twinkling as they catch the light and all but vanishing\n * in shadow. This is the thing that sells a light shaft as volumetric. Additive\n * spheres read the same from any angle, so no billboarding is needed.\n *\n * Call {@link DustMotesEffect.update} each frame with elapsed time in seconds.\n *\n * @example\n * ```typescript\n * const dust = new DustMotesEffect({\n *   count: 150,\n *   width: 8,\n *   height: 9,\n *   depth: 8,\n *   color: \"#aebfe6\",\n * });\n * scene.add(dust);\n *\n * onFrame((dt) => dust.update(dt));\n * ```\n */\nexport class DustMotesEffect extends InstancedMesh {\n  private readonly width: number;\n  private readonly height: number;\n  private readonly depth: number;\n  private readonly floorY: number;\n  private readonly waft: number;\n  private readonly scaleMin: number;\n  private readonly scaleMax: number;\n  private readonly px: Float32Array;\n  private readonly py: Float32Array;\n  private readonly pz: Float32Array;\n  private readonly settle: Float32Array;\n  private readonly twinkle: Float32Array;\n  private readonly phase: Float32Array;\n  private readonly dummy = new Object3D();\n  private clock = 0;\n\n  constructor(options: DustMotesEffectOptions = {}) {\n    const {\n      count = 150,\n      width = 12,\n      height = 8,\n      depth = 12,\n      floorY = 0,\n      color = \"#aebfe6\",\n      radius = 0.02,\n      opacity = 0.9,\n      material,\n      settleMin = 0.1,\n      settleMax = 0.35,\n      waft = 0.08,\n      scaleMin = 0.6,\n      scaleMax = 1.2,\n      twinkleMin = 0.7,\n      twinkleMax = 1.6,\n    } = options;\n\n    const dustMaterial =\n      material ??\n      new MeshBasicMaterial({\n        color: new Color(color),\n        transparent: true,\n        opacity,\n        blending: AdditiveBlending,\n        depthWrite: false,\n        toneMapped: false,\n      });\n\n    super(new SphereGeometry(radius, 5, 5), dustMaterial, count);\n    this.instanceMatrix.setUsage(DynamicDrawUsage);\n    this.frustumCulled = false;\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n    this.floorY = floorY;\n    this.waft = waft;\n    this.scaleMin = scaleMin;\n    this.scaleMax = scaleMax;\n\n    this.px = new Float32Array(count);\n    this.py = new Float32Array(count);\n    this.pz = new Float32Array(count);\n    this.settle = new Float32Array(count);\n    this.twinkle = new Float32Array(count);\n    this.phase = new Float32Array(count);\n\n    for (let i = 0; i < count; i++) {\n      this.respawn(i, true);\n      this.settle[i] = randomFloat(settleMin, settleMax);\n      this.twinkle[i] = randomFloat(twinkleMin, twinkleMax);\n      this.phase[i] = randomFloat(0, Math.PI * 2);\n    }\n\n    this.writeMatrices();\n  }\n\n  /**\n   * Advance the drift and twinkle. Pass elapsed frame time in seconds.\n   */\n  update(dt: number): void {\n    this.clock += dt;\n\n    for (let i = 0; i < this.count; i++) {\n      // Slow settle plus a gentle lateral waft on both axes so the field reads\n      // as air movement from any viewing angle, not a single-axis slide.\n      this.py[i] -= this.settle[i] * dt;\n      this.px[i] += Math.sin(this.clock * 0.3 + this.phase[i]) * this.waft * dt;\n      this.pz[i] += Math.cos(this.clock * 0.24 + this.phase[i]) * this.waft * dt;\n\n      if (this.py[i] < this.floorY) this.respawn(i, false);\n    }\n\n    this.writeMatrices();\n  }\n\n  /** Release geometry and materials held by the field. */\n  dispose(): this {\n    this.geometry.dispose();\n    const materials = Array.isArray(this.material) ? this.material : [this.material];\n    for (const entry of materials) entry.dispose();\n    return this;\n  }\n\n  private respawn(index: number, randomHeight: boolean): void {\n    this.px[index] = randomFloat(-this.width * 0.5, this.width * 0.5);\n    this.pz[index] = randomFloat(-this.depth * 0.5, this.depth * 0.5);\n    this.py[index] = randomHeight\n      ? this.floorY + randomFloat(0, this.height)\n      : this.floorY + this.height;\n  }\n\n  private writeMatrices(): void {\n    const d = this.dummy;\n    for (let i = 0; i < this.count; i++) {\n      const t = Math.abs(Math.sin(this.clock * this.twinkle[i] + this.phase[i]));\n      const s = this.scaleMin + (this.scaleMax - this.scaleMin) * t;\n      d.position.set(this.px[i], this.py[i], this.pz[i]);\n      d.scale.setScalar(s);\n      d.updateMatrix();\n      this.setMatrixAt(i, d.matrix);\n    }\n    this.instanceMatrix.needsUpdate = true;\n  }\n}\n","import {\n  BufferGeometry,\n  ColorRepresentation,\n  DynamicDrawUsage,\n  InstancedMesh,\n  Material,\n  MeshStandardMaterial,\n  Object3D,\n  SphereGeometry,\n} from \"three\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\nexport interface EffervescenceEffectOptions {\n  /** Override bubble geometry. Defaults to a small `SphereGeometry`. */\n  geometry?: BufferGeometry;\n  /** Override the default bubble material. */\n  material?: Material;\n  /** Number of bubble instances. Defaults to `24`. */\n  count?: number;\n  /** Horizontal spread (world units). Defaults to `1.5`. */\n  width?: number;\n  /** Vertical column height (world units). Defaults to `3`. */\n  height?: number;\n  /** Depth spread (world units). Defaults to `1.5`. */\n  depth?: number;\n  /**\n   * Horizontal spawn inset (0–1). Scales width/depth spawn area inward so a\n   * square volume fits round vessels. `1` = full box; `0.88` (default) trims corners.\n   */\n  spread?: number;\n  /** World Y where bubbles spawn when they recycle. Defaults to `0`. */\n  baseY?: number;\n  /** Minimum rise speed (units/s). Defaults to `0.35`. */\n  speedMin?: number;\n  /** Maximum rise speed (units/s). Defaults to `0.85`. */\n  speedMax?: number;\n  /** Bubble tint when using the default material. Defaults to `0xffffff`. */\n  color?: ColorRepresentation;\n  /** Default material opacity. Defaults to `0.6`. */\n  opacity?: number;\n  /** Default material emissive intensity. Defaults to `0`. */\n  emissiveIntensity?: number;\n}\n\n/**\n * Carbonation bubbles rising through a bounded volume — seltzer, soda, or\n * brewing liquid. Each instance drifts upward at its own speed and respawns\n * near {@link EffervescenceEffectOptions.baseY} after reaching the top.\n *\n * Spawn positions use a square footprint (`width` × `depth`). {@link EffervescenceEffectOptions.spread}\n * pulls that box inward so round jars do not get occasional corner outliers.\n * Position and scale the effect to sit inside a jar, flask, or panel viewport.\n * Call {@link EffervescenceEffect.update} each frame with elapsed time in seconds.\n *\n * @example\n * ```typescript\n * const fizz = new EffervescenceEffect({ width: 1.2, height: 2.5, count: 30 });\n * fizz.position.set(0, 1.2, 0);\n * scene.add(fizz);\n *\n * onFrame((dt) => fizz.update(dt));\n * ```\n */\nexport class EffervescenceEffect extends InstancedMesh {\n  private readonly width: number;\n  private readonly height: number;\n  private readonly depth: number;\n  private readonly baseY: number;\n  private readonly spread: number;\n  private readonly px: Float32Array;\n  private readonly py: Float32Array;\n  private readonly pz: Float32Array;\n  private readonly speed: Float32Array;\n  private readonly dummy = new Object3D();\n\n  constructor(options: EffervescenceEffectOptions = {}) {\n    const {\n      count = 24,\n      width = 1.5,\n      height = 3,\n      depth = 1.5,\n      spread = 0.88,\n      baseY = 0,\n      speedMin = 0.35,\n      speedMax = 0.85,\n      color = 0xffffff,\n      opacity = 0.6,\n      emissiveIntensity = 0,\n      geometry = new SphereGeometry(0.08, 6, 6),\n      material,\n    } = options;\n\n    const bubbleMaterial =\n      material ??\n      new MeshStandardMaterial({\n        color,\n        emissive: color,\n        emissiveIntensity,\n        transparent: true,\n        opacity,\n        roughness: 0.3,\n        metalness: 0.1,\n      });\n\n    super(geometry, bubbleMaterial, count);\n    this.instanceMatrix.setUsage(DynamicDrawUsage);\n    this.frustumCulled = false;\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n    this.baseY = baseY;\n    this.spread = Math.min(1, Math.max(0, spread));\n\n    this.px = new Float32Array(count);\n    this.py = new Float32Array(count);\n    this.pz = new Float32Array(count);\n    this.speed = new Float32Array(count);\n\n    for (let i = 0; i < count; i++) {\n      this.respawn(i, true);\n      this.speed[i] = randomFloat(speedMin, speedMax);\n    }\n\n    this.writeMatrices();\n  }\n\n  /**\n   * Advance bubble positions. Pass elapsed frame time in seconds (e.g. from\n   * `createScene`'s `onFrame` callback).\n   */\n  update(dt: number): void {\n    const ceiling = this.baseY + this.height;\n    for (let i = 0; i < this.count; i++) {\n      this.py[i] += this.speed[i] * dt;\n      if (this.py[i] > ceiling) this.respawn(i, false);\n    }\n    this.writeMatrices();\n  }\n\n  /** Release geometry and materials held by the field. */\n  dispose(): this {\n    this.geometry.dispose();\n    const materials = Array.isArray(this.material) ? this.material : [this.material];\n    for (const entry of materials) entry.dispose();\n    return this;\n  }\n\n  private respawn(index: number, randomHeight: boolean): void {\n    const halfX = this.width * 0.5 * this.spread;\n    const halfZ = this.depth * 0.5 * this.spread;\n    this.px[index] = randomFloat(-halfX, halfX);\n    this.pz[index] = randomFloat(-halfZ, halfZ);\n    this.py[index] = randomHeight\n      ? this.baseY + Math.random() * this.height\n      : this.baseY + randomFloat(0, this.height * 0.15);\n  }\n\n  private writeMatrices(): void {\n    const d = this.dummy;\n    for (let i = 0; i < this.count; i++) {\n      d.position.set(this.px[i], this.py[i], this.pz[i]);\n      d.updateMatrix();\n      this.setMatrixAt(i, d.matrix);\n    }\n    this.instanceMatrix.needsUpdate = true;\n  }\n}","import { MeshLambertMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial } from \"three\";\n\nexport type EmissivePulseMaterial =\n  | MeshStandardMaterial\n  | MeshPhysicalMaterial\n  | MeshLambertMaterial\n  | MeshPhongMaterial;\n\nexport interface EmissivePulseEffectOptions {\n  /** Material whose `emissiveIntensity` will oscillate. Must have emissive set. */\n  material: EmissivePulseMaterial;\n  /**\n   * Pulse frequency in radians per second (`abs(sin(elapsed * speed))`).\n   * Defaults to `2`. Primary differentiator when many LEDs share one color.\n   */\n  speed?: number;\n  /** Lower bound of the pulse. Defaults to `0.2`. */\n  minIntensity?: number;\n  /** Upper bound of the pulse. Defaults to `0.8`. */\n  maxIntensity?: number;\n}\n\n/**\n * Smooth emissive pulse for fake LEDs — animates `emissiveIntensity` on an existing\n * mesh material without adding geometry or scene lights.\n *\n * Attach it to any mesh whose material has an `emissive` — place the mesh, pass its\n * material here, call `update(dt)` each frame. For a bank of same-color LEDs, give\n * each instance a different `speed` so they breathe out of sync.\n *\n * Note the effect drives the MATERIAL, not the mesh. A bank of LEDs can therefore\n * share one geometry, but each needs its own material, or they all pulse as one.\n *\n * @example\n * ```ts\n * const led = new Mesh(\n *   new SphereGeometry(0.05, 8, 8),\n *   new MeshStandardMaterial({ color: 0xffc7c7, emissive: 0xff0000 }),\n * );\n * scene.add(led);\n *\n * const pulse = new EmissivePulseEffect({\n *   material: led.material,\n *   speed: 1.4,\n *   minIntensity: 0.05,\n *   maxIntensity: 2,\n * });\n *\n * onFrame((dt) => pulse.update(dt));\n * ```\n */\nexport class EmissivePulseEffect {\n  public speed: number;\n  public minIntensity: number;\n  public maxIntensity: number;\n  public readonly material: EmissivePulseMaterial;\n\n  private elapsed = 0;\n\n  constructor({\n    material,\n    speed = 2,\n    maxIntensity = 0.8,\n    minIntensity = 0.2,\n  }: EmissivePulseEffectOptions) {\n    this.material = material;\n    this.speed = speed;\n    this.maxIntensity = maxIntensity;\n    this.minIntensity = minIntensity;\n  }\n\n  /** Advance the pulse by `dt` seconds (elapsed time, not frame index). */\n  update(dt: number): void {\n    this.elapsed += dt;\n    const t = Math.abs(Math.sin(this.elapsed * this.speed));\n    this.material.emissiveIntensity = this.minIntensity + t * (this.maxIntensity - this.minIntensity);\n  }\n}","import { Color, ColorRepresentation, MeshBasicMaterial, PointLight } from \"three\";\nimport { GlowHalo } from \"./GlowHalo\";\n\nexport interface FlameFlickerEffectOptions {\n  /**\n   * Desyncs multiple instances. Defaults to `Math.random() * 100`.\n   */\n  seed?: number;\n  /** Optional real light — use sparingly; casts on geometry but counts against light limits. */\n  light?: PointLight;\n  /** Base intensity when `light` is set. Defaults to `4`. */\n  lightIntensity?: number;\n  /**\n   * Optional flame core material (e.g. `MeshBasicMaterial` on a small sphere).\n   *\n   * TODO: `update()` mutates `flame.color` in place, so several flickers sharing one material will\n   * fight over it and gutter in lockstep. Give each flame its own material, or move the tint off\n   * the shared material. Worth a runtime warning or a doc note at minimum.\n   */\n  flame?: MeshBasicMaterial;\n  /** Flame tint when `flame` is set. Defaults to `0xffaa44`. */\n  flameColor?: ColorRepresentation;\n  /** Optional {@link GlowHalo}; opacity scales with the flicker factor. */\n  halo?: GlowHalo;\n  /**\n   * Halo opacity multiplier at flicker peak. Defaults to `0.75`.\n   *\n   * TODO: this duplicates {@link GlowHalo}'s own `opacity`. Once a flicker drives a halo, whatever\n   * `opacity` the halo was constructed with is overwritten every frame, so two places own one\n   * number. Decide which is authoritative — probably read the halo's own opacity as the peak.\n   */\n  haloOpacity?: number;\n}\n\n/**\n * Calm flame flicker from detuned sines — drives optional real lights, flame\n * materials, and {@link GlowHalo} opacity in sync. Time-based (`update(dt)`).\n *\n * Use without a `light` for mass candlefields (halo + bloom only). Add a\n * `light` for hero sconces that must cast on walls and props.\n */\n/**\n * Flicker factor from a sum of detuned sines — roughly `0.5`–`1.1`, never repeating because the two\n * frequencies are incommensurate.\n *\n * A plain function on purpose: **mass populations want one loop over an array, not N flicker objects each\n * carrying a clock.** {@link FlameFlickerEffect} is the convenience wrapper for a single flame; anything\n * driving many flames — a chandelier, a votive rack — should call this directly with a per-item `phase`.\n *\n * @param elapsed - Seconds.\n * @param phase - Per-item offset, so a population doesn't gutter in unison.\n */\nexport function flameFlicker(elapsed: number, phase = 0): number {\n  return 0.8 + 0.2 * Math.sin(elapsed * 9 + phase) + 0.08 * Math.sin(elapsed * 23 + phase * 2);\n}\n\nexport class FlameFlickerEffect {\n  public readonly seed: number;\n  public light?: PointLight;\n  public lightIntensity: number;\n  public flame?: MeshBasicMaterial;\n  public halo?: GlowHalo;\n  public haloOpacity: number;\n\n  private readonly flameColor = new Color();\n  private elapsed = 0;\n\n  constructor({\n    seed = Math.random() * 100,\n    light,\n    lightIntensity = 4,\n    flame,\n    flameColor = 0xffaa44,\n    halo,\n    haloOpacity = 0.75,\n  }: FlameFlickerEffectOptions = {}) {\n    this.seed = seed;\n    this.light = light;\n    this.lightIntensity = lightIntensity;\n    this.flame = flame;\n    this.flameColor.set(flameColor);\n    this.halo = halo;\n    this.haloOpacity = haloOpacity;\n  }\n\n  /**\n   * Flicker factor at elapsed time — lazy sum of detuned sines (~0.5–1.1).\n   */\n  static factor(elapsed: number, seed: number): number {\n    return flameFlicker(elapsed, seed);\n  }\n\n  /** Current flicker factor after the last `update`. */\n  get level(): number {\n    return FlameFlickerEffect.factor(this.elapsed, this.seed);\n  }\n\n  update(dt: number): void {\n    this.elapsed += dt;\n    const f = FlameFlickerEffect.factor(this.elapsed, this.seed);\n\n    if (this.light) {\n      this.light.intensity = this.lightIntensity * f;\n    }\n\n    if (this.flame) {\n      this.flame.color.copy(this.flameColor).multiplyScalar(0.8 + 0.4 * f);\n    }\n\n    if (this.halo) {\n      this.halo.setOpacity(this.haloOpacity * f);\n    }\n  }\n\n}","import {\n  ClampToEdgeWrapping,\n  Color,\n  ColorRepresentation,\n  DataTexture,\n  LinearFilter,\n  LinearMipmapLinearFilter,\n  RGBAFormat,\n  SRGBColorSpace,\n  UnsignedByteType,\n} from \"three\";\nimport { Easing, type EasingFunction } from \"../constants/Easing\";\n\n/** One stop of a radial falloff. */\nexport interface RadialGradientStop {\n  /** Distance from the core: `0` at the center, `1` at the rim. */\n  offset: number;\n  color: ColorRepresentation;\n  /** Opacity at this stop, `0`–`1`. Defaults to `1`. */\n  alpha?: number;\n}\n\nexport interface RadialGradientTextureOptions {\n  /** Falloff from core to rim. Sorted internally, so declaration order doesn't matter. */\n  stops: RadialGradientStop[];\n  /**\n   * Edge length in texels. Defaults to `128`, which is a power of two (so mipmaps are exact) and\n   * ample for a smooth ramp: bilinear filtering interpolates *between* texels, so upsampling a\n   * gradient loses nothing perceptible. Banding in a glow comes from the 8-bit framebuffer, not\n   * from texture size, so raising this rarely helps.\n   */\n  size?: number;\n  /**\n   * How each pair of stops is interpolated. Defaults to {@link Easing.linear}, which matches a\n   * canvas gradient exactly.\n   *\n   * Linear interpolation leaves a **slope discontinuity** at every stop — brightness stays\n   * continuous, but its rate of change kinks. Human vision exaggerates precisely those kinks\n   * (Mach banding), so a linear ramp reads as having a faint ring at the rim and a hard edge where\n   * two glows overlap. {@link Easing.smoothstep} brings the derivative to zero at each stop, which\n   * removes the ring and softens the outer edge without changing overall brightness.\n   */\n  easing?: EasingFunction;\n  /**\n   * TODO: add an opt-in `dither` amount. A sub-1/255 ordered or blue-noise perturbation per texel\n   * breaks up the concentric plateaus an 8-bit framebuffer produces in a glow's faint tail — the one\n   * remaining lever on that banding, and it needs no shader, no DOM, and no renderer requirement.\n   * Note `Easing.smoothstep` makes the plateaus *wider* near the rim, so the two interact.\n   */\n}\n\ninterface ResolvedStop {\n  offset: number;\n  r: number;\n  g: number;\n  b: number;\n  a: number;\n}\n\n/**\n * A soft radial falloff as a {@link DataTexture} — for the additive cards that give a light source\n * its bloom of haze.\n *\n * Pixels are computed here in plain JavaScript, so unlike a `CanvasTexture` this needs **no DOM**\n * and builds fine headless or in a worker. And unlike a TSL node gradient it produces a standard\n * texture, so it runs on **either renderer** rather than requiring `WebGPURenderer` — which matters\n * for near-field glows (lanterns, candles, flames) that have no other reason to demand WebGPU.\n *\n * Stops interpolate in **sRGB**, matching what a canvas gradient does, and the texture is tagged\n * `SRGBColorSpace` so color management converts it correctly. Pass `easing` to soften the kink each\n * stop otherwise leaves in the falloff's slope.\n *\n * @example\n * ```typescript\n * const material = new MeshBasicMaterial({\n *   map: createRadialGradientTexture({\n *     stops: [\n *       { offset: 0, color: 0xffcd8c, alpha: 0.45 },\n *       { offset: 0.25, color: 0xff963c, alpha: 0.14 },\n *       { offset: 1, color: 0xff6e1e, alpha: 0 },\n *     ],\n *   }),\n *   blending: AdditiveBlending,\n *   transparent: true,\n *   depthWrite: false,\n * });\n * ```\n */\nexport const createRadialGradientTexture = ({\n  stops,\n  size = 128,\n  easing = Easing.linear,\n}: RadialGradientTextureOptions): DataTexture => {\n  if (stops.length === 0) throw new Error(\"createRadialGradientTexture requires at least one stop.\");\n\n  // Resolve to sRGB bytes once. `getHex` returns sRGB regardless of the working color space, so\n  // this stays correct whether or not color management is enabled.\n  const ordered: ResolvedStop[] = [...stops]\n    .sort((first, second) => first.offset - second.offset)\n    .map((stop) => {\n      const hex = new Color(stop.color).getHex(SRGBColorSpace);\n      return {\n        offset: stop.offset,\n        r: (hex >> 16) & 255,\n        g: (hex >> 8) & 255,\n        b: hex & 255,\n        a: Math.round((stop.alpha ?? 1) * 255),\n      };\n    });\n\n  const first = ordered[0]!;\n  const last = ordered[ordered.length - 1]!;\n  const data = new Uint8Array(size * size * 4);\n  const center = size / 2;\n\n  for (let y = 0; y < size; y++) {\n    for (let x = 0; x < size; x++) {\n      // Texel center, normalized so 1 lands on the inscribed circle. Corners exceed 1 and clamp to\n      // the final stop, which is why a fully transparent rim leaves no square edge.\n      const dx = (x + 0.5 - center) / center;\n      const dy = (y + 0.5 - center) / center;\n      const distance = Math.min(Math.sqrt(dx * dx + dy * dy), 1);\n\n      let low = first;\n      let high = last;\n      for (let i = 1; i < ordered.length; i++) {\n        if (distance <= ordered[i]!.offset) {\n          low = ordered[i - 1]!;\n          high = ordered[i]!;\n          break;\n        }\n      }\n\n      const span = high.offset - low.offset;\n      const raw = span <= 0 ? 0 : Math.min(Math.max((distance - low.offset) / span, 0), 1);\n      const t = easing(raw);\n\n      const stride = (y * size + x) * 4;\n      data[stride] = low.r + (high.r - low.r) * t;\n      data[stride + 1] = low.g + (high.g - low.g) * t;\n      data[stride + 2] = low.b + (high.b - low.b) * t;\n      data[stride + 3] = low.a + (high.a - low.a) * t;\n    }\n  }\n\n  const texture = new DataTexture(data, size, size, RGBAFormat, UnsignedByteType);\n  texture.colorSpace = SRGBColorSpace;\n  // `DataTexture` defaults both filters to `NearestFilter`, which would render a smooth ramp as\n  // visible blocks. Mipmaps cover the case where the card is minified far from the viewer.\n  texture.magFilter = LinearFilter;\n  texture.minFilter = LinearMipmapLinearFilter;\n  texture.generateMipmaps = true;\n  // Clamped so the transparent rim can't bleed across from the opposite edge.\n  texture.wrapS = ClampToEdgeWrapping;\n  texture.wrapT = ClampToEdgeWrapping;\n  texture.needsUpdate = true;\n\n  return texture;\n};\n","import { AdditiveBlending, Color, ColorRepresentation, DataTexture, Sprite, SpriteMaterial } from \"three\";\nimport { Easing } from \"../constants/Easing\";\nimport { createRadialGradientTexture, type RadialGradientStop } from \"../textures/radialGradient\";\n\n/**\n * Neutral falloff — white, so the tint lives entirely on the material. Alphas match the original\n * canvas ramp (`ff` / `aa` / `00`).\n */\nconst NEUTRAL_FALLOFF: RadialGradientStop[] = [\n  { offset: 0, color: 0xffffff, alpha: 1 },\n  { offset: 0.25, color: 0xffffff, alpha: 0.67 },\n  { offset: 1, color: 0xffffff, alpha: 0 },\n];\n\nlet sharedFalloff: DataTexture | undefined;\n\n/**\n * **The library's canonical glow falloff** — one cached texture, shared by everything that draws a glow.\n *\n * This exists so there is exactly one definition of what a glow looks like. A single {@link GlowHalo} and\n * a batched field of hundreds must be visually identical, and the only way to guarantee that is for both\n * to call this rather than each restating the same stops and easing. Duplicating the ramp is how a seam\n * appears.\n *\n * Colorless by design, so the tint lives on the material and one 64 KB texture serves any population and\n * any color. Never disposed — it is a module-level singleton other halos are still using.\n *\n * TODO: settle `smoothstep` vs `linear` here. Because everything now shares this ramp, the choice is a\n * library-wide aesthetic decision rather than a per-asset one. `smoothstep` softens the rim (it zeroes the\n * slope at each stop, which kills the Mach band a linear kink produces) but raises core and mid alpha, so\n * a dense arrangement merges into one mass instead of reading as distinct glows. Jason confirmed `linear`\n * reproduces his existing look and holds up better in a packed ring. Changing it here changes every glow —\n * which is the point, and why it needs deciding once rather than exposing an `easing` dial per asset.\n */\nexport function glowFalloffTexture(): DataTexture {\n  sharedFalloff ??= createRadialGradientTexture({ stops: NEUTRAL_FALLOFF, easing: Easing.smoothstep });\n  return sharedFalloff;\n}\n\nexport interface GlowHaloOptions {\n  /**\n   * Glow tint, multiplied over the falloff. Defaults to `0xffaa44` — or to white when `map` is\n   * supplied, so a colored texture passes through untouched.\n   *\n   * The default ramp is colorless, so with it this *is* the halo's color.\n   */\n  color?: ColorRepresentation;\n  /** Card edge length in world units. Defaults to `1.2`. */\n  size?: number;\n  /** Base opacity before flicker scaling. Defaults to `0.75`. */\n  opacity?: number;\n  /**\n   * Supply your own falloff texture instead of the shared default — typically one built with\n   * {@link createRadialGradientTexture} and **shared across many halos**, which is what makes a large\n   * population cheap: one texture, N materials.\n   *\n   * The caller owns it. {@link GlowHalo.dispose} will not release a texture it did not create.\n   *\n   * A supplied map may carry its own colors (a blue core inside a warm rim, say), so `color` defaults\n   * to white here — a tint multiplies, and multiply can only darken, never add a hue that isn't there.\n   */\n  map?: DataTexture;\n}\n\n/**\n * Soft glow card — reads as light without spending a `PointLight`.\n *\n * Lights are a **fixed budget**, capped independently of how much geometry you draw, and\n * exhausting the fragment-uniform space makes materials fail to compile outright rather than\n * degrade. Halos scale the other way: they are ordinary blended sprites, so hundreds cost hundreds\n * of cheap quads and no light slots at all. The usual arrangement is many halos plus one\n * real light per fixture, its intensity driven by the aggregate of the fakes.\n *\n * **It billboards itself.** `GlowHalo` *is* a `Sprite`, so it faces the viewer with no per-frame\n * call to forget, and every instance shares one internal quad geometry. Position and scale it\n * directly — `halo.position.copy(flame.position)`.\n *\n * **Tint is applied once, on the material.** The falloff texture is colorless and shared across\n * every default halo, so `setColor` is free and a rack of hundreds still holds a single texture.\n *\n * **Additive on purpose.** Light adds, and additive keeps overlaps *flat*: as one glow dims the next\n * brightens at the same rate, so their sum stays constant where they meet. Bright cores can clip at\n * 1.0 without an HDR target, which reads as a blown-out white center — acceptable, and the consumer's\n * to solve with tone mapping or bloom if they want to.\n *\n * A screen blend (`a + b - ab`) is tempting since it never clips, but its `-ab` term is largest where\n * two contributions are *equal* — the middle of every overlap — so it carves a shallow dark basin\n * exactly where glows meet. Measurably worse for a cluster of candles. `material` is public if you\n * want to try it anyway; screen also needs `premultipliedAlpha`, or the destination factor will refer\n * to the untinted texel and darken everything behind the card.\n *\n * **A card that intersects its fixture gets sliced along the intersection.** That seam is inherent to\n * representing glare as a world-space quad: real glare is a camera effect and spills *over* whatever\n * sits in front of the flame, while a quad occupying world space cannot. The levers are physical —\n * keep `size` modest relative to the fixture, and let a bloom pass (the consumer's choice) carry the\n * wide spread. `depthWrite` stays off so halos never occlude one another.\n *\n * @example\n * ```typescript\n * const halo = new GlowHalo({ color: 0xffaa44, size: 0.9 });\n * halo.position.set(0, 1.4, 0);\n * scene.add(halo);\n *\n * // Drive it from a flicker, or leave it steady.\n * halo.setOpacity(0.75 * flicker);\n * ```\n *\n * @see {@link FlameFlickerEffect} to modulate opacity, and {@link createRadialGradientTexture}\n * for the falloff itself.\n */\n// TODO: extract a reusable `GlowHaloField`. The pattern is already PROVEN in `VotiveRack` — one\n// `InstancedMesh` + `SpriteNodeMaterial`, `positionNode`/`scaleNode`/`colorNode` as instanced attributes,\n// and per-item flicker folded into color (identical to scaling opacity, because the blending is additive).\n// So this is no longer a question of feasibility, only of whether a second mass consumer appears to justify\n// lifting it out of the rack. Cost either way: screen-aligned instancing needs a node material, i.e.\n// WebGPU. N separate `GlowHalo`s stays correct for dozens.\nexport class GlowHalo extends Sprite {\n  declare material: SpriteMaterial;\n\n  constructor({ color, size = 1.2, opacity = 0.75, map }: GlowHaloOptions = {}) {\n    const texture = map ?? glowFalloffTexture();\n    // A supplied map may already be colored, so the tint falls back to identity rather than\n    // multiplying someone's blue core by warm orange. Multiply can only darken.\n    const tint = color ?? (map !== undefined ? 0xffffff : 0xffaa44);\n\n    super(\n      new SpriteMaterial({\n        map: texture,\n        // Single tint. Baking color into the ramp *and* setting it here squares the color, which\n        // is why glows used to read darker and more saturated than the value asked for.\n        color: new Color(tint),\n        transparent: true,\n        opacity,\n        blending: AdditiveBlending,\n        depthWrite: false,\n        toneMapped: false,\n        fog: false,\n      }),\n    );\n\n    this.scale.setScalar(size);\n  }\n\n  /** Set halo opacity (e.g. scaled each frame by {@link FlameFlickerEffect}). */\n  setOpacity(opacity: number): void {\n    this.material.opacity = opacity;\n  }\n\n  get opacity(): number {\n    return this.material.opacity;\n  }\n\n  /** Retint. Free — the falloff is colorless, so nothing is rebuilt. */\n  setColor(color: ColorRepresentation): void {\n    this.material.color.set(color);\n  }\n\n  /**\n   * Release the material. Textures are never released here — the shared default is still in use by other\n   * halos, and a supplied `map` belongs to whoever built it.\n   */\n  dispose(): void {\n    this.material.dispose();\n  }\n}\n","import {\n  CanvasTexture,\n  Color,\n  ColorRepresentation,\n  Mesh,\n  MeshBasicMaterial,\n  Object3D,\n  PlaneGeometry,\n  SRGBColorSpace,\n} from \"three\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\ntype Edge = \"n\" | \"s\" | \"e\" | \"w\";\n\ninterface FogPatch {\n  mesh: Mesh<PlaneGeometry, MeshBasicMaterial>;\n  driftX: number;\n  driftZ: number;\n  spin: number;\n  baseOpacity: number;\n  phase: number;\n  perimeter?: {\n    edge: Edge;\n    plotHalf: number;\n    terrainHalf: number;\n  };\n}\n\nexport interface GroundFogEffectOptions {\n  /** Interior mist cards scattered across the plot. Defaults to `14`. */\n  count?: number;\n  /** Half-extent of the interior scatter (world units). Defaults to `16`. */\n  area?: number;\n  /**\n   * Large cards hugging the plot perimeter — softens terrain cutoffs on the\n   * horizons opposite the camera. Defaults to `0` (interior only).\n   */\n  perimeterCount?: number;\n  /** Half-extent of the inner bounded plot (fence, wall, scene edge, etc.). Defaults to `12`. */\n  plotHalf?: number;\n  /** Terrain half-extent; perimeter cards spill outward toward this edge. Defaults to `16`. */\n  terrainHalf?: number;\n  /**\n   * Horizontal direction from plot center toward the camera. Perimeter patches\n   * concentrate on the opposite edges. Defaults to `{ x: 1, z: 1 }`.\n   */\n  cameraFacing?: { x: number; z: number };\n  /** Mist tint. Defaults to `#9fb0c8`. */\n  color?: ColorRepresentation;\n  /**\n   * Sample ground height at world (x, z). Defaults to flat `y = 0`.\n   * Portfolio graveyard uses undulating terrain via the same callback.\n   */\n  heightAt?: (x: number, z: number) => number;\n}\n\n/**\n * Creeping ground mist — soft horizontal cards drifting above the floor.\n * Interior patches wrap toroidally within `area`; optional perimeter patches\n * sit on/outside the fence on edges opposite the camera. One texture, one\n * `update(dt)` — perimeter is placement logic, not a separate effect.\n */\nexport class GroundFogEffect extends Object3D {\n  private readonly patches: FogPatch[] = [];\n  private readonly area: number;\n  private readonly heightAt: (x: number, z: number) => number;\n  private readonly texture: CanvasTexture;\n  private elapsed = 0;\n\n  constructor({\n    count = 14,\n    area = 16,\n    perimeterCount = 0,\n    plotHalf = 12,\n    terrainHalf = 16,\n    cameraFacing = { x: 1, z: 1 },\n    color = \"#9fb0c8\",\n    heightAt = () => 0,\n  }: GroundFogEffectOptions = {}) {\n    super();\n\n    this.area = area;\n    this.heightAt = heightAt;\n    this.texture = createFogTexture(color);\n    const focus = focusEdges(cameraFacing);\n\n    for (let i = 0; i < count; i++) {\n      this.patches.push(this.makeInteriorPatch());\n    }\n    for (let i = 0; i < perimeterCount; i++) {\n      this.patches.push(this.makePerimeterPatch(plotHalf, terrainHalf, focus));\n    }\n  }\n\n  update(dt: number): void {\n    this.elapsed += dt;\n\n    for (const patch of this.patches) {\n      const mesh = patch.mesh;\n      mesh.position.x += patch.driftX * dt;\n      mesh.position.z += patch.driftZ * dt;\n      mesh.rotation.z += patch.spin * dt;\n\n      if (patch.perimeter) {\n        constrainPerimeter(mesh, patch.perimeter.edge, patch.perimeter.plotHalf, patch.perimeter.terrainHalf);\n        mesh.position.y =\n          this.heightAt(mesh.position.x, mesh.position.z) +\n          0.15 +\n          0.35 * (0.5 + 0.5 * Math.sin(this.elapsed * 0.22 + patch.phase));\n      } else {\n        if (mesh.position.x > this.area) mesh.position.x -= this.area * 2;\n        if (mesh.position.x < -this.area) mesh.position.x += this.area * 2;\n        if (mesh.position.z > this.area) mesh.position.z -= this.area * 2;\n        if (mesh.position.z < -this.area) mesh.position.z += this.area * 2;\n      }\n\n      mesh.material.opacity = patch.baseOpacity * (0.6 + 0.4 * Math.sin(this.elapsed * 0.3 + patch.phase));\n    }\n  }\n\n  dispose(): void {\n    this.texture.dispose();\n    for (const patch of this.patches) {\n      patch.mesh.geometry.dispose();\n      patch.mesh.material.dispose();\n    }\n    this.patches.length = 0;\n    this.clear();\n  }\n\n  private makeInteriorPatch(): FogPatch {\n    const size = randomFloat(8, 16);\n    const baseOpacity = randomFloat(0.05, 0.16);\n    const x = randomFloat(-this.area, this.area);\n    const z = randomFloat(-this.area, this.area);\n    const mesh = this.makeMesh(size, baseOpacity);\n    mesh.position.set(x, this.heightAt(x, z) + randomFloat(0.3, 1.2), z);\n    this.add(mesh);\n\n    return {\n      mesh,\n      driftX: randomFloat(-0.15, 0.15),\n      driftZ: randomFloat(-0.15, 0.15),\n      spin: randomFloat(-0.03, 0.03),\n      baseOpacity,\n      phase: randomFloat(0, Math.PI * 2),\n    };\n  }\n\n  private makePerimeterPatch(plotHalf: number, terrainHalf: number, focus: Edge[]): FogPatch {\n    const edge = pickEdge(focus);\n    const { x, z, driftX, driftZ } = placeOnPerimeter(edge, plotHalf, terrainHalf);\n    const size = randomFloat(18, 30);\n    const baseOpacity = randomFloat(0.1, 0.26);\n    const mesh = this.makeMesh(size, baseOpacity);\n    mesh.position.set(x, this.heightAt(x, z) + randomFloat(0.15, 0.55), z);\n    this.add(mesh);\n\n    return {\n      mesh,\n      driftX,\n      driftZ,\n      spin: randomFloat(-0.012, 0.012),\n      baseOpacity,\n      phase: randomFloat(0, Math.PI * 2),\n      perimeter: { edge, plotHalf, terrainHalf },\n    };\n  }\n\n  private makeMesh(size: number, baseOpacity: number): Mesh<PlaneGeometry, MeshBasicMaterial> {\n    const mesh = new Mesh(\n      new PlaneGeometry(size, size),\n      new MeshBasicMaterial({\n        map: this.texture,\n        transparent: true,\n        depthWrite: false,\n        opacity: baseOpacity,\n        toneMapped: false,\n      }),\n    );\n    mesh.rotation.x = -Math.PI / 2;\n    mesh.rotation.z = randomFloat(0, Math.PI * 2);\n    return mesh;\n  }\n}\n\n/**\n * TODO: replace with `createRadialGradientTexture` from `../textures/radialGradient`. Two wins beyond\n * dropping the DOM dependency: build the ramp **colorless** and tint via `material.color`, which makes\n * retinting free (no texture rebuild) and lets every patch share one texture instead of one each.\n */\nfunction createFogTexture(color: ColorRepresentation): CanvasTexture {\n  const hex = `#${new Color(color).getHexString()}`;\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = canvas.height = 256;\n  const ctx = canvas.getContext(\"2d\")!;\n  const gradient = ctx.createRadialGradient(128, 128, 8, 128, 128, 128);\n  gradient.addColorStop(0, `${hex}dd`);\n  gradient.addColorStop(0.45, `${hex}66`);\n  gradient.addColorStop(1, `${hex}00`);\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, 256, 256);\n  const texture = new CanvasTexture(canvas);\n  texture.colorSpace = SRGBColorSpace;\n  return texture;\n}\n\nfunction focusEdges(facing: { x: number; z: number }): Edge[] {\n  const edges: Edge[] = [];\n  if (facing.z > 0) edges.push(\"n\");\n  if (facing.z < 0) edges.push(\"s\");\n  if (facing.x > 0) edges.push(\"w\");\n  if (facing.x < 0) edges.push(\"e\");\n  return edges.length ? edges : [\"n\", \"w\"];\n}\n\nfunction pickEdge(focus: Edge[]): Edge {\n  if (focus.length === 1) return focus[0]!;\n  const r = Math.random();\n  const hasN = focus.includes(\"n\");\n  const hasW = focus.includes(\"w\");\n  if (hasN && hasW) return r < 0.58 ? \"n\" : \"w\";\n  return focus[Math.floor(Math.random() * focus.length)]!;\n}\n\nfunction placeOnPerimeter(\n  edge: Edge,\n  plotHalf: number,\n  terrainHalf: number,\n): { x: number; z: number; driftX: number; driftZ: number } {\n  const spill = randomFloat(0.6, terrainHalf - plotHalf - 1.5);\n  const along = randomFloat(-terrainHalf + 3, terrainHalf - 3);\n  const alongSpeed = randomFloat(0.03, 0.09) * (Math.random() < 0.5 ? -1 : 1);\n  const outwardSpeed = randomFloat(0.01, 0.04);\n\n  switch (edge) {\n    case \"n\":\n      return { x: along, z: -plotHalf - spill, driftX: alongSpeed, driftZ: -outwardSpeed };\n    case \"s\":\n      return { x: along, z: plotHalf + spill, driftX: -alongSpeed, driftZ: outwardSpeed };\n    case \"e\":\n      return { x: plotHalf + spill, z: along, driftX: outwardSpeed, driftZ: -alongSpeed };\n    case \"w\":\n      return { x: -plotHalf - spill, z: along, driftX: -outwardSpeed, driftZ: alongSpeed };\n  }\n}\n\nfunction constrainPerimeter(mesh: Mesh, edge: Edge, plotHalf: number, terrainHalf: number): void {\n  const margin = 3;\n  const maxSpill = terrainHalf - 1;\n  const { x, z } = mesh.position;\n\n  switch (edge) {\n    case \"n\": {\n      mesh.position.z = clamp(z, -maxSpill, -plotHalf + 0.8);\n      if (x < -maxSpill + margin) mesh.position.x = maxSpill - margin;\n      if (x > maxSpill - margin) mesh.position.x = -maxSpill + margin;\n      break;\n    }\n    case \"s\": {\n      mesh.position.z = clamp(z, plotHalf - 0.8, maxSpill);\n      if (x < -maxSpill + margin) mesh.position.x = maxSpill - margin;\n      if (x > maxSpill - margin) mesh.position.x = -maxSpill + margin;\n      break;\n    }\n    case \"e\": {\n      mesh.position.x = clamp(x, plotHalf - 0.8, maxSpill);\n      if (z < -maxSpill + margin) mesh.position.z = maxSpill - margin;\n      if (z > maxSpill - margin) mesh.position.z = -maxSpill + margin;\n      break;\n    }\n    case \"w\": {\n      mesh.position.x = clamp(x, -maxSpill, -plotHalf + 0.8);\n      if (z < -maxSpill + margin) mesh.position.z = maxSpill - margin;\n      if (z > maxSpill - margin) mesh.position.z = -maxSpill + margin;\n      break;\n    }\n  }\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n  return Math.max(min, Math.min(max, v));\n}","import { DirectionalLight } from \"three\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\nexport interface LightningEffectOptions {\n  /** Directional light driven by lightning flashes. Start at intensity `0`. */\n  light: DirectionalLight;\n  /** Peak light intensity at full flash. Defaults to `12`. */\n  peak?: number;\n  /** Minimum seconds between strikes. Defaults to `3`. */\n  minGap?: number;\n  /** Maximum seconds between strikes. Defaults to `9`. */\n  maxGap?: number;\n}\n\n/**\n * Thunderstorm lightning that drives a {@link DirectionalLight} each frame.\n *\n * Rather than scheduling timeouts (which need careful teardown), each strike\n * enqueues two or three decaying intensity spikes a few frames apart — the\n * characteristic stutter of real lightning. Read {@link LightningEffect.level}\n * after {@link LightningEffect.update} to sync fog, sky, rain, or emissive\n * surfaces with the flash.\n *\n * @example\n * ```typescript\n * const bolt = new DirectionalLight(0xcdd8ff, 0);\n * bolt.position.set(5, 12, -8);\n * bolt.target.position.set(0, 0, 0);\n * scene.add(bolt, bolt.target);\n *\n * const storm = new LightningEffect({ light: bolt, peak: 12, minGap: 3, maxGap: 9 });\n *\n * function animate(delta: number) {\n *   storm.update(delta);\n *   const flash = storm.level; // 0..~1.2\n *   renderer.render(scene, camera);\n * }\n * ```\n */\nexport class LightningEffect {\n  /** Current flash level, 0 = dark. Read after each {@link LightningEffect.update}. */\n  level = 0;\n\n  private readonly light: DirectionalLight;\n  private readonly peak: number;\n  private readonly minGap: number;\n  private readonly maxGap: number;\n  private readonly spikes: { at: number; amp: number }[] = [];\n  private clock = 0;\n  private nextStrike: number;\n\n  constructor({ light, peak = 12, minGap = 3, maxGap = 9 }: LightningEffectOptions) {\n    this.light = light;\n    this.peak = peak;\n    this.minGap = minGap;\n    this.maxGap = maxGap;\n    this.nextStrike = randomFloat(minGap * 0.3, maxGap);\n  }\n\n  /**\n   * Advance the strike schedule and update the driven light intensity. Pass\n   * elapsed frame time in seconds.\n   */\n  update(dt: number): void {\n    this.clock += dt;\n    if (this.clock >= this.nextStrike) this.strike();\n\n    let level = 0;\n    for (let i = this.spikes.length - 1; i >= 0; i--) {\n      const spike = this.spikes[i];\n      const age = this.clock - spike.at;\n      if (age < 0) continue;\n      const value = spike.amp * Math.exp(-age * 9);\n      if (value < 0.002 && age > 0) {\n        this.spikes.splice(i, 1);\n      } else {\n        level += value;\n      }\n    }\n\n    this.level = Math.min(1.2, level);\n    this.light.intensity = this.level * this.peak;\n  }\n\n  /** Force the driven light dark, e.g. when lightning is toggled off. */\n  quiet(): void {\n    this.level = 0;\n    this.light.intensity = 0;\n  }\n\n  private strike(): void {\n    const bursts = Math.random() < 0.5 ? 2 : 3;\n    for (let i = 0; i < bursts; i++) {\n      this.spikes.push({\n        at: this.clock + i * randomFloat(0.04, 0.12),\n        amp: randomFloat(0.5, 1),\n      });\n    }\n    this.nextStrike = this.clock + randomFloat(this.minGap, this.maxGap);\n  }\n}","import { BufferGeometry, Float32BufferAttribute } from \"three\";\n\nexport class EllipticLeafGeometry extends BufferGeometry {\n  constructor(size = 0.1) {\n    super();\n\n    const vertices = [];\n    const indices = [];\n\n    // Define vertices to approximate a simple, elongated, oval leaf shape\n    const leafPoints = [\n      [0, 1],         // Top point\n      [0.5, 0.75],    // Right upper middle\n      [0.75, 0.25],   // Right lower middle\n      [0.5, -0.5],    // Right bottom middle\n      [0, -1],        // Bottom point\n      [-0.5, -0.5],   // Left bottom middle\n      [-0.75, 0.25],  // Left lower middle\n      [-0.5, 0.75],   // Left upper middle\n    ];\n\n    // Add vertices to the geometry, scaling them with the `size` parameter\n    for (let i = 0; i < leafPoints.length; i++) {\n      const [x, y] = leafPoints[i];\n      vertices.push(x * size, y * size, 0);\n    }\n\n    // Define indices to form triangular faces of the leaf\n    // Create a fan-like structure connecting the top vertex (index 0) to other neighboring vertices\n    for (let i = 1; i < leafPoints.length - 1; i++) {\n      indices.push(0, i, i + 1);\n    }\n    // Close the fan with the last triangle\n    indices.push(0, leafPoints.length - 1, 1);\n\n    // Convert the vertices and indices to buffer attributes\n    const positionAttribute = new Float32BufferAttribute(vertices, 3);\n    this.setAttribute('position', positionAttribute);\n    this.setIndex(indices);\n\n    this.computeVertexNormals();\n  }\n}\n","import { Color, SRGBColorSpace, type ColorRepresentation } from \"three\";\nimport type { RandomSource } from \"./Random\";\n\n/** Per-item input, owned by the caller. Index identifies a logical item, not a vertex. */\nexport interface ColorSampleContext {\n  readonly index: number;\n  readonly random: RandomSource;\n}\n\n/**\n * Fully writes a reusable color in Three's working color space. Do not retain or\n * mutate the context, retain the target, or depend on its previous contents.\n * Randomness comes only from the supplied source; fixed draw counts are not promised.\n */\nexport type ColorSampler = (target: Color, context: ColorSampleContext) => void;\n\n/** An explicit gradient position in [0, 1]. Stops must strictly increase from 0 to 1. */\nexport interface ColorGradientStop {\n  readonly at: number;\n  readonly color: ColorRepresentation;\n}\n\n/** HSL coordinates in sRGB; ranges are absolute percentages, not offsets. */\nexport interface AnalogousColorOptions {\n  /** Center hue in degrees; wraps around the color wheel. */\n  readonly hue: number;\n  /** Half-width in degrees, from 0 to 180. */\n  readonly spread: number;\n  readonly saturation: readonly [number, number];\n  readonly lightness: readonly [number, number];\n}\n\nfunction analogous({ hue, spread, saturation, lightness }: AnalogousColorOptions): ColorSampler {\n  if (!Number.isFinite(hue) || !Number.isFinite(spread) || spread < 0 || spread > 180) {\n    throw new Error(\"RandomColor.analogous needs a finite hue and spread in [0, 180] degrees\");\n  }\n  for (const range of [saturation, lightness]) {\n    if (range.length !== 2 || !range.every(Number.isFinite) || range[0] < 0 || range[1] > 100 || range[0] > range[1]) {\n      throw new Error(\"RandomColor.analogous ranges must be ordered percentages in [0, 100]\");\n    }\n  }\n  const center = ((hue % 360) + 360) % 360;\n  const [s0, s1] = saturation;\n  const [l0, l1] = lightness;\n  return (target, { random }) => {\n    target.setHSL(\n      (center + (random.next() * 2 - 1) * spread) / 360,\n      (s0 + random.next() * (s1 - s0)) / 100,\n      (l0 + random.next() * (l1 - l0)) / 100,\n      SRGBColorSpace,\n    );\n  };\n}\n\nfunction prepareWeights(count: number, weights?: readonly number[]): number[] | undefined {\n  if (count === 0) throw new Error(\"RandomColor requires a non-empty selection\");\n  if (weights === undefined) return undefined;\n  if (weights.length !== count) throw new Error(\"RandomColor needs one weight per entry\");\n  let max = 0;\n  for (const weight of weights) {\n    if (!Number.isFinite(weight)) throw new Error(\"RandomColor weights must be finite\");\n    max = Math.max(max, weight);\n  }\n  // Relative scaling prevents overflow when several individually finite weights are huge.\n  return weights.map((weight) => (max > 0 ? Math.max(0, weight) / max : 0));\n}\n\nfunction constant(color: ColorRepresentation): ColorSampler {\n  const value = new Color(color);\n  return (target) => {\n    target.copy(value);\n  };\n}\n\nfunction pick(colors: readonly ColorRepresentation[], weights?: readonly number[]): ColorSampler {\n  const probabilities = prepareWeights(colors.length, weights);\n  const palette = colors.map((color) => new Color(color));\n  return (target, { random }) => {\n    target.copy(probabilities ? random.weighted(palette, probabilities) : random.pick(palette));\n  };\n}\n\nfunction between(start: ColorRepresentation, end: ColorRepresentation): ColorSampler {\n  const a = new Color(start);\n  const b = new Color(end);\n  return (target, { random }) => {\n    target.copy(a).lerp(b, random.next());\n  };\n}\n\nfunction gradient(stops: readonly ColorGradientStop[]): ColorSampler {\n  if (stops.length < 2 || stops[0].at !== 0 || stops[stops.length - 1].at !== 1) {\n    throw new Error(\"RandomColor.gradient requires at least two stops spanning 0 to 1\");\n  }\n  for (let i = 0; i < stops.length; i++) {\n    const at = stops[i].at;\n    if (!Number.isFinite(at) || at < 0 || at > 1 || (i > 0 && at <= stops[i - 1].at)) {\n      throw new Error(\"RandomColor.gradient positions must be finite and strictly increasing in [0, 1]\");\n    }\n  }\n  const points = stops.map((stop) => ({ at: stop.at, color: new Color(stop.color) }));\n  return (target, { random }) => {\n    const t = random.next();\n    // Find the segment ending at or after t without allocating per sample.\n    let low = 1;\n    let high = points.length - 1;\n    while (low < high) {\n      const middle = (low + high) >>> 1;\n      if (t > points[middle].at) low = middle + 1;\n      else high = middle;\n    }\n    const a = points[low - 1];\n    const b = points[low];\n    target.copy(a.color).lerp(b.color, (t - a.at) / (b.at - a.at));\n  };\n}\n\nfunction mix(samplers: readonly ColorSampler[], weights?: readonly number[]): ColorSampler {\n  const probabilities = prepareWeights(samplers.length, weights);\n  const choices = [...samplers];\n  return (target, context) => {\n    const sample = probabilities ? context.random.weighted(choices, probabilities) : context.random.pick(choices);\n    sample(target, context);\n  };\n}\n\n/**\n * Prepare reusable color samplers, then supply a RandomSource when sampling.\n * Colors/arrays/weights are copied at construction (custom function closures remain caller-owned).\n * No global color-management settings or random sources are changed.\n * Hex/string colors use Three's normal conversion; Color inputs are already working-space values.\n * Configure the working color space before constructing samplers. Results can be passed directly\n * to setColorAt or copied to vertex colors. Sampling allocates no Color objects.\n *\n * @example\n * ```ts\n * const sample = RandomColor.between(\"#493729\", \"#93714f\");\n * const target = new Color();\n * const context = { index: 0, random: createRandom(1337) };\n * sample(target, context);\n * mesh.setColorAt(context.index, target);\n * ```\n */\nexport const RandomColor = {\n  /** Independently sample neighboring hues and absolute S/L ranges in sRGB; output is working-space Color. */\n  analogous,\n  /** One configured color; consumes no randomness. */\n  constant,\n  /**\n   * Exact palette selection. Optional finite weights are relative probabilities;\n   * negative weights contribute zero, all-zero weights fall back to uniform selection.\n   * Empty selections and mismatched/non-finite weights throw at construction.\n   */\n  pick,\n  /** Component-wise working-RGB interpolation; normally Linear-sRGB, not perceptually uniform. */\n  between,\n  /**\n   * Sample an ordered working-RGB path at a uniform t. Segment probability is proportional\n   * to its width. Requires at least two strictly increasing stops, starting at 0 and ending at 1.\n   */\n  gradient,\n  /**\n   * Select a sampler, then invoke it with the same context. Does not interpolate between families.\n   * Weight validation and fallback are identical to pick. The selected sampler consumes its own draws.\n   */\n  mix,\n} as const;\n","import {\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  DoubleSide,\n  DynamicDrawUsage,\n  InstancedMesh,\n  Material,\n  MeshStandardMaterial,\n  Object3D,\n} from \"three\";\nimport { EllipticLeafGeometry } from \"../geometry/foliage/EllipticLeafGeometry\";\nimport { createRandom, deriveSubSeed, type RandomSource } from \"../utils/Random\";\nimport { RandomColor, type ColorSampler } from \"../utils/RandomColor\";\n\nexport interface PetalDriftEffectOptions {\n  /** Optional seed for initial state and respawns; reproduce motion with the same update steps. */\n  seed?: number;\n  /** Per-petal working-space color overriding color. Sampled once at construction; retained on respawn. */\n  colors?: ColorSampler;\n  /** Override petal geometry. Defaults to {@link EllipticLeafGeometry}. */\n  geometry?: BufferGeometry;\n  /** Override the default petal material. */\n  material?: Material;\n  /** Number of petal instances. Defaults to `120`. */\n  count?: number;\n  /** Horizontal spread (world units). Defaults to `16`. */\n  width?: number;\n  /** Vertical spawn span (world units). Defaults to `8`. */\n  height?: number;\n  /** Depth spread (world units). Defaults to `16`. */\n  depth?: number;\n  /** World Y where petals respawn after drifting below the floor. Defaults to `0`. */\n  floorY?: number;\n  /** Minimum fall speed (units/s). Defaults to `0.12`. */\n  fallSpeedMin?: number;\n  /** Maximum fall speed (units/s). Defaults to `0.28`. */\n  fallSpeedMax?: number;\n  /** Minimum horizontal drift speed (units/s). Defaults to `0.04`. */\n  driftMin?: number;\n  /** Maximum horizontal drift speed (units/s). Defaults to `0.14`. */\n  driftMax?: number;\n  /**\n   * Flutter strength (radians). Subtle rotation sway as each petal falls.\n   * Defaults to `0.35`.\n   */\n  flutter?: number;\n  /** Single petal color or palette; multiple entries pick a random color per petal. */\n  color?: ColorRepresentation | ColorRepresentation[];\n}\n\n/**\n * Soft, slow-drifting petals (or leaves) falling through a bounded volume —\n * cherry-blossom float rather than stiff tumble. Each instance drifts downward\n * with gentle horizontal wander and a light sinusoidal flutter.\n *\n * Call {@link PetalDriftEffect.update} each frame with elapsed time in seconds.\n *\n * @example\n * ```typescript\n * const petals = new PetalDriftEffect({\n *   count: 80,\n *   color: [0xffd6f0, 0xfff0f8, 0xf8c8e0],\n *   flutter: 0.3,\n * });\n * scene.add(petals);\n *\n * onFrame((dt) => petals.update(dt));\n * ```\n */\nexport class PetalDriftEffect extends InstancedMesh {\n  private readonly source: RandomSource;\n  private readonly width: number;\n  private readonly height: number;\n  private readonly depth: number;\n  private readonly floorY: number;\n  private readonly flutter: number;\n  private readonly px: Float32Array;\n  private readonly py: Float32Array;\n  private readonly pz: Float32Array;\n  private readonly fallSpeed: Float32Array;\n  private readonly driftX: Float32Array;\n  private readonly driftZ: Float32Array;\n  private readonly rotX: Float32Array;\n  private readonly rotY: Float32Array;\n  private readonly rotZ: Float32Array;\n  private readonly phase: Float32Array;\n  private readonly dummy = new Object3D();\n  private clock = 0;\n\n  constructor(options: PetalDriftEffectOptions = {}) {\n    const {\n      seed,\n      colors,\n      count = 120,\n      width = 16,\n      height = 8,\n      depth = 16,\n      floorY = 0,\n      fallSpeedMin = 0.12,\n      fallSpeedMax = 0.28,\n      driftMin = 0.04,\n      driftMax = 0.14,\n      flutter = 0.35,\n      color = [0xffd6f0, 0xfff5fa, 0xf0b8d0],\n      geometry = new EllipticLeafGeometry(),\n      material,\n    } = options;\n\n    const palette = (Array.isArray(color) ? color : [color]).map((entry) => new Color(entry));\n    if (palette.length === 0 && !colors) throw new Error(\"PetalDriftEffect requires a non-empty color palette\");\n    const petalMaterial =\n      material ??\n      new MeshStandardMaterial({\n        color: !colors && palette.length === 1 ? palette[0].getHex() : 0xffffff,\n        metalness: 0.05,\n        roughness: 0.85,\n        flatShading: true,\n        side: DoubleSide,\n      });\n\n    super(geometry, petalMaterial, count);\n    this.source = createRandom(seed);\n    const sample = colors ?? RandomColor.pick(palette);\n    const context = {\n      index: 0,\n      random: colors ? createRandom(seed === undefined ? undefined : deriveSubSeed(seed, 0x70657461)) : this.source,\n    };\n    const tint = new Color();\n    this.instanceMatrix.setUsage(DynamicDrawUsage);\n    this.frustumCulled = false;\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n    this.floorY = floorY;\n    this.flutter = flutter;\n\n    this.px = new Float32Array(count);\n    this.py = new Float32Array(count);\n    this.pz = new Float32Array(count);\n    this.fallSpeed = new Float32Array(count);\n    this.driftX = new Float32Array(count);\n    this.driftZ = new Float32Array(count);\n    this.rotX = new Float32Array(count);\n    this.rotY = new Float32Array(count);\n    this.rotZ = new Float32Array(count);\n    this.phase = new Float32Array(count);\n\n    for (let i = 0; i < count; i++) {\n      this.respawn(i, true);\n      this.fallSpeed[i] = this.source.float(fallSpeedMin, fallSpeedMax);\n      const drift = this.source.float(driftMin, driftMax);\n      const angle = this.source.float(0, Math.PI * 2);\n      this.driftX[i] = Math.cos(angle) * drift;\n      this.driftZ[i] = Math.sin(angle) * drift;\n      this.phase[i] = this.source.float(0, Math.PI * 2);\n\n      if (colors || palette.length > 1) {\n        if (colors && palette.length > 1) this.source.next();\n        context.index = i;\n        sample(tint, context);\n        this.setColorAt(i, tint);\n      }\n    }\n\n    if (this.instanceColor) this.instanceColor.needsUpdate = true;\n    this.writeMatrices();\n  }\n\n  /**\n   * Advance petal positions and flutter. Pass elapsed frame time in seconds.\n   */\n  update(dt: number): void {\n    this.clock += dt;\n\n    for (let i = 0; i < this.count; i++) {\n      const sway = Math.sin(this.clock * 1.6 + this.phase[i]) * this.flutter * 0.12;\n      this.px[i] += (this.driftX[i] + sway) * dt;\n      this.py[i] -= this.fallSpeed[i] * dt;\n      this.pz[i] += (this.driftZ[i] + Math.cos(this.clock * 1.3 + this.phase[i]) * this.flutter * 0.08) * dt;\n\n      if (this.py[i] < this.floorY) this.respawn(i, false);\n    }\n\n    this.writeMatrices();\n  }\n\n  /** Release geometry and materials held by the field. */\n  dispose(): this {\n    this.geometry.dispose();\n    const materials = Array.isArray(this.material) ? this.material : [this.material];\n    for (const entry of materials) entry.dispose();\n    return this;\n  }\n\n  private respawn(index: number, randomHeight: boolean): void {\n    this.px[index] = this.source.float(-this.width * 0.5, this.width * 0.5);\n    this.pz[index] = this.source.float(-this.depth * 0.5, this.depth * 0.5);\n    this.py[index] = randomHeight\n      ? this.floorY + this.source.float(0, this.height * 1.25)\n      : this.floorY + this.height + this.source.float(0, this.height * 0.25);\n    this.rotX[index] = this.source.float(-0.6, 0.6);\n    this.rotY[index] = this.source.float(-Math.PI, Math.PI);\n    this.rotZ[index] = this.source.float(-0.8, 0.8);\n  }\n\n  private writeMatrices(): void {\n    const d = this.dummy;\n    for (let i = 0; i < this.count; i++) {\n      const flutterX = Math.sin(this.clock * 2.1 + this.phase[i]) * this.flutter;\n      const flutterZ = Math.cos(this.clock * 1.7 + this.phase[i]) * this.flutter * 0.6;\n      d.position.set(this.px[i], this.py[i], this.pz[i]);\n      d.rotation.set(this.rotX[i] + flutterX, this.rotY[i], this.rotZ[i] + flutterZ);\n      d.updateMatrix();\n      this.setMatrixAt(i, d.matrix);\n    }\n    this.instanceMatrix.needsUpdate = true;\n  }\n}\n","import {\n  ClampToEdgeWrapping,\n  Color,\n  ColorRepresentation,\n  DataTexture,\n  LinearFilter,\n  LinearMipmapLinearFilter,\n  RGBAFormat,\n  SRGBColorSpace,\n  UnsignedByteType,\n} from \"three\";\nimport { Easing, type EasingFunction } from \"../constants/Easing\";\n\n/** One stop of a linear ramp. */\nexport interface LinearGradientStop {\n  /** Position along the ramp: `0` at the start (first row), `1` at the end (last row). */\n  offset: number;\n  color: ColorRepresentation;\n  /** Opacity at this stop, `0`–`1`. Defaults to `1`. */\n  alpha?: number;\n}\n\nexport interface LinearGradientTextureOptions {\n  /** The ramp, start to end. Sorted internally, so declaration order doesn't matter. */\n  stops: LinearGradientStop[];\n  /** Length of the ramp in texels. Defaults to `128` (a power of two, so mipmaps are exact). */\n  size?: number;\n  /**\n   * How each pair of stops is interpolated. Defaults to {@link Easing.linear}, matching a canvas gradient.\n   * {@link Easing.smoothstep} brings the slope to zero at each stop, removing the faint Mach band a linear\n   * ramp leaves.\n   */\n  easing?: EasingFunction;\n}\n\n/**\n * A linear gradient as a {@link DataTexture} — the straight-ramp sibling of {@link createRadialGradientTexture}.\n *\n * A linear gradient varies only along one axis, so the image is just a tall strip a few columns wide (the\n * texture's V axis, first row → last). Rotate the texture (`texture.rotation`) for any other direction.\n * Computed in plain JS, so no DOM — builds headless, on either renderer. Stops interpolate in sRGB.\n *\n * Useful as a `scene.background` (a moody backdrop), a sky strip, or the fade of a rain/fog card.\n *\n * @example\n * ```typescript\n * scene.background = createLinearGradientTexture({\n *   stops: [\n *     { offset: 0, color: 0x28323f }, // bottom\n *     { offset: 1, color: 0x0c1016 }, // top\n *   ],\n * });\n * ```\n */\nexport const createLinearGradientTexture = ({\n  stops,\n  size = 128,\n  easing = Easing.linear,\n}: LinearGradientTextureOptions): DataTexture => {\n  if (stops.length === 0) throw new Error(\"createLinearGradientTexture requires at least one stop.\");\n\n  const ordered = [...stops]\n    .sort((first, second) => first.offset - second.offset)\n    .map((stop) => {\n      const hex = new Color(stop.color).getHex(SRGBColorSpace);\n      return {\n        offset: stop.offset,\n        r: (hex >> 16) & 255,\n        g: (hex >> 8) & 255,\n        b: hex & 255,\n        a: Math.round((stop.alpha ?? 1) * 255),\n      };\n    });\n\n  const first = ordered[0]!;\n  const last = ordered[ordered.length - 1]!;\n  const width = 4; // only varies along the ramp; a few columns is the whole image\n  const data = new Uint8Array(width * size * 4);\n\n  for (let y = 0; y < size; y++) {\n    const pos = size === 1 ? 0 : y / (size - 1);\n\n    let low = first;\n    let high = last;\n    for (let i = 1; i < ordered.length; i++) {\n      if (pos <= ordered[i]!.offset) {\n        low = ordered[i - 1]!;\n        high = ordered[i]!;\n        break;\n      }\n    }\n\n    const span = high.offset - low.offset;\n    const raw = span <= 0 ? 0 : Math.min(Math.max((pos - low.offset) / span, 0), 1);\n    const t = easing(raw);\n    const r = low.r + (high.r - low.r) * t;\n    const g = low.g + (high.g - low.g) * t;\n    const b = low.b + (high.b - low.b) * t;\n    const a = low.a + (high.a - low.a) * t;\n\n    for (let x = 0; x < width; x++) {\n      const stride = (y * width + x) * 4;\n      data[stride] = r;\n      data[stride + 1] = g;\n      data[stride + 2] = b;\n      data[stride + 3] = a;\n    }\n  }\n\n  const texture = new DataTexture(data, width, size, RGBAFormat, UnsignedByteType);\n  texture.colorSpace = SRGBColorSpace;\n  texture.magFilter = LinearFilter;\n  texture.minFilter = LinearMipmapLinearFilter;\n  texture.generateMipmaps = true;\n  texture.wrapS = ClampToEdgeWrapping;\n  texture.wrapT = ClampToEdgeWrapping;\n  texture.needsUpdate = true;\n\n  return texture;\n};\n","import {\n  BufferGeometry,\n  DataTexture,\n  Color,\n  ColorRepresentation,\n  DoubleSide,\n  DynamicDrawUsage,\n  InstancedMesh,\n  Material,\n  MeshBasicMaterial,\n  Object3D,\n  PlaneGeometry,\n  Quaternion,\n  Vector3,\n} from \"three\";\nimport { createLinearGradientTexture } from \"../textures/linearGradient\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\nexport interface RainEffectOptions {\n  /** Override the streak quad geometry. Defaults to a thin `PlaneGeometry`. */\n  geometry?: BufferGeometry;\n  /** Override the default streak material. */\n  material?: Material;\n  /** Maximum number of streak instances. Defaults to `1400`. */\n  count?: number;\n  /** Horizontal half-extent of the rainfall area (square centered on the origin). Defaults to `26`. */\n  area?: number;\n  /** Vertical span above `groundY`. Defaults to `22`. */\n  height?: number;\n  /** World Y where streaks recycle. Defaults to `0`. */\n  groundY?: number;\n  /** Streak quad width. Defaults to `0.009`. */\n  width?: number;\n  /** Streak color. Defaults to `#aebfd6`. */\n  color?: ColorRepresentation;\n  /** Base material opacity at full intensity. Defaults to `0.16`. */\n  opacity?: number;\n  /** Minimum streak length. Defaults to `0.18`. */\n  lengthMin?: number;\n  /** Maximum streak length. Defaults to `0.42`. */\n  lengthMax?: number;\n  /** Minimum fall speed (units/s). Defaults to `11`. */\n  speedMin?: number;\n  /** Maximum fall speed (units/s). Defaults to `19`. */\n  speedMax?: number;\n  /**\n   * Horizontal compass direction the wind blows (radians, 0 = +X, π/2 = +Z).\n   * Only used when {@link RainEffectOptions.windStrength} is greater than zero.\n   * Defaults to `0`.\n   */\n  windDirection?: number;\n  /**\n   * How much rain tilts and drifts from vertical, as `tan(angleFromVertical)`.\n   * `0` = straight down (default). `0.15` ≈ 8.5° lean with matching horizontal drift.\n   */\n  windStrength?: number;\n  /**\n   * Rainfall strength (0–1). Scales visible instance count, fall speed, and opacity.\n   * Defaults to `0.5`.\n   */\n  intensity?: number;\n}\n\nfunction createStreakTexture(): DataTexture {\n  return createLinearGradientTexture({\n    size: 64,\n    stops: [\n      { offset: 0, color: 0xffffff, alpha: 0 },\n      { offset: 0.42, color: 0xffffff, alpha: 0.55 },\n      { offset: 0.58, color: 0xffffff, alpha: 0.55 },\n      { offset: 1, color: 0xffffff, alpha: 0 },\n    ],\n  });\n}\n\n/**\n * Misty rainfall as instanced vertical streaks.\n *\n * Each streak is a thin, gradient-textured quad animated through a bounded\n * volume. By default streaks fall straight down with no rotation. Optional\n * {@link RainEffectOptions.windDirection} / {@link RainEffectOptions.windStrength}\n * tilt streaks **and** drift them horizontally together, so motion matches the\n * visual angle. Streak materials use `DoubleSide` so thin quads stay visible\n * from any camera angle. Scene fog dissolves distant streaks when the material's\n * `fog` flag is enabled.\n *\n * **`intensity`** (0–1) scales how many instances draw, how fast they fall, and\n * their opacity — useful for storm ramps or lightning flashes.\n *\n * @example\n * ```typescript\n * const rain = new RainEffect({ area: 12, height: 16, intensity: 0.4 });\n * scene.add(rain);\n * scene.fog = new Fog(0x0a0a12, 4, 28);\n *\n * function animate(delta: number) {\n *   rain.update(delta);\n *   renderer.render(scene, camera);\n * }\n * ```\n */\nexport class RainEffect extends InstancedMesh {\n  /** Rainfall strength (0–1). Adjust at runtime for storm variation. */\n  intensity: number;\n\n  private readonly maxCount: number;\n  private readonly area: number;\n  private readonly height: number;\n  private readonly groundY: number;\n  private readonly baseOpacity: number;\n  private readonly windDirection: number;\n  private readonly windStrength: number;\n  private readonly fallDirection = new Vector3(0, -1, 0);\n  private readonly streakOrientation = new Quaternion();\n  private readonly sx: Float32Array;\n  private readonly sz: Float32Array;\n  private readonly topY: Float32Array;\n  private readonly len: Float32Array;\n  private readonly speed: Float32Array;\n  private readonly streakTexture?: DataTexture;\n  private readonly dummy = new Object3D();\n  private clock = 0;\n\n  constructor(options: RainEffectOptions = {}) {\n    const {\n      count = 1400,\n      area = 26,\n      height = 22,\n      groundY = 0,\n      width = 0.009,\n      color = \"#aebfd6\",\n      opacity = 0.16,\n      lengthMin = 0.18,\n      lengthMax = 0.42,\n      speedMin = 11,\n      speedMax = 19,\n      windDirection = 0,\n      windStrength = 0,\n      intensity = 0.5,\n      geometry = new PlaneGeometry(width, 1),\n      material,\n    } = options;\n\n    const streakTexture = material ? undefined : createStreakTexture();\n    const rainMaterial =\n      material ??\n      new MeshBasicMaterial({\n        color: new Color(color),\n        map: streakTexture,\n        transparent: true,\n        opacity,\n        depthWrite: false,\n        toneMapped: false,\n        fog: true,\n        side: DoubleSide,\n      });\n\n    super(geometry, rainMaterial, count);\n    this.instanceMatrix.setUsage(DynamicDrawUsage);\n    this.frustumCulled = false;\n\n    this.maxCount = count;\n    this.area = area;\n    this.height = height;\n    this.groundY = groundY;\n    this.baseOpacity = opacity;\n    this.windDirection = windDirection;\n    this.windStrength = Math.max(0, windStrength);\n    this.updateFallDirection(0);\n    this.intensity = intensity;\n    this.streakTexture = streakTexture;\n\n    this.sx = new Float32Array(count);\n    this.sz = new Float32Array(count);\n    this.topY = new Float32Array(count);\n    this.len = new Float32Array(count);\n    this.speed = new Float32Array(count);\n\n    for (let i = 0; i < count; i++) {\n      this.sx[i] = randomFloat(-area, area);\n      this.sz[i] = randomFloat(-area, area);\n      this.topY[i] = randomFloat(groundY, groundY + height);\n      this.len[i] = randomFloat(lengthMin, lengthMax);\n      this.speed[i] = randomFloat(speedMin, speedMax);\n    }\n\n    this.writeMatrices();\n    this.applyIntensity();\n  }\n\n  /**\n   * Advance streak positions and refresh instance transforms. Pass elapsed frame\n   * time in seconds (e.g. from `createScene`'s `onFrame` callback).\n   */\n  update(dt: number): void {\n    this.clock += dt;\n\n    const gust =\n      this.windStrength > 0 ? Math.sin(this.clock * 0.18) * 0.04 * this.windStrength : 0;\n    this.updateFallDirection(gust);\n\n    const fall = 0.85 + this.intensity * 0.7;\n    const top = this.groundY + this.height;\n    const driftX = this.fallDirection.x;\n    const driftZ = this.fallDirection.z;\n    const fallY = -this.fallDirection.y;\n\n    for (let i = 0; i < this.maxCount; i++) {\n      const travel = this.speed[i] * fall * dt;\n      this.sx[i] += driftX * travel;\n      this.sz[i] += driftZ * travel;\n      this.topY[i] -= fallY * travel;\n\n      if (this.topY[i] < this.groundY - this.len[i]) this.topY[i] += this.height;\n      if (this.topY[i] > top) this.topY[i] -= this.height;\n\n      if (this.sx[i] < -this.area) this.sx[i] += this.area * 2;\n      else if (this.sx[i] > this.area) this.sx[i] -= this.area * 2;\n      if (this.sz[i] < -this.area) this.sz[i] += this.area * 2;\n      else if (this.sz[i] > this.area) this.sz[i] -= this.area * 2;\n    }\n\n    this.writeMatrices();\n    this.applyIntensity();\n  }\n\n  /** Release geometry, materials, and the procedural streak texture. */\n  dispose(): this {\n    this.geometry.dispose();\n    const materials = Array.isArray(this.material) ? this.material : [this.material];\n    for (const entry of materials) entry.dispose();\n    this.streakTexture?.dispose();\n    return this;\n  }\n\n  private applyIntensity(): void {\n    // Light drizzle still reads at low rainfall.\n    this.count = Math.round(this.maxCount * Math.min(1, Math.max(0, 0.38 + this.intensity * 0.62)));\n\n    const mat = this.material as MeshBasicMaterial;\n    mat.opacity = this.baseOpacity * (0.55 + 0.65 * this.intensity);\n  }\n\n  private updateFallDirection(gust: number): void {\n    const lean = this.windStrength + gust;\n    if (lean <= 0) {\n      this.fallDirection.set(0, -1, 0);\n      this.streakOrientation.identity();\n      return;\n    }\n\n    const horizontal = lean;\n    const vertical = 1;\n    const invLength = 1 / Math.hypot(horizontal, vertical);\n    this.fallDirection.set(\n      Math.cos(this.windDirection) * horizontal * invLength,\n      -vertical * invLength,\n      Math.sin(this.windDirection) * horizontal * invLength,\n    );\n    this.streakOrientation.setFromUnitVectors(new Vector3(0, 1, 0), this.fallDirection);\n  }\n\n  private writeMatrices(): void {\n    const d = this.dummy;\n    for (let i = 0; i < this.maxCount; i++) {\n      const length = this.len[i];\n      d.position.set(this.sx[i], this.topY[i] - length / 2, this.sz[i]);\n      d.quaternion.copy(this.streakOrientation);\n      d.scale.set(1, length, 1);\n      d.updateMatrix();\n      this.setMatrixAt(i, d.matrix);\n    }\n    this.instanceMatrix.needsUpdate = true;\n  }\n}\n","import {\n  Color,\n  ColorRepresentation,\n  Mesh,\n  MeshBasicMaterial,\n  Object3D,\n  PointLight,\n  SphereGeometry,\n  Vector3,\n} from \"three\";\nimport { randomFloat } from \"../utils/RandomNumberUtils\";\n\nexport interface WispEffectOptions {\n  /** Number of drifting wisps. Defaults to `3`. */\n  count?: number;\n  /** Horizontal spawn extent (world units, centered on the effect). Defaults to `16`. */\n  width?: number;\n  /** Depth spawn maximum (world units, from z = 0). Defaults to `8`. */\n  depth?: number;\n  /** Minimum spawn height. Defaults to `1`. */\n  heightMin?: number;\n  /** Maximum spawn height. Defaults to `2`. */\n  heightMax?: number;\n  /** Wisp tint. Defaults to `0x6dffb0` (portfolio graveyard). */\n  color?: ColorRepresentation;\n  /** Orb radius. Defaults to `0.08`. */\n  orbRadius?: number;\n  /** Horizontal drift radius (X). Defaults to `1.6`. */\n  driftX?: number;\n  /** Vertical drift radius (Y). Defaults to `0.3`. */\n  driftY?: number;\n  /** Depth drift radius (Z). Defaults to `1.6`. */\n  driftZ?: number;\n  /** Minimum motion speed multiplier. Defaults to `0.3`. */\n  speedMin?: number;\n  /** Maximum motion speed multiplier. Defaults to `0.7`. */\n  speedMax?: number;\n  /**\n   * Attach a {@link PointLight} per wisp (keep `count` low).\n   * Defaults to `true` to match the portfolio graveyard.\n   */\n  castLight?: boolean;\n  /** Point light distance when `castLight`. Defaults to `6`. */\n  lightDistance?: number;\n  /** Point light decay when `castLight`. Defaults to `2`. */\n  lightDecay?: number;\n  /**\n   * Light intensity = `lightIntensity + sin(t * lightPulseSpeed) * lightPulseAmplitude`.\n   * Defaults to `2.5`.\n   */\n  lightIntensity?: number;\n  /** Defaults to `1.2`. */\n  lightPulseAmplitude?: number;\n  /** Defaults to `3`. */\n  lightPulseSpeed?: number;\n}\n\ninterface WispInstance {\n  orb: Mesh<SphereGeometry, MeshBasicMaterial>;\n  home: Vector3;\n  phase: number;\n  speed: number;\n  light?: PointLight;\n}\n\n/**\n * Will-o'-the-wisps drifting through a bounded volume — eerie green orbs that\n * bob around spawn points with a pulsing point light. Ported from the portfolio\n * graveyard scene.\n *\n * @example\n * ```ts\n * const wisps = new WispEffect({ count: 3 });\n * scene.add(wisps);\n * onFrame((dt) => wisps.update(dt));\n * ```\n */\nexport class WispEffect extends Object3D {\n  private readonly wisps: WispInstance[] = [];\n  private readonly orbGeometry: SphereGeometry;\n  private readonly orbMaterial: MeshBasicMaterial;\n  private readonly halfWidth: number;\n  private readonly depth: number;\n  private readonly heightMin: number;\n  private readonly heightMax: number;\n  private readonly driftX: number;\n  private readonly driftY: number;\n  private readonly driftZ: number;\n  private readonly speedMin: number;\n  private readonly speedMax: number;\n  private readonly castLight: boolean;\n  private readonly lightDistance: number;\n  private readonly lightDecay: number;\n  private readonly lightIntensity: number;\n  private readonly lightPulseAmplitude: number;\n  private readonly lightPulseSpeed: number;\n\n  private elapsed = 0;\n\n  constructor({\n    count = 3,\n    width = 16,\n    depth = 8,\n    heightMin = 1,\n    heightMax = 2,\n    color = 0x6dffb0,\n    orbRadius = 0.08,\n    driftX = 1.6,\n    driftY = 0.3,\n    driftZ = 1.6,\n    speedMin = 0.3,\n    speedMax = 0.7,\n    castLight = true,\n    lightDistance = 6,\n    lightDecay = 2,\n    lightIntensity = 2.5,\n    lightPulseAmplitude = 1.2,\n    lightPulseSpeed = 3,\n  }: WispEffectOptions = {}) {\n    super();\n\n    this.halfWidth = width / 2;\n    this.depth = depth;\n    this.heightMin = heightMin;\n    this.heightMax = heightMax;\n    this.driftX = driftX;\n    this.driftY = driftY;\n    this.driftZ = driftZ;\n    this.speedMin = speedMin;\n    this.speedMax = speedMax;\n    this.castLight = castLight;\n    this.lightDistance = lightDistance;\n    this.lightDecay = lightDecay;\n    this.lightIntensity = lightIntensity;\n    this.lightPulseAmplitude = lightPulseAmplitude;\n    this.lightPulseSpeed = lightPulseSpeed;\n\n    this.orbGeometry = new SphereGeometry(orbRadius, 8, 8);\n    this.orbMaterial = new MeshBasicMaterial({ color: new Color(color), toneMapped: false });\n\n    for (let i = 0; i < count; i++) {\n      const home = new Vector3(\n        randomFloat(-this.halfWidth, this.halfWidth),\n        randomFloat(this.heightMin, this.heightMax),\n        randomFloat(0, this.depth),\n      );\n\n      const orb = new Mesh(this.orbGeometry, this.orbMaterial);\n      orb.position.copy(home);\n      this.add(orb);\n\n      let light: PointLight | undefined;\n      if (castLight) {\n        light = new PointLight(color, 3, this.lightDistance, this.lightDecay);\n        orb.add(light);\n      }\n\n      this.wisps.push({\n        orb,\n        home,\n        phase: randomFloat(0, Math.PI * 2),\n        speed: randomFloat(this.speedMin, this.speedMax),\n        light,\n      });\n    }\n  }\n\n  update(dt: number): void {\n    this.elapsed += dt;\n\n    for (const wisp of this.wisps) {\n      const t = this.elapsed * wisp.speed + wisp.phase;\n      wisp.orb.position.set(\n        wisp.home.x + Math.sin(t) * this.driftX,\n        wisp.home.y + Math.sin(t * 1.7) * this.driftY,\n        wisp.home.z + Math.cos(t * 0.8) * this.driftZ,\n      );\n\n      if (wisp.light) {\n        wisp.light.intensity =\n          this.lightIntensity + Math.sin(t * this.lightPulseSpeed) * this.lightPulseAmplitude;\n      }\n    }\n  }\n\n  dispose(): void {\n    this.orbGeometry.dispose();\n    this.orbMaterial.dispose();\n    for (const wisp of this.wisps) {\n      wisp.light?.dispose();\n    }\n    this.clear();\n    this.wisps.length = 0;\n  }\n}","import type { Vec2 } from \"../mesh/GeometryBuffers\";\n\n\n\n/** Centered CCW rectangle: thickness along station normal, width along binormal. */\nexport function rectProfile(width: number, thickness: number): Vec2[] {\n  const hw = width / 2;\n  const ht = thickness / 2;\n\n  return [\n    [-ht, -hw],\n    [ht, -hw],\n    [ht, hw],\n    [-ht, hw],\n  ];\n}\n\n/**\n * CCW circle in the station (normal, binormal) plane; rotation sets the seam angle in radians.\n * A half-segment offset aligns flats with the axes; θ=0 aligns vertices.\n */\nexport function circleProfile(radius: number, segments: number, rotation = Math.PI / segments): Vec2[] {\n  return Array.from({ length: segments }, (_, i) => {\n    const a = rotation + (i / segments) * Math.PI * 2;\n    return [Math.cos(a) * radius, Math.sin(a) * radius] as Vec2;\n  });\n}\n","import { Vector3 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\n/** Sample a straight segment including both endpoints; from and to must differ for a usable tangent. */\nexport function linePath(from: Vector3, to: Vector3, segments = 1): PathPoint[] {\n  const tangent = new Vector3().subVectors(to, from).normalize();\n\n  return Array.from({ length: segments + 1 }, (_, i) => ({\n    position: new Vector3().lerpVectors(from, to, i / segments),\n    tangent: tangent.clone(),\n  }));\n}\n","import { Path } from \"three\";\n\n/**\n * Arch outlines traced between springings and crown.\n *\n * ```\n *   square        semicircle      segmental       horseshoe\n *   ________       _______         _______         _______\n *  |        |     /       \\       /        \\      |       |\n *  |        |    |         |     |          |      \\     /\n *\n *   elliptical      pointed          ogee\n *    _______          /\\             /\\\n *   /       \\        /  \\           (  )\n *  |         |      |    |          |  |\n * ```\n */\nexport type ArchStyle =\n  /** Flat lintel at the springing height. */\n  | \"square\"\n  /** Half-circle; rise equals halfSpan. */\n  | \"semicircle\"\n  /** Circular arc with rise limited to halfSpan; meets the jamb at an angle. */\n  | \"segmental\"\n  /** Circular arc with rise at least halfSpan; can extend beyond the springing span. */\n  | \"horseshoe\"\n  /** Elliptical arc with vertical tangents at the springings. */\n  | \"elliptical\"\n  /** Two circular arcs meeting at an apex; rise = halfSpan * √3 gives an equilateral arch. */\n  | \"pointed\"\n  /** Two tangent-continuous quadratic segments per side, meeting at a pointed crown. */\n  | \"ogee\";\n\n/** Named endpoints for a partial or complete arch trace. */\nexport type ArchEnd = \"left\" | \"crown\" | \"right\";\n\nexport interface ArchProfileOptions {\n  /** Arch shape; determines the curve and permitted rise. */\n  style?: ArchStyle;\n  /** Arch centerline X coordinate. */\n  x?: number;\n  /** The springing line — the Y where the arch leaves the jambs. */\n  y: number;\n  /** Half the arch's span. */\n  halfSpan: number;\n  /** Rise above the springing, in coordinate units; archRise applies style-specific limits. */\n  rise?: number;\n  /** Endpoint where the existing path ends. */\n  from?: ArchEnd;\n  /** Endpoint where the appended trace ends. */\n  to?: ArchEnd;\n}\n\n/** Resolved rise: square 0; semicircle halfSpan; segmental ≤ halfSpan; horseshoe and pointed ≥ halfSpan. */\nexport function archRise({ style = \"elliptical\", halfSpan, rise = halfSpan }: ArchProfileOptions): number {\n  if (style === \"square\") return 0;\n  if (style === \"semicircle\") return halfSpan;\n\n  // Style limits meet at the semicircle:\n  //\n  //   segmental  <--- halfSpan --->  horseshoe\n  //              (the semicircle)\n  if (style === \"segmental\") return Math.min(rise, halfSpan);\n  if (style === \"horseshoe\") return Math.max(rise, halfSpan);\n\n  // Below halfSpan, the pointed arcs rise above the apex before meeting it.\n  if (style === \"pointed\") return Math.max(rise, halfSpan);\n\n  return rise;\n}\n\n/**\n * Circle through both springings and crown: radius = (halfSpan² + rise²) / (2·rise).\n * Center is below/on/above springing for rise < / = / > halfSpan, respectively.\n */\nfunction circle(halfSpan: number, rise: number): { radius: number; cy: number } {\n  const radius = (halfSpan * halfSpan + rise * rise) / (2 * rise);\n  return { radius, cy: rise - radius }; // cy is relative to the springing line\n}\n\n/**\n * Pointed-arch arc centers lie on the springing line. At rise === halfSpan both arcs form a semicircle;\n * at rise = halfSpan·√3 each center is the opposite springing.\n */\nfunction pointedArc(halfSpan: number, rise: number): { offset: number; radius: number } {\n  const offset = (halfSpan * halfSpan - rise * rise) / (2 * halfSpan);\n  return { offset, radius: halfSpan - offset };\n}\n\n/** Normalized ogee control-point fractions. */\nconst OGEE_SPRING_HANDLE = 0.4;\nconst OGEE_INFLECT_X = 0.45;\nconst OGEE_INFLECT_Y = 0.55;\nconst OGEE_TANGENT = 0.5;\n\n/**\n * Append an arch to an existing Path whose current point is at from. Jambs remain caller-owned.\n *\n * ```ts\n * // A door's silhouette: up the right side, over the top, down the left.\n * const shape = new Shape();\n * shape.moveTo(-hw, 0);\n * shape.lineTo(hw, 0);\n * shape.lineTo(hw, height);\n * traceArch(shape, { style: \"semicircle\", y: height, halfSpan: hw, from: \"right\", to: \"left\" });\n * shape.closePath();\n * ```\n *\n * ```ts\n * // Half an arch — one leaf of a double door, split at the crown.\n * traceArch(shape, { style: \"ogee\", y: h, halfSpan: hw, rise, from: \"crown\", to: \"left\" });\n * ```\n */\nexport function traceArch(path: Path, options: ArchProfileOptions): void {\n  const { style = \"elliptical\", x = 0, y, halfSpan, from = \"right\", to = \"left\" } = options;\n  const rise = archRise(options);\n\n  if (from === to) return;\n\n  if (style === \"square\" || rise <= 0) {\n    path.lineTo(x + endX(to, halfSpan), y);\n    return;\n  }\n\n  if (style === \"pointed\" || style === \"ogee\") {\n    tracePointy(path, style, x, y, halfSpan, rise, from, to);\n    return;\n  }\n\n  // Keep a single ellipse segment; splitting at the crown changes curve-based sampling density.\n  const [cy, xRadius, yRadius] = ellipseOf(style, halfSpan, rise);\n  const springAngle = Math.atan2(-cy, halfSpan);\n  const angle = (end: ArchEnd) =>\n    end === \"crown\" ? Math.PI / 2 : end === \"right\" ? springAngle : Math.PI - springAngle;\n\n  const start = angle(from);\n  const finish = angle(to);\n  path.absellipse(x, y + cy, xRadius, yRadius, start, finish, start > finish);\n}\n\n/** An ellipse for `elliptical`; the shared circle for `semicircle` / `segmental` / `horseshoe`. */\nfunction ellipseOf(style: ArchStyle, halfSpan: number, rise: number): [cy: number, rx: number, ry: number] {\n  if (style === \"elliptical\") return [0, halfSpan, rise];\n  const { radius, cy } = circle(halfSpan, rise);\n  return [cy, radius, radius];\n}\n\n/** X of a springing (or the crown) relative to the arch's centerline. */\nfunction endX(end: ArchEnd, halfSpan: number): number {\n  return end === \"crown\" ? 0 : end === \"right\" ? halfSpan : -halfSpan;\n}\n\n/** Trace pointed and ogee arches one side at a time, splitting at the crown. */\nfunction tracePointy(\n  path: Path,\n  style: \"pointed\" | \"ogee\",\n  x: number,\n  y: number,\n  halfSpan: number,\n  rise: number,\n  from: ArchEnd,\n  to: ArchEnd,\n): void {\n  const order: ArchEnd[] = [\"right\", \"crown\", \"left\"];\n  const start = order.indexOf(from);\n  const end = order.indexOf(to);\n  const step = start < end ? 1 : -1;\n\n  for (let i = start; i !== end; i += step) {\n    const a = order[i];\n    const b = order[i + step];\n    const side: \"left\" | \"right\" = a === \"left\" || b === \"left\" ? \"left\" : \"right\";\n    // Each half runs springing → apex; reverse it when the trace is heading the other way.\n    const toApex = b === \"crown\";\n    half(path, style, x, y, halfSpan, rise, side, toApex);\n  }\n}\n\nfunction half(\n  path: Path,\n  style: \"pointed\" | \"ogee\",\n  x: number,\n  y: number,\n  halfSpan: number,\n  rise: number,\n  side: \"left\" | \"right\",\n  toApex: boolean,\n): void {\n  const sign = side === \"right\" ? 1 : -1;\n\n  if (style === \"pointed\") {\n    const { offset, radius } = pointedArc(halfSpan, rise);\n    const cx = x + sign * offset;\n    // The springing sits at angle 0 (or π); the apex wherever the geometry puts it.\n    const springAngle = side === \"right\" ? 0 : Math.PI;\n    const apexAngle = Math.atan2(rise, -sign * offset);\n    const start = toApex ? springAngle : apexAngle;\n    const finish = toApex ? apexAngle : springAngle;\n    path.absarc(cx, y, radius, start, finish, start > finish);\n    return;\n  }\n\n  // Two quadratics share a tangent at the inflection and meet the apex with a nonhorizontal tangent.\n  const spring = { x: x + sign * halfSpan, y };\n  const apex = { x, y: y + rise };\n  const handle = { x: spring.x, y: y + OGEE_SPRING_HANDLE * rise };\n  const inflect = { x: x + sign * OGEE_INFLECT_X * halfSpan, y: y + OGEE_INFLECT_Y * rise };\n  // Collinear handles preserve the tangent through the inflection.\n  const carry = {\n    x: inflect.x + OGEE_TANGENT * (inflect.x - handle.x),\n    y: inflect.y + OGEE_TANGENT * (inflect.y - handle.y),\n  };\n\n  if (toApex) {\n    path.quadraticCurveTo(handle.x, handle.y, inflect.x, inflect.y);\n    path.quadraticCurveTo(carry.x, carry.y, apex.x, apex.y);\n  } else {\n    path.quadraticCurveTo(carry.x, carry.y, inflect.x, inflect.y);\n    path.quadraticCurveTo(handle.x, handle.y, spring.x, spring.y);\n  }\n}\n","import { Path, Shape } from \"three\";\nimport { ArchStyle, archRise, traceArch } from \"../modeling/profiles/ArchProfile\";\n\n/** An opening in a wall — a doorway or a window. The same description; a different way in. */\nexport interface WallOpeningOptions {\n  /** Width of the opening. Defaults to `1.2`. */\n  width?: number;\n  /** Height of the straight sides, up to where the arch springs. Defaults to `1.4`. */\n  height?: number;\n  /**\n   * Rise of the arch above the springing. Defaults to half the width — a semicircle.\n   *\n   * A radius, not an angle. Some styles override it: `square` has none, `semicircle` forces it.\n   */\n  archHeight?: number;\n  /** Which arch tops the opening. Defaults to `semicircle`. See {@link ArchStyle}. */\n  arch?: ArchStyle;\n  /** Where the opening sits across the wall. Defaults to `0` — centered. */\n  x?: number;\n  /** **Windows only.** Height of the sill above the wall's base. A doorway's sill IS the floor. */\n  y?: number;\n}\n\nexport interface WallShapeOptions {\n  /** Width of the wall. Defaults to `4`. */\n  width?: number;\n  /** Height of the wall. Defaults to `3`. */\n  height?: number;\n  /** An opening that reaches the floor. Carved into the OUTLINE. Omit for a solid wall. */\n  doorway?: WallOpeningOptions;\n  /** Openings that float clear of every edge. Punched as HOLES. */\n  windows?: WallOpeningOptions[];\n  /** Raw holes, for shapes this class does not describe. Appended to {@link windows}. */\n  holes?: Path[];\n}\n\n/**\n * A wall, with a doorway carved out of it and windows punched through it — and those are **not the same\n * operation**, which is the entire reason this shape exists.\n *\n * ```\n *    ______________________          A WINDOW is strictly interior: the wall\n *   |    ___               |         completely surrounds it. That is a HOLE.\n *   |   |   |    ______    |\n *   |   |___|   /      \\   |         A DOORWAY reaches the floor. It touches the\n *   |          |        |  |         boundary, so it is NOT a hole — it is a notch\n *   |__________|        |__|         in the wall's own OUTLINE.\n * ```\n *\n * **Why a doorway cannot be a hole.** `Shape.holes` promises the triangulator a void it can enclose, and\n * it breaks in two ways when the void touches an edge. The triangulator bridges each hole to the outer\n * contour, and with the contours coincident that seam is degenerate — it will fill straight across your\n * threshold. Worse, and unavoidably: `ExtrudeGeometry` builds side walls along **every contour, holes\n * included**. A doorway-as-hole has a bottom segment lying in the sill, so extruding it produces a\n * horizontal face spanning the doorway at floor level. You cannot triangulate your way out of that one.\n * You asked for an edge there, so you got a face.\n *\n * Drawing the doorway into the outline means **there is no edge across the threshold at all**, so the\n * face never exists to be removed. And the notch's side walls become the REVEALS — the jamb faces and\n * the arch soffit — which is what a real doorway has and what you would otherwise have to fake.\n *\n * The rule generalizes: **an interior void is a hole; a void that touches the boundary belongs to the\n * outline.** It is the same rule that makes an arched door's arch part of its silhouette rather than\n * something cut out of a rectangle.\n *\n * Doorways and windows are described identically and topped by any {@link ArchStyle} — the difference is\n * only which way in they take. A door built from the same `width` / `height` / `archHeight` / `arch` as\n * the doorway will match it exactly, since both draw the same arc; give the opening a hair more for\n * clearance.\n *\n * @example\n * ```ts\n * const wall = new WallShape({\n *   width: 6,\n *   height: 4,\n *   doorway: { width: 1.3, arch: \"semicircle\" },\n *   windows: [\n *     { width: 0.7, height: 0.9, arch: \"ogee\", x: -2, y: 1.6 },\n *     { width: 0.7, height: 0.9, arch: \"ogee\", x: 2, y: 1.6 },\n *   ],\n * });\n *\n * const geometry = new ExtrudeGeometry(wall, { depth: 0.3, bevelEnabled: false });\n * ```\n */\nexport class WallShape extends Shape {\n  constructor({ width = 4, height = 3, doorway, windows, holes }: WallShapeOptions = {}) {\n    super();\n\n    const hw = width / 2;\n\n    // Counter-clockwise from the bottom-left, along the floor...\n    this.moveTo(-hw, 0);\n\n    if (doorway) {\n      const { half, springing, x } = resolve(doorway, width);\n\n      // ...but the floor stops at the near jamb. Up and over the opening, and back down to the floor on\n      // the far side: the wall walks AROUND the doorway rather than cutting it out.\n      this.lineTo(x - half, 0);\n      this.lineTo(x - half, springing);\n\n      // Left to right, because the outline is traveling that way along the top of the opening.\n      traceArch(this, { ...archOf(doorway, half, springing), x, from: \"left\", to: \"right\" });\n\n      this.lineTo(x + half, 0);\n    }\n\n    // ...on along the floor, up the far side, and back across the top.\n    this.lineTo(hw, 0);\n    this.lineTo(hw, height);\n    this.lineTo(-hw, height);\n    this.closePath();\n\n    // Windows never touch an edge, so these are genuine holes and `holes` is exactly right for them.\n    for (const window of windows ?? []) this.holes.push(openingCutout(window, width));\n    if (holes) this.holes.push(...holes);\n  }\n}\n\n/** An opening's half-width, springing height, and centerline — clamped to the wall it lives in. */\nfunction resolve(opening: WallOpeningOptions, wallWidth: number) {\n  const { width = 1.2, height = 1.4, x = 0, y = 0 } = opening;\n  return { half: Math.min(width, wallWidth) / 2, springing: y + height, x, sill: y };\n}\n\nfunction archOf(opening: WallOpeningOptions, half: number, springing: number) {\n  const { arch = \"semicircle\", archHeight } = opening;\n  return { style: arch, y: springing, halfSpan: half, rise: archHeight ?? half };\n}\n\n/**\n * A window, as a hole. Wound CLOCKWISE — the reverse of the wall's outline, which is what tells the\n * triangulator this is a void rather than another island of material.\n */\nexport function openingCutout(opening: WallOpeningOptions, wallWidth = Infinity): Path {\n  const { half, springing, x, sill } = resolve(opening, wallWidth);\n  const profile = archOf(opening, half, springing);\n\n  const path = new Path();\n  path.moveTo(x - half, sill);\n  path.lineTo(x - half, springing);\n\n  // Over the top, left to right...\n  traceArch(path, { ...profile, x, from: \"left\", to: \"right\" });\n\n  // ...and back down the far jamb to the sill. Unlike a doorway, a window HAS a bottom edge — it is\n  // interior, so that edge extrudes into a real sill face instead of a face lying across your floor.\n  path.lineTo(x + half, sill);\n  path.closePath();\n\n  return path;\n}\n\n/** The crown of an opening, measured from the wall's base. Useful for checking it clears the wall. */\nexport function wallOpeningTop(opening: WallOpeningOptions, wallWidth = Infinity): number {\n  const { half, springing } = resolve(opening, wallWidth);\n  return springing + archRise(archOf(opening, half, springing));\n}\n\n/**\n * An opening's outline as a filled {@link Shape} — the SAME curve the wall punches out of itself.\n *\n * This is the piece everything else in a window hangs off. The hole, the pane of glass that fills it, and\n * the frame ringing it are not three shapes that happen to line up; they are one shape used three ways.\n * Cut them from anywhere else and they will drift the moment somebody changes an arch.\n *\n * Wound counter-clockwise, and closed — up one jamb, over the arch, down the other, and back along the\n * SILL. A window has a bottom edge (a doorway does not, which is why a doorway can never be a hole).\n *\n * @example\n * ```ts\n * const opening = { width: 0.8, height: 1, arch: \"ogee\" } as const;\n *\n * const wall = new WallShape({ width: 6, height: 4, windows: [{ ...opening, x: -2, y: 1.5 }] });\n * const glass = new ShapeGeometry(openingOutline(opening)); // fits, by construction\n * ```\n */\nexport function openingOutline(opening: WallOpeningOptions): Shape {\n  const { half, springing, x, sill } = resolve(opening, Infinity);\n  const profile = archOf(opening, half, springing);\n\n  const shape = new Shape();\n\n  // Counter-clockwise: along the sill, up the far jamb...\n  shape.moveTo(x - half, sill);\n  shape.lineTo(x + half, sill);\n  shape.lineTo(x + half, springing);\n\n  // ...over the arch, right to left...\n  traceArch(shape, { ...profile, x, from: \"right\", to: \"left\" });\n\n  // ...and closePath drops back down the near jamb to the sill.\n  shape.closePath();\n\n  return shape;\n}\n","import { Quaternion, Vector3 } from \"three\";\nimport type { PathPoint } from \"../paths/PathPoint\";\nimport type { Station } from \"./Sweep\";\n\n/** Remove adjacent coincident positions; a closed run also removes the repeated endpoint. */\nfunction distinct<T>(items: T[], positionOf: (item: T) => Vector3, closed: boolean): T[] {\n  const kept = items.filter(\n    (item, i) => i === 0 || positionOf(item).distanceToSquared(positionOf(items[i - 1]!)) > 1e-12,\n  );\n  if (closed && kept.length > 1) {\n    const first = positionOf(kept[0]!);\n    const last = positionOf(kept[kept.length - 1]!);\n    if (first.distanceToSquared(last) < 1e-12) kept.pop();\n  }\n  return kept;\n}\n\n/** Incoming/outgoing unit edge directions; each open endpoint reuses its sole incident direction. */\nfunction edgeDirections(points: Vector3[], closed: boolean): { incoming: Vector3[]; outgoing: Vector3[] } {\n  const count = points.length;\n\n  const outgoing: Vector3[] = [];\n  for (let i = 0; i < count; i++) {\n    if (!closed && i === count - 1) {\n      outgoing.push(outgoing[i - 1]!.clone());\n      continue;\n    }\n    outgoing.push(points[(i + 1) % count]!.clone().sub(points[i]!).normalize());\n  }\n\n  const incoming: Vector3[] = [];\n  for (let i = 0; i < count; i++) {\n    if (!closed && i === 0) {\n      incoming.push(outgoing[0]!.clone());\n      continue;\n    }\n    incoming.push(outgoing[(i - 1 + count) % count]!.clone());\n  }\n\n  return { incoming, outgoing };\n}\n\n/** The bisecting plane at each corner: `normalize(incoming + outgoing)`. */\nfunction cutPlanes(incoming: Vector3[], outgoing: Vector3[]): Vector3[] {\n  return incoming.map((a, i) => {\n    const bisector = a.clone().add(outgoing[i]!);\n    // A full reversal has no bisector — the two directions cancel. Fall back to the segment.\n    return bisector.lengthSq() < 1e-10 ? outgoing[i]!.clone() : bisector.normalize();\n  });\n}\n\nexport interface MiterCutsOptions {\n  /** Treat the corners as a closed loop, so the last corner joins back to the first. Do not repeat the\n   * start point. */\n  closed?: boolean;\n}\n\n/**\n * Bisecting cut normals for separate members sharing a polyline; duplicate corners are removed.\n * Use matching sections/roll and widenSeatCuts for mirrored members; widening is 1 / cos φ.\n *\n * ```typescript\n * // A picture frame as four separate sticks, each mitered at both ends.\n * const cuts = miterCuts(corners, { closed: true });\n *\n * const sides = corners.map((from, i) => {\n *   const to = corners[(i + 1) % corners.length];\n *   return sweep(\n *     rectProfile(faceWidth, depth),\n *     miterFrames(linePath(from, to, 1), {\n *       startCut: cuts[i],\n *       endCut: cuts[(i + 1) % corners.length],\n *       widenSeatCuts: true,\n *     }),\n *   );\n * });\n * ```\n */\nexport function miterCuts(corners: Vector3[], { closed = false }: MiterCutsOptions = {}): Vector3[] {\n  const points = distinct(corners, (position) => position, closed);\n  if (points.length < 2) return [];\n\n  const { incoming, outgoing } = edgeDirections(points, closed);\n  return cutPlanes(incoming, outgoing);\n}\n\nexport interface MiterFramesOptions {\n  /** Reference for the initial perpendicular frame; must yield a nonzero seed. */\n  reference?: Vector3;\n  /**\n   * Treat the path as a closed loop — the last point joins back to the first, and both get mitered.\n   * Do not repeat the start point.\n   */\n  closed?: boolean;\n  /** First endpoint cut normal, oriented to the path; ignored on a closed path. */\n  startCut?: Vector3;\n  /** Last endpoint cut normal, oriented to the path; ignored on a closed path. */\n  endCut?: Vector3;\n  /**\n * false preserves the cut-plane footprint; true preserves section width through widening by 1 / cos φ.\n * Without widening, section-width loss is 1 − cos φ (about 1.1% at 8.5°); internal corners always widen.\n */\n  widenSeatCuts?: boolean;\n  /**\n * Clamp widening 1 / cos φ to miterLimit; Infinity removes the bound.\n * Clamping shortens the miter without adding bevel topology.\n */\n  miterLimit?: number;\n}\n\n/**\n * Frame polyline corners on normalize(incoming + outgoing), using positions rather than supplied tangents.\n * Widen along the lean axis by 1 / cos φ (√2 at a 90° corner); matched sections share each corner ring.\n *\n * ```typescript\n * // A mitered square frame, swept as one closed loop.\n * const corners = [a, b, c, d].map((position) => ({ position, tangent: new Vector3() }));\n * const rail = sweep(rectProfile(0.03, 0.02), miterFrames(corners, { closed: true }), { closed: true });\n * ```\n *\n * ```typescript\n * // A raked post seat-cut flat at both ends, so it sits flush on horizontal plates.\n * const up = new Vector3(0, 1, 0);\n * const post = sweep(circleProfile(0.015, 4), miterFrames(linePath(foot, head, 2), { startCut: up, endCut: up }));\n * ```\n */\nexport function miterFrames(\n  path: PathPoint[],\n  {\n    reference = new Vector3(0, 0, 1),\n    closed = false,\n    startCut,\n    endCut,\n    widenSeatCuts = false,\n    miterLimit = 4,\n  }: MiterFramesOptions = {},\n): Station[] {\n  const points = distinct(path, (p) => p.position, closed);\n  const count = points.length;\n  if (count < 2) return [];\n\n  // Internal corners use bisectors; open endpoints use their segment direction.\n  const { incoming, outgoing } = edgeDirections(\n    points.map((p) => p.position),\n    closed,\n  );\n  const cuts = cutPlanes(incoming, outgoing);\n\n  // Orient supplied seat normals along the path to avoid a 180° frame reversal.\n  const seated = new Set<number>();\n  if (!closed) {\n    const seat = (supplied: Vector3 | undefined, index: number, direction: Vector3) => {\n      if (!supplied || supplied.lengthSq() < 1e-12) return;\n      const normal = supplied.clone().normalize();\n      cuts[index] = normal.dot(direction) < 0 ? normal.negate() : normal;\n      seated.add(index);\n    };\n    seat(startCut, 0, outgoing[0]!);\n    seat(endCut, count - 1, incoming[count - 1]!);\n  }\n\n  let normal = reference.clone().sub(cuts[0]!.clone().multiplyScalar(reference.dot(cuts[0]!)));\n  if (normal.lengthSq() < 1e-8) {\n    normal = new Vector3(1, 0, 0).sub(cuts[0]!.clone().multiplyScalar(cuts[0]!.x));\n  }\n  normal.normalize();\n\n  const stations: Station[] = [];\n  const axis = new Vector3();\n  const rotation = new Quaternion();\n\n  for (let i = 0; i < count; i++) {\n    const cut = cuts[i]!;\n\n    // Carry the normal from the previous cut plane so rings don't twist relative to each other.\n    if (i > 0) {\n      axis.crossVectors(cuts[i - 1]!, cut);\n      if (axis.lengthSq() > 1e-12) {\n        const angle = Math.acos(Math.min(1, Math.max(-1, cuts[i - 1]!.dot(cut))));\n        rotation.setFromAxisAngle(axis.normalize(), angle);\n        normal.applyQuaternion(rotation);\n      }\n      normal.sub(cut.clone().multiplyScalar(normal.dot(cut))).normalize();\n    }\n\n    // `1 / cos φ` — how much a slanted cut widens the section. The dot product IS cos φ, so this is\n    // stable without computing the angle.\n    const direction = incoming[i]!;\n    const cosPhi = Math.abs(direction.dot(cut));\n    // Seat widening is optional; internal corners widen to preserve the member section.\n    const raw = cosPhi > 1e-6 && (widenSeatCuts || !seated.has(i)) ? 1 / cosPhi : 1;\n    const widen = Math.min(raw, Math.max(1, miterLimit));\n\n    const frameNormal = normal.clone();\n    const frameBinormal = new Vector3().crossVectors(cut, frameNormal).normalize();\n\n    // Apply I + (widen - 1) * lean ⊗ lean to both profile basis vectors.\n    // This stretches position + normal * px + binormal * py along the in-plane lean axis.\n    const lean = direction.clone().sub(cut.clone().multiplyScalar(direction.dot(cut)));\n    if (widen > 1 + 1e-9 && lean.lengthSq() > 1e-12) {\n      lean.normalize();\n      const stretch = (v: Vector3) => v.addScaledVector(lean, (widen - 1) * lean.dot(v));\n      stretch(frameNormal);\n      stretch(frameBinormal);\n    }\n\n    stations.push({\n      position: points[i]!.position.clone(),\n      tangent: cut.clone(),\n      normal: frameNormal,\n      binormal: frameBinormal,\n      scale: points[i]!.scale,\n    });\n  }\n\n  return stations;\n}\n\n// Joint identities: seat widening = 1 / |d · n|; crossing-member notch length = width / sin(angle).\n// Skew-axis separation is measured along normalize(dA × dB); parallel axes require a separate case.\n","import { BufferAttribute, BufferGeometry } from \"three\";\n\n/** 3D coordinate tuple. */\nexport type Vec3 = [number, number, number];\n/** 2D coordinate tuple, used for profiles and UVs. */\nexport type Vec2 = [number, number];\n\n/** The four flat arrays a `BufferGeometry` is assembled from. */\nexport interface GeometryBuffers {\n  positions: number[];\n  normals: number[];\n  uvs: number[];\n  indices: number[];\n}\n\n\nexport function createGeometryBuffers(): GeometryBuffers {\n  return { positions: [], normals: [], uvs: [], indices: [] };\n}\n\n/** Per-corner UV order: (0,0), (0,1), (1,1), (1,0). */\nexport const UNIT_QUAD_UV: [Vec2, Vec2, Vec2, Vec2] = [\n  [0, 0],\n  [0, 1],\n  [1, 1],\n  [1, 0],\n];\n\n/**\n * Append four face-local vertices and triangles (0,1,2), (0,2,3). Corners must face outward by winding.\n * An omitted normal uses the first three corners; supplied normals are copied unchanged.\n *\n * ```ts\n * const buffers = createGeometryBuffers();\n *\n * pushQuad(\n *   buffers,\n *   [[-1, 0, -1], [-1, 0, 1], [1, 0, 1], [1, 0, -1]], // CCW seen from +Y\n *   [0, 1, 0],\n *   UNIT_QUAD_UV,\n * );\n *\n * const geometry = toBufferGeometry(buffers);\n * ```\n */\nexport function pushQuad(\n  buffers: GeometryBuffers,\n  corners: [Vec3, Vec3, Vec3, Vec3],\n  normal: Vec3 | undefined,\n  cornerUvs: [Vec2, Vec2, Vec2, Vec2] = UNIT_QUAD_UV,\n): void {\n  const face = normal ?? faceNormal(corners[0], corners[1], corners[2]);\n  const base = buffers.positions.length / 3;\n\n  for (const [x, y, z] of corners) {\n    buffers.positions.push(x, y, z);\n    buffers.normals.push(face[0], face[1], face[2]);\n  }\n  for (const [u, v] of cornerUvs) buffers.uvs.push(u, v);\n\n  buffers.indices.push(base, base + 1, base + 2, base, base + 2, base + 3);\n}\n\n/**\n * Append three face-local vertices and one triangle, counter-clockwise as seen from the outward side.\n * An omitted normal is derived from winding; supplied normals are copied unchanged.\n *\n * ```ts\n * // One face of a pyramid cap: two shoulder corners rising to the apex.\n * pushTriangle(\n *   buffers,\n *   [shoulderA, shoulderB, apex],\n *   undefined,                          // slanted face — let the winding derive the normal\n *   [[0, 0], [1, 0], [0.5, 1]],         // apex sits at the top-center of the texture\n * );\n * ```\n */\nexport function pushTriangle(\n  buffers: GeometryBuffers,\n  corners: [Vec3, Vec3, Vec3],\n  normal?: Vec3,\n  cornerUvs: [Vec2, Vec2, Vec2] = [\n    [0, 0],\n    [1, 0],\n    [0.5, 1],\n  ],\n): void {\n  const face = normal ?? faceNormal(corners[0], corners[1], corners[2]);\n  const base = buffers.positions.length / 3;\n\n  for (const [x, y, z] of corners) {\n    buffers.positions.push(x, y, z);\n    buffers.normals.push(face[0], face[1], face[2]);\n  }\n  for (const [u, v] of cornerUvs) buffers.uvs.push(u, v);\n\n  buffers.indices.push(base, base + 1, base + 2);\n}\n\n/** Normalized (b − a) × (c − a), facing the viewer of CCW corners; degenerate triangles return a zero vector. */\nexport function faceNormal(a: Vec3, b: Vec3, c: Vec3): Vec3 {\n  const ux = b[0] - a[0];\n  const uy = b[1] - a[1];\n  const uz = b[2] - a[2];\n  const vx = c[0] - a[0];\n  const vy = c[1] - a[1];\n  const vz = c[2] - a[2];\n\n  const nx = uy * vz - uz * vy;\n  const ny = uz * vx - ux * vz;\n  const nz = ux * vy - uy * vx;\n\n  const length = Math.hypot(nx, ny, nz) || 1;\n  return [nx / length, ny / length, nz / length];\n}\n\n/**\n * Copy arrays into an indexed BufferGeometry with position, normal and UV attributes and a bounding sphere.\n *\n * ```ts\n * const buffers = createGeometryBuffers();\n * pushQuad(buffers, corners, [0, 1, 0]);\n * const geometry = toBufferGeometry(buffers);\n * ```\n */\nexport function toBufferGeometry(buffers: GeometryBuffers): BufferGeometry {\n  const geometry = new BufferGeometry();\n\n  geometry.setIndex(buffers.indices);\n  geometry.setAttribute(\"position\", new BufferAttribute(new Float32Array(buffers.positions), 3));\n  geometry.setAttribute(\"normal\", new BufferAttribute(new Float32Array(buffers.normals), 3));\n  geometry.setAttribute(\"uv\", new BufferAttribute(new Float32Array(buffers.uvs), 2));\n  geometry.computeBoundingSphere();\n\n  return geometry;\n}\n","import { BufferGeometry, Vector2, Vector3 } from \"three\";\nimport { linePath } from \"../../modeling/paths/LinePath\";\nimport { openingOutline, type WallOpeningOptions } from \"../../shapes/WallShape\";\nimport { miterFrames } from \"../../modeling/surfaces/MiterFrames\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type Vec2,\n  type Vec3,\n} from \"../../modeling/mesh/GeometryBuffers\";\n\n/**\n * Bars spanning an opening, each cut into its boundary at BOTH ends.\n *\n * Shared by every lattice, because the lattice type is only ever a choice of angles: a diamond is two\n * families at `±45°`, a Gregorian is `90°` and `0°`. Nothing here knows which it is building.\n *\n * The construction is a LOFT, not a sweep. Every point of a bar's ring runs along its own axis to\n * whichever segment of the boundary it meets, and the ring is split wherever that choice changes — so the\n * ends follow an arch exactly as closely as the arch itself is cut, and a square head degenerates to a\n * plain square cut with no special case. That is what stops a bar poking a tooth out through the frame.\n *\n * Bars CROSS one another and are left to interpenetrate, which is correct rather than lazy: lead came\n * crosses lead came and mullion crosses transom, and an X-junction has no bisector to share.\n */\n\n/** How close to a shared vertex a crossing counts as being ON it. See {@link lineChords}. */\nconst VERTEX_EPSILON = 1e-9;\n\nconst cross2 = (a: Vector2, b: Vector2) => a.x * b.y - a.y * b.x;\n\n/** One family of parallel bars. */\nexport interface BarFamily {\n  /** Direction, in degrees from horizontal. `90` is upright, `0` is level, `45` is a diamond's leg. */\n  angle: number;\n  /** Perpendicular distance between neighbors — the same at any angle, unlike spacing along an axis. */\n  spacing: number;\n  /** Slides the family across the opening. Defaults to `0`. */\n  phase?: number;\n}\n\n/** The opening's outline as a closed polyline, normalized to the origin and de-duplicated. */\nexport function openingBoundary(opening: WallOpeningOptions, curveSegments: number): Vector2[] {\n  const points = openingOutline({ ...opening, x: 0, y: 0 })\n    .getPoints(Math.max(2, Math.round(curveSegments)))\n    .map((p) => new Vector2(p.x, p.y));\n  // `getPoints` closes the loop by repeating the start, and a repeated point is a zero-length edge.\n  if (points.length > 1 && points[0]!.distanceToSquared(points[points.length - 1]!) < 1e-12) {\n    points.pop();\n  }\n  return points;\n}\n\n/** Where a ray from `p` along `d` first meets the boundary. `owner: -1` when it misses entirely. */\nfunction castToBoundary(p: Vector2, d: Vector2, boundary: Vector2[]): { t: number; owner: number } {\n  let best = Infinity;\n  let owner = -1;\n  for (let i = 0; i < boundary.length; i++) {\n    const a = boundary[i]!;\n    const b = boundary[(i + 1) % boundary.length]!;\n    const edge = b.clone().sub(a);\n    const denominator = cross2(d, edge);\n    if (Math.abs(denominator) < 1e-12) continue;\n    const w = a.clone().sub(p);\n    const t = cross2(w, edge) / denominator;\n    const u = cross2(w, d) / denominator;\n    // The SEGMENT, not its infinite line. Taking the nearest LINE would cut against edges that are not\n    // there, which is what makes a concave head — an ogee — come out wrong.\n    if (t > 1e-9 && u >= -1e-9 && u <= 1 + 1e-9 && t < best) {\n      best = t;\n      owner = i;\n    }\n  }\n  return { t: best, owner };\n}\n\n/**\n * Every stretch of the infinite line through `p` along `d` that lies INSIDE the boundary.\n *\n * A bar laid across an ogee or a horseshoe enters and leaves more than once, so the crossings are\n * collected, sorted, and taken in pairs — between the first and second you are inside, between the second\n * and third you are out.\n */\nfunction lineChords(p: Vector2, d: Vector2, boundary: Vector2[]): [number, number][] {\n  const hits: number[] = [];\n  for (let i = 0; i < boundary.length; i++) {\n    const a = boundary[i]!;\n    const b = boundary[(i + 1) % boundary.length]!;\n    const edge = b.clone().sub(a);\n    const denominator = cross2(d, edge);\n    if (Math.abs(denominator) < 1e-12) continue;\n    const w = a.clone().sub(p);\n    const u = cross2(w, d) / denominator;\n    // Half-open on `u`, so a crossing landing exactly on a shared VERTEX is counted once rather than\n    // twice — that would be `u = 1` on the arriving edge and `u = 0` on the leaving one.\n    //\n    // The tolerance is not decoration. Exact `u < 1` fails to exclude a `u` that rounds to\n    // `0.999999999...`, the vertex is counted twice, the crossing count goes ODD, and the inside/outside\n    // pairing shifts for the rest of the line — turning the real chord into a zero-length one and losing\n    // the bar. It fires only when a line passes exactly through a corner, and then on one side and not\n    // the other, purely by how the arithmetic rounded.\n    if (u >= -VERTEX_EPSILON && u < 1 - VERTEX_EPSILON) hits.push(cross2(w, edge) / denominator);\n  }\n  hits.sort((a, b) => a - b);\n\n  const chords: [number, number][] = [];\n  for (let i = 0; i + 1 < hits.length; i += 2) chords.push([hits[i]!, hits[i + 1]!]);\n  return chords;\n}\n\n/** Even-odd ray cast. A bar that starts outside gets a perfect cut to a meaningless question. */\nfunction insideBoundary(p: Vector2, boundary: Vector2[]): boolean {\n  let inside = false;\n  for (let i = 0, j = boundary.length - 1; i < boundary.length; j = i++) {\n    const a = boundary[i]!;\n    const b = boundary[j]!;\n    if (a.y > p.y !== b.y > p.y && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x) {\n      inside = !inside;\n    }\n  }\n  return inside;\n}\n\n/**\n * Where along a ring edge the winning boundary segment changes, for one direction.\n *\n * The ray through `lerp(a, b, s)` passes through a boundary vertex when `(vertex − p(s)) × d = 0`, and\n * `p(s)` is linear in `s`, so each root is one division. A wide bar crosses several vertices at once, so\n * every one between the two owners contributes a split.\n */\nfunction splitParams(a: Vector3, b: Vector3, d: Vector2, boundary: Vector2[]): number[] {\n  const here = castToBoundary(new Vector2(a.x, a.y), d, boundary);\n  const next = castToBoundary(new Vector2(b.x, b.y), d, boundary);\n  if (here.owner < 0 || next.owner < 0 || here.owner === next.owner) return [];\n\n  // Segment indices are CYCLIC — the sill is `0` and the last jamb is the final index, and they share the\n  // opening's bottom corner. Comparing them as plain numbers sends the walk the long way over the crown.\n  const count = boundary.length;\n  const ahead = (next.owner - here.owner + count) % count;\n  const behind = (here.owner - next.owner + count) % count;\n  const step = ahead <= behind ? 1 : -1;\n  const steps = Math.min(ahead, behind);\n\n  const out: number[] = [];\n  let k = here.owner;\n  for (let i = 0; i < steps; i++) {\n    const vertex = boundary[step > 0 ? (k + 1) % count : k]!;\n    const gi = cross2(vertex.clone().sub(new Vector2(a.x, a.y)), d);\n    const gj = cross2(vertex.clone().sub(new Vector2(b.x, b.y)), d);\n    const s = gi / (gi - gj);\n    if (Number.isFinite(s) && s > 1e-9 && s < 1 - 1e-9) out.push(s);\n    k = (k + step + count) % count;\n  }\n  return out;\n}\n\ninterface Span {\n  ring: Vector3;\n  back: Vector3;\n  front: Vector3;\n}\n\n/**\n * Both ends cut to the boundary, on ONE ring.\n *\n * The two ends cross different segments, so each wants its own splits — and a side band built on a ring\n * carrying only one set tears where the other falls. So the splits are unioned before the ring is\n * subdivided. A split the far end did not ask for costs a degenerate seam, never a hole.\n */\nfunction spanOpening(ring: Vector3[], axis: Vector3, boundary: Vector2[]): Span[] {\n  const forward = new Vector2(axis.x, axis.y).normalize();\n  const backward = forward.clone().negate();\n\n  const points: Vector3[] = [];\n  for (let i = 0; i < ring.length; i++) {\n    const a = ring[i]!;\n    const b = ring[(i + 1) % ring.length]!;\n    points.push(a.clone());\n    const cuts = [...splitParams(a, b, forward, boundary), ...splitParams(a, b, backward, boundary)]\n      .sort((p, q) => p - q)\n      .filter((s, index, all) => index === 0 || s - all[index - 1]! > 1e-9);\n    for (const s of cuts) points.push(a.clone().lerp(b, s));\n  }\n\n  const spans: Span[] = [];\n  for (const p of points) {\n    const flat = new Vector2(p.x, p.y);\n    const ahead = castToBoundary(flat, forward, boundary);\n    const behind = castToBoundary(flat, backward, boundary);\n    // A point that escapes in either direction has no bar at all. Refusing beats silently dropping a\n    // vertex out of a closed loop, which would open the solid.\n    if (ahead.owner < 0 || behind.owner < 0) return [];\n    spans.push({\n      ring: p,\n      front: p.clone().addScaledVector(axis, ahead.t),\n      back: p.clone().addScaledVector(axis, -behind.t),\n    });\n  }\n  return spans;\n}\n\n/**\n * One end cap, triangulated so no triangle spans two facets.\n *\n * The cast reads only a point's LATERAL offset, so facet boundaries are lines of constant lateral offset,\n * and the cap — which projects exactly onto the ring, every point traveling along the same axis — is a\n * polygon monotone in that coordinate with a vertex on both chains at every cut. Walking the two chains in\n * lateral order therefore never reaches past a cut. Fanning each facet instead leaves a hole the moment\n * there are more than two crossings.\n */\nfunction capEnd(\n  buffers: ReturnType<typeof createGeometryBuffers>,\n  spans: Span[],\n  pick: (s: Span) => Vector3,\n  axis: Vector3,\n  flip: boolean,\n): void {\n  const at = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n  const count = spans.length;\n  const center = spans.reduce((sum, s) => sum.add(s.ring), new Vector3()).divideScalar(count);\n  const lateral = new Vector3(-axis.y, axis.x, 0).normalize();\n  const u = spans.map((s) => s.ring.clone().sub(center).dot(lateral));\n\n  let low = 0;\n  let high = 0;\n  for (let i = 1; i < count; i++) {\n    if (u[i]! < u[low]!) low = i;\n    if (u[i]! > u[high]!) high = i;\n  }\n  const chain = (step: number) => {\n    const out = [low];\n    for (let i = (low + step + count) % count; i !== high; i = (i + step + count) % count) out.push(i);\n    out.push(high);\n    return out;\n  };\n  const forward = chain(1);\n  const backward = chain(-1);\n\n  const emit = (a: number, b: number, c: number) => {\n    const tri: [Vec3, Vec3, Vec3] = [at(pick(spans[a]!)), at(pick(spans[b]!)), at(pick(spans[c]!))];\n    pushTriangle(buffers, flip ? [tri[0], tri[2], tri[1]] : tri, undefined);\n  };\n\n  let a = 0;\n  let b = 0;\n  while (a < forward.length - 1 || b < backward.length - 1) {\n    const advance =\n      b >= backward.length - 1 || (a < forward.length - 1 && u[forward[a + 1]!]! <= u[backward[b + 1]!]!);\n    if (advance) {\n      emit(forward[a]!, backward[b]!, forward[a + 1]!);\n      a++;\n    } else {\n      emit(forward[a]!, backward[b]!, backward[b + 1]!);\n      b++;\n    }\n  }\n}\n\n/** One bar: sides between its two cut ends, and a cap on each. */\nfunction buildBar(spans: Span[], axis: Vector3): BufferGeometry | null {\n  if (spans.length < 3) return null;\n\n  const buffers = createGeometryBuffers();\n  const at = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n  // Wound so the faces point OUT of the bar. Reverse this and the mesh is still watertight, still inside\n  // the opening, still free of NaN — every structural check passes — and it renders inside out, showing\n  // its back faces to the camera. Orientation is a separate property from closure, and the test for it is\n  // the SIGNED VOLUME: positive means outward.\n  for (let i = 0; i < spans.length; i++) {\n    const j = (i + 1) % spans.length;\n    pushQuad(\n      buffers,\n      [at(spans[i]!.back), at(spans[j]!.back), at(spans[j]!.front), at(spans[i]!.front)],\n      undefined,\n    );\n  }\n  capEnd(buffers, spans, (s) => s.front, axis, true);\n  capEnd(buffers, spans, (s) => s.back, axis, false);\n  return toBufferGeometry(buffers);\n}\n\n/**\n * Every bar of every family, each spanning the opening and cut into its boundary at both ends.\n *\n * Bars shorter than `minLength` are dropped: a chord barely longer than the stock is an offcut no glazier\n * would cut, and its ring would straddle the boundary anyway.\n */\nexport function buildLatticeBars(\n  boundary: Vector2[],\n  families: BarFamily[],\n  profile: Vec2[],\n  minLength: number,\n): BufferGeometry[] {\n  const parts: BufferGeometry[] = [];\n\n  for (const family of families) {\n    const angle = (family.angle * Math.PI) / 180;\n    const axis = new Vector3(Math.cos(angle), Math.sin(angle), 0);\n    const normal = new Vector2(-Math.sin(angle), Math.cos(angle));\n    const flat = new Vector2(axis.x, axis.y);\n    const phase = family.phase ?? 0;\n    const step = Math.max(family.spacing, 1e-4);\n\n    const offsets = boundary.map((p) => p.dot(normal));\n    const from = Math.ceil((Math.min(...offsets) - phase) / step);\n    const to = Math.floor((Math.max(...offsets) - phase) / step);\n\n    for (let k = from; k <= to; k++) {\n      const seed = normal.clone().multiplyScalar(k * step + phase);\n      for (const [near, far] of lineChords(seed, flat, boundary)) {\n        if (far - near < minLength) continue;\n\n        const center = new Vector3(seed.x, seed.y, 0).addScaledVector(axis, (near + far) / 2);\n        const station = miterFrames(linePath(center, center.clone().add(axis), 1), {\n          reference: new Vector3(0, 0, 1),\n        })[0]!;\n        const ring = profile.map(([px, py]) =>\n          station.position\n            .clone()\n            .addScaledVector(station.normal, px)\n            .addScaledVector(station.binormal, py),\n        );\n        if (ring.some((p) => !insideBoundary(new Vector2(p.x, p.y), boundary))) continue;\n\n        const bar = buildBar(spanOpening(ring, axis, boundary), axis);\n        if (bar) parts.push(bar);\n      }\n    }\n  }\n  return parts;\n}\n","import { BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport type { WallOpeningOptions } from \"../../shapes/WallShape\";\nimport { circleProfile } from \"../../modeling/profiles/Profiles\";\nimport type { Vec2 } from \"../../modeling/mesh/GeometryBuffers\";\nimport { buildLatticeBars, openingBoundary } from \"./latticeBars\";\n\nexport interface DiamondLatticeGeometryOptions {\n  /**\n   * The opening the lattice fills. The SAME description a wall is punched with and a\n   * {@link WindowFrameGeometry} is built from, so the three agree by construction.\n   *\n   * **There is no separate rectangular case.** `arch: \"square\"` is a flat head — an arch-shaped hole with\n   * no curve in it — so a mullioned rectangle and a gothic light are one geometry with different points.\n   */\n  opening?: WallOpeningOptions;\n  /**\n   * Half the angle between the two came families, in degrees. Defaults to `45`, which is the square\n   * diamond everyone pictures.\n   *\n   * Lower leans the quarries tall, higher leans them wide. The families are symmetric: `+angle` and\n   * `−angle`.\n   */\n  angle?: number;\n  /**\n   * Perpendicular distance between neighboring cames. Defaults to `0.19`.\n   *\n   * Measured across the cames rather than along an axis, so it means the same thing at any `angle` —\n   * spacing measured on an axis would compress as the lattice leans.\n   */\n  spacing?: number;\n  /**\n   * Slides the whole grid across the opening, in world units. Defaults to `0`.\n   *\n   * The difference between a quarry centered on the crown and a came running up it. Nothing else moves the\n   * pattern relative to the opening, and it is what decides which cames clip a corner and get dropped.\n   */\n  phase?: number;\n  /** Width of the came ACROSS the glass — what you see from the front. Defaults to `0.022`. */\n  cameWidth?: number;\n  /**\n   * Depth of the came THROUGH the glass. Defaults to `cameWidth`, a square section.\n   *\n   * Free to vary because it is the one dimension none of the cutting depends on: a came's end is decided\n   * by casting in the opening's own plane, so a point's depth never reaches the boundary maths. Real lead\n   * is deeper than it is wide, and a flat came reads as painted rather than leaded.\n   */\n  cameDepth?: number;\n  /** Sides on the came's section — the low-poly knob. `4` is square lead, `12` reads round. Defaults to `4`. */\n  cameSides?: number;\n  /**\n   * How finely the arch is followed. Defaults to `20`.\n   *\n   * This is also the ceiling on the came ENDS: they are cut against the outline's segments, so a came can\n   * never be finer than the boundary it dies into — and is never rougher.\n   */\n  curveSegments?: number;\n}\n\n/**\n * Diamond lattice leading — the cames of a leaded light, cut into the opening at both ends.\n *\n * Every came SPANS the opening, and both of its ends are cut by the boundary itself rather than stopping\n * square. That is the whole point: a square-ended bar leaves teeth poking out through the frame, which is\n * what has always made an arched lattice hard. Here each ring point of the came runs along its own axis to\n * whichever segment of the outline it meets, with the ring split wherever that choice changes, so the ends\n * follow the arch exactly as closely as the arch itself is cut.\n *\n * **Not \"arched\" in the name, deliberately.** `arch: \"square\"` is a flat head, so a rectangular light and\n * a gothic one are the same geometry with different points — exactly as {@link WindowFrameGeometry} rings\n * any arch without saying so in its name. Two names would rebuild the split this construction removes.\n *\n * Cames CROSS one another and are left to interpenetrate, which is correct rather than lazy: lead came\n * crosses lead came, and an X-junction has no bisector to share.\n *\n * Baked to a single `BufferGeometry` — one draw call for the whole leading.\n *\n * Drawn at the ORIGIN — centered on X, sill at `y = 0` — whatever the opening's own `x` and `y` say, so\n * one lattice can be positioned into many openings and so it lands on a `WindowFrameGeometry` built from\n * the same description. Material groups: none; pass one material, not an array.\n *\n * A leaded light is the obvious use, but nothing here knows that. The same thing is a garden trellis, a\n * gate infill, or a screen.\n *\n * @example\n * ```ts\n * const opening = { width: 1.24, height: 1.15, arch: \"pointed\", archHeight: 0.78 } as const;\n *\n * const lattice = new Mesh(new DiamondLatticeGeometry({ opening }), lead);\n * const frame = new Mesh(new WindowFrameGeometry({ opening }), iron);\n * ```\n */\nexport class DiamondLatticeGeometry extends BufferGeometry {\n  /** How many cames were built. Short offcuts that clip a corner are dropped, so this is not derivable. */\n  readonly cameCount: number;\n\n  constructor({\n    opening = {},\n    angle = 45,\n    spacing = 0.19,\n    phase = 0,\n    cameWidth = 0.022,\n    cameDepth = cameWidth,\n    cameSides = 4,\n    curveSegments = 20,\n  }: DiamondLatticeGeometryOptions = {}) {\n    super();\n\n    // At the origin: the lattice does not care where its opening sits in a wall, only what shape it is.\n    const boundary = openingBoundary(opening, curveSegments);\n\n    // `circleProfile` is a regular polygon, so it is square by construction. Scaling the axis that maps\n    // to the frame's NORMAL — the one running through the glass — makes it rectangular without touching\n    // the axis the cutting reads.\n    const depthScale = cameWidth > 0 ? cameDepth / cameWidth : 1;\n    const profile = circleProfile(cameWidth / 2, Math.max(3, Math.round(cameSides))).map(\n      ([px, py]) => [px * depthScale, py] as Vec2,\n    );\n    // Two families at ±angle. The lattice TYPE is only ever a choice of angles — a Gregorian is the same\n    // call at 90° and 0° — which is why the bar machinery lives in `latticeBars` and neither geometry\n    // knows what the other is building.\n    const parts = buildLatticeBars(\n      boundary,\n      [\n        { angle, spacing, phase },\n        { angle: -angle, spacing, phase },\n      ],\n      profile,\n      cameWidth * 3,\n    );\n    this.cameCount = parts.length;\n\n    if (parts.length === 0) {\n      this.computeBoundingSphere();\n      return;\n    }\n\n    // Not cast — `mergeGeometries` returns null on mismatched attributes, and a cast turns that into an\n    // unreadable \"cannot read properties of null\" three frames later.\n    const merged = mergeGeometries(parts, false);\n    if (!merged) throw new Error(\"DiamondLatticeGeometry: came parts have incompatible attributes.\");\n\n    this.copy(merged);\n    merged.dispose();\n    parts.forEach((part) => part.dispose());\n    this.computeBoundingSphere();\n  }\n}\n\n/** How close to a shared vertex a crossing counts as being ON it. See {@link lineChords}. */\nconst VERTEX_EPSILON = 1e-9;\n\n","import { Vector2 } from \"three\";\n\n/**\n * Offset a CCW loop: positive distance expands, negative contracts; repeated closing points are removed.\n * Miters beyond miterLimit × |distance| bevel; right-angle miter ratio is √2 ≈ 1.41. No global intersection repair.\n *\n * ```ts\n * const outline = openingOutline(opening).getPoints(48);\n * const outer = offsetLoop(outline, 0.06);   // the frame's outer edge, out on the wall\n * const inner = offsetLoop(outline, -0.03);  // its inner edge, biting into the aperture\n * ```\n */\nexport function offsetLoop(points: Vector2[], distance: number, miterLimit = 4): Vector2[] {\n  // Remove zero-length edges before calculating normals.\n  const loop = points.filter((p, i) => i === 0 || p.distanceToSquared(points[i - 1]!) > 1e-12);\n  if (loop.length > 1 && loop[0]!.distanceToSquared(loop[loop.length - 1]!) < 1e-12) loop.pop();\n\n  const count = loop.length;\n  if (count < 3 || distance === 0) return loop.map((p) => p.clone());\n\n  // The outward normal of an edge, for a counter-clockwise loop: turn its direction right.\n  const normals = loop.map((p, i) => {\n    const next = loop[(i + 1) % count]!;\n    const dx = next.x - p.x;\n    const dy = next.y - p.y;\n    const length = Math.hypot(dx, dy) || 1;\n    return new Vector2(dy / length, -dx / length);\n  });\n\n  const offset: Vector2[] = [];\n\n  loop.forEach((p, i) => {\n    const into = normals[(i - 1 + count) % count]!; // the edge arriving here\n    const outOf = normals[i]!; // the edge leaving\n\n    const bisector = new Vector2().addVectors(into, outOf);\n    if (bisector.lengthSq() < 1e-12) {\n      // A full reversal — the miter is infinite. Bevel across the two offset edges.\n      offset.push(p.clone().addScaledVector(into, distance), p.clone().addScaledVector(outOf, distance));\n      return;\n    }\n    bisector.normalize();\n\n    // cos is the cosine of the normals' half-angle; 1/|cos| is the miter-length ratio.\n    // Beyond the limit, join the offset edge endpoints with a bevel.\n    const cos = bisector.dot(outOf);\n    if (Math.abs(cos) < 1e-6 || 1 / Math.abs(cos) > miterLimit) {\n      offset.push(p.clone().addScaledVector(into, distance), p.clone().addScaledVector(outOf, distance));\n      return;\n    }\n\n    offset.push(p.clone().addScaledVector(bisector, distance / cos));\n  });\n\n  // Heuristic: discard points closer than |distance| to a source vertex.\n  // This tests vertex distance, not edge distance or self-intersections; keep the offset if fewer than three survive.\n  const reach = Math.abs(distance) * (1 - 1e-3);\n  const kept = offset.filter((p) => nearest(p, loop) >= reach);\n\n  return kept.length >= 3 ? kept : offset;\n}\n\n/** Distance from a point to the closest vertex of a loop. */\nfunction nearest(point: Vector2, loop: Vector2[]): number {\n  let best = Infinity;\n  for (const q of loop) best = Math.min(best, point.distanceToSquared(q));\n  return Math.sqrt(best);\n}\n","import { Shape, ShapeGeometry, Vector2 } from \"three\";\nimport { openingOutline, type WallOpeningOptions } from \"../../shapes/WallShape\";\nimport { offsetLoop } from \"../../modeling/profiles/OffsetLoop\";\n\nexport interface PaneGeometryOptions {\n  /**\n   * The opening the pane glazes. The SAME description a wall is punched with, a\n   * {@link WindowFrameGeometry} rings, and a {@link DiamondLatticeGeometry} fills — so the four agree by\n   * construction rather than by keeping numbers in step.\n   */\n  opening?: WallOpeningOptions;\n  /**\n   * How far the pane runs PAST the opening, into the frame's rebate. Defaults to `0`.\n   *\n   * A real pane is oversize, not undersize: its edge is hidden in the groove that holds it, and the\n   * visible opening is the frame. `0` fills the opening exactly, which is what a leaded light does, since\n   * there the came holds the glass rather than a rebate. A NEGATIVE value pulls the pane in and leaves a\n   * deliberate reveal — rarely what you want, because it reads as glass that does not fit.\n   */\n  rebate?: number;\n  /** How finely the arch is followed — the low-poly knob. Defaults to `24`. */\n  curveSegments?: number;\n  /**\n   * How far a corner's offset may reach before it bevels, as a multiple of `rebate`. Defaults to `4`,\n   * the SVG default. Only consulted when `rebate` is non-zero.\n   *\n   * It matters when the pane is glazed inside a JAMB: the lining's inner edge is offset with a tight\n   * limit so a sharp ogee or pointed head blunts rather than growing a needle, and the glass has to be\n   * offset the same way or it will spike where the lining does not. Pass the same value the lining used —\n   * {@link WindowFrameGeometry} uses `2` for its inner aperture.\n   */\n  miterLimit?: number;\n}\n\n/**\n * The glass: a flat pane filling an opening.\n *\n * The third of the trio that share one `opening` description, and the one that was missing — a wall can\n * be punched, {@link WindowFrameGeometry} can ring the hole, {@link DiamondLatticeGeometry} can lead it,\n * and until now nothing could glaze it. `ArchedSlabGeometry` is a solid with depth; this is a surface.\n *\n * **A plane, not a solid.** Glass at this scale is a surface: giving it thickness doubles its triangles,\n * buys nothing a low-poly scene can see, and introduces two coincident faces to z-fight. Give it a\n * double-sided material and be done.\n *\n * Follows ANY arch, including `square` — a flat head is an arch-shaped hole with no curve in it — so\n * there is no separate rectangular pane, and the name does not pretend otherwise.\n *\n * Drawn at the ORIGIN — centered on X, sill at `y = 0`, lying in the XY plane at `z = 0` — regardless of\n * where the opening sits in its wall, so one pane can be positioned into many openings and so it lands on\n * a frame and a lattice built from the same description. Material groups: none.\n *\n * @example\n * ```ts\n * const opening = { width: 1.24, height: 1.15, arch: \"pointed\", archHeight: 0.78 } as const;\n *\n * const glass = new Mesh(new PaneGeometry({ opening }), glazing);\n * const leading = new Mesh(new DiamondLatticeGeometry({ opening }), lead);\n * const frame = new Mesh(new WindowFrameGeometry({ opening }), iron);\n * ```\n */\nexport class PaneGeometry extends ShapeGeometry {\n  constructor({\n    opening = {},\n    rebate = 0,\n    curveSegments = 24,\n    miterLimit = 4,\n  }: PaneGeometryOptions = {}) {\n    // At the origin: the pane does not care where its opening sits in the wall, only what shape it is.\n    const outline = openingOutline({ ...opening, x: 0, y: 0 });\n    const segments = Math.max(2, Math.round(curveSegments));\n\n    if (Math.abs(rebate) < 1e-9) {\n      super(outline, segments);\n      return;\n    }\n\n    // An OFFSET, not a scale. Growing a rectangle's width and height moves the two axes by different\n    // amounts, and on an arch nothing lines up at all — the pane has to keep a constant engagement all\n    // the way round, which is what `offsetLoop` is for.\n    const points = outline.getPoints(segments).map((p) => new Vector2(p.x, p.y));\n    super(new Shape(offsetLoop(points, rebate, miterLimit)), segments);\n  }\n}\n","import { ExtrudeGeometry, Path, Shape } from \"three\";\nimport { openingOutline, type WallOpeningOptions } from \"../../shapes/WallShape\";\nimport { offsetLoop } from \"../../modeling/profiles/OffsetLoop\";\n\n/** The frame's outer silhouette follows the arch to a point — a finial on an ogee. */\nconst OUTER_MITER = 6;\n/** The inner aperture blunts a sharp point rather than spiking a needle into the glass. */\nconst INNER_MITER = 2;\n\nexport interface WindowFrameGeometryOptions {\n  /** The opening this frame rings. The SAME description the wall was punched with. */\n  opening: WallOpeningOptions;\n  /**\n   * How far the frame's inner edge bites INTO the aperture. Defaults to `0.03`.\n   *\n   * This is what holds the glass, and what you actually see: the thin line of wood or iron running all\n   * the way around the pane, arch included.\n   */\n  inset?: number;\n  /**\n   * How far the frame's outer edge sits OUT on the wall, past the opening. Defaults to `0.06`.\n   *\n   * `0` gives a frame that fills the aperture and stops — a glazing bead, flush with the reveal. Anything\n   * more and it becomes a casing, lying on the wall's face like a picture frame.\n   */\n  outset?: number;\n  /** How far the frame stands out of the wall. Defaults to `0.05`. */\n  depth?: number;\n  /** How finely the arch is followed — the low-poly knob. Defaults to `48`. */\n  curveSegments?: number;\n}\n\n/**\n * The frame around a window: a flat RING that follows the opening all the way around — up the jambs, over\n * the arch, and closed along the sill.\n *\n * **A glazing bead and a picture-frame casing are the same geometry.** One bites into the aperture, the\n * other spills out onto the wall, and both are just two offsets of one outline. So there is one class,\n * with a signed `inset` and `outset`, rather than two that would drift apart.\n *\n * The ring's inner boundary is strictly interior to its outer one, so here `Shape.holes` is exactly\n * right — unlike a doorway, which touches the floor and must be notched into the outline instead. Same\n * rule, opposite answer, which is the whole reason the rule is worth stating.\n *\n * **It rings ANY arch**, because it offsets the CURVE rather than the opening's parameters: an offset\n * ellipse is not an ellipse and an offset ogee is not an ogee, so a frame built by shrinking `width` and\n * `archHeight` would pinch and swell around the arch instead of holding its width. See {@link offsetLoop}.\n *\n * Drawn at the ORIGIN — centered on X, sill at `y = 0` — regardless of where the opening sits in its\n * wall, so one frame can be positioned into many openings. Extrudes into `+z`.\n *\n * @example\n * ```ts\n * const opening = { width: 0.8, height: 1, arch: \"ogee\" } as const;\n *\n * const bead   = new WindowFrameGeometry({ opening, inset: 0.04, outset: 0 });     // holds the glass\n * const casing = new WindowFrameGeometry({ opening, inset: 0.02, outset: 0.1 });   // sits on the wall\n * ```\n */\nexport class WindowFrameGeometry extends ExtrudeGeometry {\n  constructor({\n    opening,\n    inset = 0.03,\n    outset = 0.06,\n    depth = 0.05,\n    curveSegments = 48,\n  }: WindowFrameGeometryOptions) {\n    // At the origin: the frame does not care where its opening sits in the wall, only what shape it is.\n    const outline = openingOutline({ ...opening, x: 0, y: 0 }).getPoints(curveSegments);\n\n    // The two edges of the frame want opposite things at a sharp arch (an ogee or a pointed crown), and\n    // it is the same corner offset in two directions:\n    //   - the OUTER edge is the frame's silhouette, so it should come to a POINT like the arch does — a\n    //     generous miter limit lets that finial form;\n    //   - the INNER edge is the aperture, where a sharp point would stab a needle down into the glass —\n    //     a tight limit bevels it into a clean blunt.\n    const outer = new Shape(offsetLoop(outline, outset, OUTER_MITER));\n\n    // Wound the other way, because a hole runs against its container.\n    const inner = new Path(offsetLoop(outline, -inset, INNER_MITER).reverse());\n    outer.holes.push(inner);\n\n    super(outer, { depth, bevelEnabled: false, curveSegments });\n  }\n}\n","import {\n  Color,\n  type ColorRepresentation,\n  DoubleSide,\n  Group,\n  type Path,\n  MathUtils,\n  Mesh,\n  MeshPhysicalMaterial,\n  MeshStandardMaterial,\n} from \"three\";\nimport { DiamondLatticeGeometry } from \"../../geometry/architecture/DiamondLatticeGeometry\";\nimport { PaneGeometry } from \"../../geometry/architecture/PaneGeometry\";\nimport { WindowFrameGeometry } from \"../../geometry/architecture/WindowFrameGeometry\";\nimport { openingCutout, type WallOpeningOptions } from \"../../shapes/WallShape\";\n\nexport interface DiamondLatticeWindowOptions {\n  /**\n   * The opening. **The same object that punches the wall** — pass one description to both and the hole and\n   * the window cannot drift apart.\n   *\n   * Any arch, including `square`: a flat head is an arch-shaped hole with no curve in it, so a rectangular\n   * light and a gothic one are this one window with different points.\n   */\n  opening?: WallOpeningOptions;\n  /**\n   * Diamonds across the opening's width. Defaults to `4`.\n   *\n   * Counts rather than an angle, because alignment is the point: with counts, the diamonds' corners land\n   * exactly on the jambs, the sill, and the springing line. Above the springing the head cuts what it\n   * cuts — a curve is not a whole number of anything, and real leaded lights accept that too.\n   */\n  cellsX?: number;\n  /** Diamonds from the sill up to the springing. Defaults to `4`. */\n  cellsY?: number;\n  /**\n   * Width of the came across the glass. Defaults to `0.022`.\n   *\n   * An assembly option, not a lattice one: the frame's inner band is sized from it, which is what makes\n   * the leading and the frame read as one piece of work rather than two.\n   */\n  cameWidth?: number;\n  /** Depth of the came through the glass. Defaults to `cameWidth * 1.4` — real lead is deeper than wide. */\n  cameDepth?: number;\n  /** How finely the arch is followed. Shared by all three parts so they tessellate identically. Defaults to `24`. */\n  curveSegments?: number;\n  /** The frame. `false` omits it; an object overrides what the assembly would have chosen. */\n  frame?: boolean | { inset?: number; outset?: number; depth?: number };\n  /** The glass. `false` omits it; `rebate` runs the pane past the opening into a frame's groove. */\n  glass?: boolean | { rebate?: number };\n  /** Lead tint. Defaults to `#0c0f14`. */\n  leadColor?: ColorRepresentation;\n  /** Frame tint. Defaults to the lead's, because the two are one piece of ironwork. */\n  frameColor?: ColorRepresentation;\n  /** Glass tint. Defaults to `#6a7d8c`. */\n  glassColor?: ColorRepresentation;\n  /** Glass emissive, for moonlit or storm backlight. Defaults to off. */\n  glassEmissive?: ColorRepresentation;\n  /** Defaults to `0`. */\n  glassEmissiveIntensity?: number;\n}\n\n/**\n * A leaded light: glass, diamond leading, and the frame that carries it.\n *\n * **Why these three and not some other bundle.** Leading has to be framed — cames cannot support cut glass\n * on their own — so this is a unit that exists in the world rather than a convenience grouping. The test\n * worth applying to any factory: *is the assembly a thing people have a name for?* A leaded light is.\n *\n * **A factory exposes what the ASSEMBLY decides, and delegates the rest.** So `cellsX` / `cellsY` are here\n * and `angle` / `spacing` are not — alignment determines them, and they are reported on the instance\n * rather than asked for. `cameWidth` is here because the frame is sized from it. `cameSides` is not,\n * because it has to agree with nothing; reach for {@link DiamondLatticeGeometry} directly for that.\n *\n * All three parts are built from ONE `opening`, which is also what you punch the wall with, so nothing has\n * to be kept in step by hand. Every part is exposed as a field, so any of them can be replaced without\n * forking this.\n *\n * Local frame: centered on X, sill at `y = 0`, facing `+Z` — the anchor the whole trio shares, so the\n * window drops straight into a wall hole built from the same description.\n *\n * @example\n * ```ts\n * const opening = { width: 1.24, height: 1.15, arch: \"pointed\", archHeight: 0.78 } as const;\n *\n * const wall = new Mesh(new ExtrudeGeometry(new WallShape({ windows: [opening] }), { depth: 0.3 }), stone);\n * const light = new DiamondLatticeWindow({ opening, cellsX: 4, cellsY: 4 });\n * ```\n */\nexport class DiamondLatticeWindow extends Group {\n  /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */\n  readonly cutout: Path;\n\n  readonly lattice: Mesh<DiamondLatticeGeometry, MeshStandardMaterial>;\n  readonly frame?: Mesh<WindowFrameGeometry, MeshStandardMaterial>;\n  readonly glass?: Mesh<PaneGeometry, MeshPhysicalMaterial>;\n\n  readonly cellsX: number;\n  readonly cellsY: number;\n  /** The angle the cell counts worked out to, in degrees. An OUTPUT — see `cellsX`. */\n  readonly angle: number;\n  /** The came spacing the cell counts worked out to. An OUTPUT. */\n  readonly spacing: number;\n\n  constructor({\n    opening = {},\n    cellsX = 4,\n    cellsY = 4,\n    cameWidth = 0.022,\n    cameDepth = cameWidth * 1.4,\n    curveSegments = 24,\n    frame = true,\n    glass = true,\n    leadColor = \"#0c0f14\",\n    frameColor = leadColor,\n    glassColor = \"#6a7d8c\",\n    glassEmissive,\n    glassEmissiveIntensity = 0,\n  }: DiamondLatticeWindowOptions = {}) {\n    super();\n\n    this.cutout = openingCutout(opening);\n\n    const width = opening.width ?? 1.2;\n    const springing = opening.height ?? 1.4;\n\n    // The alignment, solved. Crossings of the two came families land at x = v·s/(2 sin θ) and\n    // y = u·s/(2 cos θ), so one diamond measures W = s/sin θ by H = s/cos θ. Read backwards:\n    // θ = atan(H / W) and s = W·sin θ. Nothing is left to tune — which is exactly why the angle and the\n    // spacing are reported here rather than accepted.\n    const cellWidth = width / Math.max(1, cellsX);\n    const cellHeight = springing / Math.max(1, cellsY);\n    const theta = Math.atan2(cellHeight, cellWidth);\n\n    this.cellsX = cellsX;\n    this.cellsY = cellsY;\n    this.angle = MathUtils.radToDeg(theta);\n    this.spacing = cellWidth * Math.sin(theta);\n\n    const leadMaterial = new MeshStandardMaterial({\n      color: new Color(leadColor),\n      roughness: 0.7,\n      metalness: 0.35,\n      flatShading: true,\n      // The leading shares a plane with the glass, so it has to win the depth test.\n      polygonOffset: true,\n      polygonOffsetFactor: -1,\n      polygonOffsetUnits: -1,\n    });\n\n    this.lattice = new Mesh(\n      new DiamondLatticeGeometry({\n        opening,\n        angle: this.angle,\n        spacing: this.spacing,\n        phase: 0,\n        cameWidth,\n        cameDepth,\n        curveSegments,\n      }),\n      leadMaterial,\n    );\n    this.lattice.castShadow = true;\n    this.lattice.renderOrder = 1;\n    this.add(this.lattice);\n\n    if (frame) {\n      // The assembly's decision, and the reason the two read as one piece of work: the frame's inner band\n      // is the came's own width, so the leading appears to continue into the frame rather than stop at\n      // it. The depth matches too, so nothing stands proud of anything.\n      const settings = typeof frame === \"object\" ? frame : {};\n      const depth = settings.depth ?? cameDepth;\n      this.frame = new Mesh(\n        new WindowFrameGeometry({\n          opening,\n          inset: settings.inset ?? cameWidth,\n          outset: settings.outset ?? cameWidth * 1.6,\n          depth,\n          curveSegments,\n        }),\n        new MeshStandardMaterial({\n          color: new Color(frameColor),\n          roughness: 0.7,\n          metalness: 0.35,\n          flatShading: true,\n        }),\n      );\n      // `WindowFrameGeometry` extrudes into +z from zero; center it on the came so the two sit flush.\n      this.frame.position.z = -depth / 2;\n      this.frame.castShadow = true;\n      this.add(this.frame);\n    }\n\n    if (glass) {\n      const settings = typeof glass === \"object\" ? glass : {};\n      this.glass = new Mesh(\n        new PaneGeometry({ opening, rebate: settings.rebate ?? 0, curveSegments }),\n        new MeshPhysicalMaterial({\n          color: new Color(glassColor),\n          emissive: glassEmissive ? new Color(glassEmissive) : new Color(0x000000),\n          emissiveIntensity: glassEmissiveIntensity,\n          transparent: true,\n          depthWrite: false,\n          roughness: 0.08,\n          metalness: 0,\n          transmission: glassEmissive ? 0.5 : 0.88,\n          thickness: cameDepth * 0.5,\n          side: DoubleSide,\n        }),\n      );\n      // The pane is now the OPENING's own shape, not a rectangle behind it — so an arched light no longer\n      // shows glass squared off in the corners.\n      //\n      // On the came's centerline at z = 0, because a came is an H-section wrapping the glass edge: the\n      // glass runs through its middle, not behind it.\n      this.glass.renderOrder = 0;\n      this.glass.castShadow = false;\n      this.glass.receiveShadow = false;\n      this.add(this.glass);\n    }\n  }\n\n  /** Release every geometry and material this window owns. */\n  dispose(): void {\n    for (const part of [this.lattice, this.frame, this.glass]) {\n      if (!part) continue;\n      part.geometry.dispose();\n      part.material.dispose();\n    }\n  }\n}\n","import { Shape } from \"three\";\nimport { ArchStyle, archRise, traceArch } from \"../modeling/profiles/ArchProfile\";\n\n/** One side of a slab, split down the middle — see {@link ArchedSlabShapeOptions.half}. */\nexport type ArchedSlabHalf = \"left\" | \"right\";\n\nexport interface ArchedSlabShapeOptions {\n  /** Width of the slab. Defaults to `1.2`. */\n  width?: number;\n  /** Height of the rectangular body, up to where the arch springs. Defaults to `1.4`. */\n  height?: number;\n  /**\n   * Span of the arch. Defaults to `width`.\n   *\n   * Equal to `width` gives a smooth line sweeping across the top — a door, or a window. Pull it in and\n   * SHOULDERS appear at the corners, and the arch sits *on* the slab: a headstone. The shoulders are\n   * not modeled; they are what is left over.\n   */\n  archWidth?: number;\n  /**\n   * Rise of the arch above the springing — the ellipse's VERTICAL RADIUS, in world units. Defaults\n   * to `0.6`.\n   *\n   * **`archHeight === archWidth / 2` is a perfect semicircle**, and it is the one you almost always\n   * want. At that value the two radii are equal, so the ellipse *is* a circle — a Roman arch.\n   *\n   * | Span | Semicircle rise |\n   * | --- | --- |\n   * | `1.2` | `0.6` |\n   * | `2.6` | `1.3` |\n   * | `w` | `w / 2` |\n   *\n   * Below `w / 2` the arch flattens into a segmental one — wide and low, a gatehouse. Above it, the\n   * arch stretches taller than it is wide.\n   *\n   * **The rise does NOT follow the span.** They are independent radii, which is what lets this be a\n   * styling knob rather than a proportion — but it also means halving `width` leaves a rise that is\n   * now too tall for it. Keep them in step yourself, or the arch quietly changes character when you\n   * resize the slab.\n   */\n  archHeight?: number;\n  /**\n   * Return only the `left` or `right` half of the slab — the leaf of a double door. Omit for the whole\n   * slab.\n   *\n   * **The half is CARVED OUT of the full outline; it is not a slab of half the width.** Halving `width`\n   * instead would build a new, narrower ellipse, and each leaf would crown at its own center — stand\n   * the pair side by side and you get an `M`, not an arch. The arc here is the same arc: the full\n   * span's ellipse, sampled over half its sweep. Which makes the half a QUARTER ellipse, because the\n   * whole arch was already half of one.\n   *\n   * The result is asymmetric, and that is the point: it is short at its outer edge (`height`, where the\n   * arch springs) and tall at its inner edge (`height + archHeight`, the crown). The tall edge is the\n   * meeting stile, where the two leaves come together.\n   */\n  half?: ArchedSlabHalf;\n  /**\n   * Which arch sits on top. Defaults to `elliptical`, which springs vertically out of the slab's sides\n   * at any rise.\n   *\n   * `semicircle` is the one most callers want and forces `archHeight` to half the span. `pointed` and\n   * `ogee` come to a point at the crown — and a half slab still splits cleanly there, so one leaf of an\n   * ogee-arched double door works exactly like one leaf of a round-arched one. See {@link ArchStyle}.\n   */\n  arch?: ArchStyle;\n}\n\n/**\n * A rectangle with an arched top.\n *\n * One outline for three things that look like three: a **door**, an arched **window**, and a\n * **headstone**. Only the arch's span changes.\n *\n * ```\n *   archWidth == width      a smooth line across the top        -> a door, a window\n *\n *   archWidth <  width           /\\     the arch sits ON the slab,\n *                             ___/  \\___  leaving shoulders     -> a headstone\n *                            |          |\n * ```\n *\n * The arch is an ELLIPSE, not a circle, so its rise is independent of its span: a squat Roman arch and\n * a tall pointed one are the same outline with a different `archHeight`.\n *\n * **Which makes the semicircle a value, not a mode: `archHeight === archWidth / 2`.** Set the vertical\n * radius equal to the horizontal one and the ellipse is a circle. That is the arch most callers actually\n * want, and it is the one thing to remember here — see {@link ArchedSlabShapeOptions.archHeight}.\n *\n * Note this is a FILLED outline, not a swept band. An archway you walk through is a sweep — it follows\n * the curve. A door is an extrude — it fills it. Same arc, different operation.\n *\n * Pass {@link ArchedSlabShapeOptions.half} to get one leaf of a double door. Either way the outline is\n * drawn in the SLAB's frame — the arch stays centered on `x = 0` — so a half sits on its own side of\n * the centerline rather than being re-centered. Callers that want it elsewhere translate it; that is\n * how the door factory puts a leaf's origin on its hinge.\n */\nexport class ArchedSlabShape extends Shape {\n  constructor({\n    width = 1.2,\n    height = 1.4,\n    archWidth = width,\n    archHeight = 0.6,\n    half,\n    arch = \"elliptical\",\n  }: ArchedSlabShapeOptions = {}) {\n    super();\n\n    const hw = width / 2;\n    const ha = Math.min(archWidth, width) / 2;\n\n    // The arch decides its own rise — a semicircle forces one, a square head has none — so ask, rather\n    // than assuming `archHeight` is what you get.\n    const profile = { style: arch, x: 0, y: height, halfSpan: ha, rise: archHeight };\n    const crown = height + archRise(profile);\n\n    if (half === \"left\") {\n      // Counter-clockwise: along the floor to the centerline, up the meeting stile to the crown...\n      this.moveTo(-hw, 0);\n      this.lineTo(0, 0);\n      this.lineTo(0, crown);\n\n      // ...down the left half of the SAME arch. Half an arch, not a half-size arch — the two leaves of a\n      // double door are cut from one curve, or they crown separately and you get an `M`.\n      traceArch(this, { ...profile, from: \"crown\", to: \"left\" });\n\n      // ...out along the left shoulder if there is one, and close down the left side.\n      if (ha < hw) this.lineTo(-hw, height);\n      this.closePath();\n      return;\n    }\n\n    if (half === \"right\") {\n      // Counter-clockwise: along the floor, up the right side, in along the shoulder...\n      this.moveTo(0, 0);\n      this.lineTo(hw, 0);\n      this.lineTo(hw, height);\n      if (ha < hw) this.lineTo(ha, height);\n\n      // ...up the right half of the same arch to the crown, then closePath drops down the meeting stile.\n      traceArch(this, { ...profile, from: \"right\", to: \"crown\" });\n      this.closePath();\n      return;\n    }\n\n    // Counter-clockwise, starting bottom-left: along the floor, up the right side...\n    this.moveTo(-hw, 0);\n    this.lineTo(hw, 0);\n    this.lineTo(hw, height);\n\n    // ...in along the right shoulder, when the arch is narrower than the slab. When it is not, this is\n    // a zero-length step, and a duplicate point is exactly what a triangulator does not need to see.\n    if (ha < hw) this.lineTo(ha, height);\n\n    // ...over the top...\n    traceArch(this, { ...profile, from: \"right\", to: \"left\" });\n\n    // ...back out along the left shoulder, and close down the left side.\n    if (ha < hw) this.lineTo(-hw, height);\n    this.closePath();\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface ClubShapeOptions {\n  /** Overall scale factor. Defaults to `1`. */\n  size?: number;\n  /** Club width across the side lobes. Defaults to `1.84`. */\n  width?: number;\n  /** Height of the top lobe above the origin. Defaults to `1`. */\n  height?: number;\n  /** Stem width. Defaults to `0.56`. */\n  stemWidth?: number;\n  /** Depth of the stem below the origin. Defaults to `0.85`. */\n  stemDepth?: number;\n  /**\n   * How far the stem's sides bow INWARD, as a fraction of the way to the centerline. Defaults to `0.18`.\n   *\n   * `0` is a straight trapezoid; higher pinches the waist and flares the foot — the concave sweep of a\n   * printed club. Same idea as {@link DiamondShapeOptions.concavity}, applied to the stem.\n   */\n  stemConcavity?: number;\n}\n\n/**\n * Club profile — three lobes on a stem, drawn counter-clockwise from the stem.\n *\n * The lobes are bezier bulges rather than true circles: circles have to be tangent to each other or\n * they leave a notch where they meet, and pinning that down turns a silhouette into a solved\n * equation. Bezier lobes just overlap, and you can move them.\n */\nexport class ClubShape extends Shape {\n  constructor({\n    size = 1,\n    width = 1.84,\n    height = 1,\n    stemWidth = 0.56,\n    stemDepth = 0.85,\n    stemConcavity = 0.18,\n  }: ClubShapeOptions = {}) {\n    super();\n\n    const x = (width / 2) * size; // half-width, at the widest of the side lobes\n    const y = height * size; //      the crown of the top lobe\n    const sw = (stemWidth / 2) * size;\n    const sd = stemDepth * size;\n\n    const waistX = 0.15 * x;\n    const waistY = -0.32 * y;\n\n    // Each stem side bows toward the centerline: its control point is the straight midpoint, pulled in\n    // toward x = 0 by `stemConcavity`. At 0 the control sits on the line and the side stays straight.\n    const k = 1 - stemConcavity;\n    const cyMid = (waistY - sd) / 2;\n\n    // Up the left of the stem (concave), across its foot, and up its right side (concave).\n    this.moveTo(-waistX, waistY);\n    this.quadraticCurveTo(((-waistX - sw) / 2) * k, cyMid, -sw, -sd);\n    this.lineTo(sw, -sd);\n    this.quadraticCurveTo(((sw + waistX) / 2) * k, cyMid, waistX, waistY);\n\n    // The right lobe: out, and up.\n    this.bezierCurveTo(0.46 * x, -0.3 * y, 1.0 * x, -0.3 * y, 1.0 * x, 0.05 * y);\n    this.bezierCurveTo(1.0 * x, 0.42 * y, 0.52 * x, 0.52 * y, 0.37 * x, 0.38 * y);\n\n    // Over the top lobe.\n    this.bezierCurveTo(0.52 * x, 0.62 * y, 0.41 * x, 1.0 * y, 0, 1.0 * y);\n    this.bezierCurveTo(-0.41 * x, 1.0 * y, -0.52 * x, 0.62 * y, -0.37 * x, 0.38 * y);\n\n    // Down the left lobe, back to the stem.\n    this.bezierCurveTo(-0.52 * x, 0.52 * y, -1.0 * x, 0.42 * y, -1.0 * x, 0.05 * y);\n    this.bezierCurveTo(-1.0 * x, -0.3 * y, -0.46 * x, -0.3 * y, -waistX, waistY);\n\n    this.closePath();\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface SpadeShapeOptions {\n  /** Overall scale factor. Defaults to `1`. */\n  size?: number;\n  /** Spade width across the lobes. Defaults to `1.9`. */\n  width?: number;\n  /** Height of the point above the origin. Defaults to `1`. */\n  height?: number;\n  /** Stem width. Defaults to `0.6`. */\n  stemWidth?: number;\n  /** Depth of the stem below the origin. Defaults to `0.75`. */\n  stemDepth?: number;\n  /**\n   * How far the stem's sides bow INWARD, as a fraction of the way to the centerline. Defaults to `0.18`.\n   *\n   * `0` is a straight trapezoid; higher pinches the waist and flares the foot — the concave sweep of a\n   * printed spade. Same idea as {@link DiamondShapeOptions.concavity}, applied to the stem.\n   */\n  stemConcavity?: number;\n}\n\n/**\n * Spade profile — a heart inverted onto a stem, drawn counter-clockwise from the point.\n *\n * The card suit, and the terminal a smith forges onto the end of a strap hinge. Same outline, two\n * traditions.\n */\nexport class SpadeShape extends Shape {\n  constructor({\n    size = 1,\n    width = 1.9,\n    height = 1,\n    stemWidth = 0.6,\n    stemDepth = 0.75,\n    stemConcavity = 0.18,\n  }: SpadeShapeOptions = {}) {\n    super();\n\n    const x = (width / 2) * size; // half-width, at the widest of the lobes\n    const y = height * size; //      the point\n    const sw = (stemWidth / 2) * size;\n    const sd = stemDepth * size;\n\n    const waistX = 0.17 * x;\n    const waistY = -0.28 * y;\n\n    // Each stem side bows toward the centerline: its control point is the straight midpoint, pulled in\n    // toward x = 0 by `stemConcavity`.\n    const k = 1 - stemConcavity;\n    const cyMid = (waistY - sd) / 2;\n\n    // From the point, down the left lobe, around, into the stem, and back up the right.\n    this.moveTo(0, y);\n\n    // Left shoulder, then around the left lobe and in to the waist.\n    this.bezierCurveTo(-0.16 * x, 0.55 * y, -0.58 * x, 0.5 * y, -0.76 * x, 0.2 * y);\n    this.bezierCurveTo(-1.0 * x, -0.15 * y, -0.58 * x, -0.5 * y, -waistX, waistY);\n\n    // The stem — sides bowing inward to the flared foot.\n    this.quadraticCurveTo(((-waistX - sw) / 2) * k, cyMid, -sw, -sd);\n    this.lineTo(sw, -sd);\n    this.quadraticCurveTo(((sw + waistX) / 2) * k, cyMid, waistX, waistY);\n\n    // Around the right lobe, and back up to the point.\n    this.bezierCurveTo(0.58 * x, -0.5 * y, 1.0 * x, -0.15 * y, 0.76 * x, 0.2 * y);\n    this.bezierCurveTo(0.58 * x, 0.5 * y, 0.16 * x, 0.55 * y, 0, y);\n\n    this.closePath();\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface StrapHingeShapeOptions {\n  /** How far the strap reaches across the door, from the pin to the tip. Defaults to `0.85`. */\n  length?: number;\n  /** Width at the pin, where the strap is widest. Defaults to `0.22`. */\n  width?: number;\n  /**\n   * How far the strap's edges bow INWARD, as a fraction of the half-width. Defaults to `0.28`.\n   *\n   * At `0.5` the sides are straight and you get a dull triangle. Below that they curve in, and it\n   * starts to look forged — a smith DRAWS the metal out, and the taper of drawn metal is a curve, not\n   * a chamfer. This one number is most of the strap's character.\n   */\n  sweep?: number;\n}\n\n/**\n * A wrought strap hinge — wide at the pin, drawn out to a point, with its sides bowing inward.\n *\n * Drawn counter-clockwise from the pin edge, which is the straight one: the strap hangs off the door's\n * hinge side and reaches across its face.\n *\n * Local frame: the pin edge on X=0, centered on Y, reaching +X.\n */\nexport class StrapHingeShape extends Shape {\n  constructor({ length = 0.85, width = 0.22, sweep = 0.28 }: StrapHingeShapeOptions = {}) {\n    super();\n\n    const hw = width / 2;\n\n    this.moveTo(0, -hw);\n    this.lineTo(0, hw); // the straight edge, against the pin\n\n    // Out to the tip and back. Each control point is pulled toward the centerline by `sweep`, which is\n    // what bows the edge inward instead of running it straight.\n    this.quadraticCurveTo(length * 0.55, hw * sweep, length, 0);\n    this.quadraticCurveTo(length * 0.55, -hw * sweep, 0, -hw);\n\n    this.closePath();\n  }\n}\n","import {\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  CylinderGeometry,\n  ExtrudeGeometry,\n  Group,\n  Material,\n  Mesh,\n  MeshStandardMaterial,\n  Shape,\n  SphereGeometry,\n} from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { ArchStyle } from \"../../modeling/profiles/ArchProfile\";\nimport { ArchedSlabHalf, ArchedSlabShape } from \"../../shapes/ArchedSlabShape\";\nimport { ClubShape } from \"../../shapes/ClubShape\";\nimport { SpadeShape } from \"../../shapes/SpadeShape\";\nimport { StrapHingeShape } from \"../../shapes/StrapHingeShape\";\n\n/** Which jamb a door hangs from — and therefore where its origin sits. */\nexport type DoorHinge = \"left\" | \"right\";\n\n/** A door slab, wood and iron in their own geometry groups. */\nexport type DoorLeaf = Mesh<BufferGeometry, Material[]>;\n\nexport interface ArchedDoorOptions {\n  /** Width of the door. Defaults to `1.3`. */\n  width?: number;\n  /**\n   * Which jamb the door hangs from. Defaults to `\"left\"`.\n   *\n   * This places the strap hinges — and, because the door's origin is its hinge, it also decides which\n   * edge lands on `x = 0`. See the note on the returned mesh's frame.\n   */\n  hinge?: DoorHinge;\n  /** Height of the rectangular body, up to where the arch springs. Defaults to `1.9`. */\n  height?: number;\n  /**\n   * Rise of the arch above the springing — the ellipse's VERTICAL RADIUS, in world units. Defaults to\n   * `0.65`, which against the default `width` of `1.3` is a **perfect semicircle**.\n   *\n   * **`archHeight === width / 2` is the arch you want.** Equal radii make the ellipse a circle: a Roman\n   * arch. Below that it flattens (segmental — a gatehouse); above it, it stretches tall.\n   *\n   * | Opening | Semicircle rise |\n   * | --- | --- |\n   * | `1.3` | `0.65` |\n   * | `2.6` | `1.3` |\n   * | `w` | `w / 2` |\n   *\n   * **The rise does not follow the width.** Resize the door and the arch keeps whatever rise it had, so\n   * it quietly changes character — `width: 2.6` with the default `0.65` is a squat segmental arch, not a\n   * bigger version of the same door.\n   */\n  archHeight?: number;\n  /**\n   * Which arch tops the door. Defaults to `elliptical`. See {@link ArchStyle}.\n   *\n   * Give a door the same arch as the opening it hangs in and the two match exactly, because they draw\n   * the same curve. A double door splits at the CROWN, so a pointed or ogee arch still parts cleanly\n   * down the middle — each leaf just carries half the point.\n   */\n  arch?: ArchStyle;\n  /** Thickness of the slab. Defaults to `0.12`. */\n  thickness?: number;\n  /** How finely the arch is tessellated — the low-poly knob. `3` is chiseled; `24` is cast. Defaults to `16`. */\n  curveSegments?: number;\n\n  /** Number of strap hinges. Defaults to `3`. */\n  hinges?: number;\n  /** How far each strap reaches across the door. Defaults to `0.85`. */\n  hingeLength?: number;\n  /** Width of a strap at the pin. Defaults to `0.22`. */\n  hingeWidth?: number;\n  /** How far a strap's edges bow inward. `0.5` is straight; less is forged. Defaults to `0.28`. */\n  hingeSweep?: number;\n  /** The decorative shape forged onto a strap's tip. Defaults to `\"spade\"`. */\n  hingeTerminal?: \"spade\" | \"club\" | \"none\";\n\n  /** Rows of studs across the face. Defaults to `4`. */\n  studRows?: number;\n  /** Columns of studs across the face. Defaults to `3`. */\n  studCols?: number;\n  /** Radius of a stud head. Defaults to `0.035`. */\n  studRadius?: number;\n\n  /** Wood material. Omit to build a flat-shaded standard material from `woodColor`. */\n  woodMaterial?: Material;\n  /** Wood tint when `woodMaterial` is omitted. Defaults to `#6b4f34`. */\n  woodColor?: ColorRepresentation;\n  /** Iron material. Omit to build a flat-shaded standard material from `ironColor`. */\n  ironMaterial?: Material;\n  /** Iron tint when `ironMaterial` is omitted. Defaults to `#2b2b2b`. */\n  ironColor?: ColorRepresentation;\n}\n\n/**\n * One door leaf, hung on its hinge.\n *\n * `span` is the opening the ARCH spans; `half` says whether this leaf fills that opening or only one\n * side of it. Everything is drawn in the doorway's frame — the arch centered on `x = 0` — and then slid\n * so the hinge edge lands on the origin, which is the last thing that happens here.\n */\nfunction buildLeaf(\n  options: ArchedDoorOptions,\n  span: number,\n  hinge: DoorHinge,\n  half: ArchedSlabHalf | undefined,\n): DoorLeaf {\n  const {\n    height = 1.9,\n    archHeight = 0.65,\n    arch = \"elliptical\",\n    thickness = 0.12,\n    curveSegments = 16,\n    hinges = 3,\n    hingeLength = 0.85,\n    hingeWidth = 0.22,\n    hingeSweep = 0.28,\n    hingeTerminal = \"spade\",\n    studRows = 4,\n    studCols = 3,\n    studRadius = 0.035,\n    woodMaterial,\n    woodColor = \"#6b4f34\",\n    ironMaterial,\n    ironColor = \"#2b2b2b\",\n  } = options;\n\n  const extrude = { bevelEnabled: false, curveSegments };\n\n  // The hinge always hangs on the OUTER edge of the opening — for a half leaf, that is the edge away\n  // from the meeting stile, so the crown is always opposite the pin. There is no way to ask for a leaf\n  // hinged on its tall side, because no such door exists.\n  const hingeX = (hinge === \"left\" ? -1 : 1) * (span / 2);\n  const reach = hinge === \"left\" ? 1 : -1; // the direction a strap runs, away from the pin\n\n  // The leaf's own footprint in doorway coordinates — where the studs get spread.\n  const leafMinX = half === \"right\" ? 0 : -span / 2;\n  const leafWidth = half ? span / 2 : span;\n\n  // The slab: one outline, arch included.\n  const slab = new ExtrudeGeometry(\n    new ArchedSlabShape({ width: span, height, archWidth: span, archHeight, half, arch }),\n    { ...extrude, depth: thickness },\n  );\n\n  const iron: BufferGeometry[] = [];\n  const face = thickness + 0.001; // sit the ironwork just proud of the door's face\n\n  for (let i = 0; i < hinges; i++) {\n    // Space the straps up the body, keeping clear of the springing.\n    const y = height * ((i + 1) / (hinges + 1)) * 0.92 + height * 0.04;\n\n    const strap = new ExtrudeGeometry(\n      new StrapHingeShape({ length: hingeLength, width: hingeWidth, sweep: hingeSweep }),\n      { ...extrude, depth: 0.02 },\n    );\n    // A right-hung strap is the mirror of a left-hung one — but the strap is symmetric about its own\n    // length axis, so a half turn about Z IS that mirror. Rotating rather than scaling by -1 keeps the\n    // winding (and therefore the normals) intact, with no need to rebuild them afterwards.\n    if (reach < 0) strap.rotateZ(Math.PI);\n    strap.translate(hingeX, y, face);\n    iron.push(strap);\n\n    // The terminal forged onto the tip — a card suit, which is exactly what a smith would reach for.\n    if (hingeTerminal !== \"none\") {\n      const size = hingeWidth * 0.55;\n\n      const shape: Shape =\n        hingeTerminal === \"spade\" ? new SpadeShape({ size }) : new ClubShape({ size });\n\n      const tip = new ExtrudeGeometry(shape, { ...extrude, depth: 0.02 });\n      // The point faces along the strap, away from the pin — whichever way that is. Spade and club are\n      // both symmetric about their stems, so turning one is the same as mirroring it.\n      tip.rotateZ((-Math.PI / 2) * reach);\n      tip.translate(hingeX + reach * (hingeLength + hingeWidth * 0.35), y, face);\n      iron.push(tip);\n    }\n\n    // The pin barrel at the hinge edge.\n    const barrel = new CylinderGeometry(0.022, 0.022, hingeWidth * 1.3, 6);\n    barrel.translate(hingeX, y, face + 0.01);\n    iron.push(barrel);\n  }\n\n  for (let r = 0; r < studRows; r++) {\n    for (let c = 0; c < studCols; c++) {\n      const x = leafMinX + (leafWidth * (c + 1)) / (studCols + 1);\n      const y = (height * (r + 1)) / (studRows + 1);\n\n      const stud = new SphereGeometry(studRadius, 6, 4);\n      stud.scale(1, 1, 0.6); // squashed — a rivet head, not a ball\n      stud.translate(x, y, face + studRadius * 0.3);\n      iron.push(stud);\n    }\n  }\n\n  const wood = woodMaterial ?? new MeshStandardMaterial({ color: new Color(woodColor), roughness: 0.95, flatShading: true });\n\n  const forged =\n    ironMaterial ??\n    new MeshStandardMaterial({\n      color: new Color(ironColor),\n      metalness: 0.8,\n      roughness: 0.45,\n      flatShading: true,\n    });\n\n  // ExtrudeGeometry is non-indexed; CylinderGeometry and SphereGeometry are indexed. mergeGeometries\n  // will not mix the two, so the indexed ones are flattened first. Note `toNonIndexed()` returns THIS\n  // when a geometry is already non-indexed — it does not copy — so it must only be called on the\n  // indexed ones, or the extrudes get double-disposed and warn on every rebuild.\n  const flattened = iron.map((part) => (part.index ? part.toNonIndexed() : part));\n\n  // One merge, with groups: group 0 is the wood, group 1 is the iron. Merging even when there is no\n  // ironwork means the returned geometry is never one of the parts, so every part can be disposed.\n  const ironwork = flattened.length ? (mergeGeometries(flattened, false) as BufferGeometry) : null;\n\n  const geometry = mergeGeometries(ironwork ? [slab, ironwork] : [slab], true) as BufferGeometry;\n\n  // Put the origin on the hinge — and the hinge is a LINE, so both coordinates have to land on it. `x`\n  // is the hinge edge, and `z` is the door's FRONT face, because that is the side the straps are bolted\n  // to and the pin stands on. Anchor `z` at the back face instead and the door swings about its far\n  // edge while its pin orbits out in front of it, which is not a hinge; it is a turntable.\n  //\n  // So the slab hangs BEHIND the origin plane, in -Z, and the ironwork sits a hair proud of it in +Z.\n  // The door opens toward +Z — toward its own hinges — which is what \"outward\" means on a real door.\n  geometry.translate(-hingeX, 0, -thickness);\n\n  slab.dispose();\n  ironwork?.dispose();\n  flattened.forEach((part, i) => {\n    if (part !== iron[i]) part.dispose(); // a genuine copy, made by toNonIndexed\n  });\n  iron.forEach((part) => part.dispose());\n\n  const mesh = new Mesh<BufferGeometry, Material[]>(geometry, ironwork ? [wood, forged] : [wood]);\n  mesh.castShadow = true;\n  mesh.receiveShadow = true;\n\n  return mesh;\n}\n\n/**\n * A medieval arched door: a plank slab under an arch, with wrought strap hinges and iron studs.\n *\n * **Every part of this is the same operation.** The slab, the hinge strap, and the hinge's terminal are\n * not three techniques — they are three OUTLINES, each closed, filled, and given depth. Once that lands,\n * the whole medieval vocabulary opens up: fleurs, quatrefoils, escutcheons, escapes. They are drawings.\n *\n * Note what is NOT here: a hole. The arch is part of the door's OUTLINE, not a void cut out of a\n * rectangle. `Shape.holes` is for strictly INTERIOR voids — a \"hole\" that reaches an edge is not a hole\n * at all, and the triangulator will fill straight across it and hand you a face you never asked for.\n *\n * **The origin is the HINGE, not the center.** `y = 0` is the sill, as everywhere else in this library,\n * but `x = 0` is the hinge edge and `z = 0` is the front face, where the straps are bolted and the pin\n * stands. Those two together are the hinge AXIS, so the door opens with `door.rotation.y` and nothing\n * more. The rule is the same one that seam-anchors a tile: anchor a thing where it JOINS the world. A\n * door joins at its hinge.\n *\n * The slab therefore lies entirely to one side of the origin — in `+x` for a left-hung door — and\n * entirely BEHIND it, in `-z`, with the ironwork proud of the face in `+z`.\n *\n * **Which way it opens is the sign of the angle, and the sign depends on the hand.** A door opens\n * OUTWARD, toward `+z`, because that is the side its hinges are on:\n *\n * ```ts\n * left.rotation.y  = -angle; // outward\n * right.rotation.y =  angle; // outward — mirrored, so the sign flips\n * ```\n *\n * Reverse the signs and it swings inward instead, through where the wall would be.\n *\n * Returned as a single {@link Mesh} carrying a material ARRAY, with the wood and the iron in their own\n * geometry groups. A `Group` of two meshes would have worked too, and it would have pushed the cost onto\n * every caller: `door.castShadow = true` would silently do nothing, and you would be writing\n * `traverse(child => …)` forever. One mesh, one transform, one shadow flag.\n *\n * Dispose the geometry and both materials when finished.\n *\n * @example\n * ```ts\n * const door = createArchedDoor({ width: 1.4, hingeTerminal: \"club\" });\n * door.position.x = -1.4 / 2; // hang the hinge on the jamb\n * scene.add(door);\n *\n * door.rotation.y = -0.9; // swings out on its hinge, because the hinge is its origin\n * ```\n */\nexport function createArchedDoor(options: ArchedDoorOptions = {}): DoorLeaf {\n  const { width = 1.3, hinge = \"left\" } = options;\n  return buildLeaf(options, width, hinge, undefined);\n}\n\nexport interface DoubleDoorOptions extends Omit<ArchedDoorOptions, \"hinge\"> {\n  /**\n   * Width of the whole opening, jamb to jamb — **not** the width of one leaf. Defaults to `2.6`, which\n   * is two doors of the standard `1.3`.\n   *\n   * The arch spans this, and each leaf takes half of it. Which is the thing to watch: **`archHeight` is\n   * measured against this OPENING, not against a leaf.** A semicircle over a `2.6` opening wants\n   * `archHeight: 1.3`, not `0.65` — see {@link ArchedDoorOptions.archHeight}.\n   */\n  width?: number;\n}\n\n/**\n * Two leaves under one arch, each hung on its own jamb.\n *\n * **The arch is shared, and that is the entire trick.** Each leaf carries half of ONE ellipse spanning\n * the full opening — not its own smaller arch. Build two doors of half the width instead and each\n * crowns at its own center: you get an `M`. So the leaves are asymmetric, short at the hinge and tall at\n * the meeting stile, and they only make sense as a pair.\n *\n * **Opening them.** Each leaf's origin is its own hinge axis — the hinge edge, on the front face — so a\n * leaf swings with `rotation.y` alone: no pivot group, no offset. The leaves mirror, so **their angles\n * are opposite in sign**, and doors open OUTWARD, toward the face their hinges are on:\n *\n * ```ts\n * doors.left.rotation.y = -angle; // outward, toward +Z\n * doors.right.rotation.y = angle;\n * ```\n *\n * Flip both signs and they swing inward. Give them different magnitudes and one stands ajar. The leaves\n * are ordinary meshes and the {@link Group} does not own their motion, which is deliberate — a door that\n * owns its own animation is a door you cannot animate any other way.\n *\n * Dispose each leaf's geometry and materials when finished.\n *\n * @example\n * ```ts\n * const doors = createDoubleDoor({ width: 2.6, archHeight: 0.9 });\n * scene.add(doors);\n *\n * doors.left.rotation.y = -0.8;\n * doors.right.rotation.y = 0.8;\n * ```\n *\n * @example\n * The default single door, split down the middle — same opening, same circle, two leaves. `archHeight`\n * is half the OPENING (not half a leaf), so it stays `0.65`, and the ironwork scales to the narrower\n * leaf.\n *\n * ```ts\n * const doors = createDoubleDoor({\n *   width: 1.3,        // the whole opening — each leaf is 0.65\n *   archHeight: 0.65,  // = width / 2, so the arch is a true semicircle\n *   hingeLength: 0.42, // the stock 0.85 would overshoot a 0.65-wide leaf\n *   studCols: 2,\n * });\n * ```\n */\nexport function createDoubleDoor(options: DoubleDoorOptions = {}): DoubleDoor {\n  const { width = 2.6 } = options;\n\n  const doors = new Group() as DoubleDoor;\n\n  doors.left = buildLeaf(options, width, \"left\", \"left\");\n  doors.right = buildLeaf(options, width, \"right\", \"right\");\n\n  // Each leaf's origin is its hinge, so hanging it is just putting that hinge on its jamb.\n  doors.left.position.x = -width / 2;\n  doors.right.position.x = width / 2;\n\n  doors.add(doors.left, doors.right);\n\n  return doors;\n}\n\n/** Two leaves under one arch. See {@link createDoubleDoor} for how to swing them. */\nexport interface DoubleDoor extends Group {\n  /** Hung on the left jamb. Opens OUTWARD on a NEGATIVE `rotation.y`. */\n  left: DoorLeaf;\n  /** Hung on the right jamb. Opens OUTWARD on a POSITIVE `rotation.y` — it mirrors the left. */\n  right: DoorLeaf;\n}\n","import { BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport type { WallOpeningOptions } from \"../../shapes/WallShape\";\nimport { circleProfile } from \"../../modeling/profiles/Profiles\";\nimport type { Vec2 } from \"../../modeling/mesh/GeometryBuffers\";\nimport { buildLatticeBars, openingBoundary } from \"./latticeBars\";\n\nexport interface GregorianLatticeGeometryOptions {\n  /**\n   * The opening the lattice fills. The SAME description a wall is punched with, a\n   * {@link WindowFrameGeometry} rings, and a {@link PaneGeometry} glazes.\n   *\n   * Any arch, including `square`. **A rectangular Gregorian light needs no cutting at all** — every\n   * boundary a bar meets is perpendicular to it, so a square end is already correct. Put the same lattice\n   * under an ARCH and the mullions run into a curve, and the ends have to follow it. Both cases are this\n   * one geometry.\n   */\n  opening?: WallOpeningOptions;\n  /** Distance between neighboring MULLIONS — the upright bars. Defaults to `0.24`. */\n  mullionSpacing?: number;\n  /** Distance between neighboring TRANSOMS — the level bars. Defaults to `0.3`. */\n  transomSpacing?: number;\n  /**\n   * Slides the mullions across the opening. Defaults to `0`, which puts one on the centerline.\n   *\n   * Half a spacing puts a LIGHT on the centerline instead, which is what an even number of lights wants.\n   * {@link GregorianLatticeWindow} works this out from the light counts.\n   */\n  mullionPhase?: number;\n  /** Slides the transoms up the opening. Defaults to `0`, which puts one on the sill line. */\n  transomPhase?: number;\n  /** Width of the bar across the glass. Defaults to `0.03`. */\n  barWidth?: number;\n  /** Depth of the bar through the glass. Defaults to `barWidth`, a square section. */\n  barDepth?: number;\n  /** Sides on the bar's section — the low-poly knob. `4` is square stock. Defaults to `4`. */\n  barSides?: number;\n  /** How finely the arch is followed. Defaults to `20`. */\n  curveSegments?: number;\n}\n\n/**\n * Gregorian lattice — upright MULLIONS and level TRANSOMS dividing an opening into rectangular lights.\n *\n * The sibling of {@link DiamondLatticeGeometry}, and the same construction underneath: **a lattice type is\n * only ever a choice of angles.** A diamond is two families at `±45°`; this is two families at `90°` and\n * `0°`. Both hand their families to the same bar builder, so neither knows what the other is making.\n *\n * **No miters here, and none wanted.** Mullion crosses transom, and an X-junction has no bisector to\n * share — real glazing bars are halved into each other or simply butted, and interpenetration is the\n * honest model. What the bars DO need is their ends cut to the boundary, which is a different thing: in a\n * square opening every boundary is perpendicular to the bar meeting it, so a square end is already right\n * and nothing happens; under an arch the mullions run into a curve, and the ends follow it.\n *\n * Bars that would lie ON the boundary — a transom on the sill line, a mullion on a jamb — are dropped by\n * the same rule that drops offcuts, since their section straddles the edge. The frame occupies those\n * positions.\n *\n * Baked to a single `BufferGeometry` — one draw call for the whole lattice.\n *\n * Drawn at the ORIGIN — centered on X, sill at `y = 0` — so it lands on a frame and a pane built from the\n * same opening. Material groups: none.\n *\n * @example\n * ```ts\n * const opening = { width: 1.2, height: 1.6, arch: \"semicircle\" } as const;\n *\n * const bars = new Mesh(new GregorianLatticeGeometry({ opening }), painted);\n * ```\n */\nexport class GregorianLatticeGeometry extends BufferGeometry {\n  /** How many bars were built. Offcuts and bars lying on the boundary are dropped, so this is not derivable. */\n  readonly barCount: number;\n\n  constructor({\n    opening = {},\n    mullionSpacing = 0.24,\n    transomSpacing = 0.3,\n    mullionPhase = 0,\n    transomPhase = 0,\n    barWidth = 0.03,\n    barDepth = barWidth,\n    barSides = 4,\n    curveSegments = 20,\n  }: GregorianLatticeGeometryOptions = {}) {\n    super();\n\n    const boundary = openingBoundary(opening, curveSegments);\n\n    // `circleProfile` is a regular polygon, so it is square by construction. Scaling the axis that maps to\n    // the frame's NORMAL — the one running through the glass — makes it rectangular without touching the\n    // axis the cutting reads.\n    const depthScale = barWidth > 0 ? barDepth / barWidth : 1;\n    const profile = circleProfile(barWidth / 2, Math.max(3, Math.round(barSides))).map(\n      ([px, py]) => [px * depthScale, py] as Vec2,\n    );\n\n    const parts = buildLatticeBars(\n      boundary,\n      [\n        // 90° is upright — a mullion. 0° is level — a transom.\n        { angle: 90, spacing: mullionSpacing, phase: mullionPhase },\n        { angle: 0, spacing: transomSpacing, phase: transomPhase },\n      ],\n      profile,\n      barWidth * 3,\n    );\n    this.barCount = parts.length;\n\n    if (parts.length === 0) {\n      this.computeBoundingSphere();\n      return;\n    }\n\n    // Not cast — `mergeGeometries` returns null on mismatched attributes, and a cast turns that into an\n    // unreadable \"cannot read properties of null\" three frames later.\n    const merged = mergeGeometries(parts, false);\n    if (!merged) throw new Error(\"GregorianLatticeGeometry: bar parts have incompatible attributes.\");\n\n    this.copy(merged);\n    merged.dispose();\n    parts.forEach((part) => part.dispose());\n    this.computeBoundingSphere();\n  }\n}\n","import {\n  Color,\n  type ColorRepresentation,\n  DoubleSide,\n  Group,\n  type Path,\n  Mesh,\n  MeshPhysicalMaterial,\n  MeshStandardMaterial,\n} from \"three\";\nimport { GregorianLatticeGeometry } from \"../../geometry/architecture/GregorianLatticeGeometry\";\nimport { PaneGeometry } from \"../../geometry/architecture/PaneGeometry\";\nimport { WindowFrameGeometry } from \"../../geometry/architecture/WindowFrameGeometry\";\nimport { openingCutout, type WallOpeningOptions } from \"../../shapes/WallShape\";\n\nexport interface GregorianLatticeWindowOptions {\n  /**\n   * The opening. **The same object that punches the wall** — pass one description to both and the hole and\n   * the window cannot drift apart. Any arch, including `square`.\n   */\n  opening?: WallOpeningOptions;\n  /**\n   * Lights across the opening's width. Defaults to `3`.\n   *\n   * A LIGHT is one pane; `3` gives two mullions between three lights. Counts rather than a spacing,\n   * because the divisions have to land evenly — the spacing and the phase are worked out from this and\n   * reported back.\n   */\n  lightsAcross?: number;\n  /** Lights from the sill up to the springing. Defaults to `4`. */\n  lightsUp?: number;\n  /**\n   * Width of the glazing bar. Defaults to `0.03`.\n   *\n   * An assembly option: the frame's inner band is sized from it, which is what makes the bars and the\n   * frame read as one piece of joinery.\n   */\n  barWidth?: number;\n  /** Depth of the bar through the glass. Defaults to `barWidth`. */\n  barDepth?: number;\n  /** How finely the arch is followed, shared by all three parts. Defaults to `24`. */\n  curveSegments?: number;\n  /** The frame. `false` omits it; an object overrides what the assembly would have chosen. */\n  frame?: boolean | { inset?: number; outset?: number; depth?: number };\n  /** The glass. `false` omits it; `rebate` runs the pane past the opening into a frame's groove. */\n  glass?: boolean | { rebate?: number };\n  /** Bar and frame tint. Defaults to `#5c4033` — painted wood. */\n  barColor?: ColorRepresentation;\n  /** Frame tint. Defaults to the bar's, because the two are one piece of joinery. */\n  frameColor?: ColorRepresentation;\n  /** Glass tint. Defaults to `#6a7d8c`. */\n  glassColor?: ColorRepresentation;\n  /** Glass emissive, for a lit window seen from outside. Defaults to off. */\n  glassEmissive?: ColorRepresentation;\n  /** Defaults to `0`. */\n  glassEmissiveIntensity?: number;\n}\n\n/**\n * A Gregorian light: glass, glazing bars, and the frame that carries them.\n *\n * The sibling of {@link DiamondLatticeWindow}, assembled the same way and for the same reason — bars have\n * to be framed, so the three are a unit rather than a convenience grouping.\n *\n * **A factory exposes what the ASSEMBLY decides, and delegates the rest.** So `lightsAcross` / `lightsUp`\n * are here and the bar spacings are not: even divisions determine them, and they are reported on the\n * instance rather than asked for. `barWidth` is here because the frame is sized from it. `barSides` is\n * not, because it has to agree with nothing — reach for {@link GregorianLatticeGeometry} for that.\n *\n * All three parts are built from ONE `opening`, which is also what you punch the wall with. Every part is\n * exposed as a field, so any of them can be replaced without forking this.\n *\n * Local frame: centered on X, sill at `y = 0`, facing `+Z`.\n *\n * @example\n * ```ts\n * const opening = { width: 1.2, height: 1.6, arch: \"semicircle\" } as const;\n *\n * const light = new GregorianLatticeWindow({ opening, lightsAcross: 3, lightsUp: 4 });\n * ```\n */\nexport class GregorianLatticeWindow extends Group {\n  /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */\n  readonly cutout: Path;\n\n  readonly bars: Mesh<GregorianLatticeGeometry, MeshStandardMaterial>;\n  readonly frame?: Mesh<WindowFrameGeometry, MeshStandardMaterial>;\n  readonly glass?: Mesh<PaneGeometry, MeshPhysicalMaterial>;\n\n  readonly lightsAcross: number;\n  readonly lightsUp: number;\n  /** The mullion spacing the light counts worked out to. An OUTPUT. */\n  readonly mullionSpacing: number;\n  /** The transom spacing the light counts worked out to. An OUTPUT. */\n  readonly transomSpacing: number;\n\n  constructor({\n    opening = {},\n    lightsAcross = 3,\n    lightsUp = 4,\n    barWidth = 0.03,\n    barDepth = barWidth,\n    curveSegments = 24,\n    frame = true,\n    glass = true,\n    barColor = \"#5c4033\",\n    frameColor = barColor,\n    glassColor = \"#6a7d8c\",\n    glassEmissive,\n    glassEmissiveIntensity = 0,\n  }: GregorianLatticeWindowOptions = {}) {\n    super();\n\n    this.cutout = openingCutout(opening);\n\n    const width = opening.width ?? 1.2;\n    const springing = opening.height ?? 1.4;\n\n    const across = Math.max(1, Math.round(lightsAcross));\n    const up = Math.max(1, Math.round(lightsUp));\n    this.lightsAcross = across;\n    this.lightsUp = up;\n    this.mullionSpacing = width / across;\n    this.transomSpacing = springing / up;\n\n    // The phase decides whether the centerline carries a BAR or a LIGHT, and the light COUNT decides which\n    // is wanted. `n` lights need `n − 1` bars between them:\n    //\n    //   ODD count  -> an even number of bars -> they pair up about the center, and none sits on it,\n    //                 so the family is offset by half a spacing.\n    //   EVEN count -> an odd number of bars  -> one lands on the centerline, so no offset.\n    //\n    // Get it backwards and you get one bar too many, every time — the extra one being the centerline bar\n    // that should have been a light.\n    //\n    // Bars landing exactly on the sill, the head, or a jamb need no special case: their section straddles\n    // the boundary, so the same rule that drops offcuts drops them, and the frame occupies those spots.\n    const mullionPhase = across % 2 === 0 ? 0 : this.mullionSpacing / 2;\n\n    const barMaterial = new MeshStandardMaterial({\n      color: new Color(barColor),\n      roughness: 0.75,\n      metalness: 0.05,\n      flatShading: true,\n      // The bars share a plane with the glass, so they have to win the depth test.\n      polygonOffset: true,\n      polygonOffsetFactor: -1,\n      polygonOffsetUnits: -1,\n    });\n\n    this.bars = new Mesh(\n      new GregorianLatticeGeometry({\n        opening,\n        mullionSpacing: this.mullionSpacing,\n        transomSpacing: this.transomSpacing,\n        mullionPhase,\n        // Zero puts a transom on the sill line, which is dropped — so the remaining ones land on exact\n        // fractions of the springing height, which is what an evenly divided window means.\n        transomPhase: 0,\n        barWidth,\n        barDepth,\n        curveSegments,\n      }),\n      barMaterial,\n    );\n    this.bars.castShadow = true;\n    this.bars.renderOrder = 1;\n    this.add(this.bars);\n\n    if (frame) {\n      // The assembly's decision: the frame's inner band is the bar's own width, so the glazing bars appear\n      // to run into the frame rather than stop at it. The depth matches too.\n      const settings = typeof frame === \"object\" ? frame : {};\n      const depth = settings.depth ?? barDepth;\n      this.frame = new Mesh(\n        new WindowFrameGeometry({\n          opening,\n          inset: settings.inset ?? barWidth,\n          outset: settings.outset ?? barWidth * 1.6,\n          depth,\n          curveSegments,\n        }),\n        new MeshStandardMaterial({\n          color: new Color(frameColor),\n          roughness: 0.75,\n          metalness: 0.05,\n          flatShading: true,\n        }),\n      );\n      // `WindowFrameGeometry` extrudes into +z from zero; center it on the bar so the two sit flush.\n      this.frame.position.z = -depth / 2;\n      this.frame.castShadow = true;\n      this.add(this.frame);\n    }\n\n    if (glass) {\n      const settings = typeof glass === \"object\" ? glass : {};\n      this.glass = new Mesh(\n        new PaneGeometry({ opening, rebate: settings.rebate ?? 0, curveSegments }),\n        new MeshPhysicalMaterial({\n          color: new Color(glassColor),\n          emissive: glassEmissive ? new Color(glassEmissive) : new Color(0x000000),\n          emissiveIntensity: glassEmissiveIntensity,\n          transparent: true,\n          depthWrite: false,\n          roughness: 0.08,\n          metalness: 0,\n          transmission: glassEmissive ? 0.5 : 0.88,\n          thickness: barDepth * 0.5,\n          side: DoubleSide,\n        }),\n      );\n      this.glass.renderOrder = 0;\n      this.glass.castShadow = false;\n      this.glass.receiveShadow = false;\n      this.add(this.glass);\n    }\n  }\n\n  /** Release every geometry and material this window owns. */\n  dispose(): void {\n    for (const part of [this.bars, this.frame, this.glass]) {\n      if (!part) continue;\n      part.geometry.dispose();\n      part.material.dispose();\n    }\n  }\n}\n","import { pushQuad, UNIT_QUAD_UV, type GeometryBuffers } from \"../../modeling/mesh/GeometryBuffers\";\n\n/**\n * Stair vocabulary over the {@link pushQuad} primitive — a riser, a tread, a landing.\n *\n * Everything here works in the flight's own frame: centered on X, rising +Y, running +Z. There are\n * deliberately no X-axis variants. A turned flight is the *same* flight rotated, not a second one\n * hand-derived in a rotated frame.\n */\n\n/** Riser — the vertical face of a step, facing +Z at `zFront`. */\nexport function pushRiser(\n  buffers: GeometryBuffers,\n  hw: number,\n  yBottom: number,\n  yTop: number,\n  zFront: number,\n): void {\n  pushQuad(\n    buffers,\n    [\n      [-hw, yBottom, zFront],\n      [-hw, yTop, zFront],\n      [hw, yTop, zFront],\n      [hw, yBottom, zFront],\n    ],\n    [0, 0, 1],\n    UNIT_QUAD_UV,\n  );\n}\n\n/** Tread — the horizontal face you stand on, facing +Y between `zFront` and `zBack`. */\nexport function pushTread(\n  buffers: GeometryBuffers,\n  hw: number,\n  yTop: number,\n  zFront: number,\n  zBack: number,\n): void {\n  pushQuad(\n    buffers,\n    [\n      [-hw, yTop, zFront],\n      [-hw, yTop, zBack],\n      [hw, yTop, zBack],\n      [hw, yTop, zFront],\n    ],\n    [0, 1, 0],\n    UNIT_QUAD_UV,\n  );\n}\n\n/** Point on the XZ circle at `angle` (radians, +Y up, 0 = +X, CCW = +Z). */\nexport function polarXZ(radius: number, angle: number): [number, number] {\n  return [radius * Math.cos(angle), radius * Math.sin(angle)];\n}\n\n/** Tangent on the XZ circle at `angle`, CCW when viewed from +Y. */\nexport function tangentXZ(angle: number): [number, number, number] {\n  return [-Math.sin(angle), 0, Math.cos(angle)];\n}\n\n/**\n * Spiral riser — vertical quad along one radial line at `angle`.\n * Normal faces the CCW climb direction (turret stair ascending counter-clockwise).\n */\nexport function pushSpiralRiser(\n  buffers: GeometryBuffers,\n  innerRadius: number,\n  outerRadius: number,\n  yBottom: number,\n  yTop: number,\n  angle: number,\n): void {\n  const [xi, zi] = polarXZ(innerRadius, angle);\n  const [xo, zo] = polarXZ(outerRadius, angle);\n\n  pushQuad(\n    buffers,\n    [\n      [xi, yBottom, zi],\n      [xi, yTop, zi],\n      [xo, yTop, zo],\n      [xo, yBottom, zo],\n    ],\n    tangentXZ(angle),\n    UNIT_QUAD_UV,\n  );\n}\n\n/**\n * Spiral tread — horizontal trapezoid between inner/outer radii from `angleStart`\n * to `angleEnd` (exclusive overlap with the next step when angles are contiguous).\n */\nexport function pushSpiralTread(\n  buffers: GeometryBuffers,\n  innerRadius: number,\n  outerRadius: number,\n  yTop: number,\n  angleStart: number,\n  angleEnd: number,\n): void {\n  const [xIf, zIf] = polarXZ(innerRadius, angleStart);\n  const [xIb, zIb] = polarXZ(innerRadius, angleEnd);\n  const [xOb, zOb] = polarXZ(outerRadius, angleEnd);\n  const [xOf, zOf] = polarXZ(outerRadius, angleStart);\n\n  pushQuad(\n    buffers,\n    [\n      [xIf, yTop, zIf],\n      [xIb, yTop, zIb],\n      [xOb, yTop, zOb],\n      [xOf, yTop, zOf],\n    ],\n    [0, 1, 0],\n    [\n      [0, 0],\n      [1, 0],\n      [1, 1],\n      [0, 1],\n    ],\n  );\n}\n","import { BufferGeometry } from \"three\";\nimport { createGeometryBuffers, toBufferGeometry } from \"../../modeling/mesh/GeometryBuffers\";\nimport { pushRiser, pushTread } from \"./staircaseQuad\";\n\nexport interface StaircaseGeometryOptions {\n  /** Stair width (tread left–right extent). Defaults to `2`. */\n  width?: number;\n  /** Vertical rise per step (riser). Defaults to `0.3`. */\n  riserHeight?: number;\n  /** Horizontal run per step (tread depth). Defaults to `0.5`. */\n  treadDepth?: number;\n  /** Number of steps — counted as risers, the way a stair is actually measured. Defaults to `10`. */\n  stepCount?: number;\n  /**\n   * Emit the tread at the very top. Defaults to `true`.\n   *\n   * Set `false` when the flight climbs to a landing or a floor, because that surface *is* the top\n   * tread — the last riser lifts you onto it. Emitting one anyway leaves a tread lying coplanar with\n   * the landing: you would climb the last riser, arrive on a step, and then walk *forward* rather\n   * than up. It also silently deepens the landing by one tread.\n   *\n   * So a flight of 5 steps into a landing is 5 risers and 4 treads; the landing is the fifth.\n   */\n  topTread?: boolean;\n}\n\n/**\n * Straight run staircase — open risers and treads (no side stringers yet).\n *\n * Local frame: centered on width, rises along +Y, runs along +Z. Each step\n * emits a front riser (+Z) and a top tread (+Y). UVs are normalized per face\n * (0–1) so materials can tile per step.\n */\nexport class StaircaseGeometry extends BufferGeometry {\n  readonly width: number;\n  readonly riserHeight: number;\n  readonly treadDepth: number;\n  readonly stepCount: number;\n  readonly topTread: boolean;\n  readonly totalHeight: number;\n  /** Run from the foot to the last surface — one tread shorter when a landing tops the flight. */\n  readonly totalDepth: number;\n\n  constructor({\n    width = 2,\n    riserHeight = 0.3,\n    treadDepth = 0.5,\n    stepCount = 10,\n    topTread = true,\n  }: StaircaseGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.riserHeight = riserHeight;\n    this.treadDepth = treadDepth;\n    this.stepCount = Math.max(1, Math.round(stepCount));\n    this.topTread = topTread;\n    this.totalHeight = this.stepCount * this.riserHeight;\n    // Without a top tread the run stops at the last riser — the landing takes it from there.\n    this.totalDepth = (this.stepCount - (topTread ? 0 : 1)) * this.treadDepth;\n\n    const hw = width / 2;\n    const buffers = createGeometryBuffers();\n\n    for (let i = 0; i < this.stepCount; i++) {\n      const yBottom = i * this.riserHeight;\n      const yTop = yBottom + this.riserHeight;\n      const zFront = i * this.treadDepth;\n      const zBack = zFront + this.treadDepth;\n\n      pushRiser(buffers, hw, yBottom, yTop, zFront);\n\n      // Every riser gets a tread to land on — except the last one, when a landing provides it.\n      if (topTread || i < this.stepCount - 1) {\n        pushTread(buffers, hw, yTop, zFront, zBack);\n      }\n    }\n\n    this.copy(toBufferGeometry(buffers));\n  }\n}","import {\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  MathUtils,\n  Material,\n  Mesh,\n  MeshStandardMaterial,\n  PlaneGeometry,\n} from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { StaircaseGeometry } from \"../../geometry/architecture/StaircaseGeometry\";\n\nexport interface StaircaseOptions {\n  /** Number of flights. Landings sit between them, so a run has `flights - 1` landings. Defaults to `2`. */\n  flights?: number;\n  /** Steps in each flight. Defaults to `5`. */\n  stepsPerFlight?: number;\n  /** Stair width — the tread's left-right extent. Defaults to `2`. */\n  width?: number;\n  /** Vertical rise per step. Defaults to `0.3`. */\n  riserHeight?: number;\n  /** Horizontal run per step. Defaults to `0.5`. */\n  treadDepth?: number;\n  /** Landing depth along the direction of travel. Defaults to `width` — a square landing. */\n  landingSize?: number;\n  /**\n   * Degrees the run turns at each landing. Defaults to `90`.\n   *\n   * - `90` / `-90` — a quarter turn. Four of them wrap a stairwell, so the fifth flight climbs\n   *   directly above the first. The sign picks which way it winds.\n   * - `0` — a straight run, broken by flat landings.\n   * - `180` / `-180` — a switchback. The next flight reverses, so it must be displaced sideways by a\n   *   full stair width or it would climb back through the flight below it. The landing widens to\n   *   span both. Two of them stack the run vertically: the third flight sits above the first. The\n   *   sign picks which side the run steps to.\n   */\n  turn?: number;\n  /**\n   * Gap between the two flights of a switchback — the open well down the middle of the stair.\n   * Ignored unless `turn` is ±180. Defaults to `0`, flights shoulder to shoulder.\n   */\n  well?: number;\n  /** Material. Omit to build a flat-shaded standard material from `color`. */\n  material?: Material;\n  /** Tint when `material` is omitted. Defaults to `#9a9a9a`. */\n  color?: ColorRepresentation;\n}\n\n/**\n * A staircase of any number of flights, turning at each landing.\n *\n * A flight is the geometry ({@link StaircaseGeometry}); a *staircase* is the assembly. The landing\n * is the whole point of the assembly — it is where the run turns. Chain four quarter-turns and you\n * have wrapped a stairwell: the fifth flight climbs directly above the first, which is how a\n * stairwell in a tall building actually works.\n *\n * Every flight is the **same geometry, rotated** — never a second flight re-derived by hand in a\n * turned coordinate frame. Everything merges into one geometry, so a twenty-flight tower is still\n * one draw call.\n *\n * Local frame: the first flight starts at the origin, rises +Y, and runs +Z.\n *\n * @example\n * ```ts\n * // An L-shaped staircase: two flights, one landing, a quarter turn.\n * const stairs = createStaircase({ flights: 2, stepsPerFlight: 5 });\n * scene.add(stairs);\n *\n * // A stairwell climbing five stories, wrapping a square shaft.\n * const tower = createStaircase({ flights: 20, stepsPerFlight: 8, turn: 90 });\n *\n * // A straight run broken by landings, no turn.\n * const long = createStaircase({ flights: 3, turn: 0 });\n * ```\n */\nexport function createStaircase({\n  flights = 2,\n  stepsPerFlight = 5,\n  width = 2,\n  riserHeight = 0.3,\n  treadDepth = 0.5,\n  landingSize = width,\n  turn = 90,\n  well = 0,\n  material,\n  color = \"#9a9a9a\",\n}: StaircaseOptions = {}): Mesh {\n  const flightCount = Math.max(1, Math.round(flights));\n  const steps = Math.max(1, Math.round(stepsPerFlight));\n\n  const flightRise = steps * riserHeight;\n  const turnRadians = MathUtils.degToRad(turn);\n\n  // A flight into a landing stops at its last riser: the landing IS that flight's top tread, so the\n  // flight runs one tread shorter. The final flight has no landing above it and keeps its own.\n  const runToLanding = (steps - 1) * treadDepth;\n  const runToTop = steps * treadDepth;\n\n  // A reversing flight would climb straight back through the one below it, so a switchback has to\n  // step sideways by a full stair width. A quarter turn needs no such shift: its new direction is\n  // perpendicular, so the landing can simply pivot about its own center.\n  const isSwitchback = Math.abs(turn) === 180;\n  const shift = isSwitchback ? (Math.sign(turn) || 1) * (width + well) : 0;\n  const landingWidth = isSwitchback ? Math.abs(shift) + width : width;\n\n  const parts: BufferGeometry[] = [];\n\n  // Walk the run: a cursor at the foot of the current flight, and the direction it faces.\n  let x = 0;\n  let y = 0;\n  let z = 0;\n  let yaw = 0;\n\n  for (let flight = 0; flight < flightCount; flight++) {\n    const isLast = flight === flightCount - 1;\n\n    const steps3D = new StaircaseGeometry({\n      width,\n      riserHeight,\n      treadDepth,\n      stepCount: steps,\n      topTread: isLast,\n    });\n    steps3D.rotateY(yaw);\n    steps3D.translate(x, y, z);\n    parts.push(steps3D);\n\n    // Climb it: forward along this flight's own axis, and up.\n    const run = isLast ? runToTop : runToLanding;\n    x += Math.sin(yaw) * run;\n    z += Math.cos(yaw) * run;\n    y += flightRise;\n\n    if (isLast) break;\n\n    // Unit vectors in this flight's own frame: where \"forward\" and \"sideways\" point in the world.\n    const fx = Math.sin(yaw);\n    const fz = Math.cos(yaw);\n    const sx = Math.cos(yaw);\n    const sz = -Math.sin(yaw);\n\n    if (isSwitchback) {\n      // The landing sits beyond the flight it tops, spanning sideways to cover the next one too.\n      const landing = new PlaneGeometry(landingWidth, landingSize);\n      landing.rotateX(-Math.PI / 2);\n      landing.rotateY(yaw);\n      landing.translate(\n        x + fx * (landingSize / 2) + sx * (shift / 2),\n        y,\n        z + fz * (landingSize / 2) + sz * (shift / 2),\n      );\n      parts.push(landing);\n\n      // Step SIDEWAYS only. The next flight begins where this one ended — not beyond the landing —\n      // so the two run parallel, front to back, the way a real dog-leg does. The landing's depth is\n      // turning room, not travel: stepping forward through it would offset the flights and send the\n      // next one climbing back over the platform it just left.\n      x += sx * shift;\n      z += sz * shift;\n      yaw += turnRadians;\n    } else {\n      // Step to the landing's center, turn there, then step out to its far edge — which is where the\n      // next flight begins. A square landing makes those half-steps equal, so the run pivots about\n      // the landing's center exactly as a real quarter turn does.\n      const half = landingSize / 2;\n\n      x += fx * half;\n      z += fz * half;\n\n      const landing = new PlaneGeometry(landingWidth, landingSize);\n      landing.rotateX(-Math.PI / 2); // stand it flat: +Y up, spanning X and Z\n      landing.rotateY(yaw);\n      landing.translate(x, y, z);\n      parts.push(landing);\n\n      yaw += turnRadians;\n\n      x += Math.sin(yaw) * half;\n      z += Math.cos(yaw) * half;\n    }\n  }\n\n  const geometry = mergeGeometries(parts, false) as BufferGeometry;\n  parts.forEach((part) => part.dispose());\n\n  const stone =\n    material ?? new MeshStandardMaterial({ color: new Color(color), flatShading: true, roughness: 0.9 });\n\n  const stairs = new Mesh(geometry, stone);\n  stairs.userData.totalHeight = flightCount * flightRise;\n  stairs.userData.flights = flightCount;\n\n  return stairs;\n}\n","import {\n  BoxGeometry,\n  Color,\n  ColorRepresentation,\n  DoubleSide,\n  Group,\n  type Path,\n  Material,\n  Mesh,\n  MeshStandardMaterial,\n} from \"three\";\nimport { PaneGeometry } from \"../../geometry/architecture/PaneGeometry\";\nimport {\n  WindowFrameGeometry,\n  type WindowFrameGeometryOptions,\n} from \"../../geometry/architecture/WindowFrameGeometry\";\nimport { openingCutout, wallOpeningTop, type WallOpeningOptions } from \"../../shapes/WallShape\";\n\n/** Match the jamb's inner edge (WindowFrameGeometry's INNER_MITER) so the glass fits it exactly. */\nconst JAMB_INNER_MITER = 2;\n\nexport interface WindowSillOptions {\n  /**\n   * How far the sill juts out of the wall. Defaults to `0.09`.\n   *\n   * The overhang is most of why a window reads as real — a flush sill reads as a sticker.\n   */\n  jut?: number;\n  /** Thickness of the slab. Defaults to `0.04`. */\n  thickness?: number;\n  /**\n   * How far the sill runs PAST the opening on each side — its horns. Defaults to `0.05`.\n   *\n   * Real sills overhang their jambs. Square them off at the opening and the window looks cut out rather\n   * than built in.\n   */\n  horn?: number;\n  /**\n   * How far the sill's top face sits ABOVE the opening's sill line. Defaults to `0`.\n   *\n   * At `0` the top face lands exactly on the sill line, which is where the glass starts — so the frame's\n   * inner edge, which bites `inset` into the aperture, stands proud of it and the sill reads as sunk.\n   * Setting this to the same value as `inset` brings the two flush, and it is what a real sill does\n   * anyway: the glass sits in a rebate cut into the sill rather than balancing on its surface.\n   */\n  rise?: number;\n}\n\nexport interface WindowJambOptions {\n  /**\n   * How far the jamb lining bites into the aperture from the wall's cut edge — the visible board width.\n   * Defaults to `0.05`.\n   */\n  width?: number;\n}\n\nexport interface WindowOptions extends Omit<WindowFrameGeometryOptions, \"opening\"> {\n  /** The opening this window fills — the SAME description the wall was punched with. */\n  opening: WallOpeningOptions;\n  /** Omit for a frameless aperture. */\n  frame?: boolean;\n  /** A sill under it. Pass `true` for the defaults, or an object to size it. Omit for none. */\n  sill?: boolean | WindowSillOptions;\n  /** Omit to leave the aperture empty — a broken window. */\n  glass?: boolean;\n  /**\n   * The jamb — the lining of the reveal, running the full depth of the wall. Pass `true` for the\n   * defaults, an object to size it. Omit for a bare opening.\n   *\n   * The decorative {@link WindowOptions.frame} is a shallow ring on the wall's FACE; the jamb is the\n   * same ring turned 90° INTO the wall — deep and flush — so it lines the hole instead of the surface.\n   * With a jamb, the glass drops to the wall's mid-depth and sits inside it, rather than clinging to the\n   * front where it leaves the hole open behind. Needs {@link WindowOptions.wallThickness} to know how\n   * deep to run.\n   */\n  jamb?: boolean | WindowJambOptions;\n  /**\n   * Thickness of the wall the window is set into — the depth the {@link WindowOptions.jamb} spans and the\n   * span the glass centers in. Defaults to `0.3`. Ignored without a jamb.\n   */\n  wallThickness?: number;\n\n  /** Frame material. Omit to build a flat-shaded standard material from `frameColor`. */\n  frameMaterial?: Material;\n  /** Frame tint when `frameMaterial` is omitted. Defaults to `#4a3b2a` — wood. */\n  frameColor?: ColorRepresentation;\n  /** Glass material. Omit to build a translucent one from `glassColor`. */\n  glassMaterial?: Material;\n  /** Glass tint when `glassMaterial` is omitted. Defaults to `#9fb6c4`. */\n  glassColor?: ColorRepresentation;\n  /** Glass opacity when `glassMaterial` is omitted. Defaults to `0.35`. */\n  glassOpacity?: number;\n}\n\n/** A window: glass, the frame ringing it, the jamb lining the reveal, and the sill under it. */\nexport interface WindowAssembly extends Group {\n  /** Clockwise hole at opening.x/y; independent of subsequent assembly transforms. */\n  readonly cutout: Path;\n  /** The pane. Flat, and `DoubleSide`, so it survives being looked at from behind. */\n  glass?: Mesh;\n  /** The decorative ring on the wall's face. */\n  frame?: Mesh;\n  /** The lining of the reveal, running the wall's full depth. */\n  jamb?: Mesh;\n  /** The slab under it. */\n  sill?: Mesh;\n  /**\n   * Release every geometry and material this window owns.\n   *\n   * Materials may be SHARED — the frame, jamb and sill are one timber by default — so each is disposed\n   * once rather than once per part.\n   */\n  dispose(): void;\n}\n\n/**\n * A window assembly, cut to fit an opening exactly — because it is cut from the SAME outline the wall was\n * punched with.\n *\n * ```ts\n * const opening = { width: 0.8, height: 1, arch: \"ogee\", x: -2, y: 1.5 };\n *\n * const wall = new WallShape({ width: 6, height: 4, windows: [opening] });\n * const window = createWindow({ opening, sill: true });\n * window.position.set(opening.x, opening.y, wallThickness); // the wall's outer face\n * ```\n *\n * The fit is not a coincidence to be maintained; it is the same `traceArch` call. Change the opening's\n * arch to a horseshoe and the glass, the frame and the hole all become horseshoes together.\n *\n * **Anchored at the SILL, centered on X** — `y = 0` is the sill and `z = 0` is the wall face the window\n * is mounted on, so hanging it is `position.set(opening.x, opening.y, faceZ)` and nothing else. The frame\n * and sill stand out in `+z`; the glass sits back inside the frame's depth.\n *\n * A {@link Group} with the parts named, not one merged mesh — glass has to be transparent and the frame\n * must not be, and a shared material array would force them to render together. Dispose each part.\n *\n * @example\n * ```ts\n * // A leaded pane in a stone wall, with the sill it needs to look built rather than cut.\n * const window = createWindow({\n *   opening: { width: 0.7, height: 0.9, arch: \"pointed\", archHeight: 0.5 },\n *   inset: 0.03,\n *   outset: 0.05,\n *   frameColor: \"#2b2b2b\",\n *   sill: { jut: 0.12, horn: 0.06 },\n * });\n * ```\n */\nexport function createWindow({\n  opening,\n  frame = true,\n  sill,\n  glass = true,\n  jamb,\n  wallThickness = 0.3,\n  inset = 0.03,\n  outset = 0.06,\n  depth = 0.05,\n  curveSegments = 48,\n  frameMaterial,\n  frameColor = \"#4a3b2a\",\n  glassMaterial,\n  glassColor = \"#9fb6c4\",\n  glassOpacity = 0.35,\n}: WindowOptions): WindowAssembly {\n  const window = Object.assign(new Group(), { cutout: openingCutout(opening) }) as WindowAssembly;\n\n  // At the origin, so the assembly can be dropped into any opening of this shape.\n  const centered: WallOpeningOptions = { ...opening, x: 0, y: 0 };\n\n  const timber =\n    frameMaterial ??\n    new MeshStandardMaterial({ color: new Color(frameColor), roughness: 0.85, flatShading: true });\n\n  if (frame) {\n    window.frame = new Mesh(\n      new WindowFrameGeometry({ opening: centered, inset, outset, depth, curveSegments }),\n      timber,\n    );\n    window.frame.castShadow = true;\n    window.frame.receiveShadow = true;\n    window.add(window.frame);\n  }\n\n  const jambWidth = jamb ? (jamb === true ? 0.05 : jamb.width ?? 0.05) : 0;\n\n  if (jamb) {\n    // The same ring as the face frame, turned INTO the wall: deep (the wall's full thickness) and flush\n    // (no outset), so its outer edge sits against the cut hole and its inner edge is the reveal you see.\n    // The window mounts on the front face (z = 0), so the jamb runs back through the wall in -z.\n    window.jamb = new Mesh(\n      new WindowFrameGeometry({ opening: centered, inset: jambWidth, outset: 0, depth: wallThickness, curveSegments }),\n      timber,\n    );\n    window.jamb.position.z = -wallThickness;\n    window.jamb.castShadow = true;\n    window.jamb.receiveShadow = true;\n    window.add(window.jamb);\n  }\n\n  if (glass) {\n    // With a jamb the glass fits the jamb's inner opening — the outline pulled in by the board width —\n    // and centers in the wall; without one it fills the bare hole and clings to the frame's face. That\n    // pull-in is a REBATE, which is what `PaneGeometry` is for, and it must be offset with the same miter\n    // limit the lining used or the glass could spike where the lining blunts.\n    //\n    // Flat, and DOUBLE-SIDED. A single-sided pane simply vanishes when the camera swings behind the\n    // wall — the glass is still there, you are just looking at the back of a face that was never drawn.\n    window.glass = new Mesh(\n      new PaneGeometry({\n        opening: centered,\n        rebate: jamb ? -jambWidth : 0,\n        curveSegments,\n        miterLimit: JAMB_INNER_MITER,\n      }),\n      glassMaterial ??\n        new MeshStandardMaterial({\n          color: new Color(glassColor),\n          transparent: true,\n          opacity: glassOpacity,\n          roughness: 0.15,\n          metalness: 0,\n          side: DoubleSide,\n        }),\n    );\n    // Centered in the wall when there is a jamb to hold it; otherwise inside the frame's depth.\n    window.glass.position.z = jamb ? -wallThickness / 2 : frame ? depth / 2 : 0;\n    window.add(window.glass);\n  }\n\n  if (sill) {\n    const { jut = 0.09, thickness = 0.04, horn = 0.05, rise = 0 } = sill === true ? {} : sill;\n\n    // As wide as the frame it sits under, plus its horns.\n    const half = (opening.width ?? 1.2) / 2 + outset + horn;\n\n    window.sill = new Mesh(new BoxGeometry(half * 2, thickness, jut), timber);\n    // Its top face meets the sill line of the opening — or `rise` above it, to come flush with the\n    // frame's inner edge — and it juts forward out of the wall.\n    window.sill.position.set(0, rise - thickness / 2, jut / 2);\n    window.sill.castShadow = true;\n    window.sill.receiveShadow = true;\n    window.add(window.sill);\n  }\n\n  window.dispose = () => {\n    const materials = new Set<Material>();\n    for (const part of [window.glass, window.frame, window.jamb, window.sill]) {\n      if (!part) continue;\n      part.geometry.dispose();\n      materials.add(part.material as Material);\n    }\n    materials.forEach((material) => material.dispose());\n  };\n\n  return window;\n}\n\n/** The crown of a window's opening, above its sill. Handy for checking it clears the wall above. */\nexport function windowHeight(opening: WallOpeningOptions): number {\n  return wallOpeningTop({ ...opening, y: 0 });\n}\n","import { BoxGeometry, BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  toBufferGeometry,\n  type Vec2,\n  type Vec3,\n} from \"../../modeling/mesh/GeometryBuffers\";\n\nexport interface BookGeometryOptions {\n  /** Cover width, spine to fore-edge. Defaults to `1`. */\n  width?: number;\n  /** Cover height. Defaults to `1.5`. */\n  height?: number;\n  /** Spine depth, cover to cover. Defaults to `0.5`. */\n  depth?: number;\n  /** Cover board thickness. Defaults to `0.05`. */\n  coverThickness?: number;\n  /** Inset of the page block from the cover edges. Defaults to `0.05`. */\n  pageIndent?: number;\n}\n\n/** Material slot for the cover shell. */\nexport const BOOK_COVER_MATERIAL = 0;\n/** Material slot for the page block. */\nexport const BOOK_PAGES_MATERIAL = 1;\n\n/**\n * A closed book — cover shell (group 0) and page block (group 1), merge-baked into one geometry.\n *\n * Fourteen quads make the shell: three outer boards, three inner faces, three top edges, three bottom\n * edges, and the two fore-edges. The inner faces are what make it a SHELL rather than a slab — a book\n * seen from its fore-edge shows the inside of both boards and the page block held between them.\n *\n * Local frame: **spine at X = 0, fore-edge at +X, sitting on Y = 0**, with the book extending to −Z. Not\n * centred in XZ, and deliberately: books are placed against each other, so the spine is the useful\n * anchor. A row lays them out along Z; a shelf stands them along X.\n *\n * ## The two groups are the point\n *\n * A cover is red and its pages are white, so the two need different materials — and merging them with\n * groups is what keeps a single book to one geometry and one draw pair. It also rules something out:\n * Three's `InstancedMesh` carries ONE colour per instance for the whole object, so a shelf of books with\n * differently coloured spines cannot be a single instanced mesh here. (Metal can do it — `setColorAt`\n * against a material group — which is why the Swift port of this reads differently.) Merging a whole\n * shelf into one baked geometry is the way that works here, and it is what the row and stack factories\n * do.\n *\n * ## The cover UV wraps front to back\n *\n * `u` runs continuously across **back cover → spine → front cover**, in proportion to `2·width + depth`,\n * so the three outer boards share one unbroken 0→1 span. That is the layout a real dust jacket is\n * printed on: one flat sheet, folded around the boards. Apply a paper texture and it wraps correctly\n * across the spine instead of restarting at every face.\n *\n * The three INNER faces carry the same spans reversed, so a texture continues around the fold rather\n * than mirroring at it. The edge, top and bottom strips take a plain 0→1: they are thin, and nothing on\n * a jacket is registered to them.\n *\n * @example\n * ```ts\n * const book = new Mesh(new BookGeometry({ depth: 0.32 }), [\n *   new MeshStandardMaterial({ color: 0x8c2f2f, roughness: 0.62 }), // cover\n *   new MeshStandardMaterial({ color: 0xe8e0cc, roughness: 0.92 }), // pages\n * ]);\n * ```\n */\nexport class BookGeometry extends BufferGeometry {\n  readonly width: number;\n  readonly height: number;\n  readonly depth: number;\n  readonly coverThickness: number;\n  readonly pageIndent: number;\n\n  constructor({\n    width = 1,\n    height = 1.5,\n    depth = 0.5,\n    coverThickness = 0.05,\n    pageIndent = 0.05,\n  }: BookGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n    this.coverThickness = coverThickness;\n    this.pageIndent = pageIndent;\n\n    const w = width;\n    const h = height;\n    const d = depth;\n    const t = coverThickness;\n    const i = pageIndent;\n\n    // The jacket's two folds, as fractions of the flat sheet `2w + d`. Everything the cover UV does is\n    // these two numbers: back cover [0, u1], spine [u1, u2], front cover [u2, 1].\n    const sheet = w * 2 + d;\n    const u1 = w / sheet;\n    const u2 = (w + d) / sheet;\n\n    const buffers = createGeometryBuffers();\n\n    /**\n     * One planar quad of the shell.\n     *\n     * The normal is left to the winding rather than transcribed. For a planar quad the two are identical,\n     * and it removes a parallel table that has to be kept in step by hand — the original carried fifty-six\n     * normals written out longhand beside fifty-six positions.\n     */\n    const quad = (corners: [Vec3, Vec3, Vec3, Vec3], uvs: [Vec2, Vec2, Vec2, Vec2]) =>\n      pushQuad(buffers, corners, undefined, uvs);\n\n    /** A thin strip — edges, tops and bottoms. Nothing on a jacket registers to these. */\n    const STRIP: [Vec2, Vec2, Vec2, Vec2] = [\n      [0, 0],\n      [1, 0],\n      [1, 1],\n      [0, 1],\n    ];\n\n    //  Outer boards. Their `u` spans are the jacket, unbroken from back through spine to front.\n    quad(\n      [[0, 0, 0], [w, 0, 0], [w, h, 0], [0, h, 0]],\n      [[u2, 0], [1, 0], [1, 1], [u2, 1]],\n    ); // front cover\n    quad(\n      [[w, 0, -d], [0, 0, -d], [0, h, -d], [w, h, -d]],\n      [[0, 0], [u1, 0], [u1, 1], [0, 1]],\n    ); // back cover\n    quad(\n      [[0, 0, -d], [0, 0, 0], [0, h, 0], [0, h, -d]],\n      [[u1, 0], [u2, 0], [u2, 1], [u1, 1]],\n    ); // spine\n\n    //  Inner faces of the boards — what makes this a shell and not a slab. Their spans run the other\n    //  way, so a jacket texture carries around the fold rather than mirroring at it.\n    quad(\n      [[w, 0, -t], [t, 0, -t], [t, h, -t], [w, h, -t]],\n      [[1, 0], [u2, 0], [u2, 1], [1, 1]],\n    ); // inside front\n    quad(\n      [[t, 0, -d + t], [w, 0, -d + t], [w, h, -d + t], [t, h, -d + t]],\n      [[u1, 0], [0, 0], [0, 1], [u1, 1]],\n    ); // inside back\n    quad(\n      [[t, 0, -t], [t, 0, -d + t], [t, h, -d + t], [t, h, -t]],\n      [[u2, 0], [u1, 0], [u1, 1], [u2, 1]],\n    ); // inside spine\n\n    //  Top edges of the three boards.\n    quad([[0, h, 0], [w, h, 0], [w, h, -t], [t, h, -t]], [[u2, 0], [1, 0], [1, 1], [u2, 1]]);\n    quad([[0, h, -d], [t, h, -d + t], [w, h, -d + t], [w, h, -d]], STRIP);\n    quad([[0, h, 0], [t, h, -t], [t, h, -d + t], [0, h, -d]], STRIP);\n\n    //  Bottom edges.\n    quad([[0, 0, 0], [t, 0, -t], [w, 0, -t], [w, 0, 0]], STRIP);\n    quad([[0, 0, -d], [w, 0, -d], [w, 0, -d + t], [t, 0, -d + t]], STRIP);\n    quad([[0, 0, 0], [0, 0, -d], [t, 0, -d + t], [t, 0, -t]], STRIP);\n\n    //  Fore-edges of the two boards — the open side of the book.\n    quad([[w, 0, 0], [w, 0, -t], [w, h, -t], [w, h, 0]], STRIP);\n    quad([[w, 0, -d], [w, h, -d], [w, h, -d + t], [w, 0, -d + t]], STRIP);\n\n    const shell = toBufferGeometry(buffers);\n\n    // The page block, inset from the boards on every side. A box today; see `docs/books.md` for the\n    // curved fore-edge a real block has.\n    const pages = new BoxGeometry(w - t - i, h - i * 2, d - t * 2);\n    pages.translate((w - t - i) / 2 + t, h / 2, -d / 2);\n\n    // `true` keeps the two as separate groups rather than flattening them, which is the whole reason\n    // this is one geometry instead of two meshes.\n    const merged = mergeGeometries([shell, pages], true);\n    shell.dispose();\n    pages.dispose();\n    if (merged) {\n      this.copy(merged);\n      merged.dispose();\n    }\n  }\n}\n","import { BookGeometry } from \"../../geometry/books/BookGeometry\";\nimport { InstancedMesh, Matrix4, Material, Quaternion, Vector3 } from \"three\";\nimport { createRandom, type RandomSource } from \"../../utils/Random\";\nimport { randomSkewMax, randomSkewMin, randomFloat } from \"../../utils/RandomNumberUtils\";\n\n/** Default {@link BookGeometry} spine depth — stack layer height when books lay flat. */\nconst BOOK_UNIT_DEPTH = 0.5;\n\n/** Lay a book flat on its cover (+90° X) so spine depth becomes stack height (Y+). */\nconst LAY_FLAT_X = Math.PI / 2;\n\ninterface RandomScaleOptions {\n  scaleXMin?: number;\n  scaleXMax?: number;\n  scaleYMin?: number;\n  scaleYMax?: number;\n  scaleZMin?: number;\n  scaleZMax?: number;\n  source: RandomSource;\n}\n\nexport interface RowOfBooksByScalesOptions<T extends Material = Material> {\n  coverMaterial: T;\n  pagesMaterial: T;\n  /** One scale per book. `z` is the thickness, and thickness is what fills the shelf. */\n  scales: Vector3[];\n  /** Shared stream for shelf jitter — must be the same source that built `scales`. */\n  source: RandomSource;\n}\n\ninterface BookScaleOptions<T extends Material = Material> {\n  coverMaterial: T;\n  pagesMaterial: T;\n  scaleXMin?: number;\n  scaleXMax?: number;\n  scaleYMin?: number;\n  scaleYMax?: number;\n  scaleZMin?: number;\n  scaleZMax?: number;\n  /** Optional seed for reproducible layout. Omit for unique runtime. */\n  seed?: number;\n}\n\nexport interface RowOfBooksByCountOptions<T extends Material = Material> extends BookScaleOptions<T> {\n  /** Number of books. The row is as long as they turn out. Defaults to `10`. */\n  count?: number;\n}\n\nexport interface RowOfBooksByLengthOptions<T extends Material = Material> extends BookScaleOptions<T> {\n  /**\n   * Shelf length to pack, in world units along Z. Defaults to `10`.\n   *\n   * Book count is an *output* of packing, never an input — see {@link rowOfBooksByLength}.\n   */\n  length?: number;\n}\n\nexport interface StackOfBooksOptions<T extends Material = Material> {\n  coverMaterial: T;\n  pagesMaterial: T;\n  /** Number of books in the stack. Defaults to `6`. */\n  count?: number;\n  scaleXMin?: number;\n  scaleXMax?: number;\n  scaleYMin?: number;\n  scaleYMax?: number;\n  scaleZMin?: number;\n  scaleZMax?: number;\n  /**\n   * Max in-plane spin (world Y, radians) once the book is laid flat — a lazy\n   * turn on the floor, not a tilt. Defaults to `0.55` (~31°).\n   */\n  yawMax?: number;\n  /** Max horizontal drift per layer on X/Z. Defaults to `0.06`. */\n  offsetMax?: number;\n  /** Optional seed for reproducible layout. Omit for unique runtime. */\n  seed?: number;\n}\n\nfunction randomScale({\n  scaleXMin = 0.4,\n  scaleXMax = 0.7,\n  scaleYMin = 0.3,\n  scaleYMax = 0.95,\n  scaleZMin = 0.1,\n  scaleZMax = 0.5,\n  source,\n}: RandomScaleOptions): Vector3 {\n  return new Vector3(\n    randomFloat(scaleXMin, scaleXMax, source),\n    randomSkewMax(4, scaleYMin, scaleYMax, source),\n    randomSkewMin(1.25, scaleZMin, scaleZMax, source),\n  );\n}\n\n/**\n * Row of books from scales you supply yourself — the escape hatch beneath\n * {@link rowOfBooksByCount} and {@link rowOfBooksByLength}, for when you want to choose every\n * book's size rather than have one drawn for you.\n *\n * Both of the other row factories are thin wrappers over this: they only differ in how they build\n * the `scales` array.\n *\n * Local frame: the row starts at Z=0 and grows along +Z.\n *\n * @example\n * ```ts\n * const source = createRandom(1337);\n * const scales = [\n *   new Vector3(0.5, 0.9, 0.3),  // z is the thickness\n *   new Vector3(0.5, 0.7, 0.2),\n *   new Vector3(0.6, 0.8, 0.4),\n * ];\n * const row = rowOfBooksByScales({ coverMaterial, pagesMaterial, scales, source });\n * ```\n */\nexport function rowOfBooksByScales<T extends Material>({\n  coverMaterial,\n  pagesMaterial,\n  scales,\n  source,\n}: RowOfBooksByScalesOptions<T>): InstancedMesh {\n  const geometry = new BookGeometry();\n  const row = new InstancedMesh(geometry, [coverMaterial, pagesMaterial], scales.length);\n  const matrix = new Matrix4();\n  let currentZ = 0;\n\n  for (let i = 0; i < scales.length; i++) {\n    const scale = scales[i];\n    const scaleMatrix = new Matrix4();\n    scaleMatrix.makeScale(scale.x, scale.y, scale.z);\n    matrix.identity();\n    matrix.multiply(scaleMatrix);\n    matrix.setPosition(0.01 + source.float(0, 0.1), 0, currentZ + scale.z * 0.5);\n    row.setMatrixAt(i, matrix);\n    currentZ += scale.z * 0.5;\n  }\n  return row;\n}\n\n/**\n * Row of books by count — the row is however long the books turn out.\n *\n * This is the **partially-filled shelf**, and it is the more common one: a real bookshelf is almost\n * never packed wall to wall. Ask for twelve books, get a run you then position on a shelf with space\n * beside it. Pinning `count` is safe here precisely *because* nothing else is pinned — the books\n * keep their natural thicknesses and the length simply falls out.\n *\n * Reach for {@link rowOfBooksByLength} instead when the shelf is the fixed thing and you want it\n * full.\n *\n * Local frame: the row starts at Z=0 and grows along +Z. Read the row's bounding box to place it —\n * its length is not knowable in advance.\n *\n * @example\n * ```ts\n * // Twelve books; the row is as long as they happen to be.\n * const row = rowOfBooksByCount({ coverMaterial, pagesMaterial, count: 12, seed: 1337 });\n * scene.add(row);\n *\n * // Same count, different seed -> a different length. That is the point.\n * //   seed 1337 -> 12 books spanning 2.00\n * //   seed 7    -> 12 books spanning 1.87\n * ```\n */\nexport function rowOfBooksByCount<T extends Material>({\n  coverMaterial,\n  pagesMaterial,\n  count = 10,\n  scaleXMin = 0.4,\n  scaleXMax = 0.7,\n  scaleYMin = 0.3,\n  scaleYMax = 0.95,\n  scaleZMin = 0.1,\n  scaleZMax = 0.5,\n  seed,\n}: RowOfBooksByCountOptions<T>): InstancedMesh {\n  const source = createRandom(seed);\n  const scales = Array.from({ length: count }, () =>\n    randomScale({ scaleXMin, scaleXMax, scaleYMin, scaleYMax, scaleZMin, scaleZMax, source }),\n  );\n\n  return rowOfBooksByScales({ coverMaterial, pagesMaterial, scales, source });\n}\n\n/**\n * Pack a shelf of a given length with plausibly-sized books.\n *\n * **Book count is an output, not an input.** Books touch — there is no gap to absorb slack — so\n * the only variable left to solve is thickness, and thickness has a physical floor (`scaleZMin`).\n * Pinning both `length` and `count` would drive the solver straight through that floor and produce\n * paper-thin books, so `count` is deliberately not accepted here. Ask for a shelf; get however many\n * books fit.\n *\n * Packing stops once the space left is thinner than the thinnest legal book, leaving a small gap at\n * the end — the way a real shelf does. A final book is trimmed to close the gap only when trimming\n * still leaves it above `scaleZMin`.\n *\n * This is the **shelf packed full**, wall to wall. For a partially-filled shelf — the more common\n * look — use {@link rowOfBooksByCount} and position the row within the shelf.\n *\n * Local frame: the row starts at Z=0 and grows along +Z.\n *\n * @example\n * ```ts\n * // Fill a 6-unit shelf. You do not say how many books; you find out.\n * const shelf = rowOfBooksByLength({ coverMaterial, pagesMaterial, length: 6, seed: 1337 });\n * scene.add(shelf);\n *\n * shelf.count; // 34 — an output. A different seed gives a different number.\n * ```\n */\nexport function rowOfBooksByLength<T extends Material>({\n  coverMaterial,\n  pagesMaterial,\n  length = 10,\n  scaleXMin = 0.4,\n  scaleXMax = 0.7,\n  scaleYMin = 0.3,\n  scaleYMax = 0.95,\n  scaleZMin = 0.1,\n  scaleZMax = 0.5,\n  seed,\n}: RowOfBooksByLengthOptions<T>): InstancedMesh {\n  const source = createRandom(seed);\n  const scales: Vector3[] = [];\n\n  /** Shelf space the thinnest legal book occupies. A book is `BOOK_UNIT_DEPTH * scale.z` deep. */\n  const minDepth = BOOK_UNIT_DEPTH * scaleZMin;\n  let remaining = length;\n\n  while (remaining >= minDepth) {\n    const scale = randomScale({ scaleXMin, scaleXMax, scaleYMin, scaleYMax, scaleZMin, scaleZMax, source });\n\n    if (BOOK_UNIT_DEPTH * scale.z > remaining) {\n      // Trim the last book to close the gap. The loop guard means `remaining >= minDepth`,\n      // so the trimmed book still clears `scaleZMin`.\n      scale.z = remaining / BOOK_UNIT_DEPTH;\n    }\n\n    scales.push(scale);\n    remaining -= BOOK_UNIT_DEPTH * scale.z;\n  }\n\n  return rowOfBooksByScales({ coverMaterial, pagesMaterial, scales, source });\n}\n\n/**\n * Stack of books on the floor — each book lays flat on its cover; count sets stack height.\n * Each layer is laid flat (+90° X), then given a small in-plane spin (world Y)\n * around the book's geometric center — not the spine corner.\n *\n * Local frame: bottom of the stack at Y=0, centered on X/Z.\n *\n * @example\n * ```ts\n * const stack = stackOfBooks({\n *   coverMaterial,\n *   pagesMaterial,\n *   count: 8,\n *   yawMax: 0.6,\n *   seed: 1337,\n * });\n * scene.add(stack);\n * ```\n */\nexport function stackOfBooks<T extends Material>({\n  coverMaterial,\n  pagesMaterial,\n  count = 6,\n  scaleXMin = 0.4,\n  scaleXMax = 0.7,\n  scaleYMin = 0.3,\n  scaleYMax = 0.95,\n  scaleZMin = 0.1,\n  scaleZMax = 0.5,\n  yawMax = 0.55,\n  offsetMax = 0.06,\n  seed,\n}: StackOfBooksOptions<T>): InstancedMesh {\n  const source = createRandom(seed);\n  const geometry = new BookGeometry();\n  const stack = new InstancedMesh(geometry, [coverMaterial, pagesMaterial], count);\n\n  const matrix = new Matrix4();\n  const position = new Vector3();\n  const quaternion = new Quaternion();\n  const layFlatQuat = new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), LAY_FLAT_X);\n  const spinQuat = new Quaternion();\n  const scale = new Vector3();\n  /** Upright geometry center — spin pivots here, not the bottom-front-spine corner. */\n  const localCenter = new Vector3(\n    geometry.width * 0.5,\n    geometry.height * 0.5,\n    -geometry.depth * 0.5,\n  );\n  const scaledCenter = new Vector3();\n  const centerOffset = new Vector3();\n\n  let currentY = 0;\n\n  for (let i = 0; i < count; i++) {\n    const layerScale = randomScale({\n      scaleXMin,\n      scaleXMax,\n      scaleYMin,\n      scaleYMax,\n      scaleZMin,\n      scaleZMax,\n      source,\n    });\n\n    scale.copy(layerScale);\n    const layerHeight = BOOK_UNIT_DEPTH * layerScale.z;\n\n    spinQuat.setFromAxisAngle(new Vector3(0, 1, 0), source.float(-yawMax, yawMax));\n    quaternion.copy(layFlatQuat).premultiply(spinQuat);\n\n    scaledCenter.copy(localCenter).multiply(scale);\n    centerOffset.copy(scaledCenter).applyQuaternion(quaternion);\n\n    position.set(\n      source.float(-offsetMax, offsetMax),\n      currentY + layerHeight * 0.5,\n      source.float(-offsetMax, offsetMax),\n    );\n    position.sub(centerOffset);\n\n    matrix.compose(position, quaternion, scale);\n    stack.setMatrixAt(i, matrix);\n\n    currentY += layerHeight;\n  }\n\n  stack.instanceMatrix.needsUpdate = true;\n  return stack;\n}\n","import { BufferGeometry, ExtrudeGeometry, Path, Shape } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface CelticCrossHeadstoneGeometryOptions {\n  /** Total height, base to the top of the upper arm. Defaults to `1.3`. */\n  height?: number;\n  /** Arm span — the full horizontal extent. Defaults to `0.62`. */\n  span?: number;\n  /** Arm thickness at the neck, where it leaves the crossing. Defaults to `0.12`. */\n  thickness?: number;\n  /**\n   * Where the arms cross, as a fraction of `height`. Defaults to `0.7`.\n   *\n   * A cross carries its arms high — the shaft below is the long part.\n   */\n  crossing?: number;\n  /**\n   * How far each arm splays at its tip, beyond the neck half-width. Defaults to `0.03`. `0` gives a\n   * plain straight cross. The base never flares — it sits flat on the ground.\n   */\n  flare?: number;\n  /**\n   * The nimbus — the ring at the crossing. `true` for a default radius, a number to set the outer radius,\n   * `false` for a plain flared cross with no ring. Defaults to `true`.\n   */\n  ring?: boolean | number;\n  /** Width of the ring band. Defaults to `0.085`. */\n  ringWidth?: number;\n  /** Slab depth. Defaults to `0.14`. */\n  depth?: number;\n  /** Curve resolution of the flares and the ring — the low-poly knob. Defaults to `20`. */\n  curveSegments?: number;\n}\n\n/**\n * Celtic cross headstone — a cross with gently flared arms, ringed by a nimbus at the crossing.\n *\n * **It is two outlines, extruded and merged** — the same method as everything else in this vocabulary. The\n * cross is one closed silhouette (the shaft rises unbroken through the crossing, the three free arms splay\n * at their tips, the base stays flat), and the ring is an annulus (a disc with a disc-shaped hole). No\n * boxes, no booleans — a drawing given depth.\n *\n * The ring stands a hair PROUD of the cross, rather than flush with it. Two coplanar faces at the same\n * depth z-fight; lifting the ring's faces just clear of the arms' avoids it and reads as a raised ring,\n * which is what a carved Celtic cross actually has.\n *\n * Set `ring: false` and it is a plain flared cross — the nimbus is the only thing the ring adds.\n *\n * Local frame: base on Y=0, centered on X/Z.\n *\n * @example\n * ```ts\n * const celtic = new CelticCrossHeadstoneGeometry();\n * const flared = new CelticCrossHeadstoneGeometry({ ring: false, flare: 0.06 });\n * ```\n */\nexport class CelticCrossHeadstoneGeometry extends BufferGeometry {\n  readonly height: number;\n  readonly span: number;\n\n  constructor({\n    height = 1.3,\n    span = 0.62,\n    thickness = 0.12,\n    crossing = 0.7,\n    flare = 0.03,\n    ring = true,\n    ringWidth = 0.085,\n    depth = 0.14,\n    curveSegments = 20,\n  }: CelticCrossHeadstoneGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n    this.span = span;\n\n    const n = thickness / 2; // neck half-width\n    const t = n + flare; // tip half-width\n    const cy = height * crossing; // crossing center, in base-at-0 coordinates\n    const R = span / 2; // arm reach\n\n    const cross = new Shape();\n\n    // Up the right side of the shaft from the flat base to the crossing.\n    cross.moveTo(n, 0);\n    cross.lineTo(n, cy - n);\n\n    // The right arm: splay out under it to the flared tip, up the tip, splay back over it. Each flare\n    // bows toward the outer corner, so the arm keeps its width then opens at the very end.\n    cross.quadraticCurveTo(R, cy - n, R, cy - t);\n    cross.lineTo(R, cy + t);\n    cross.quadraticCurveTo(R, cy + n, n, cy + n);\n\n    // Up the right side of the upper shaft, and out to the flared TOP tip.\n    cross.lineTo(n, height - flare * 1.5);\n    cross.quadraticCurveTo(n, height, t, height);\n    cross.lineTo(-t, height);\n    cross.quadraticCurveTo(-n, height, -n, height - flare * 1.5);\n\n    // Down the left side of the upper shaft, and the left arm, mirror of the right.\n    cross.lineTo(-n, cy + n);\n    cross.quadraticCurveTo(-R, cy + n, -R, cy + t);\n    cross.lineTo(-R, cy - t);\n    cross.quadraticCurveTo(-R, cy - n, -n, cy - n);\n\n    // Down the left side of the shaft, and closePath runs flat across the base.\n    cross.lineTo(-n, 0);\n    cross.closePath();\n\n    const extrude = { depth, bevelEnabled: false, curveSegments };\n    const parts: BufferGeometry[] = [new ExtrudeGeometry(cross, extrude)];\n\n    // The ring sits INSIDE the arm span, so the arm tips reach past it — a cross wearing a ring, not a\n    // wheel. It also stays clear of the base, floating at the crossing.\n    const ringOuter = typeof ring === \"number\" ? ring : Math.min(R, cy - n) * 0.82;\n    if (ring !== false && ringOuter - ringWidth > 1e-3) {\n      const annulus = new Shape();\n      annulus.absarc(0, cy, ringOuter, 0, Math.PI * 2, false);\n      const hole = new Path();\n      hole.absarc(0, cy, ringOuter - ringWidth, 0, Math.PI * 2, true);\n      annulus.holes.push(hole);\n\n      // Proud of the cross so its front/back faces never sit coplanar with the arms'.\n      const ringDepth = depth * 1.08;\n      const band = new ExtrudeGeometry(annulus, { ...extrude, depth: ringDepth });\n      band.translate(0, 0, -(ringDepth - depth) / 2);\n      parts.push(band);\n    }\n\n    const merged = mergeGeometries(parts);\n    if (!merged) throw new Error(\"CelticCrossHeadstoneGeometry: merge failed\");\n    // Extrude runs local z 0 → depth; center it so the stone sinks and leans about its own middle.\n    merged.translate(0, 0, -depth / 2);\n\n    this.copy(merged);\n    merged.dispose();\n    for (const part of parts) part.dispose();\n    this.computeVertexNormals();\n  }\n}\n","import { BoxGeometry, BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface CrossHeadstoneGeometryOptions {\n  /** Arm span — the full horizontal extent. Defaults to `0.55`. */\n  width?: number;\n  /** Total height, base to top of the shaft. Defaults to `1.15`. */\n  height?: number;\n  /** Slab depth. Defaults to `0.14`. */\n  depth?: number;\n  /**\n   * Where the crossbar crosses, as a fraction of `height`. Defaults to `0.68`.\n   *\n   * A Latin cross carries its bar high — a short arm above, a long shaft below. `0.5` centers it (a\n   * Greek cross); above `0.7` starts to look top-heavy.\n   */\n  crossbar?: number;\n}\n\n/**\n * Cross headstone — a vertical shaft crossed by a horizontal arm.\n *\n * `height` is the REAL height: the shaft rises from the base to exactly `height`, so a `1.15` cross is\n * `1.15` tall. (It used to secretly build to 60% of the number you gave it, with the bar riding too high\n * and the slab too thick.)\n *\n * Local frame: base on Y=0, centered on X/Z.\n */\nexport class CrossHeadstoneGeometry extends BufferGeometry {\n  readonly width: number;\n  readonly height: number;\n  readonly depth: number;\n\n  constructor({\n    width = 0.55,\n    height = 1.15,\n    depth = 0.14,\n    crossbar = 0.68,\n  }: CrossHeadstoneGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n\n    // The shaft is the full height, so `height` means what it says. Its width is a fraction of the arm\n    // span, and the crossbar is that same thickness — a cross reads balanced when bar and shaft match.\n    const shaftWidth = width * 0.32;\n    const shaft = new BoxGeometry(shaftWidth, height, depth);\n    shaft.translate(0, height / 2, 0);\n\n    const arm = new BoxGeometry(width, shaftWidth, depth);\n    arm.translate(0, height * crossbar, 0);\n\n    this.copy(mergeGeometries([shaft, arm], false) as BufferGeometry);\n    this.computeVertexNormals();\n  }\n}\n","import { BufferGeometry } from \"three\";\nimport { createGeometryBuffers, pushQuad, pushTriangle, toBufferGeometry, type Vec3 } from \"../../modeling/mesh/GeometryBuffers\";\n\nexport interface ObeliskGeometryOptions {\n  /** Width of the shaft at its foot. Defaults to `0.5`. */\n  baseWidth?: number;\n  /** Width of the shaft at the shoulder, where the cap begins — the taper. Defaults to `0.34`. */\n  topWidth?: number;\n  /** Height of the shaft, below the cap. Defaults to `2.2`. */\n  shaftHeight?: number;\n  /** Height of the pyramidion. Defaults to `0.45`. */\n  capHeight?: number;\n}\n\n/**\n * Obelisk — a tapered four-sided shaft rising to a pyramidion. The tall Victorian monument that stands\n * over a family plot.\n *\n * Three rings of vertices, and that is the whole model:\n *\n * ```text\n *                    apex            1 vertex\n *                   /    \\\n *                  /      \\          4 CAP TRIANGLES\n *                 /________\\\n *                |          |        shoulder — 4 vertices\n *                |          |\n *                |          |        4 SIDE QUADS (trapezoids: the taper)\n *                |          |\n *                |__________|        base — 4 vertices\n * ```\n *\n * Fourteen triangles, counting the closed foot. The cap faces are triangles because they close to a single point — a quad would\n * need the apex twice.\n *\n * The side UVs INSET at the top by exactly the ratio the geometry tapers. A triangle interpolates UVs\n * linearly, so the four (position → uv) pairs must lie on ONE affine map, or the quad's two triangles\n * solve for different maps and the texture creases visibly along the diagonal. Stretching a trapezoid\n * to fill a 0–1 square is a projective transform, which triangles cannot represent.\n *\n * Distinct from {@link ObeliskHeadstoneGeometry}, which is a *stepped* stack of boxes. This one is a\n * single tapered shaft.\n *\n * Local frame: base on Y=0, centered on X/Z.\n *\n * @example\n * ```ts\n * const geometry = new ObeliskGeometry({ shaftHeight: 2.6 });\n * ```\n */\nexport class ObeliskGeometry extends BufferGeometry {\n  readonly totalHeight: number;\n  readonly baseWidth: number;\n\n  constructor({\n    baseWidth = 0.5,\n    topWidth = 0.34,\n    shaftHeight = 2.2,\n    capHeight = 0.45,\n  }: ObeliskGeometryOptions = {}) {\n    super();\n\n    this.baseWidth = baseWidth;\n    this.totalHeight = shaftHeight + capHeight;\n\n    const buffers = createGeometryBuffers();\n\n    const b = baseWidth / 2;\n    const t = topWidth / 2;\n\n    // Walk the four corners in one consistent direction around the shaft. Every face is then built the\n    // same way — corner i, corner i+1 — and the winding comes out right without thinking about it.\n    const corners: [number, number][] = [\n      [+1, +1],\n      [+1, -1],\n      [-1, -1],\n      [-1, +1],\n    ];\n\n    const base: Vec3[] = corners.map(([sx, sz]) => [sx * b, 0, sz * b]);\n    const shoulder: Vec3[] = corners.map(([sx, sz]) => [sx * t, shaftHeight, sz * t]);\n    const apex: Vec3 = [0, shaftHeight + capHeight, 0];\n\n    // How far the top edge of a side pulls in from the bottom, as a fraction of the bottom's width.\n    const inset = (1 - topWidth / baseWidth) / 2;\n\n    for (let i = 0; i < 4; i++) {\n      const next = (i + 1) % 4;\n\n      // A trapezoid, wider at the foot than the shoulder. The normal is deliberately left undefined:\n      // a tapered face is slanted, so there is no axis-aligned normal to hand over, and `pushQuad`\n      // derives it from the winding — exactly the case `faceNormal` exists for.\n      pushQuad(\n        buffers,\n        [base[i]!, base[next]!, shoulder[next]!, shoulder[i]!],\n        undefined,\n        [\n          [0, 0],\n          [1, 0],\n          [1 - inset, 1],\n          [inset, 1],\n        ],\n      );\n\n      // The cap closes to the apex, so its UV sits at the top center — the texture converges to a\n      // point the way the geometry does.\n      pushTriangle(\n        buffers,\n        [shoulder[i]!, shoulder[next]!, apex],\n        undefined,\n        [\n          [0, 0],\n          [1, 0],\n          [0.5, 1],\n        ],\n      );\n    }\n\n    // The foot is closed. A monument standing on flat ground never shows its underside — but the\n    // headstone factory LEANS and sinks its stones, and a leaning obelisk with an open base is a\n    // hole you can see straight up. Two triangles is cheaper than a rule about how it may be used.\n    pushQuad(buffers, [base[0]!, base[3]!, base[2]!, base[1]!], undefined);\n\n    this.copy(toBufferGeometry(buffers));\n  }\n}\n","import { BoxGeometry, BufferGeometry, ConeGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface ObeliskHeadstoneGeometryOptions {\n  /** Total monument height. Defaults to `1.75`. */\n  totalHeight?: number;\n  /** Base platform width. Defaults to `0.75`. */\n  baseWidth?: number;\n}\n\n/**\n * Tiered obelisk headstone with pyramid cap.\n *\n * Local frame: base on Y=0, centered on X/Z.\n */\nexport class ObeliskHeadstoneGeometry extends BufferGeometry {\n  readonly totalHeight: number;\n  readonly baseWidth: number;\n\n  constructor({ totalHeight = 1.75, baseWidth = 0.75 }: ObeliskHeadstoneGeometryOptions = {}) {\n    super();\n\n    this.totalHeight = totalHeight;\n    this.baseWidth = baseWidth;\n\n    const baseHeight = totalHeight * 0.05;\n    const lowerSegmentHeight = totalHeight * 0.15;\n    const middleSegmentHeight = totalHeight * 0.15;\n    const topSegmentHeight = totalHeight * 0.75;\n\n    let currentHeight = 0;\n\n    const baseGeometry = new BoxGeometry(baseWidth, baseHeight, baseWidth);\n    baseGeometry.translate(0, currentHeight + baseHeight / 2, 0);\n    currentHeight += baseHeight;\n\n    const lowerSegmentGeometry = new BoxGeometry(baseWidth * 0.8, lowerSegmentHeight, baseWidth * 0.8);\n    lowerSegmentGeometry.translate(0, currentHeight + lowerSegmentHeight / 2, 0);\n    currentHeight += lowerSegmentHeight;\n\n    const middleSegmentGeometry = new BoxGeometry(baseWidth * 0.6, middleSegmentHeight, baseWidth * 0.6);\n    middleSegmentGeometry.translate(0, currentHeight + middleSegmentHeight / 2, 0);\n    currentHeight += middleSegmentHeight;\n\n    const topSegmentGeometry = new BoxGeometry(baseWidth * 0.4, topSegmentHeight, baseWidth * 0.4);\n    topSegmentGeometry.translate(0, currentHeight + topSegmentHeight / 2, 0);\n    currentHeight += topSegmentHeight;\n\n    const pyramidGeometry = new ConeGeometry((baseWidth * 0.4) / Math.sqrt(2), 0.1, 4, 1, false, Math.PI / 4);\n    pyramidGeometry.translate(0, currentHeight + 0.1 / 2, 0);\n\n    this.copy(\n      mergeGeometries(\n        [baseGeometry, lowerSegmentGeometry, middleSegmentGeometry, topSegmentGeometry, pyramidGeometry],\n        false,\n      ) as BufferGeometry,\n    );\n    this.computeVertexNormals();\n  }\n}","import { ExtrudeGeometry } from \"three\";\nimport { ArchedSlabShape, type ArchedSlabShapeOptions } from \"../../shapes/ArchedSlabShape\";\n\nexport interface ArchedSlabGeometryOptions extends ArchedSlabShapeOptions {\n  /** Extrusion depth. Defaults to `0.18`. */\n  depth?: number;\n  /**\n   * Segments in the arc — the low-poly knob. Defaults to `16`.\n   *\n   * `3` gives a chiseled, faceted arch; `24` a smooth cast one. Same outline, chosen resolution.\n   */\n  curveSegments?: number;\n}\n\n/**\n * Extruded arched slab — a door, an arched window, or a shouldered headstone, depending on the arch's\n * span. See {@link ArchedSlabShape}.\n *\n * @example\n * ```ts\n * const door      = new ArchedSlabGeometry({ width: 1.2, height: 1.4, archHeight: 0.6 });\n * const headstone = new ArchedSlabGeometry({ width: 1.2, height: 1.1, archWidth: 0.7 });\n * ```\n */\nexport class ArchedSlabGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.18, curveSegments = 16, ...shapeOptions }: ArchedSlabGeometryOptions = {}) {\n    super(new ArchedSlabShape(shapeOptions), { depth, bevelEnabled: false, curveSegments });\n  }\n}\n","import { ArchedSlabGeometry, type ArchedSlabGeometryOptions } from \"../shapes/ArchedSlabGeometry\";\n\nexport interface RoundedHeadstoneGeometryOptions extends ArchedSlabGeometryOptions {}\n\n/**\n * A round-topped headstone — the classic one.\n *\n * **It is an {@link ArchedSlabGeometry} with a headstone's defaults, and nothing else.** It used to be a\n * box welded to half a cylinder: the same silhouette arrived at the hard way, with no `curveSegments`\n * knob, no arch styles, and a smooth-shaded cap sitting on a faceted body.\n *\n * Being the slab means it inherits the whole arch vocabulary for free — including the SHOULDERS the slab\n * was designed for in the first place (`archWidth` narrower than `width`), which is the shape you see in\n * every real cemetery and which this, of all things, could not previously make.\n *\n * Defaults reproduce the original silhouette: `0.6` wide, `0.2` deep, `1.0` tall overall — a `0.7` body\n * under a `0.3` cap, which is exactly `width / 2` and therefore a true semicircle.\n *\n * Base at `y = 0`, centered on X and Z.\n *\n * @example\n * ```ts\n * const classic    = new RoundedHeadstoneGeometry();\n * const shouldered = new RoundedHeadstoneGeometry({ width: 0.7, archWidth: 0.45 });\n * const ogee       = new RoundedHeadstoneGeometry({ arch: \"ogee\", archHeight: 0.45 });\n * ```\n */\nexport class RoundedHeadstoneGeometry extends ArchedSlabGeometry {\n  constructor({\n    width = 0.6,\n    height = 0.7,\n    archHeight = width / 2,\n    depth = 0.2,\n    arch = \"semicircle\",\n    curveSegments = 16,\n    ...rest\n  }: RoundedHeadstoneGeometryOptions = {}) {\n    super({ width, height, archHeight, depth, arch, curveSegments, ...rest });\n\n    // The slab extrudes 0 → depth. A headstone leans and sinks about its own middle, so center it on Z\n    // like every other stone in the row — the row factory measures half-depth off this.\n    this.translate(0, 0, -depth / 2);\n  }\n}\n","import { BoxGeometry, BufferGeometry } from \"three\";\n\nexport class SquareHeadstoneGeometry extends BufferGeometry {\n  constructor(width = 0.5, height = 0.8, depth = 0.15) {\n    super();\n\n    // Create a rectangular slab\n    const slabGeometry = new BoxGeometry(width, height, depth);\n    slabGeometry.translate(0, height / 2, 0);\n\n    this.copy(slabGeometry);\n  }\n}\n","import {\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  Euler,\n  Group,\n  InstancedMesh,\n  Material,\n  Matrix4,\n  MeshStandardMaterial,\n  Quaternion,\n  Vector3,\n} from \"three\";\nimport { CelticCrossHeadstoneGeometry } from \"../../geometry/cemetery/CelticCrossHeadstoneGeometry\";\nimport { CrossHeadstoneGeometry } from \"../../geometry/cemetery/CrossHeadstoneGeometry\";\nimport { ObeliskGeometry } from \"../../geometry/cemetery/ObeliskGeometry\";\nimport { ObeliskHeadstoneGeometry } from \"../../geometry/cemetery/ObeliskHeadstoneGeometry\";\nimport { RoundedHeadstoneGeometry } from \"../../geometry/cemetery/RoundedHeadstoneGeometry\";\nimport { SquareHeadstoneGeometry } from \"../../geometry/cemetery/SquareHeadstoneGeometry\";\nimport { ArchStyle } from \"../../modeling/profiles/ArchProfile\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\n\n/**\n * One kind of stone in the row's palette.\n *\n * The `rounded` family is where the variety lives — it is an arched slab, so it takes the whole\n * {@link ArchStyle} vocabulary plus a narrower `archWidth` for the shouldered look (an arch sitting *on*\n * the slab). `cross`, `obelisk` (a tapered monument) and `obeliskHeadstone` (a stepped one) are single\n * silhouettes. Every style carries a `weight` — its relative frequency in the row.\n */\nexport type HeadstoneStyle =\n  | { kind: \"rounded\"; arch?: ArchStyle; archWidth?: number; archHeight?: number; weight?: number }\n  | { kind: \"square\"; weight?: number }\n  | { kind: \"cross\"; weight?: number }\n  | { kind: \"celticCross\"; weight?: number }\n  | { kind: \"obelisk\"; weight?: number }\n  | { kind: \"obeliskHeadstone\"; weight?: number };\n\n/**\n * The stock cemetery: mostly plain rounded and square stones, the occasional cross, and obelisks that\n * stand out because they are fewer. The rounded family dominates and fans into four tops — a full-width\n * semicircle, a shouldered one, a gently curved segmental, and a gothic point.\n *\n * These are relative weights, not counts: a row draws from them, so the mix holds at any `count`.\n */\nexport const DEFAULT_HEADSTONE_STYLES: readonly HeadstoneStyle[] = [\n  { kind: \"rounded\", arch: \"semicircle\", weight: 6 },\n  { kind: \"rounded\", arch: \"semicircle\", archWidth: 0.42, weight: 4 }, // shouldered — arch sits ON the slab\n  { kind: \"rounded\", arch: \"segmental\", archHeight: 0.16, weight: 4 }, // a gentle curve, not a full round\n  { kind: \"rounded\", arch: \"pointed\", archHeight: 0.5, weight: 2 }, // gothic\n  { kind: \"rounded\", arch: \"ogee\", archHeight: 0.26, weight: 1 }, // a low ogee flourish — rare\n  { kind: \"square\", weight: 4 },\n  { kind: \"cross\", weight: 2 },\n  { kind: \"celticCross\", weight: 2 }, // the gothic flourish — flared arms and a nimbus\n  { kind: \"obeliskHeadstone\", weight: 2 }, // the stepped one — the everyday obelisk marker\n  { kind: \"obelisk\", weight: 1 }, // the tall tapered monument — rarer, so it stands out\n];\n\n/** Everything that ages a stone — shared by a single {@link rowOfHeadstones} and a whole {@link fieldOfHeadstones}. */\nexport interface HeadstoneSettleOptions {\n  /** Optional seed for a reproducible layout. Omit for unique per runtime. */\n  seed?: number;\n  /** Max lean off vertical, in radians, on both X and Z. Defaults to `0.12` (~7°). */\n  leanMax?: number;\n  /** Max twist about Y, in radians. Keep it small — a turned stone reads as settled, not knocked over. Defaults to `0.4`. */\n  twistMax?: number;\n  /**\n   * How the twist is distributed within `±twistMax`, via {@link RandomSource.skewCenter}. Defaults to\n   * `1.6`.\n   *\n   * `1` is uniform — every angle equally likely, so half the stones are dramatically turned. Higher pulls\n   * most stones toward straight while still letting the occasional one reach the full `twistMax`, so a\n   * hard-turned stone reads as the exception it should be, not the rule.\n   */\n  twistBias?: number;\n  /**\n   * Max *additional* depth a stone settles into the ground, beyond whatever its lean already\n   * demands. Stones only ever sink, never rise. Defaults to `0.08`.\n   *\n   * Leaning is not free: a stone pivots about its base, so tilting lifts one edge of its footing out\n   * of the earth. That much burial is compulsory — it is what the geometry costs. This is the depth\n   * the stone has settled *on top of* it, so the two stay independent and a hard-leaning stone still\n   * sinks as deep as an upright one.\n   */\n  sinkMax?: number;\n  /** Max lateral drift off the plot center, on X and Z. Defaults to `0.05`. */\n  driftMax?: number;\n  /** Min uniform scale. Defaults to `0.85`. */\n  scaleMin?: number;\n  /** Max uniform scale. Defaults to `1.2`. */\n  scaleMax?: number;\n  /** Base stone tint. Defaults to `#777777`. */\n  color?: ColorRepresentation;\n  /** Legacy HSL tint spread around the base color. `0` makes them identical. Defaults to `0.09`. */\n  weathering?: number;\n  /** Overrides color/weathering. Index counts retained stones before grouping by silhouette.\n   * Uses an independent seeded stream. The generated material is white; supplied materials still multiply the tint. */\n  colors?: ColorSampler;\n  /** Stone material. Omit for a flat-shaded standard material: white with `colors`, otherwise tinted by `color`. */\n  material?: Material;\n  /**\n   * The palette the row draws from. Defaults to {@link DEFAULT_HEADSTONE_STYLES}.\n   *\n   * Pass your own to reshape the graveyard — `[{ kind: \"cross\" }]` for a war plot, all-`rounded` with\n   * one `arch` for a uniform churchyard. Weights are relative; omit `weight` for `1`.\n   */\n  styles?: readonly HeadstoneStyle[];\n}\n\nexport interface HeadstoneRowOptions extends HeadstoneSettleOptions {\n  /** Number of plots. Defaults to `8`. */\n  count?: number;\n  /**\n   * Plot pitch — center to center along the row. Defaults to `1`.\n   *\n   * A cemetery is surveyed on a uniform grid, so the *plot* is what repeats, not the gap. Headstones\n   * vary wildly in width (a cross is 0.4, an obelisk 0.75), so spacing them by a fixed gap would put\n   * their centers at irregular intervals — and a row of graves reads by its plot rhythm. Irregular\n   * centers do not look aged; they look wrong.\n   */\n  spacing?: number;\n}\n\nexport interface HeadstoneFieldOptions extends HeadstoneSettleOptions {\n  /** Plots across each row — the X axis. Defaults to `10`. */\n  columns?: number;\n  /** Number of rows, front to back — the Z axis. Defaults to `10`. */\n  rows?: number;\n  /** Plot pitch ACROSS a row — the plot's width. Defaults to `1`. */\n  spacing?: number;\n  /**\n   * Plot pitch BETWEEN rows — the plot's length. Defaults to `2.2`.\n   *\n   * A grave is longer than it is wide, so a cemetery's rows sit further apart than the stones within a\n   * row. Leave this at the default and the field reads as real surveyed plots rather than a square grid.\n   */\n  rowSpacing?: number;\n  /**\n   * Fraction of plots that actually hold a stone, `0`–`1`. Defaults to `1` (every plot filled).\n   *\n   * Below `1`, plots are left empty at random — the gaps of an old churchyard where stones were never\n   * cut or have since been lost. It is also what a sparse, distant fill wants: a thin scatter of stones\n   * rather than a solid block.\n   */\n  density?: number;\n}\n\n/** The geometry a style builds, and the footprint a lean has to lift out of the ground. */\ninterface Variant {\n  geometry: BufferGeometry;\n  weight: number;\n  halfWidth: number;\n  halfDepth: number;\n}\n\nfunction styleGeometry(style: HeadstoneStyle): BufferGeometry {\n  switch (style.kind) {\n    case \"rounded\":\n      return new RoundedHeadstoneGeometry({\n        arch: style.arch,\n        archWidth: style.archWidth,\n        archHeight: style.archHeight,\n      });\n    case \"square\":\n      return new SquareHeadstoneGeometry();\n    case \"cross\":\n      return new CrossHeadstoneGeometry();\n    case \"celticCross\":\n      return new CelticCrossHeadstoneGeometry();\n    case \"obelisk\":\n      return new ObeliskGeometry();\n    case \"obeliskHeadstone\":\n      return new ObeliskHeadstoneGeometry();\n  }\n}\n\nfunction buildPalette(styles: readonly HeadstoneStyle[]): Variant[] {\n  return styles.map((style) => {\n    const geometry = styleGeometry(style);\n    geometry.computeBoundingBox();\n    const box = geometry.boundingBox!;\n\n    return {\n      geometry,\n      weight: Math.max(0, style.weight ?? 1),\n      halfWidth: Math.max(Math.abs(box.min.x), Math.abs(box.max.x)),\n      halfDepth: Math.max(Math.abs(box.min.z), Math.abs(box.max.z)),\n    };\n  });\n}\n\n/** One settled stone — which silhouette it is, where it ended up, and the shade it weathered to. */\ninterface Plot {\n  variant: number;\n  matrix: Matrix4;\n  tint: Color;\n}\n\n/** Resolved aging knobs — defaults applied once, then handed to every stone. */\ninterface Settle {\n  leanMax: number;\n  twistMax: number;\n  twistBias: number;\n  sinkMax: number;\n  driftMax: number;\n  scaleMin: number;\n  scaleMax: number;\n  weathering: number;\n  base: Color;\n}\n\n// Reused across every stone in a layout — synchronous, so scratch is safe.\nconst _position = new Vector3();\nconst _quaternion = new Quaternion();\nconst _scale = new Vector3();\nconst _rotation = new Euler();\n\n/**\n * Draw and age ONE stone at a plot center — the atomic unit a row and a field both repeat.\n *\n * This is the shared base. A row and a field differ only in where they put the plot centers; each\n * center still gets a stone the same way. It advances `source` in a fixed order, so a given seed lays\n * out the same graveyard every time, and the same first stones whether the layout is a row or a field.\n *\n * A stone **sinks but never rises**. Leaning is not free: a stone pivots about its base, so tilting\n * lifts one edge of its footing clear of the ground, and burying it that far is compulsory. `sinkMax`\n * is the settling *on top of* that, which keeps the two independent — a hard-leaning stone still sinks\n * as deep as an upright one, rather than being pinned at whatever depth its lean forced.\n */\nfunction settleStone(\n  source: ReturnType<typeof createRandom>,\n  variants: Variant[],\n  indices: number[],\n  weights: number[],\n  x: number,\n  z: number,\n  s: Settle,\n): Plot {\n  const variant = source.weighted(indices, weights);\n  const { halfWidth, halfDepth } = variants[variant]!;\n\n  const uniform = source.float(s.scaleMin, s.scaleMax);\n  const leanX = source.float(-s.leanMax, s.leanMax);\n  const leanZ = source.float(-s.leanMax, s.leanMax);\n\n  const lifted = halfWidth * uniform * Math.abs(Math.sin(leanZ)) + halfDepth * uniform * Math.abs(Math.sin(leanX));\n  const sink = lifted + source.float(0, s.sinkMax);\n\n  _rotation.set(leanX, source.skewCenter(s.twistBias, -s.twistMax, s.twistMax), leanZ, \"YXZ\");\n  _quaternion.setFromEuler(_rotation);\n  _position.set(x + source.float(-s.driftMax, s.driftMax), -sink, z + source.float(-s.driftMax, s.driftMax));\n  _scale.setScalar(uniform);\n\n  const tint = s.base\n    .clone()\n    .offsetHSL(\n      source.float(-s.weathering * 0.4, s.weathering * 0.4),\n      source.float(-s.weathering * 0.2, s.weathering * 0.3),\n      source.float(-s.weathering, s.weathering * 0.6),\n    );\n\n  return { variant, matrix: new Matrix4().compose(_position, _quaternion, _scale), tint };\n}\n\n/**\n * Group the settled plots into one {@link InstancedMesh} PER SILHOUETTE — the whole reason a field is\n * cheap. Ten thousand stones drawn from an eight-style palette are eight draw calls, not ten thousand,\n * because every plot of a given style shares one instanced mesh regardless of which row it sits in.\n */\nfunction instancePlots(variants: Variant[], plots: Plot[], material: Material): Group {\n  const group = new Group();\n\n  variants.forEach((variant, index) => {\n    const mine = plots.filter((plot) => plot.variant === index);\n    if (mine.length === 0) {\n      variant.geometry.dispose();\n      return;\n    }\n\n    const mesh = new InstancedMesh(variant.geometry, material, mine.length);\n    mine.forEach((plot, i) => {\n      mesh.setMatrixAt(i, plot.matrix);\n      mesh.setColorAt(i, plot.tint);\n    });\n    mesh.instanceMatrix.needsUpdate = true;\n    if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;\n\n    group.add(mesh);\n  });\n\n  return group;\n}\n\n/** Lay stones on a set of plot centers and instance them — the common tail of a row and a field. */\nfunction layStones(\n  centers: { x: number; z: number }[],\n  {\n    seed,\n    leanMax = 0.12,\n    twistMax = 0.4,\n    twistBias = 1.6,\n    sinkMax = 0.08,\n    driftMax = 0.05,\n    scaleMin = 0.85,\n    scaleMax = 1.2,\n    color = \"#777777\",\n    weathering = 0.09,\n    colors,\n    material,\n    styles = DEFAULT_HEADSTONE_STYLES,\n    density = 1,\n  }: HeadstoneSettleOptions & { density?: number },\n): Group {\n  const source = createRandom(seed);\n  const colorContext = { index: 0, random: createRandom(seed === undefined ? undefined : deriveSubSeed(seed, 0x73746f6e)) };\n  const variants = buildPalette(styles);\n  const indices = variants.map((_, i) => i);\n  const weights = variants.map((variant) => variant.weight);\n  const settle: Settle = {\n    leanMax,\n    twistMax,\n    twistBias,\n    sinkMax,\n    driftMax,\n    scaleMin,\n    scaleMax,\n    weathering,\n    base: new Color(color),\n  };\n\n  const stone =\n    material ?? new MeshStandardMaterial({ color: new Color(colors ? 0xffffff : color), roughness: 0.9, flatShading: true });\n\n  // Draw every stone up front so each silhouette's instance count is known before its mesh is built.\n  const plots: Plot[] = [];\n  for (const center of centers) {\n    // An empty plot: rolled only when density < 1, so a full layout draws the exact same sequence a\n    // seed always did.\n    if (density < 1 && source.next() >= density) continue;\n    // settleStone retains legacy tint draws, keeping density, silhouettes and placement stable.\n    const plot = settleStone(source, variants, indices, weights, center.x, center.z, settle);\n    if (colors) {\n      colorContext.index = plots.length;\n      colors(plot.tint, colorContext);\n    }\n    plots.push(plot);\n  }\n\n  return instancePlots(variants, plots, stone);\n}\n\n/**\n * A row of headstones that has been standing for a hundred years.\n *\n * Perfectly upright, perfectly aligned stones read as *brand new* — which is exactly wrong for a\n * graveyard. Age is the point here, so each stone is drawn from a random silhouette, then settled by\n * {@link settleStone}: it leans, twists a little, sinks, drifts off its plot, and weathers to its own\n * shade of gray.\n *\n * Returns a {@link Group} of {@link InstancedMesh}es — one per silhouette used. The first plot sits at\n * the origin and the row runs out along `+x`; position the group to place it. For many rows at once, see\n * {@link fieldOfHeadstones}, which shares its instancing so the whole field stays a handful of draw\n * calls. Dispose each child's geometry and the shared material when removing it.\n *\n * @example\n * ```ts\n * const row = rowOfHeadstones({ count: 8, spacing: 1, seed: 1337 });\n * scene.add(row);\n *\n * // A newer plot: upright, evenly set, barely weathered.\n * const fresh = rowOfHeadstones({ count: 8, leanMax: 0.01, sinkMax: 0, weathering: 0.02 });\n * ```\n */\nexport function rowOfHeadstones({ count = 8, spacing = 1, ...settle }: HeadstoneRowOptions = {}): Group {\n  const centers = Array.from({ length: count }, (_, i) => ({ x: i * spacing, z: 0 }));\n  return layStones(centers, settle);\n}\n\n/**\n * A whole graveyard — a grid of rows, aged the same way a single {@link rowOfHeadstones} is.\n *\n * **It is the row's instancing, shared across every row.** Call `rowOfHeadstones` once per row and each\n * call builds its own instanced meshes, so a 10×10 field costs ten rows × a mesh-per-style ≈ eighty draw\n * calls. This lays every plot up front and instances them together: one mesh per silhouette for the\n * *entire field*, so a hundred stones — or ten thousand — stay the same handful of draw calls. That is\n * the whole reason to reach for it over a loop.\n *\n * The grid gives the structure a surveyed cemetery has; the per-stone settling ({@link settleStone})\n * gives the age that keeps it from reading as a spreadsheet. `density` below `1` thins it to the gappy\n * scatter of an old churchyard — or of a sparse fill trailing off into the distance.\n *\n * The first plot sits at the origin; the field runs out along `+x` (`columns`) and `+z` (`rows`). Its\n * extent is `(columns − 1) · spacing` by `(rows − 1) · rowSpacing`, so center it with\n * `field.position.set(-width / 2, 0, -depth / 2)`.\n *\n * @example\n * ```ts\n * const graveyard = fieldOfHeadstones({ columns: 10, rows: 10, seed: 1337 });\n * scene.add(graveyard);\n *\n * // A thin, weathered scatter for the distance — still one handful of draw calls at any size.\n * const distant = fieldOfHeadstones({ columns: 40, rows: 40, density: 0.35, weathering: 0.14 });\n * ```\n */\nexport function fieldOfHeadstones({\n  columns = 10,\n  rows = 10,\n  spacing = 1,\n  rowSpacing = 2.2,\n  density = 1,\n  ...settle\n}: HeadstoneFieldOptions = {}): Group {\n  const centers: { x: number; z: number }[] = [];\n  for (let row = 0; row < rows; row++) {\n    for (let column = 0; column < columns; column++) {\n      centers.push({ x: column * spacing, z: row * rowSpacing });\n    }\n  }\n  return layStones(centers, { ...settle, density });\n}\n","import { ExtrudeGeometry, Shape } from \"three\";\n\nexport interface WoodPicketGeometryOptions {\n  /** Plank width — the board's face. A 1×4 is `3.5`, a 1×6 is `5.5`, in inches. Defaults to `0.35`. */\n  width?: number;\n  /**\n   * Overall height of the plank — the board, tip included. Defaults to `1.38`.\n   *\n   * The board you would buy, and the height a fence is quoted at: *\"a four-foot fence\"* means the highest point\n   * sits at 48in. The top is cut **out of** this, so it is knowable before any cutting happens. See\n   * `docs/option-parameter-conventions.md`.\n   */\n  height?: number;\n  /**\n   * Depth of the top cut, measured **down from the tip**. Defaults to `0.175`. `0` gives a flat-topped plank.\n   *\n   * Subtractive: taken out of {@link WoodPicketGeometryOptions.height}, never added to it, so the plank measures\n   * `height` whatever this is set to and {@link WoodPicketGeometry.shoulderHeight} falls out as the difference.\n   *\n   * Sized by **itself** rather than by where the shoulder lands, because the cut is what should survive a change\n   * of board length — a six-foot picket and a four-foot picket carry the same two-inch ear.\n   *\n   * Negative values invert the cut into a chevron notched out of the top. A legitimate shape, deliberately\n   * unguarded, and one you ask for by sign rather than reach by accident.\n   */\n  tipDrop?: number;\n  /**\n   * Depth of the top cut, measured **in from each side**. Defaults to `0.175` — half the default width, so the\n   * stock picket comes to a point. `0` gives a flat-topped plank.\n   *\n   * The other half of the same cut, and subtractive in the same way — taken out of\n   * {@link WoodPicketGeometryOptions.width}, per side, so the flat left between the two is\n   * `width − tipInset × 2` and is published as {@link WoodPicketGeometry.tipFlat}.\n   *\n   * **Equal to {@link WoodPicketGeometryOptions.tipDrop} is a 45° cut — the trade's dog-ear.** Equal cuts on\n   * perpendicular axes, so the angle needs no solving: *\"1-inch dog ears\"* is `tipInset: 1, tipDrop: 1` and\n   * stays that on any board width. Reaching `width / 2` brings the flanks together and the top to a point,\n   * after which `tipDrop` alone decides blunt versus steep.\n   *\n   * Clamped to `width / 2`: beyond it the two chamfers cross and the outline folds through itself.\n   */\n  tipInset?: number;\n  /** Plank thickness — the board's Z depth. Untouched by the top cut. Defaults to `0.04`. */\n  thickness?: number;\n}\n\n/**\n * Wooden fence picket — a plank with a cut top, the white-picket-fence silhouette.\n *\n * Built as an extruded profile, so the top style lives in the outline rather than in the mesh.\n *\n * **The board is the input; the top is cut out of it.** `height` is the whole plank and `width` the whole face;\n * {@link WoodPicketGeometryOptions.tipDrop} and {@link WoodPicketGeometryOptions.tipInset} are the two halves of\n * one corner cut, taken *out of* those bounds. Neither can move the silhouette, and \"how tall is this picket\"\n * never means adding two numbers.\n *\n * **Flat, dog-ear and pointed are one continuum, not three styles** — two numbers slide between them:\n *\n * | style | condition |\n * |---|---|\n * | flat top | `tipDrop: 0` |\n * | dog ear | `tipInset === tipDrop` — a 45° cut, whatever the board |\n * | pointed | `tipInset: width / 2` — the flanks meet, no flat left |\n * | blunt / steep point | vary `tipDrop` at that inset |\n * | chevron | a negative `tipDrop` |\n *\n * A *gothic* top is **not** on this dial and never can be: its ornamental neck is a curve, and these two\n * parameters only ever generate straight chamfers. That would be a different profile, the way\n * {@link ArchProfile} keeps a style union over genuinely different curve families.\n *\n * Unlike a fence post, a picket publishes no width profile — it is infill, not structure. Nothing\n * attaches to it, so nothing needs to ask how wide it is at a given height.\n *\n * Local frame: base at Y=0, centered on X and Z.\n *\n * @example\n * ```ts\n * // A four-foot fence of 1x4 stock with standard dog ears — equal cuts, so 45 degrees.\n * const geometry = new WoodPicketGeometry({ width: 3.5, height: 48, tipInset: 0.5, tipDrop: 0.5 });\n * geometry.tipFlat; // 2.5 — the flat left across the top\n * ```\n */\nexport class WoodPicketGeometry extends ExtrudeGeometry {\n  readonly width: number;\n  /** Overall height of the plank, tip included. */\n  readonly height: number;\n  /** Depth of the cut from the tip down. Negative when inverted into a chevron. */\n  readonly tipDrop: number;\n  /** Depth of the cut in from each side, after clamping to `width / 2`. */\n  readonly tipInset: number;\n  readonly thickness: number;\n  /** Height of the shoulder, where the cut begins — `height − tipDrop`. */\n  readonly shoulderHeight: number;\n  /** Flat left across the top — `width − tipInset × 2`. Zero once the flanks meet at a point. */\n  readonly tipFlat: number;\n\n  constructor({\n    width = 0.35,\n    height = 1.38,\n    // The defaults are a 45° point: both cuts equal (so 45°) and at half the width (so the flanks meet).\n    // Change one and you are somewhere nameable on the continuum rather than off in a decimal.\n    tipDrop = 0.175,\n    tipInset = 0.175,\n    thickness = 0.04,\n  }: WoodPicketGeometryOptions = {}) {\n    const half = width / 2;\n    // The only clamp here. Past half the width the two chamfers cross and the outline folds through itself,\n    // leaving no polygon to triangulate — it buys a constructible shape, not a tasteful one.\n    const inset = Math.max(0, Math.min(tipInset, half));\n    const flatHalf = half - inset;\n    const shoulderHeight = height - tipDrop;\n\n    const profile = new Shape();\n    profile.moveTo(-half, 0);\n    profile.lineTo(half, 0);\n    profile.lineTo(half, shoulderHeight);\n    if (tipDrop !== 0) {\n      // Flanks that meet collapse to a single apex rather than a doubled vertex, the way a gear's tooth does.\n      if (flatHalf > 0) {\n        profile.lineTo(flatHalf, height);\n        profile.lineTo(-flatHalf, height);\n      } else {\n        profile.lineTo(0, height);\n      }\n    }\n    profile.lineTo(-half, shoulderHeight);\n    profile.closePath();\n\n    super(profile, { depth: thickness, bevelEnabled: false });\n\n    this.width = width;\n    this.height = height;\n    this.tipDrop = tipDrop;\n    this.tipInset = inset;\n    this.thickness = thickness;\n    this.shoulderHeight = shoulderHeight;\n    this.tipFlat = flatHalf * 2;\n\n    // Extrude runs +Z from the profile plane; center it on Z so the plank straddles the run.\n    this.translate(0, 0, -thickness / 2);\n  }\n\n  /**\n   * Height of the plank's highest point.\n   *\n   * The same as {@link height} for any upright picket. They differ only when the cut is inverted (a negative\n   * {@link tipDrop}), where the shoulder is the top and the chevron is notched below it.\n   */\n  get totalHeight(): number {\n    return Math.max(this.height, this.shoulderHeight);\n  }\n\n  /**\n   * Angle of the cut flank from horizontal, in radians — `atan2(tipDrop, tipInset)`.\n   *\n   * An output of the two cuts, never a third dial. `Math.PI / 4` exactly when they are equal, which is the\n   * dog-ear the trade assumes by default.\n   */\n  get cutAngle(): number {\n    return Math.atan2(this.tipDrop, this.tipInset);\n  }\n}\n","import { BufferGeometry, ConeGeometry, CylinderGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface WroughtIronPicketGeometryOptions {\n  /** Picket height, excluding the finial. Defaults to `2`. */\n  height?: number;\n  /** Picket radius. Defaults to `0.05`. */\n  radius?: number;\n  /** Finial height. Defaults to `0.3`. */\n  finialHeight?: number;\n  /** Finial base radius. Defaults to `0.075`. */\n  finialRadius?: number;\n  /** Finial depth scale on Z — flattens the spear point. Defaults to `1`. */\n  finialScaleZ?: number;\n  /**\n   * Circumference segments. Defaults to `8` (round).\n   *\n   * Drop to `4` for square tubing, the way most wrought iron is actually made. The ring starts at a\n   * half-segment offset so the flats face the viewer — without it, four segments would present a\n   * corner and read as a diamond.\n   */\n  radialSegments?: number;\n}\n\n/**\n * Wrought-iron fence picket — a vertical bar topped with a spear-point finial.\n *\n * A picket is *infill*: many of them, evenly spaced between rails. It publishes no width profile,\n * because nothing attaches to it — that is a post's job.\n *\n * Local frame: base at Y=0.\n *\n * @example\n * ```ts\n * const geometry = new WroughtIronPicketGeometry({ height: 2, finialHeight: 0.3 });\n * const picket = new Mesh(geometry, ironMaterial);\n * scene.add(picket);\n * ```\n */\nexport class WroughtIronPicketGeometry extends BufferGeometry {\n  /** Picket height, excluding the finial. */\n  readonly height: number;\n  readonly radius: number;\n  readonly finialHeight: number;\n\n  constructor({\n    height = 2.0,\n    radius = 0.05,\n    finialHeight = 0.3,\n    finialRadius = 0.075,\n    finialScaleZ = 1.0,\n    radialSegments = 8,\n  }: WroughtIronPicketGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n    this.radius = radius;\n    this.finialHeight = finialHeight;\n\n    // Turn the cross-section a half-segment so its flats face the run rather than its corners.\n    // At `radialSegments: 4` this is the difference between square tubing and a diamond.\n    const thetaStart = Math.PI / radialSegments;\n\n    const shaft = new CylinderGeometry(radius, radius, height, radialSegments, 1, false, thetaStart);\n    shaft.translate(0, height / 2, 0);\n\n    const finial = new ConeGeometry(finialRadius, finialHeight, radialSegments, 1, false, thetaStart);\n    finial.translate(0, height + finialHeight / 2, 0);\n    finial.scale(1, 1, finialScaleZ);\n\n    this.copy(mergeGeometries([shaft, finial], false) as BufferGeometry);\n  }\n\n  /**\n   * Nominal picket width — the diameter across, matching how tubing is specced (a ½\" square tube is\n   * ½\" nominal). This is what a fence measures its gap against.\n   */\n  get width(): number {\n    return this.radius * 2;\n  }\n\n  /** Overall height, finial included. */\n  get totalHeight(): number {\n    return this.height + this.finialHeight;\n  }\n}\n","import { BoxGeometry, BufferGeometry, Color, ColorRepresentation, Material, Mesh, MeshStandardMaterial } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { WoodPicketGeometry, type WoodPicketGeometryOptions } from \"../../geometry/fence/WoodPicketGeometry\";\nimport {\n  WroughtIronPicketGeometry,\n  type WroughtIronPicketGeometryOptions,\n} from \"../../geometry/fence/WroughtIronPicketGeometry\";\n\nexport interface FenceSpanOptions {\n  /** Center-to-center picket spacing. Defaults to `0.4`. */\n  pitch?: number;\n  /** Number of pickets. Defaults to `10`. */\n  count?: number;\n  /** Target run length. Pickets divide it equally, overriding `pitch`. */\n  length?: number;\n  /**\n   * Width of a single picket. Optional, but pass it whenever `length` is pinned: it is what stops a\n   * request for more pickets than physically fit from producing an overlapping run. Defaults to `0`\n   * (no limit).\n   */\n  itemWidth?: number;\n}\n\n/** A resolved fence run: every value concrete, mutually consistent. */\nexport interface FenceSpan {\n  /** Center-to-center bar spacing. */\n  pitch: number;\n  /** Number of bars. */\n  count: number;\n  /** Total run length, always `count * pitch`. */\n  length: number;\n}\n\n/**\n * Resolve a fence run from any two of `pitch`, `count`, and `length`.\n *\n * The three are bound by `length = count * pitch`, so pinning two solves the third:\n *\n * - `count` alone — the run is as long as it needs to be.\n * - `length` alone — pickets divide it equally; `pitch` is recomputed to land them on the span.\n * - both — `pitch` is whatever divides `length` into `count` pickets.\n *\n * A run spans `[0, length]` with pickets inset a half-pitch from each end. That inset is what puts a\n * run's end pickets a half-gap clear of the posts it sits between.\n *\n * Pinning `count` *and* `length` leaves only the gap to absorb the difference, and the gap has a\n * floor — pickets may touch, but they cannot overlap. Pass `itemWidth` and an impossible request\n * (more pickets than physically fit) yields on the count rather than producing intersecting\n * geometry. Read the returned `count` rather than assuming you got the one you asked for.\n *\n * @example\n * ```ts\n * resolveFenceSpan({ length: 4.2, pitch: 0.4 });\n * // → { count: 11, pitch: 0.3818…, length: 4.2 }\n *\n * // 20 planks of 0.35 need 7.0 of plank alone — they cannot fit in 6.\n * resolveFenceSpan({ length: 6, count: 20, itemWidth: 0.35 });\n * // → { count: 17, … }  the count yielded; nothing overlaps\n * ```\n */\nexport function resolveFenceSpan({ pitch = 0.4, count, length, itemWidth = 0 }: FenceSpanOptions = {}): FenceSpan {\n  if (length !== undefined) {\n    // Pinning both `count` and `length` leaves only the gap to absorb the difference — and the gap\n    // has a floor, because pickets may touch but cannot overlap. When the request cannot be\n    // honored, the count yields: it is the only variable free to move.\n    const fits = itemWidth > 0 ? Math.max(1, Math.floor(length / itemWidth)) : Infinity;\n    const asked = count ?? Math.max(1, Math.round(length / pitch));\n    const bars = Math.min(asked, fits);\n\n    return { pitch: length / bars, count: bars, length };\n  }\n\n  const bars = count ?? 10;\n  return { pitch, count: bars, length: bars * pitch };\n}\n\nexport interface WroughtIronFenceOptions extends WroughtIronPicketGeometryOptions {\n  /**\n   * Clear air between adjacent pickets. Defaults to `0.3`.\n   *\n   * This is how fences are actually specced — and regulated: the gap, not the center-to-center\n   * pitch, is what a building code constrains (a child's head mustn't pass through). Picket width\n   * is a stock tubing size, so the gap is where the design lives. `pitch = width + gap`.\n   */\n  gap?: number;\n  /** Number of pickets. The run is as long as they turn out. */\n  count?: number;\n  /** Target run length. Pickets divide it equally, adjusting the gap to land them on the span. */\n  length?: number;\n  /** Rail height. Defaults to `0.1`. */\n  railHeight?: number;\n  /** Rail thickness. Defaults to `0.05`. */\n  railThickness?: number;\n  /** Height of the lower rail's center. Defaults to `railHeight / 2`, resting on the ground. */\n  lowerRailY?: number;\n  /** Height of the upper rail's center. Defaults to `height - railHeight / 2`, tucked under the finials. */\n  upperRailY?: number;\n  /**\n   * How far the rails run past each end of the picket span, to embed into a supporting post.\n   * Defaults to `0` (rails flush with the run).\n   *\n   * Pickets and rails want opposite things at a post: pickets must *clear* it or they end up buried\n   * in the stonework, while rails must *reach* it or they float unattached. A post whose profile\n   * changes with height — a wide base, a narrower column, a wide cap — cannot satisfy both with one\n   * extent. Size `length` to clear the post's widest part at picket height, then let `railOverhang`\n   * carry the rails back in to meet the column.\n   */\n  railOverhang?: number;\n  /** Iron material. Omit to build a flat-shaded standard material from `color`. */\n  material?: Material;\n  /** Iron tint when `material` is omitted. Defaults to `#2b2b2b`. */\n  color?: ColorRepresentation;\n}\n\n/**\n * Wrought-iron fence run — evenly pitched pickets held by an upper and lower rail.\n *\n * The rails pass *through* the pickets, the way a real punched-channel rail does. (Its wood\n * counterpart, {@link createWoodPicketFence}, nails its stringers to the back instead.)\n *\n * Local frame: the picket span is `[0, length]` along +X, with pickets inset a half-pitch from each\n * end. That inset is what lets runs tile: butt one against the next and the pitch carries across the\n * seam unbroken, so a fence assembled from several runs reads as one continuous fence. Rails extend\n * to `[-railOverhang, length + railOverhang]`, which is how they reach into a supporting post.\n *\n * A run is a single merged {@link Mesh} — one geometry, one material, one draw call — so\n * `castShadow` and `clone()` work directly on it.\n *\n * The resolved {@link FenceSpan} is on `mesh.userData.span` — read `length` from it to place the\n * next run, or call {@link resolveFenceSpan} up front with the same options.\n *\n * Dispose the geometry and material when finished.\n *\n * There are three ways to ask for a run, and they differ in *what you are holding fixed*:\n *\n * @example\n * ```ts\n * // 1. BY COUNT — the run grows. You control how the pickets and gaps look; the fence is as long\n * //    as it needs to be. Reach for this when nothing constrains the length.\n * const fence = createWroughtIronFence({ count: 10, gap: 0.3 });\n * fence.userData.span.length; // 4.0 — however long 10 pickets came out\n *\n * // 2. BY LENGTH — the span is fixed and the count falls out. The gap flexes a hair so the\n * //    pickets land exactly on the span. Reach for this when the fence must fill an opening.\n * const fence = createWroughtIronFence({ length: 4.8, gap: 0.3 });\n * fence.userData.span.count; // 12\n * fence.userData.gap;        // 0.30 — nudged, if it had to be, to fit\n *\n * // 3. BY BOTH — pack an exact number of pickets into an exact span. `gap` is then an OUTPUT,\n * //    not an input: it is solved for, and whatever you passed is ignored.\n * const fence = createWroughtIronFence({ length: 4.8, count: 20 });\n * fence.userData.gap; // 0.14 — tighter, because 20 pickets must fit in 4.8\n * ```\n *\n * In every case the resolved truth is on `userData.span` and `userData.gap` — read them rather than\n * assuming you got what you asked for.\n */\nexport function createWroughtIronFence({\n  gap = 0.3,\n  count,\n  length,\n  height = 2.0,\n  radius = 0.05,\n  finialHeight = 0.3,\n  finialRadius = 0.075,\n  finialScaleZ = 1.0,\n  radialSegments = 8,\n  railHeight = 0.1,\n  railThickness = 0.05,\n  lowerRailY = railHeight / 2,\n  upperRailY = height - railHeight / 2,\n  railOverhang = 0.0,\n  material,\n  color = \"#2b2b2b\",\n}: WroughtIronFenceOptions = {}): Mesh {\n  // A picket's width is a stock tubing size; the gap is the design. Convert to pitch and the run\n  // resolves exactly as the wood one does — same helper, same tiling, same by-count / by-length.\n  const span = resolveFenceSpan({ pitch: radius * 2 + gap, count, length, itemWidth: radius * 2 });\n\n  const picket = new WroughtIronPicketGeometry({\n    height,\n    radius,\n    finialHeight,\n    finialRadius,\n    finialScaleZ,\n    radialSegments,\n  });\n\n  const parts: BufferGeometry[] = [];\n\n  for (let i = 0; i < span.count; i++) {\n    const bar = picket.clone();\n    bar.translate((i + 0.5) * span.pitch, 0, 0);\n    parts.push(bar);\n  }\n  picket.dispose();\n\n  // Rails run past the picket span at both ends so they can embed into a supporting post.\n  const rail = new BoxGeometry(span.length + railOverhang * 2, railHeight, railThickness);\n\n  for (const y of [lowerRailY, upperRailY]) {\n    const bracing = rail.clone();\n    bracing.translate(span.length / 2, y, 0);\n    parts.push(bracing);\n  }\n\n  rail.dispose();\n\n  const geometry = mergeGeometries(parts, false) as BufferGeometry;\n  parts.forEach((part) => part.dispose());\n\n  const iron = material ?? new MeshStandardMaterial({ color: new Color(color), flatShading: true });\n\n  const fence = new Mesh(geometry, iron);\n  fence.userData.span = span;\n  fence.userData.gap = span.pitch - radius * 2;\n\n  return fence;\n}\n\nexport interface WoodPicketFenceOptions extends WoodPicketGeometryOptions {\n  /**\n   * Clear air between adjacent pickets. Defaults to `0.18`.\n   *\n   * A plank has real width, so the gap and the center-to-center pitch are different numbers:\n   * `pitch = width + gap`. The gap is the one worth exposing — plank width is a lumber constant\n   * (a 1×4 is 3.5\" whatever you do), so the gap is where the design actually lives. Zero gap would\n   * mean planks planed seamlessly together, which is a wall, not a fence.\n   */\n  gap?: number;\n  /** Number of pickets. The run is as long as they turn out. */\n  count?: number;\n  /** Target run length. Pickets divide it equally, adjusting the gap to land them on the span. */\n  length?: number;\n  /** Stringer (horizontal rail) height. Defaults to `0.12`. */\n  railHeight?: number;\n  /** Stringer thickness. Defaults to `0.04`. */\n  railThickness?: number;\n  /** Height of the lower stringer's center. Defaults to `0.25`. */\n  lowerRailY?: number;\n  /** Height of the upper stringer's center. Defaults to `height - 0.25`. */\n  upperRailY?: number;\n  /** How far the stringers run past each end of the picket span, to reach a post. Defaults to `0`. */\n  railOverhang?: number;\n  /** Wood material. Omit to build a flat-shaded standard material from `color`. */\n  material?: Material;\n  /** Wood tint when `material` is omitted. Defaults to `#e8e4da`. */\n  color?: ColorRepresentation;\n}\n\n/**\n * Wood picket fence run — pointed planks on two stringers.\n *\n * Every {@link WoodPicketGeometry} option passes through, so the whole run takes its top treatment together:\n * `tipInset` and `tipDrop` sweep the pickets from a flat top through a dog-ear to a point, and are equal for\n * the 45° dog-ear the trade assumes.\n *\n * The iron fence's counterpart, and it differs in two ways that matter. Its spacing is specified as\n * the **gap** between planks rather than a center-to-center pitch, because a plank's width is a\n * lumber constant while the gap is the design choice. And its stringers sit **behind** the pickets\n * rather than intersecting them — nailed to the back, the way a real picket fence is built.\n *\n * Underneath it is the same run: `pitch = width + gap` feeds the same {@link resolveFenceSpan}.\n *\n * Local frame: the picket span is `[0, length]` along +X, planks inset a half-pitch from each end,\n * so runs tile with the gap carrying across the seam. Stringers sit behind, at -Z.\n *\n * Takes the same three forms as {@link createWroughtIronFence} — by count, by length, or both:\n *\n * @example\n * ```ts\n * // 1. BY COUNT — the run grows to fit 10 pickets.\n * const fence = createWoodPicketFence({ count: 10, gap: 0.18 });\n *\n * // 2. BY LENGTH — fill a 6-unit span; the count falls out and the gap flexes to land the planks.\n * const fence = createWoodPicketFence({ length: 6, gap: 0.18 });\n * fence.userData.span.count; // 11\n *\n * // 3. BY BOTH — pack exactly 20 planks into 6 units; `gap` becomes an output.\n * const fence = createWoodPicketFence({ length: 6, count: 20 });\n * fence.userData.gap; // solved\n * ```\n */\nexport function createWoodPicketFence({\n  width = 0.35,\n  height = 1.38,\n  // A 45° point, matching WoodPicketGeometry's own defaults.\n  tipDrop = 0.175,\n  tipInset = 0.175,\n  thickness = 0.04,\n  gap = 0.18,\n  count,\n  length,\n  railHeight = 0.12,\n  railThickness = 0.04,\n  lowerRailY = 0.25,\n  // Below the SHOULDER, not below the tip: the upper stringer is nailed under where the planks start to taper,\n  // so it should not move when the top is reshaped.\n  upperRailY = height - tipDrop - 0.25,\n  railOverhang = 0.0,\n  material,\n  color = \"#e8e4da\",\n}: WoodPicketFenceOptions = {}): Mesh {\n  // A plank's width is fixed; the gap is what flexes. Convert to pitch and the run resolves exactly\n  // as the iron one does — same helper, same tiling, same by-count / by-length behavior.\n  const span = resolveFenceSpan({ pitch: width + gap, count, length, itemWidth: width });\n\n  // Every picket option must be forwarded by hand. `WoodPicketFenceOptions extends WoodPicketGeometryOptions`,\n  // so a new picket option is ACCEPTED here the moment it is declared there — and silently dropped unless it is\n  // also added to the destructure above and this call. Type-checking cannot catch the omission.\n  const picket = new WoodPicketGeometry({ width, height, tipDrop, tipInset, thickness });\n\n  const parts: BufferGeometry[] = [];\n\n  for (let i = 0; i < span.count; i++) {\n    const plank = picket.clone();\n    plank.translate((i + 0.5) * span.pitch, 0, 0);\n    parts.push(plank);\n  }\n  picket.dispose();\n\n  // Stringers sit behind the pickets, not through them.\n  const railZ = -(thickness / 2 + railThickness / 2);\n  // `ExtrudeGeometry` is non-indexed while `BoxGeometry` is indexed, and `mergeGeometries` refuses\n  // to mix the two — drop the rails' index so every part matches the pickets.\n  const rail = new BoxGeometry(span.length + railOverhang * 2, railHeight, railThickness).toNonIndexed();\n\n  for (const y of [lowerRailY, upperRailY]) {\n    const stringer = rail.clone();\n    stringer.translate(span.length / 2, y, railZ);\n    parts.push(stringer);\n  }\n  rail.dispose();\n\n  const geometry = mergeGeometries(parts, false) as BufferGeometry;\n  parts.forEach((part) => part.dispose());\n\n  const wood = material ?? new MeshStandardMaterial({ color: new Color(color), flatShading: true });\n\n  const fence = new Mesh(geometry, wood);\n  fence.userData.span = span;\n  fence.userData.gap = span.pitch - width;\n\n  return fence;\n}\n","import { CylinderGeometry } from \"three\";\n\nexport interface HewnTimberGeometryOptions {\n  /** Radius at the top. Defaults to `0.5`. */\n  topRadius?: number;\n  /**\n   * Radius at the bottom. Defaults to `0.55`.\n   *\n   * The taper is what stops a rank of timbers reading as extruded pipe — a real pole is thicker at the\n   * butt. Keep it small; past about `1.2 ×` the top it stops looking hewn and starts looking turned.\n   */\n  bottomRadius?: number;\n  /**\n   * Facets around the pole. Defaults to `6`.\n   *\n   * This is the low-poly knob. `6` is a coarse split log; `7`–`8` reads finer and more dressed. Even\n   * counts put a flat toward the viewer, odd counts put an edge — which is why `7` looks subtly less\n   * machined than `6` at the same radius.\n   */\n  radialSegments?: number;\n  /** Rings along its length. Defaults to `3`. More rings let the irregularity vary along the timber. */\n  heightSegments?: number;\n  /**\n   * Spatial frequency of the surface perturbation, per axis. Defaults to `[17.3, 11.7, 23.1]`.\n   *\n   * Deliberately mutually prime-ish and unequal: equal frequencies produce a visible helical banding\n   * because the three terms come back into phase along the axis.\n   */\n  frequency?: [number, number, number];\n  /** How far the perturbation pushes the surface, as a fraction of radius. Defaults to `0.075`. */\n  amplitude?: number;\n}\n\n/**\n * A split-timber log: a low-segment cylinder pushed off-round so the facets read as axe-hewn rather than\n * turned.\n *\n * Authored along local **Y at unit length** and roughly unit diameter, so callers scale it to the beam\n * they need — one geometry serves a whole rank of posts and rails at different sizes.\n *\n * **The perturbation is derived from the vertex's own position**, not from a random source. That keeps\n * the asset deterministic — the same timber every load, no seed to thread through — and keeps the end\n * caps watertight, because a cap vertex and the side vertex it coincides with compute the same offset.\n * Vertices on the axis are skipped: they have no radial direction to push along, and scaling them would\n * tear the cap open.\n *\n * Contrast {@link WeatheredPlankGeometry}, which is the *sawn* member — flat faces, square section, bow\n * and end-skew. This is the *round* one. A frame uses both: hewn posts carrying sawn boards.\n *\n * @example\n * ```typescript\n * // The default: a coarse 6-facet split log.\n * const post = new Mesh(createHewnTimberGeometry(), timber);\n * post.scale.set(0.14, 1.8, 0.14);\n *\n * // Finer and softer — the city-park footbridge tuning.\n * const rail = createHewnTimberGeometry({\n *   bottomRadius: 0.54,\n *   radialSegments: 7,\n *   frequency: [19.1, 13.7, 29.3],\n *   amplitude: 0.065,\n * });\n * ```\n */\nexport function createHewnTimberGeometry({\n  topRadius = 0.5,\n  bottomRadius = 0.55,\n  radialSegments = 6,\n  heightSegments = 3,\n  frequency = [17.3, 11.7, 23.1],\n  amplitude = 0.075,\n}: HewnTimberGeometryOptions = {}): CylinderGeometry {\n  const geometry = new CylinderGeometry(\n    topRadius,\n    bottomRadius,\n    1,\n    radialSegments,\n    heightSegments,\n    false,\n  );\n  const position = geometry.attributes.position;\n  const [fx, fy, fz] = frequency;\n\n  for (let i = 0; i < position.count; i++) {\n    const x = position.getX(i);\n    const y = position.getY(i);\n    const z = position.getZ(i);\n\n    // On the axis there is no radial direction to push along, and scaling would tear the cap.\n    if (Math.hypot(x, z) < 0.01) continue;\n\n    // Derived from the existing vertex, keeping the asset deterministic and its end caps watertight\n    // while breaking the lathed look.\n    const irregularity = 1 + Math.sin(x * fx + y * fy + z * fz) * amplitude;\n    position.setXYZ(i, x * irregularity, y, z * irregularity);\n  }\n\n  position.needsUpdate = true;\n  geometry.computeVertexNormals();\n  return geometry;\n}\n","import { Color, Group, Mesh, MeshStandardMaterial, Quaternion, Vector3, type BufferGeometry, type Material } from \"three\";\nimport { createHewnTimberGeometry } from \"../../geometry/timber/HewnTimberGeometry\";\n\nexport interface RusticFenceOptions {\n  /** Number of bays between posts. */\n  sections?: number;\n  sectionLength?: number;\n  railCount?: 2 | 3;\n  postHeight?: number;\n  postThickness?: number;\n  railThickness?: number;\n  seed?: number;\n  /** Overrides the repeating timber palette. Index counts posts first, then rails by section.\n   * Each distinct sampled color needs a material in this Mesh-based factory. */\n  colors?: ColorSampler;\n}\n\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\n\nconst UP = new Vector3(0, 1, 0);\n\nfunction randomGenerator(seed: number): () => number {\n  let state = seed >>> 0;\n  return () => {\n    state = (Math.imul(state, 1664525) + 1013904223) >>> 0;\n    return state / 0x100000000;\n  };\n}\n\n/**\n * A straight run of rough split-rail country fence, centered on local X with\n * its feet on y=0. Short runs can be rotated and joined to trace a boundary.\n */\nexport class RusticFence extends Group {\n  readonly #geometry: BufferGeometry;\n  readonly #materials: Material[];\n\n  constructor({\n    sections = 4,\n    sectionLength = 2.4,\n    railCount = 3,\n    postHeight = 1.65,\n    postThickness = 0.22,\n    railThickness = 0.16,\n    seed = 0xf3ce,\n    colors,\n  }: RusticFenceOptions = {}) {\n    super();\n\n    const random = randomGenerator(seed);\n    const signed = (amount: number) => (random() - 0.5) * 2 * amount;\n    this.#geometry = createHewnTimberGeometry();\n    this.#materials = [\"#5b3820\", \"#6a4326\", \"#49301f\"].map(\n      (color) =>\n        new MeshStandardMaterial({\n          color: new Color(color),\n          roughness: 1,\n          metalness: 0,\n          flatShading: true,\n        }),\n    );\n\n    const context = { index: 0, random: createRandom(deriveSubSeed(seed, 0x66656e63)) };\n    const tint = new Color();\n    const sampledMaterials = new Map<string, MeshStandardMaterial>();\n    const timberMaterial = (defaultIndex: number): Material => {\n      if (!colors) return this.#materials[defaultIndex % 3];\n      colors(tint, context);\n      context.index++;\n      const key = `${tint.r},${tint.g},${tint.b}`;\n      let material = sampledMaterials.get(key);\n      if (!material) {\n        material = new MeshStandardMaterial({ color: tint.clone(), roughness: 1, metalness: 0, flatShading: true });\n        sampledMaterials.set(key, material);\n        this.#materials.push(material);\n      }\n      return material;\n    };\n\n    const width = sections * sectionLength;\n    const postX: number[] = [];\n    const postTop: number[] = [];\n\n    for (let i = 0; i <= sections; i++) {\n      const height = postHeight * (1 + signed(0.07));\n      const x = i * sectionLength - width / 2 + signed(0.055);\n      postX.push(x);\n      postTop.push(height);\n\n      const post = new Mesh(this.#geometry, timberMaterial(i));\n      post.position.set(x, height / 2 - signed(0.025), signed(0.035));\n      post.scale.set(postThickness * (1 + signed(0.1)), height, postThickness * (1 + signed(0.1)));\n      post.rotation.set(signed(0.045), signed(0.2), signed(0.045));\n      post.castShadow = post.receiveShadow = true;\n      this.add(post);\n    }\n\n    const start = new Vector3();\n    const end = new Vector3();\n    const direction = new Vector3();\n    const orientation = new Quaternion();\n\n    for (let section = 0; section < sections; section++) {\n      for (let rail = 0; rail < railCount; rail++) {\n        const fraction = railCount === 2 ? 0.38 + rail * 0.32 : 0.3 + rail * 0.25;\n        const overlap = railThickness * 0.7;\n        const side = (section + rail) % 2 === 0 ? -1 : 1;\n\n        start.set(postX[section] - overlap, postTop[section] * fraction + signed(0.035), side * postThickness * 0.34);\n        end.set(postX[section + 1] + overlap, postTop[section + 1] * fraction + signed(0.035), -side * postThickness * 0.34);\n\n        direction.subVectors(end, start);\n        const length = direction.length();\n        orientation.setFromUnitVectors(UP, direction.normalize());\n\n        const timber = new Mesh(this.#geometry, timberMaterial(section + rail + 1));\n        timber.position.addVectors(start, end).multiplyScalar(0.5);\n        timber.quaternion.copy(orientation);\n        timber.rotateY(signed(0.12));\n        timber.scale.set(railThickness * (1 + signed(0.12)), length, railThickness * (0.85 + random() * 0.25));\n        timber.castShadow = timber.receiveShadow = true;\n        this.add(timber);\n      }\n    }\n  }\n\n  dispose(): void {\n    this.#geometry.dispose();\n    this.#materials.forEach((material) => material.dispose());\n  }\n}\n","import { BoxGeometry, Color, InstancedMesh, MeshStandardMaterial, Object3D, type ColorRepresentation } from \"three\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed, mulberry32 } from \"../../utils/Random\";\n\nexport interface FlagstoneFloorOptions {\n  /** Extent across X. Defaults to `20`. */\n  width?: number;\n  /** Extent along Z. Defaults to `24`. */\n  length?: number;\n  /**\n   * Nominal tile pitch. Defaults to `1.2`.\n   *\n   * The slab is the pitch MINUS the grout, so this stays the number you reason about and the gap eats into\n   * it rather than adding to it — widen the joint and the floor keeps its coursing.\n   */\n  tile?: number;\n  /** Gap between slabs — the grout line. Defaults to `0.06`. See the note on why it matters. */\n  gap?: number;\n  /** Slab thickness. Defaults to `0.12`. */\n  thickness?: number;\n  /** Base stone tint. Defaults to `#54524d`. */\n  color?: ColorRepresentation;\n  /**\n   * How far each slab's tint wanders, 0–1. Defaults to `0.12`.\n   *\n   * **Lightness only.** Hue drift per slab reads as STAINED rather than weathered, which is the opposite of\n   * what stone wants — a pumpkin patch wants the hue, a floor does not.\n   */\n  tintJitter?: number;\n  /** Per-slab sampler overriding color/tintJitter. Row-major index; independent seeded color stream. */\n  colors?: ColorSampler;\n  /**\n   * How far each slab settles or lifts, in world units. Defaults to `0.012`.\n   *\n   * Small numbers: this is a floor. Past about `0.05` it stops reading as worn and starts reading as broken.\n   */\n  heightJitter?: number;\n  /**\n   * Deterministic layout seed. Defaults to `1`.\n   *\n   * Stable across rebuilds — the floor is an address, not a reshuffle. Change this and you get a different\n   * floor; change anything else and you get the same floor, altered.\n   */\n  seed?: number;\n  /** Gloss, for catching lantern light. `1` is matte. Defaults to `0.72`. */\n  roughness?: number;\n}\n\n/**\n * A flagstone floor — **individual slabs, not a textured plane.**\n *\n * **The grout lines are the point.** A long floor is read down its length, and the gaps between flags give\n * perspective lines converging toward the far end — depth for free, before any light is placed. A single\n * plane with a tiled texture cannot do that, and a vertex-colored plane gives tint variation but still no\n * gaps, because the quads stay flush. Take `gap` to `0` and watch the floor collapse into one slab: the\n * converging lines vanish and so does the depth.\n *\n * Every slab also settles slightly and carries a whisper of yaw, so the surface is not perfectly flat and\n * the joints are not laser-straight. Both are seeded.\n *\n * **One `InstancedMesh`, one draw call, at any size.** Every slab is the same box; only its matrix and its\n * tint differ. That is the opposite call from {@link PlankFloor} and {@link HardwoodFloor}, whose boards are\n * each a different shape and therefore merge — identical items instance, differing items merge.\n *\n * Centered on the origin, with the slab tops on `y = 0` so anything standing on the floor sits at zero.\n *\n * @example\n * ```ts\n * const floor = new FlagstoneFloor({ width: 20, length: 26, tile: 1, gap: 0.06 });\n * scene.add(floor);\n * floor.tiles; // slabs laid\n * ```\n */\nexport class FlagstoneFloor extends InstancedMesh<BoxGeometry, MeshStandardMaterial> {\n  /** Slabs laid. */\n  readonly tiles: number;\n  /** Slabs across X. */\n  readonly columns: number;\n  /** Slabs along Z. */\n  readonly rows: number;\n\n  constructor({\n    width = 20,\n    length = 24,\n    tile = 1.2,\n    gap = 0.06,\n    thickness = 0.12,\n    color = \"#54524d\",\n    tintJitter = 0.12,\n    colors,\n    heightJitter = 0.012,\n    seed = 1,\n    roughness = 0.72,\n  }: FlagstoneFloorOptions = {}) {\n    const columns = Math.max(1, Math.round(width / tile));\n    const rows = Math.max(1, Math.round(length / tile));\n\n    // The slab is the pitch minus the grout, so `tile` stays the thing you reason about.\n    const slab = Math.max(0.05, tile - gap);\n\n    const geometry = new BoxGeometry(slab, thickness, slab);\n    // Top face on y = 0: the floor's surface is the datum everything else stands on.\n    geometry.translate(0, -thickness / 2, 0);\n\n    // White, so `setColorAt` lands the exact tint rather than multiplying into it — the same reason the\n    // hexagonal tile factories and `PumpkinPatch` start white.\n    const material = new MeshStandardMaterial({\n      color: 0xffffff,\n      roughness,\n      metalness: 0,\n      flatShading: true,\n    });\n\n    super(geometry, material, columns * rows);\n\n    this.tiles = columns * rows;\n    this.columns = columns;\n    this.rows = rows;\n    this.receiveShadow = true;\n    // Slabs lie flat on the ground and cast nothing worth the shadow pass.\n    this.castShadow = false;\n\n    const random = mulberry32(seed);\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x666c6167)) };\n    const base = new Color(color);\n    const tint = new Color();\n    const placement = new Object3D();\n\n    // Centered on the origin, like every other assembly here, rather than running from a caller-supplied\n    // corner. The run's true extent is `columns * tile` — the rounding, not the request.\n    const originX = -(columns * tile) / 2;\n    const originZ = -(rows * tile) / 2;\n\n    let index = 0;\n    for (let row = 0; row < rows; row++) {\n      for (let column = 0; column < columns; column++) {\n        placement.position.set(\n          originX + (column + 0.5) * tile,\n          (random() - 0.5) * 2 * heightJitter,\n          originZ + (row + 0.5) * tile,\n        );\n        // A whisper of yaw. Enough that the grout lines are not laser-straight, not enough to open corners\n        // between neighbors.\n        placement.rotation.y = (random() - 0.5) * 0.02;\n        placement.updateMatrix();\n        this.setMatrixAt(index, placement.matrix);\n\n        tint.copy(base).offsetHSL(0, 0, (random() - 0.5) * 2 * tintJitter);\n        // Reserve the old lightness draw above to preserve the seeded slab layout.\n        if (colors) {\n          colorContext.index = index;\n          colors(tint, colorContext);\n        }\n        this.setColorAt(index, tint);\n\n        index++;\n      }\n    }\n\n    this.instanceMatrix.needsUpdate = true;\n    if (this.instanceColor) this.instanceColor.needsUpdate = true;\n  }\n\n  /** Releases the shared slab geometry and material. */\n  dispose(): void {\n    this.geometry.dispose();\n    this.material.dispose();\n    super.dispose();\n  }\n}\n","import { mulberry32 } from \"../../utils/Random\";\n\nexport interface PlankFloorLayoutOptions {\n  /** Extent along the direction the boards run. Defaults to `2.5`. */\n  length?: number;\n  /** Extent across the boards. Defaults to `2.5`. */\n  depth?: number;\n  /** Board width. Rows are fitted to `depth`, so the width actually laid is reported back. Defaults to `0.2`. */\n  plankWidth?: number;\n  /** Gap between rows. Defaults to `0.012`. */\n  gap?: number;\n  /**\n   * Shortest board laid, in world units. Defaults to `0.5`.\n   *\n   * **Absolute, not a fraction of the floor**, because a board is milled at a real size: a larger room takes\n   * *more* boards, not longer ones. Given either way round, and clamped to the floor.\n   */\n  minPlankLength?: number;\n  /** Longest board laid, in world units. Defaults to `1.4`. */\n  maxPlankLength?: number;\n  /**\n   * How far an end joint should stand clear of the joints in the row beside it, in world units. Defaults\n   * to `0.35`. **The single rule that separates a laid floor from a set of stripes.**\n   *\n   * A TARGET, not a guarantee. Each board's length is chosen from a bounded search, so the clearance\n   * actually achieved is reported back as {@link PlankFloorLayout.closestJoint} — expect roughly two\n   * thirds of what is asked for, and read the result rather than assuming it.\n   */\n  minStagger?: number;\n  /** Defaults to `0x51ab`. */\n  seed?: number;\n}\n\n/** One board, as a position and a size. No geometry — what to build it from is the caller's business. */\nexport interface PlankPlacement {\n  /** Distance from the run's start to this board's near end. */\n  start: number;\n  length: number;\n  /** Which row, counting from the near edge. */\n  row: number;\n  /** The row's center line, measured across the floor from its middle. */\n  across: number;\n  /** Laying order. Use it to derive a per-board seed and tint, so no two boards repeat. */\n  sequence: number;\n}\n\nexport interface PlankFloorLayout {\n  placements: PlankPlacement[];\n  rows: number;\n  /** The width each board actually got, after the rows were fitted to `depth`. */\n  plankWidth: number;\n  /**\n   * The closest any two neighboring-row joints came — **what the floor actually got**, against what\n   * `minStagger` asked for. Two things hold it down: the request is capped at `(longest − shortest) / 2`,\n   * since a joint can only be moved by varying its board's length, and the per-board search is bounded, so\n   * even inside the cap it lands short. Worth reading; a floor that came out at half its target is a floor\n   * whose board range is too narrow for the room.\n   */\n  closestJoint: number;\n}\n\n/**\n * Lay a boarded floor — **where the boards go, not what they are made of.**\n *\n * Returns placements, so the same laying rules serve a floor of plain boxes, one of\n * {@link WeatheredPlankGeometry}, a ceiling, or a deck. The trade knowledge is here; the geometry is not.\n *\n * **The mistake that makes a plank floor read as stripes is spanning each board across the whole room.** A\n * real floor is laid in rows of *several* boards butted end to end, with the end joints in neighboring\n * rows deliberately kept apart — the flooring trade's own rule, and the same idea as a running bond in\n * masonry. Board-to-board color and shape variation cannot rescue a floor whose joints all line up, and is\n * barely needed once they do not.\n *\n * Two smaller rules come from the same trade. Each row opens with a **shortened starter board**, so rows do\n * not all begin their run together. And a row never ends on a **runt** — a remainder shorter than the\n * minimum is absorbed by the board before it.\n *\n * `minStagger` is capped at `(longest − shortest) / 2`. A joint can only be moved by varying its board's\n * length, so that is the furthest it can travel while still landing clear of an obstruction; asking for\n * more does not tighten the floor, it makes the search fail more often and the worst joint *worse*.\n *\n * @example\n * ```ts\n * const { placements, plankWidth } = layPlankFloor({ length: 4, depth: 3, seed: 7 });\n *\n * for (const { start, length, across, sequence } of placements) {\n *   const board = new BoxGeometry(length, thickness, plankWidth);\n *   board.translate(start + length / 2 - 2, -thickness / 2, across);\n * }\n * ```\n */\nexport function layPlankFloor({\n  length = 2.5,\n  depth = 2.5,\n  plankWidth = 0.2,\n  gap = 0.012,\n  minPlankLength = 0.5,\n  maxPlankLength = 1.4,\n  minStagger = 0.35,\n  seed = 0x51ab,\n}: PlankFloorLayoutOptions = {}): PlankFloorLayout {\n  const random = mulberry32(seed);\n\n  // Take the range as given in either order, and let no board out-run the floor it is laid on — a length\n  // longer than the room would silently become one board per row and undo the staggering entirely.\n  const longest = Math.min(Math.max(minPlankLength, maxPlankLength), length);\n  const shortest = Math.min(Math.max(Math.min(minPlankLength, maxPlankLength), 0.05), longest);\n  const stagger = Math.min(minStagger, (longest - shortest) / 2);\n\n  const rowPitch = plankWidth + gap;\n  const rows = Math.max(1, Math.round(depth / rowPitch));\n  const pitch = depth / rows;\n  const laidWidth = Math.max(pitch - gap, 0.01);\n\n  const placements: PlankPlacement[] = [];\n  let previousJoints: number[] = [];\n  let sequence = 0;\n  let closestJoint = Infinity;\n\n  for (let row = 0; row < rows; row++) {\n    const across = -depth / 2 + (row + 0.5) * pitch;\n    const joints: number[] = [];\n    let cursor = 0;\n    let first = true;\n\n    while (cursor < length - 1e-4) {\n      const remaining = length - cursor;\n\n      // Sample candidate lengths and keep whichever puts its end joint furthest from the joints in the row\n      // alongside. The score saturates at `stagger`, so a candidate that is already clear is taken\n      // immediately rather than optimized past the point of mattering.\n      //\n      // 24 rather than a handful: measured over 40 seeds, 5 attempts reached a median clearance of 0.14\n      // against a 0.35 target, 10 reached 0.23, and 24 reaches ~0.30 — after which it flattens. The whole\n      // search runs once at construction, so the extra samples cost nothing anyone can see.\n      let boardLength = remaining;\n      let bestScore = -Infinity;\n\n      for (let attempt = 0; attempt < 24; attempt++) {\n        let candidate = shortest + random() * (longest - shortest);\n\n        // A shortened opening board keeps rows from starting in step.\n        if (first) candidate *= 0.3 + random() * 0.7;\n        candidate = Math.max(candidate, shortest * 0.45);\n\n        // Never leave a runt at the end of a row; absorb it into this board.\n        if (remaining - candidate < shortest) candidate = remaining;\n\n        const joint = cursor + candidate;\n        let clearance = Infinity;\n        for (const previous of previousJoints) {\n          clearance = Math.min(clearance, Math.abs(previous - joint));\n        }\n\n        const score = Math.min(clearance, stagger);\n        if (score > bestScore) {\n          bestScore = score;\n          boardLength = candidate;\n        }\n        if (score >= stagger) break;\n      }\n\n      boardLength = Math.min(boardLength, remaining);\n      placements.push({ start: cursor, length: boardLength, row, across, sequence });\n\n      cursor += boardLength;\n      if (cursor < length - 1e-4) {\n        joints.push(cursor);\n        if (previousJoints.length > 0) {\n          for (const previous of previousJoints) {\n            closestJoint = Math.min(closestJoint, Math.abs(previous - cursor));\n          }\n        }\n      }\n      sequence++;\n      first = false;\n    }\n\n    previousJoints = joints;\n  }\n\n  return {\n    placements,\n    rows,\n    plankWidth: laidWidth,\n    closestJoint: Number.isFinite(closestJoint) ? closestJoint : 0,\n  };\n}\n","import { BufferAttribute, BufferGeometry, Color, Group, Mesh, MeshStandardMaterial, type Material } from \"three\";\nimport { mergeGeometries } from \"three/examples/jsm/utils/BufferGeometryUtils.js\";\nimport { createGeometryBuffers, pushQuad, pushTriangle, toBufferGeometry, type Vec3 } from \"../../modeling/mesh/GeometryBuffers\";\nimport { createRandom } from \"../../utils/Random\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { layPlankFloor, type PlankFloorLayoutOptions } from \"./PlankFloorLayout\";\n\nexport interface HardwoodFloorOptions extends Omit<PlankFloorLayoutOptions, \"length\" | \"depth\"> {\n  /** Room extent along X. Defaults to `5`. */\n  width?: number;\n  /** Room extent along Z. Defaults to `4`. */\n  depth?: number;\n  /**\n   * Which way the boards run, in radians. Defaults to `0` — along the room's width.\n   *\n   * `Math.PI / 4` is the classic diagonal. Any angle works: the boards are laid on a sheet sized to cover\n   * the room and then cut to it, so nothing here is a special case.\n   */\n  rotation?: number;\n  /** Board thickness. Defaults to `0.055`. */\n  plankThickness?: number;\n  /**\n   * Smallest offcut worth laying, in square units. Defaults to `0.004`.\n   *\n   * Cutting boards to a room leaves scraps, and past some size a scrap is not a board. Where that line\n   * sits is a judgment rather than a calculation — set it to `0` and the corners fill with needles; set it\n   * high and real boards go in the bin, leaving a visible notch at the wall.\n   */\n  minSliverArea?: number;\n  /** Base timber color. Defaults to `#6b4b2c`. */\n  color?: string;\n  /** Per-board tint spread in HSL, so no two boards match. Defaults to `0.06`. */\n  colorVariance?: number;\n  /**\n   * Per-board sampler overriding color and colorVariance. Writes a working-space Color.\n   * Called once per retained board, in layout order after clipping/sliver removal;\n   * index is contiguous from zero. All vertices of that board receive the same color.\n   * The seeded color stream is separate from layout. Omit to preserve the original tint recipe.\n   */\n  colors?: ColorSampler;\n  /** A material to use instead of the default. **Must set `vertexColors: true`**, or every board goes white. */\n  material?: Material;\n}\n\n/** A point on the floor plane: `[x, z]`. */\ntype Point = [number, number];\n\n/**\n * Sutherland–Hodgman, against one half-plane at a time.\n *\n * Clipping a convex polygon by a convex region gives a convex polygon, so the room's four edges can be\n * applied one after another and the result stays well-behaved — which is what lets the perimeter boards be\n * fanned rather than ear-clipped.\n */\nconst clipHalfPlane = (polygon: Point[], inside: (p: Point) => boolean, cross: (a: Point, b: Point) => Point): Point[] => {\n  const out: Point[] = [];\n  for (let i = 0; i < polygon.length; i++) {\n    const a = polygon[i]!;\n    const b = polygon[(i + 1) % polygon.length]!;\n    const aIn = inside(a);\n    const bIn = inside(b);\n    if (aIn) out.push(a);\n    if (aIn !== bIn) out.push(cross(a, b));\n  }\n  return out;\n};\n\n/** A board's outline, cut to the room. Empty when the board lies entirely outside. */\nconst clipToRoom = (polygon: Point[], halfWidth: number, halfDepth: number): Point[] => {\n  const lerp = (a: Point, b: Point, t: number): Point => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\n  let result = polygon;\n  const edges: [(p: Point) => boolean, (a: Point, b: Point) => Point][] = [\n    [(p) => p[0] >= -halfWidth, (a, b) => lerp(a, b, (-halfWidth - a[0]) / (b[0] - a[0]))],\n    [(p) => p[0] <= halfWidth, (a, b) => lerp(a, b, (halfWidth - a[0]) / (b[0] - a[0]))],\n    [(p) => p[1] >= -halfDepth, (a, b) => lerp(a, b, (-halfDepth - a[1]) / (b[1] - a[1]))],\n    [(p) => p[1] <= halfDepth, (a, b) => lerp(a, b, (halfDepth - a[1]) / (b[1] - a[1]))],\n  ];\n  for (const [inside, cross] of edges) {\n    if (result.length === 0) return result;\n    result = clipHalfPlane(result, inside, cross);\n  }\n  return result;\n};\n\nconst areaOf = (polygon: Point[]): number => {\n  let twice = 0;\n  for (let i = 0; i < polygon.length; i++) {\n    const a = polygon[i]!;\n    const b = polygon[(i + 1) % polygon.length]!;\n    twice += a[0] * b[1] - b[0] * a[1];\n  }\n  return Math.abs(twice) / 2;\n};\n\n/** A flat board: the clipped outline, given thickness. Convex, so a fan tiles both faces. */\nconst prism = (polygon: Point[], thickness: number): BufferGeometry => {\n  const buffers = createGeometryBuffers();\n  const top = (i: number): Vec3 => [polygon[i]![0], 0, polygon[i]![1]];\n  const bottom = (i: number): Vec3 => [polygon[i]![0], -thickness, polygon[i]![1]];\n\n  for (let i = 1; i < polygon.length - 1; i++) {\n    pushTriangle(buffers, [top(0), top(i + 1), top(i)], [0, 1, 0]);\n    pushTriangle(buffers, [bottom(0), bottom(i), bottom(i + 1)], [0, -1, 0]);\n  }\n  for (let i = 0; i < polygon.length; i++) {\n    const j = (i + 1) % polygon.length;\n    pushQuad(buffers, [top(i), top(j), bottom(j), bottom(i)], undefined);\n  }\n  return toBufferGeometry(buffers);\n};\n\n/**\n * A hardwood floor of planed boards, laid at any angle and **cut to the room**. Walking surface on\n * `y = 0`, centered on the origin.\n *\n * The laying is {@link layPlankFloor} — the same rows, stagger, starter boards and no-runt rule the rustic\n * {@link PlankFloor} uses. It never learns that the rows are not square to the room: the boards are laid on\n * a sheet sized to COVER the room, then each is clipped to the room's outline and the overhang thrown away.\n *\n * **Clipped, not mitered.** A board crossing a corner comes back with five or six sides, which no pair of\n * cut planes on a swept box can express. Clipping handles it, handles every other case with the same code,\n * and at `rotation: 0` is a no-op — so the general case costs nothing when it is not needed. Measured, the\n * boards cover the room to within the row gaps at every angle.\n *\n * **Baked to a single geometry and a single material** at any size. Every board is a different shape once\n * cut, and differing items merge where identical ones would instance; per-board color rides a vertex\n * attribute rather than a material group, which is what keeps it to one draw call.\n *\n * A cut board at the wall is not a defect. **A wall is a boundary condition, not the end of the floor** —\n * a carpenter cuts what the room demands, and the offcuts at a diagonal's corners are what the style looks\n * like. `minSliverArea` decides only how small a scrap is still worth laying.\n *\n * Material groups: none.\n *\n * @example\n * ```ts\n * const floor = new HardwoodFloor({ width: 6, depth: 4, rotation: Math.PI / 4, seed: 12 });\n * scene.add(floor);\n * floor.boardCount;   // laid\n * floor.clippedCount; // how many met a wall\n * floor.sliverCount;  // how many offcuts were too small to lay\n * ```\n */\nexport class HardwoodFloor extends Group {\n  readonly mesh: Mesh;\n\n  /** Boards laid. */\n  readonly boardCount: number;\n  /** Of those, how many were cut by a wall. Zero at `rotation: 0`. */\n  readonly clippedCount: number;\n  /** Offcuts discarded for being smaller than `minSliverArea`. */\n  readonly sliverCount: number;\n  /** Rows across the laying sheet. */\n  readonly rowCount: number;\n  /** The width each board actually got. */\n  readonly plankWidth: number;\n  /** How close any two neighboring-row joints came. Compare to `minStagger`. */\n  readonly closestJoint: number;\n\n  readonly #geometry: BufferGeometry;\n  readonly #material: Material;\n  readonly #ownsMaterial: boolean;\n\n  constructor({\n    width = 5,\n    depth = 4,\n    rotation = 0,\n    plankThickness = 0.055,\n    minSliverArea = 0.004,\n    color = \"#6b4b2c\",\n    colorVariance = 0.06,\n    colors: colorSampler,\n    material,\n    ...layout\n  }: HardwoodFloorOptions = {}) {\n    super();\n\n    const cos = Math.cos(rotation);\n    const sin = Math.sin(rotation);\n    const halfWidth = width / 2;\n    const halfDepth = depth / 2;\n\n    // The laying frame has to COVER the room, not match it. A rotated rectangle's extent along the run is\n    // the room's own extents projected onto that axis, so a diagonal floor is laid on a bigger sheet and\n    // the overhang is cut away.\n    const extentAlong = width * Math.abs(cos) + depth * Math.abs(sin);\n    const extentAcross = width * Math.abs(sin) + depth * Math.abs(cos);\n\n    const { placements, rows, plankWidth, closestJoint } = layPlankFloor({\n      ...layout,\n      length: extentAlong,\n      depth: extentAcross,\n    });\n\n    const seed = layout.seed ?? 0x51ab;\n    // A separate stream from the layout's, so changing a color cannot move a board.\n    // Retain the established color seed so the default output stays byte-for-byte compatible.\n    const colorRandom = createRandom(seed ^ 0x9e3779b9);\n    const random = () => colorRandom.next();\n    const base = new Color(color);\n    const tint = new Color();\n    const signed = (spread: number) => (random() * 2 - 1) * spread;\n    const colorContext = { index: 0, random: colorRandom };\n    const sampleColor: ColorSampler =\n      colorSampler ??\n      ((target) => {\n        target.copy(base).offsetHSL(signed(colorVariance) / 3, signed(colorVariance), signed(colorVariance));\n      });\n\n    const boards: BufferGeometry[] = [];\n    const halfBoard = plankWidth / 2;\n    let clipped = 0;\n    let slivers = 0;\n\n    for (const { start, length, across } of placements) {\n      const u0 = start - extentAlong / 2;\n      const u1 = u0 + length;\n      const toWorld = (u: number, v: number): Point => [u * cos - v * sin, u * sin + v * cos];\n      const cut = clipToRoom(\n        [\n          toWorld(u0, across - halfBoard),\n          toWorld(u1, across - halfBoard),\n          toWorld(u1, across + halfBoard),\n          toWorld(u0, across + halfBoard),\n        ],\n        halfWidth,\n        halfDepth,\n      );\n\n      if (cut.length < 3) continue; // entirely outside the room\n      if (cut.length !== 4) clipped++;\n      if (areaOf(cut) < minSliverArea) {\n        slivers++;\n        continue;\n      }\n\n      const board = prism(cut, plankThickness);\n      colorContext.index = boards.length;\n      sampleColor(tint, colorContext);\n\n      // One color for the WHOLE board, so it reads as a board rather than a gradient across it.\n      const count = board.attributes.position!.count;\n      const colors = new Float32Array(count * 3);\n      for (let i = 0; i < count; i++) {\n        colors[i * 3] = tint.r;\n        colors[i * 3 + 1] = tint.g;\n        colors[i * 3 + 2] = tint.b;\n      }\n      board.setAttribute(\"color\", new BufferAttribute(colors, 3));\n      boards.push(board);\n    }\n\n    const merged = mergeGeometries(boards, false);\n    boards.forEach((part) => part.dispose());\n    if (!merged) throw new Error(\"HardwoodFloor: merge failed — the room may be smaller than one board.\");\n\n    this.#ownsMaterial = material === undefined;\n    this.#material =\n      material ??\n      // White, so the vertex color lands as the exact tint rather than multiplying into it.\n      new MeshStandardMaterial({\n        color: 0xffffff,\n        vertexColors: true,\n        roughness: 1,\n        metalness: 0,\n        flatShading: true,\n      });\n\n    this.#geometry = merged;\n    this.boardCount = boards.length;\n    this.clippedCount = clipped;\n    this.sliverCount = slivers;\n    this.rowCount = rows;\n    this.plankWidth = plankWidth;\n    this.closestJoint = closestJoint;\n\n    this.mesh = new Mesh(merged, this.#material);\n    this.mesh.castShadow = true;\n    this.mesh.receiveShadow = true;\n    this.add(this.mesh);\n  }\n\n  /** Releases the merged geometry, and the material when this floor made it. */\n  dispose(): void {\n    this.#geometry.dispose();\n    if (this.#ownsMaterial) this.#material.dispose();\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface PolygonShapeOptions {\n  /** Number of sides. Defaults to `6`. */\n  sides?: number;\n  /** Circumradius — center to corner. Defaults to `1`. */\n  radius?: number;\n  /** Rotation in radians from the resting state. Defaults to `0`. */\n  rotation?: number;\n}\n\n/**\n * Regular n-gon profile.\n *\n * Rests with a corner pointing up. Rotate by `Math.PI / sides` for a flat top.\n */\nexport class PolygonShape extends Shape {\n  constructor({ sides = 6, radius = 1, rotation = 0 }: PolygonShapeOptions = {}) {\n    super();\n\n    const step = (Math.PI * 2) / sides;\n    const start = Math.PI / 2 + rotation;\n\n    for (let i = 0; i < sides; i++) {\n      const angle = start + step * i;\n      const x = Math.cos(angle) * radius;\n      const y = Math.sin(angle) * radius;\n\n      if (i === 0) this.moveTo(x, y);\n      else this.lineTo(x, y);\n    }\n\n    this.closePath();\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { PolygonShape, type PolygonShapeOptions } from \"../../shapes/PolygonShape\";\n\nexport interface PolygonGeometryOptions extends PolygonShapeOptions {\n  /** Extrusion depth. Defaults to `0.01`. */\n  depth?: number;\n}\n\n/**\n * Extruded regular n-gon prism.\n */\nexport class PolygonGeometry extends ExtrudeGeometry {\n  readonly sides: number;\n  readonly radius: number;\n  readonly depth: number;\n\n  constructor({ sides = 6, radius = 1, depth = 0.01, ...shapeOptions }: PolygonGeometryOptions = {}) {\n    super(new PolygonShape({ sides, radius, ...shapeOptions }), { depth, bevelEnabled: false });\n\n    this.sides = sides;\n    this.radius = radius;\n    this.depth = depth;\n  }\n}\n","import { InstancedMesh, Material, MeshStandardMaterial, Object3D } from \"three\";\nimport { PolygonGeometry } from \"../../geometry/shapes/PolygonGeometry\";\n\n/**\n * Flat-top hexagon — a corner at ±X, edges level across the top and bottom.\n * The staggered-column spacing below assumes this orientation.\n */\nconst FLAT_TOP = Math.PI / 6;\n\n/**\n * Hexagons already tile perfectly at a given radius, with all six neighbors the same distance\n * away. So a gap is *not* extra space added to the lattice — inflating the X and Z spacings\n * separately would stretch the grid unevenly and leave the gap wider on the diagonals than in the\n * columns. Instead the lattice stays a true hex grid and the tile is shrunk inside its cell.\n *\n * Shrinking a hexagon's inradius by `gap / 2` opens a uniform `gap` to every neighbor, and that\n * costs `gap / √3` of circumradius.\n */\nconst shrinkForGap = (gap: number) => gap / Math.sqrt(3);\n\nexport interface HexagonalTileCountOptions {\n  width: number; // Total area width to fill (x-axis)\n  depth: number; // Total area depth to fill (z-axis)\n  height: number; // Height of each tile (y-axis)\n  count: number; // Number of tiles along the x-axis\n  gap: number; // Gap spacing between tiles\n  material?: Material; // Optional custom material\n}\n\n/**\n * Hexagonal tile floor, sized by tile *count* — you say how many tiles span the width, and the tile\n * radius is solved to make them fit.\n *\n * Reach for this when the tile count is what you care about (\"a 10-across floor\"), and let the tiles\n * come out whatever size they need to be. Use {@link createHexagonalTilesByRadius} when the tile\n * size is what you care about and the count should fall out instead.\n *\n * Tiles are laid in staggered columns and centered on the origin. Rows are filled to whatever depth\n * fits, so the tile count is `count * (however many rows fit)` — not `count` alone.\n *\n * @example\n * ```ts\n * // A 10x10 floor, ten tiles across.\n * const floor = createHexagonalTilesByCount({\n *   width: 10,\n *   depth: 10,\n *   height: 0.01,\n *   count: 10,\n *   gap: 0.01,\n * });\n * scene.add(floor);\n *\n * // Tint each tile individually — InstancedMesh carries per-instance color.\n * floor.setColorAt(0, new Color(\"#c8b8a0\"));\n * floor.instanceColor.needsUpdate = true;\n * ```\n */\nexport function createHexagonalTilesByCount(options: HexagonalTileCountOptions): InstancedMesh {\n  const { width, depth, height, count, gap, material } = options;\n\n  const tileMaterial = material ?? new MeshStandardMaterial({ color: 0xffffff });\n\n  // `count` tiles across `width` fixes the lattice: columns sit 1.5 lattice-radii apart.\n  const spacingX = width / count;\n  const latticeRadius = (spacingX * 2) / 3;\n  const spacingZ = Math.sqrt(3) * latticeRadius;\n\n  // The gap is carved out of the tile, not added to the lattice. Pinning both `width` and `count`\n  // leaves the tile as the only thing that can absorb the gap — and a tile cannot shrink past\n  // nothing. When the gap will not fit, the GAP yields: the tiles are the point, the gap is slack.\n  const maxShrink = latticeRadius * 0.95;\n  const radius = latticeRadius - Math.min(shrinkForGap(gap), maxShrink);\n\n  // Calculate the number of tiles that fit along the z-axis\n  const countZ = Math.floor(depth / spacingZ);\n\n  const hexTileCount = count * countZ;\n\n  // Create a hexagonal prism geometry\n  const geometry = new PolygonGeometry({ sides: 6, radius, depth: height, rotation: FLAT_TOP });\n\n  // Rotate geometry so tiles lay flat\n  geometry.rotateX(-Math.PI / 2);\n\n  // Create the instanced mesh\n  const instancedMesh = new InstancedMesh(geometry, tileMaterial, hexTileCount);\n\n  const dummy = new Object3D();\n  let index = 0;\n\n  for (let x = 0; x < count; x++) {\n    for (let z = 0; z < countZ; z++) {\n      // Calculate the staggered row offset\n      const offsetX = x * spacingX;\n      const offsetZ = z * spacingZ + (x % 2) * (spacingZ / 2); // Stagger odd columns by half a tile\n\n      // Center the grid\n      const positionX = offsetX - (count * spacingX) / 2 + spacingX / 2;\n      const positionZ = offsetZ - (countZ * spacingZ) / 2 + spacingZ / 2;\n\n      dummy.position.set(positionX, 0, positionZ);\n      dummy.updateMatrix();\n      instancedMesh.setMatrixAt(index++, dummy.matrix);\n    }\n  }\n\n  instancedMesh.instanceMatrix.needsUpdate = true;\n\n  return instancedMesh;\n}\n\nexport interface HexagonalTileRadiusOptions {\n  width: number; // Total area width to fill (x-axis)\n  depth: number; // Total area depth to fill (z-axis)\n  height: number; // Height of each tile (y-axis)\n  radius: number; // Radius of each hexagonal tile\n  gap: number; // Gap spacing between tiles\n  material?: Material; // Optional custom material\n}\n\n/**\n * Hexagonal tile floor, sized by tile *radius* — you say how big a tile is, and as many as fit are\n * laid down.\n *\n * The counterpart to {@link createHexagonalTilesByCount}: here the tile size is fixed and the count\n * falls out, which is what you want when the tiles have a real-world size. Tiles are laid in\n * staggered columns and centered on the origin; whatever does not fit is simply not placed, so the\n * floor may fall a little short of `width` / `depth`.\n *\n * @example\n * ```ts\n * // A 10x10 floor of 0.1-radius tiles — however many that turns out to be.\n * const floor = createHexagonalTilesByRadius({\n *   width: 10,\n *   depth: 10,\n *   height: 0.01,\n *   radius: 0.1,\n *   gap: 0.01,\n * });\n * scene.add(floor);\n *\n * floor.count; // an output — you find out how many fit\n * ```\n */\nexport function createHexagonalTilesByRadius(options: HexagonalTileRadiusOptions): InstancedMesh {\n  const { width, depth, height, radius, gap, material } = options;\n\n  const tileMaterial = material ?? new MeshStandardMaterial({ color: 0xffffff });\n\n  // The tile is the size you asked for; the lattice grows around it to open the gap. Expanding the\n  // lattice by `gap / √3` of radius opens a uniform `gap` to all six neighbors — inflating the X\n  // and Z spacings separately would stretch the grid and leave the diagonals wider than the columns.\n  const latticeRadius = radius + shrinkForGap(gap);\n  const spacingX = (latticeRadius * 3) / 2;\n  const spacingZ = Math.sqrt(3) * latticeRadius;\n\n  // Calculate the number of tiles that fit within the area\n  const hexTileCountX = Math.floor(width / spacingX);\n  const hexTileCountZ = Math.floor(depth / spacingZ);\n\n  const hexTileCount = hexTileCountX * hexTileCountZ;\n\n  // Create a hexagonal prism geometry\n  const geometry = new PolygonGeometry({ sides: 6, radius, depth: height, rotation: FLAT_TOP });\n\n  // Rotate geometry so tiles lay flat\n  geometry.rotateX(-Math.PI / 2);\n\n  // Create the instanced mesh\n  const instancedMesh = new InstancedMesh(geometry, tileMaterial, hexTileCount);\n\n  const dummy = new Object3D();\n  let index = 0;\n\n  for (let x = 0; x < hexTileCountX; x++) {\n    for (let z = 0; z < hexTileCountZ; z++) {\n      // Calculate the staggered row offset\n      const offsetX = x * spacingX;\n      const offsetZ = z * spacingZ + (x % 2) * (spacingZ / 2); // Stagger odd rows by half a tile\n\n      // Center the grid\n      const positionX = offsetX - (hexTileCountX * spacingX) / 2 + spacingX / 2;\n      const positionZ = offsetZ - (hexTileCountZ * spacingZ) / 2 + spacingZ / 2;\n\n      dummy.position.set(positionX, 0, positionZ);\n      dummy.updateMatrix();\n      instancedMesh.setMatrixAt(index++, dummy.matrix);\n    }\n  }\n\n  instancedMesh.instanceMatrix.needsUpdate = true;\n\n  return instancedMesh;\n}\n","import { BoxGeometry } from \"three\";\n\nexport interface WeatheredPlankGeometryOptions {\n  /** Long axis, authored along local X. */\n  length?: number;\n  width?: number;\n  thickness?: number;\n  seed?: number;\n  /** Maximum edge wander as a fraction of width. */\n  roughness?: number;\n  /** Maximum broad bow as a fraction of thickness. */\n  bow?: number;\n  /** Maximum end skew as a fraction of width. */\n  endSkew?: number;\n}\n\nfunction noise(seed: number, x: number, y: number, z: number): number {\n  const value =\n    Math.sin(seed * 0.0137 + x * 17.17 + y * 31.73 + z * 47.21) * 43758.5453;\n  return (value - Math.floor(value)) * 2 - 1;\n}\n\n/**\n * A single rough-sawn board centered at the origin, with its long axis on X.\n * The geometry owns only the board's shape; gaps and installation variation\n * belong to whichever wall, floor, or roof assembly places it.\n */\nexport class WeatheredPlankGeometry extends BoxGeometry {\n  constructor({\n    length = 2,\n    width = 0.28,\n    thickness = 0.08,\n    seed = 1,\n    roughness = 0.055,\n    bow = 0.12,\n    endSkew = 0.08,\n  }: WeatheredPlankGeometryOptions = {}) {\n    super(length, width, thickness, 8, 2, 1);\n\n    const position = this.attributes.position;\n    const halfLength = length / 2;\n    const halfWidth = width / 2;\n\n    for (let i = 0; i < position.count; i++) {\n      const x = position.getX(i);\n      const y = position.getY(i);\n      const z = position.getZ(i);\n      const nx = halfLength === 0 ? 0 : x / halfLength;\n      const edge = halfWidth === 0 ? 0 : Math.abs(y / halfWidth);\n\n      const edgeWander =\n        noise(seed, x * 1.7, Math.sign(y), 0) * width * roughness * edge;\n      const endWander =\n        Math.abs(nx) > 0.98\n          ? noise(seed + 19, 0, y * 8, z * 8) * width * endSkew\n          : 0;\n      const broadBow =\n        (1 - nx * nx) * noise(seed + 31, 0, y, 0) * thickness * bow;\n      const surface =\n        noise(seed + 47, x * 4, y * 9, z * 13) * thickness * 0.035;\n\n      position.setXYZ(\n        i,\n        x + Math.sign(x) * endWander,\n        y + Math.sign(y || 1) * edgeWander,\n        z + broadBow + surface,\n      );\n    }\n\n    position.needsUpdate = true;\n    this.computeVertexNormals();\n  }\n}\n","import { BufferAttribute, Color, Group, Mesh, MeshStandardMaterial, type BufferGeometry, type Material } from \"three\";\nimport { mergeGeometries } from \"three/examples/jsm/utils/BufferGeometryUtils.js\";\nimport { WeatheredPlankGeometry } from \"../../geometry/timber/WeatheredPlankGeometry\";\nimport { createRandom } from \"../../utils/Random\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { layPlankFloor, type PlankFloorLayoutOptions } from \"./PlankFloorLayout\";\n\nexport interface PlankFloorOptions extends PlankFloorLayoutOptions {\n  /** Board thickness. Defaults to `0.055`. */\n  plankThickness?: number;\n  /**\n   * Edge wander per board, as a fraction of its width. Defaults to `0.05`.\n   *\n   * Most of the floor's character lives here: raised, the boards' edges break up and the run stops\n   * repeating; too high and it turns cartoonish, which is a style of its own; low, and the floor reads as\n   * planed and refined.\n   */\n  plankEdgeRoughness?: number;\n  /**\n   * End skew per board, as a fraction of its width — how far a board's cut ends lean off square. Defaults\n   * to `0.06`. The other half of the character, and what makes the butt joints read as sawn rather than\n   * machined.\n   */\n  plankEndSkew?: number;\n  /**\n   * Broad bow per board, as a fraction of its thickness. Defaults to `0.12`. Nailed flooring cannot bow\n   * much, so this stays low; it mostly catches the light.\n   */\n  plankBow?: number;\n  /** Base timber color. Defaults to `#6b4b2c`. Ignored when `tints` is given. */\n  color?: string;\n  /**\n   * Per-board tint spread in HSL, so no two boards match. Defaults to `0.06`.\n   *\n   * Sampled ± about {@link PlankFloorOptions.color}, hue a third as far as saturation and lightness —\n   * timber from one delivery varies in depth far more than in hue.\n   */\n  colorVariance?: number;\n  /**\n   * Deal boards from a fixed palette instead of varying them continuously.\n   *\n   * Costs nothing extra — the tint still rides the same vertex attribute — and reads as a delivery of\n   * mixed timber rather than a run of one board dyed slightly differently each time.\n   */\n  tints?: string[];\n  /**\n   * Per-board working-space color sampler. Overrides tints, color, and colorVariance.\n   * Called once per board in layout order, with a contiguous index starting at zero.\n   * Uses a seeded stream independent of layout and plank geometry; all vertices of a\n   * board receive the same tint. Omit to preserve the original palette/HSL behavior.\n   */\n  colors?: ColorSampler;\n  /** A material to use instead of the default. **Must set `vertexColors: true`**, or every board goes white. */\n  material?: Material;\n}\n\n/**\n * A boarded floor, **laid rather than tiled**. Walking surface on `y = 0`, boards running along X, centered\n * on the origin.\n *\n * The laying is {@link layPlankFloor} — rows of boards butted end to end, joints staggered from the row\n * alongside, a shortened starter board, and no runt at the end of a run. Read that for the reasoning; this\n * factory only decides what the boards are *made of*.\n *\n * **Baked to a single geometry and a single material**, whatever the floor's size. Every board is its own\n * {@link WeatheredPlankGeometry} with its own seed, so no two repeat — and differing items merge where\n * identical ones would instance. Per-board color rides a **vertex attribute** rather than a material\n * group, which is what keeps it to one draw call: a palette would otherwise cost one group, and one draw\n * call, per tint. The board's whole shell gets one color, so it reads as a board rather than a gradient.\n *\n * **Rotation is deliberately absent.** These boards are deformed individually and butt end-grain to\n * end-grain; laying them diagonally would need every perimeter board cut to the room, which is a different\n * construction rather than an option on this one.\n *\n * Material groups: none.\n *\n * @example\n * ```ts\n * const floor = new PlankFloor({ length: 6, depth: 4, seed: 12 });\n * scene.add(floor);\n * floor.plankCount;   // how many boards it took\n * floor.closestJoint; // how close two neighboring joints came — compare to minStagger\n * ```\n */\nexport class PlankFloor extends Group {\n  readonly mesh: Mesh;\n\n  /** Boards laid. */\n  readonly plankCount: number;\n  /** Rows across the floor's depth. */\n  readonly rowCount: number;\n  /** The width each board actually got, after the rows were fitted to `depth`. */\n  readonly plankWidth: number;\n  /** How close any two neighboring-row joints came. Compare to `minStagger`. */\n  readonly closestJoint: number;\n\n  readonly #geometry: BufferGeometry;\n  readonly #material: Material;\n  readonly #ownsMaterial: boolean;\n\n  constructor({\n    plankThickness = 0.055,\n    plankEdgeRoughness = 0.05,\n    plankEndSkew = 0.06,\n    plankBow = 0.12,\n    color = \"#6b4b2c\",\n    colorVariance = 0.06,\n    tints,\n    colors: colorSampler,\n    material,\n    ...layout\n  }: PlankFloorOptions = {}) {\n    super();\n\n    const { placements, rows, plankWidth, closestJoint } = layPlankFloor(layout);\n    const seed = layout.seed ?? 0x51ab;\n    // A separate stream from the layout's, so changing a color cannot move a board.\n    const colorRandom = createRandom(seed ^ 0x9e3779b9);\n    const random = () => colorRandom.next();\n    const colorContext = { index: 0, random: colorRandom };\n\n    this.#ownsMaterial = material === undefined;\n    this.#material =\n      material ??\n      // White, so the vertex color lands as the exact tint rather than multiplying into it — the same\n      // trick `PumpkinPatch` plays with its rind.\n      new MeshStandardMaterial({\n        color: 0xffffff,\n        vertexColors: true,\n        roughness: 1,\n        metalness: 0,\n        flatShading: true,\n      });\n\n    const base = new Color(color);\n    const palette = tints?.map((t) => new Color(t));\n    const tint = new Color();\n    const signed = (spread: number) => (random() * 2 - 1) * spread;\n\n    const boards: BufferGeometry[] = [];\n\n    for (const { start, length, across, sequence } of placements) {\n      const board = new WeatheredPlankGeometry({\n        length,\n        width: plankWidth,\n        thickness: plankThickness,\n        seed: seed + sequence * 37,\n        roughness: plankEdgeRoughness,\n        endSkew: plankEndSkew,\n        bow: plankBow,\n      });\n      // The board is authored lying in XY with its thickness on Z; a quarter turn about X lays it flat,\n      // width across Z and thickness on Y.\n      board.rotateX(Math.PI / 2);\n      board.translate(start + length / 2, -plankThickness / 2, across);\n\n      if (colorSampler) {\n        colorContext.index = boards.length;\n        colorSampler(tint, colorContext);\n      } else if (palette && palette.length > 0) {\n        tint.copy(palette[sequence % palette.length]!);\n      } else {\n        // Hue drifts a third as far as saturation and lightness: one delivery of timber varies in depth,\n        // not in species.\n        tint.copy(base).offsetHSL(signed(colorVariance) / 3, signed(colorVariance), signed(colorVariance));\n      }\n\n      // One color for the WHOLE board, so it reads as a board rather than a gradient across it.\n      const count = board.attributes.position!.count;\n      const colors = new Float32Array(count * 3);\n      for (let i = 0; i < count; i++) {\n        colors[i * 3] = tint.r;\n        colors[i * 3 + 1] = tint.g;\n        colors[i * 3 + 2] = tint.b;\n      }\n      board.setAttribute(\"color\", new BufferAttribute(colors, 3));\n\n      boards.push(board);\n    }\n\n    // Center the run so the floor sits on the origin like every other assembly.\n    const merged = mergeGeometries(boards, false);\n    boards.forEach((part) => part.dispose());\n    if (!merged) throw new Error(\"PlankFloor: merge failed.\");\n    merged.translate(-(layout.length ?? 2.5) / 2, 0, 0);\n\n    this.#geometry = merged;\n    this.plankCount = placements.length;\n    this.rowCount = rows;\n    this.plankWidth = plankWidth;\n    this.closestJoint = closestJoint;\n\n    this.mesh = new Mesh(merged, this.#material);\n    this.mesh.castShadow = true;\n    this.mesh.receiveShadow = true;\n    this.add(this.mesh);\n  }\n\n  /** Releases the merged geometry, and the material when this floor made it. */\n  dispose(): void {\n    this.#geometry.dispose();\n    if (this.#ownsMaterial) this.#material.dispose();\n  }\n}\n","import { BufferGeometry, CylinderGeometry, Matrix4, SphereGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\n//------------------------------\n//  Rind\n//------------------------------\n\nexport interface PumpkinRindGeometryOptions {\n  rindRadius?: number;\n  rindWidthSegments?: number;\n  rindHeightSegments?: number;\n  rindRibs?: number;\n  rindRibDepth?: number;\n  rindSquash?: number;\n}\n\n/** Builds only the ribbed rind, resting on the local XZ plane. */\nexport function createPumpkinRindGeometry({\n  rindRadius = 1,\n  rindWidthSegments = 16,\n  rindHeightSegments = 8,\n  rindRibs = 8,\n  rindRibDepth = 0.075,\n  rindSquash = 0.82,\n}: PumpkinRindGeometryOptions = {}): BufferGeometry {\n  const geometry = new SphereGeometry(\n    rindRadius,\n    rindWidthSegments,\n    rindHeightSegments,\n  );\n  const position = geometry.getAttribute(\"position\");\n\n  for (let i = 0; i < position.count; i++) {\n    const x = position.getX(i);\n    const z = position.getZ(i);\n    const theta = Math.atan2(z, x);\n    const rib = 1 + Math.cos(theta * rindRibs) * rindRibDepth;\n    position.setX(i, x * rib);\n    position.setZ(i, z * rib);\n    position.setY(i, position.getY(i) * rindSquash);\n  }\n\n  position.needsUpdate = true;\n  geometry.translate(0, rindRadius * rindSquash, 0);\n  geometry.computeVertexNormals();\n  return geometry;\n}\n\n//------------------------------\n//  Stem\n//------------------------------\n\nexport interface PumpkinStemGeometryOptions {\n  stemTopRadius?: number;\n  stemBottomRadius?: number;\n  stemHeight?: number;\n  stemSegments?: number;\n}\n\n/**\n * Builds a standalone stem: a cylinder whose base pivot sits at the local\n * origin, resting on the XZ plane. This factory has no knowledge of anything it\n * sits on — lean, seating, and placement are the assembly layer's concern.\n */\nexport function createPumpkinStemGeometry({\n  stemTopRadius = 0.1,\n  stemBottomRadius = 0.14,\n  stemHeight = 0.38,\n  stemSegments = 5,\n}: PumpkinStemGeometryOptions = {}): BufferGeometry {\n  const geometry = new CylinderGeometry(\n    stemTopRadius,\n    stemBottomRadius,\n    stemHeight,\n    stemSegments,\n  );\n\n  // Base pivot at the local origin: the stem's only responsibility is to sit its\n  // bottom on the XZ plane. The anchor point the assembly layer translates into\n  // place — nothing here rotates or seats the stem.\n  geometry.translate(0, stemHeight / 2, 0);\n\n  return geometry;\n}\n\n//------------------------------\n//  Assembly\n//------------------------------\n\nexport interface PumpkinAssemblyOptions {\n  /**\n   * Extra depth the stem base is buried past its natural seat on the rind, for a\n   * rooted look. `0` rests the stem's footprint exactly on the surface. An\n   * assembly-tier option: it belongs to neither part, only to how they join.\n   *\n   * Subject-prefixed (`stem…`) so the vocabulary stays unambiguous as the unit\n   * gains its own lean/twist/sink at the placement tier above.\n   */\n  stemSink?: number;\n  /**\n   * Tilt (pitch) of the stem away from vertical, in radians, pivoting about its\n   * seated base. `0` stands straight up.\n   */\n  stemLean?: number;\n  /**\n   * Yaw of the stem about the vertical axis, in radians. On its own it is\n   * invisible for an axisymmetric stem; combined with `stemLean` it yaws the tilt\n   * into a chosen compass direction.\n   */\n  stemTwist?: number;\n}\n\n/**\n * The stem's placement relative to the rind, as a single transform: seat the\n * base where the footprint rests on the rind (buried by `sink`), then lean and\n * twist it about that seated pivot.\n *\n * Positioning expressed once, so both mechanics can consume it — the\n * single-instance merge bakes it into the stem's vertices via `applyMatrix4`,\n * while the instanced patch composes it into each per-instance matrix. Applied\n * to a point the order is translate · Ry(twist) · Rz(lean): lean tips the stem,\n * twist yaws the tipped stem, and the translate lifts the pivot onto the rind.\n */\nexport function pumpkinStemMatrix({\n  rindRadius = 1,\n  rindSquash = 0.82,\n  stemBottomRadius = 0.14,\n  stemSink = 0.1,\n  stemLean = 0,\n  stemTwist = 0,\n}: PumpkinGeometryOptions = {}): Matrix4 {\n  // Seat height: where the stem's circular base makes full-ring contact with the\n  // rind, treating the rind as its underlying ellipsoid (ribs vanish at the pole,\n  // so they don't matter here). The rind's base sits at y = 0, so this is measured\n  // from the ground, and it stays honest as rindRadius and rindSquash change.\n  const halfHeight = rindRadius * rindSquash;\n  const ratio = Math.min(stemBottomRadius / rindRadius, 1);\n  const seatHeight = halfHeight * (1 + Math.sqrt(1 - ratio * ratio));\n\n  // Seat, then orient about that seated base: bury by stemSink, yaw by stemTwist,\n  // tip by stemLean. Applied to a point the order is translate · Ry · Rz.\n  return new Matrix4()\n    .makeTranslation(0, seatHeight - stemSink, 0)\n    .multiply(new Matrix4().makeRotationY(stemTwist))\n    .multiply(new Matrix4().makeRotationZ(stemLean));\n}\n\n//------------------------------\n//  Geometry\n//------------------------------\n\nexport interface PumpkinGeometryOptions\n  extends PumpkinRindGeometryOptions,\n    PumpkinStemGeometryOptions,\n    PumpkinAssemblyOptions {}\n\n/** Creates one grouped geometry: material 0 is rind, material 1 is stem. */\nexport function createPumpkinGeometry(\n  options: PumpkinGeometryOptions = {},\n): BufferGeometry {\n  const rind = createPumpkinRindGeometry(options);\n  const stem = createPumpkinStemGeometry(options);\n\n  // Assembly owns placement, expressed once as pumpkinStemMatrix. Here the merge\n  // mechanic bakes that transform into the stem's vertices; the instanced patch\n  // composes the same matrix per instance. Neither part knows the other exists.\n  stem.applyMatrix4(pumpkinStemMatrix(options));\n\n  const merged = mergeGeometries([rind, stem], true);\n  rind.dispose();\n  stem.dispose();\n  return merged;\n}\n\n/** A cohesive single-instance geometry, analogous to Three.js built-in geometries. */\nexport class PumpkinGeometry extends BufferGeometry {\n  readonly type = \"PumpkinGeometry\";\n\n  constructor(options: PumpkinGeometryOptions = {}) {\n    super();\n    const geometry = createPumpkinGeometry(options);\n    this.copy(geometry);\n    geometry.dispose();\n    this.userData.parameters = { ...options };\n  }\n}\n","import { Color, Group, InstancedMesh, Material, Matrix4, MeshStandardMaterial, Object3D, Quaternion, Vector3 } from \"three\";\nimport { createPumpkinRindGeometry, createPumpkinStemGeometry, pumpkinStemMatrix } from \"../../geometry/flora/PumpkinGeometry\";\n\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\n\nconst UP = new Vector3(0, 1, 0);\nconst TILT_AXIS = new Vector3(1, 0, 0);\n\n/**\n * The instancing half of the pattern: a field of pumpkins — rows × columns,\n * potentially thousands — batched into exactly two draw calls, one rind\n * InstancedMesh and one stem InstancedMesh.\n *\n * This is why the geometry stayed separable. Because rind and stem are distinct\n * batches, each can carry its own per-instance data — `setColorAt` gives every\n * pumpkin its own rind tint, which a single merged geometry could never do.\n * Organic variety, not tuning knobs: `lean`/`twist`/`sink`/`drift` are the\n * seeded *max* ranges from the shared placement vocabulary, sampled per instance.\n *\n * The stem batch reuses `pumpkinStemMatrix` — the exact positioning the\n * single-instance merge bakes — composed here into each instance's world matrix.\n * Positioning computed once; only the mechanics (bake vs. instance) differ.\n */\nexport interface PumpkinPatchOptions {\n  rows?: number;\n  columns?: number;\n  spacing?: number;\n  seed?: number;\n  scaleMin?: number;\n  scaleMax?: number;\n\n  // Stem tier — the part, jittered per pumpkin (assembly). Prefixed `stem…`, the\n  // same vocabulary the single geometry exposes.\n  /** Max stem tilt off vertical, radians. Sampled ±value. */\n  stemLeanMax?: number;\n  /** Max extra stem burial into the rind past its seat. Sampled 0..value. */\n  stemSinkMax?: number;\n\n  // Unit tier — the whole pumpkin, jittered per pumpkin (placement). Bare names,\n  // because the unit is the subject here.\n  /** Max whole-pumpkin tilt off vertical, radians. Sampled ±value — keep subtle. */\n  leanMax?: number;\n  /** Max whole-pumpkin yaw, radians. Sampled ±value. */\n  twistMax?: number;\n  /** Max depth the whole pumpkin beds into the ground. Sampled 0..value. */\n  sinkMax?: number;\n  /** Max XZ wander off the grid point. Sampled ±value. */\n  driftMax?: number;\n\n  /** Per-instance rind tint spread in HSL, for a non-repeating field. */\n  colorVariance?: number;\n  /** Per-rind sampler overriding colorVariance. Index is row-major; seeded color draws do not alter placement. */\n  rindColors?: ColorSampler;\n}\n\n/** Repeatable LCG so a given `seed` always yields the same field. */\nfunction randomGenerator(seed: number): () => number {\n  let state = seed >>> 0;\n  return () => {\n    state = (Math.imul(state, 1664525) + 1013904223) >>> 0;\n    return state / 0x100000000;\n  };\n}\n\nexport class PumpkinPatch extends Group {\n  readonly rindInstances: InstancedMesh;\n  readonly stemInstances: InstancedMesh;\n\n  readonly #materials: Material[];\n\n  constructor({\n    rows = 12,\n    columns = 16,\n    spacing = 0.9,\n    seed = 0x51a7,\n    scaleMin = 0.22,\n    scaleMax = 0.38,\n    stemLeanMax = 0.35,\n    stemSinkMax = 0.06,\n    leanMax = 0.08,\n    twistMax = Math.PI,\n    sinkMax = 0.05,\n    driftMax = 0.18,\n    colorVariance = 0.08,\n    rindColors,\n  }: PumpkinPatchOptions = {}) {\n    super();\n\n    const count = rows * columns;\n    const random = randomGenerator(seed);\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x72696e64)) };\n\n    // Base unit-pumpkin parts (radius 1); per-instance scale sizes them, keeping\n    // the stem proportional for free.\n    const rindGeometry = createPumpkinRindGeometry();\n    const stemGeometry = createPumpkinStemGeometry();\n\n    // White rind base so setColorAt yields the exact tint rather than a product;\n    // stems share one solid color, so they need no per-instance color at all.\n    const rindMaterial = new MeshStandardMaterial({ color: 0xffffff, roughness: 0.92, metalness: 0, flatShading: true });\n    const stemMaterial = new MeshStandardMaterial({ color: new Color(\"#30311f\"), roughness: 1, metalness: 0, flatShading: true });\n    this.#materials = [rindMaterial, stemMaterial];\n\n    this.rindInstances = new InstancedMesh(rindGeometry, rindMaterial, count);\n    this.stemInstances = new InstancedMesh(stemGeometry, stemMaterial, count);\n    this.rindInstances.castShadow = this.rindInstances.receiveShadow = true;\n    this.stemInstances.castShadow = true;\n\n    const placement = new Object3D();\n    const stemWorld = new Matrix4();\n    const yaw = new Quaternion();\n    const tilt = new Quaternion();\n    const tint = new Color();\n    const baseRind = new Color(\"#804319\");\n\n    const xOffset = ((columns - 1) * spacing) / 2;\n    const zOffset = ((rows - 1) * spacing) / 2;\n    const signed = (max: number) => (random() - 0.5) * 2 * max;\n\n    let index = 0;\n    for (let row = 0; row < rows; row++) {\n      for (let column = 0; column < columns; column++) {\n        const scale = scaleMin + random() * (scaleMax - scaleMin);\n\n        // Unit tier — the whole pumpkin, as placement.\n        const unitLean = signed(leanMax);\n        const unitTwist = signed(twistMax);\n        const unitSink = random() * sinkMax;\n\n        // Grid point + drift, bedded into the ground by sink, yawed and tipped.\n        // yaw ∘ tilt so the yaw also spins the tilt into a random compass heading.\n        placement.position.set(\n          column * spacing - xOffset + signed(driftMax),\n          -unitSink,\n          row * spacing - zOffset + signed(driftMax),\n        );\n        yaw.setFromAxisAngle(UP, unitTwist);\n        tilt.setFromAxisAngle(TILT_AXIS, unitLean);\n        placement.quaternion.multiplyQuaternions(yaw, tilt);\n        placement.scale.setScalar(scale);\n        placement.updateMatrix();\n        this.rindInstances.setMatrixAt(index, placement.matrix);\n\n        // Stem tier — the part, seated on its rind then carried by the unit\n        // placement. The exact pumpkinStemMatrix the single-instance merge bakes.\n        stemWorld.multiplyMatrices(\n          placement.matrix,\n          pumpkinStemMatrix({ stemLean: signed(stemLeanMax), stemSink: random() * stemSinkMax }),\n        );\n        this.stemInstances.setMatrixAt(index, stemWorld);\n\n        tint.copy(baseRind).offsetHSL(signed(colorVariance) * 0.3, signed(colorVariance), signed(colorVariance));\n        // Keep the legacy three draws above so sampler choices cannot move later pumpkins.\n        if (rindColors) {\n          colorContext.index = index;\n          rindColors(tint, colorContext);\n        }\n        this.rindInstances.setColorAt(index, tint);\n\n        index++;\n      }\n    }\n\n    this.rindInstances.instanceMatrix.needsUpdate = true;\n    this.stemInstances.instanceMatrix.needsUpdate = true;\n    if (this.rindInstances.instanceColor) this.rindInstances.instanceColor.needsUpdate = true;\n\n    this.add(this.rindInstances, this.stemInstances);\n  }\n\n  /** Release both instanced geometries and the owned materials. */\n  dispose(): void {\n    this.rindInstances.geometry.dispose();\n    this.stemInstances.geometry.dispose();\n    this.#materials.forEach((material) => material.dispose());\n  }\n}\n","import {\n  AdditiveBlending,\n  BoxGeometry,\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  CylinderGeometry,\n  DataTexture,\n  DynamicDrawUsage,\n  Group,\n  InstancedBufferAttribute,\n  InstancedMesh,\n  Material,\n  Matrix4,\n  Mesh,\n  MeshBasicMaterial,\n  MeshStandardMaterial,\n  Object3D,\n  PlaneGeometry,\n  PointLight,\n} from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { instancedBufferAttribute, instancedDynamicBufferAttribute, texture } from \"three/tsl\";\nimport { SpriteNodeMaterial } from \"three/webgpu\";\nimport { flameFlicker } from \"../../effects/FlameFlickerEffect\";\nimport { glowFalloffTexture } from \"../../effects/GlowHalo\";\nimport { createRandom } from \"../../utils/Random\";\n\nexport interface VotiveRackOptions {\n  /** Seed for a reproducible layout. Omit for a different rack every run. */\n  seed?: number;\n  /** Shelves, bottom to top. Defaults to `4`. */\n  rows?: number;\n  /** Cups per shelf. Defaults to `8`. */\n  columns?: number;\n  /** Overall width of the frame. Defaults to `2.2`. */\n  width?: number;\n  /** Vertical rise per shelf. Defaults to `0.28`. */\n  rowRise?: number;\n  /**\n   * Depth offset per shelf, so upper rows sit back. Defaults to `0.18`.\n   *\n   * The lowest shelf is the nearest to `+Z` and each row above steps away — stadium seating, so no row hides\n   * behind the one in front. The rack stays centered on `z = 0` whatever the row count.\n   */\n  rowDepth?: number;\n  /** Height of the lowest shelf. Defaults to `0.55`. */\n  baseHeight?: number;\n  /**\n   * Fraction of cups holding a candle at all. Defaults to `0.9` — a rack in use has gaps.\n   *\n   * This is the first step of a **presence cascade**: a cup may be empty; a candle may be spent; only\n   * what survives both is lit. Absence is the one thing a per-instance value cannot express, so it is\n   * expressed by not allocating the instance.\n   */\n  density?: number;\n  /** Fraction of *present* candles that are lit. Defaults to `0.72`. */\n  litFraction?: number;\n  /** Shortest candle. Defaults to `0.055`. */\n  candleHeightMin?: number;\n  /** Tallest candle. Defaults to `0.185`. */\n  candleHeightMax?: number;\n  /** Flame and glow tint. Defaults to `0xffb347`. */\n  color?: ColorRepresentation;\n  /** Wax color. Defaults to `0xd9cdb2`. */\n  waxColor?: ColorRepresentation;\n  /** Iron color. Defaults to `0x2b2622`. */\n  ironColor?: ColorRepresentation;\n  /** Halo card size in world units. Defaults to `0.48`. */\n  glowSize?: number;\n  /** Halo opacity at flicker peak. Defaults to `0.42`. */\n  glowOpacity?: number;\n  /**\n   * Intensity of the rack's single {@link PointLight}. Defaults to `0`, which omits the light entirely —\n   * the flames are unlit emissive geometry and the halos are additive, so the rack reads as a light\n   * source while costing no light budget at all.\n   *\n   * Lights are a fixed budget capped independently of geometry; a hundred votives must never mean a\n   * hundred lights. When set, one light serves the whole rack and its intensity follows the *mean* of\n   * every fake flame, so it brightens when many happen to flare rather than tracking any one.\n   */\n  intensity?: number;\n  /**\n   * Supply a halo falloff instead of the library's canonical ramp — build one with\n   * {@link createRadialGradientTexture} if you want different stops or easing.\n   *\n   * Deliberately a *texture* rather than stops-and-easing options: one ramp shared by the whole rack is\n   * what keeps a large population cheap, and a per-asset easing dial would let a rack drift visually\n   * away from a single {@link GlowHalo} standing beside it. The caller owns and disposes what it passes.\n   */\n  haloMap?: DataTexture;\n  /** Override the iron material. */\n  ironMaterial?: Material;\n  /** Override the wax material. */\n  waxMaterial?: Material;\n}\n\ninterface Placement {\n  x: number;\n  y: number;\n  z: number;\n  height: number;\n  phase: number;\n  lit: boolean;\n}\n\n/**\n * A tiered rack of votive candles — tens or hundreds of them, in **four draw calls**.\n *\n * Whatever the population, the rack draws: one merged iron frame, one wax batch, one flame batch, one\n * halo batch. Raising the count multiplies triangles, not draws.\n *\n * **Per-instance variety without per-instance objects.** Candle heights vary, some cups are empty, some\n * candles are spent, and every flame flickers on its own phase — all of it carried in instance matrices\n * and instanced attributes rather than in separate `Mesh`es.\n *\n * The **presence cascade** (`density` → `litFraction`) is why the three batches have three different\n * counts: every present candle gets wax, only lit ones get a flame and a halo. Absence cannot be a\n * per-instance value, so the batches are sized to the survivors.\n *\n * **Per-candle flicker with one shared material** works because the halo blending is additive: folding\n * the flicker factor into per-instance *color* is mathematically identical to scaling opacity, so a\n * single material serves N independently guttering halos.\n *\n * > **Requires `WebGPURenderer`.** Screen-aligned instancing needs a node material\n * > (`SpriteNodeMaterial`) to build each halo's quad in the vertex shader. A single {@link GlowHalo} is\n * > renderer-agnostic; a *batch* of them is not.\n *\n * @example\n * ```typescript\n * const rack = new VotiveRack({ seed: 7, rows: 5, columns: 12, intensity: 1.4 });\n * scene.add(rack);\n *\n * function animate(elapsed: number) {\n *   rack.update(elapsed);\n *   renderer.render(scene, camera);\n * }\n * ```\n *\n * Call {@link dispose} when removing the rack.\n */\nexport class VotiveRack extends Group {\n  /** Every present candle. */\n  readonly waxInstances: InstancedMesh;\n  /** Only the lit ones. */\n  readonly flameInstances: InstancedMesh;\n  /** Only the lit ones — screen-aligned, one draw call. */\n  readonly haloInstances: InstancedMesh;\n  /** The rack's single light, when `intensity > 0`. */\n  readonly light?: PointLight;\n\n  readonly #placements: Placement[];\n  readonly #lit: Placement[];\n  readonly #materials: Material[] = [];\n  readonly #geometries: BufferGeometry[] = [];\n  readonly #haloScales: Float32Array;\n  readonly #haloColors: Float32Array;\n  readonly #haloScaleAttribute: InstancedBufferAttribute;\n  readonly #haloColorAttribute: InstancedBufferAttribute;\n  readonly #tint: Color;\n  readonly #glowSize: number;\n  readonly #peakIntensity: number;\n  readonly #dummy = new Object3D();\n\n  constructor({\n    seed,\n    rows = 4,\n    columns = 8,\n    width = 2.2,\n    rowRise = 0.28,\n    rowDepth = 0.18,\n    baseHeight = 0.55,\n    density = 0.9,\n    litFraction = 0.72,\n    candleHeightMin = 0.055,\n    candleHeightMax = 0.185,\n    color = 0xffb347,\n    waxColor = 0xd9cdb2,\n    ironColor = 0x2b2622,\n    glowSize = 0.48,\n    glowOpacity = 0.42,\n    intensity = 0,\n    haloMap,\n    ironMaterial,\n    waxMaterial,\n  }: VotiveRackOptions = {}) {\n    super();\n\n    const source = createRandom(seed);\n    this.#tint = new Color(color);\n    this.#glowSize = glowSize;\n    this.#peakIntensity = intensity;\n\n    // --- iron frame: merged, so the whole fixture is one draw call ------------\n    const iron = ironMaterial ?? new MeshStandardMaterial({ color: new Color(ironColor), flatShading: true });\n    this.#materials.push(iron);\n\n    const topY = baseHeight + (rows - 1) * rowRise;\n\n    // Z of a shelf, centered so the rack still straddles z=0 whatever the row count. Row 0 is the LOWEST shelf\n    // and sits FORWARD; each row above steps back by `rowDepth` — stadium seating, so every candle is visible\n    // and reachable over the row in front of it. Shared by the frame and the candle placement below: the two\n    // must agree exactly, since the candles stand on these shelves.\n    const shelfZ = (row: number) => ((rows - 1) / 2 - row) * rowDepth;\n\n    const frameParts: BufferGeometry[] = [];\n    for (const x of [-width / 2, width / 2]) {\n      frameParts.push(new CylinderGeometry(0.045, 0.045, topY, 6).translate(x, topY / 2, 0));\n    }\n    frameParts.push(new BoxGeometry(width + 0.16, 0.065, 0.065).translate(0, topY, 0));\n    for (let row = 0; row < rows; row++) {\n      const y = baseHeight + row * rowRise;\n      frameParts.push(new BoxGeometry(width + 0.1, 0.045, 0.25).translate(0, y, shelfZ(row)));\n    }\n    const frameGeometry = mergeGeometries(frameParts, false) as BufferGeometry;\n    frameParts.forEach((part) => part.dispose());\n    this.#geometries.push(frameGeometry);\n    const frame = new Mesh(frameGeometry, iron);\n    frame.castShadow = true;\n    this.add(frame);\n\n    // --- pass one: resolve the presence cascade before allocating anything ----\n    // Counts are unknown until the cascade has run, and an InstancedMesh is fixed-size, so placement\n    // has to complete first.\n    const placements: Placement[] = [];\n    const columnStep = width / Math.max(1, columns - 0.35);\n    for (let row = 0; row < rows; row++) {\n      const y = baseHeight + row * rowRise;\n      const z = shelfZ(row);\n      for (let column = 0; column < columns; column++) {\n        // Rolled only when density < 1, so a full rack draws the same sequence a seed always did.\n        if (density < 1 && source.next() >= density) continue;\n        placements.push({\n          x: -width / 2 + columnStep * (column + 0.18),\n          y: y + 0.025,\n          z,\n          height: source.float(candleHeightMin, candleHeightMax),\n          phase: source.float(0, 40),\n          lit: litFraction >= 1 || source.next() < litFraction,\n        });\n      }\n    }\n    this.#placements = placements;\n    this.#lit = placements.filter((p) => p.lit);\n\n    const presentCount = Math.max(placements.length, 1);\n    const litCount = Math.max(this.#lit.length, 1);\n\n    // --- wax: one batch, height varies via the instance matrix ----------------\n    const waxHeight = 0.12;\n    const waxGeometry = new CylinderGeometry(0.043, 0.05, waxHeight, 7);\n    waxGeometry.translate(0, waxHeight / 2, 0);\n    this.#geometries.push(waxGeometry);\n    const wax = waxMaterial ?? new MeshStandardMaterial({ color: new Color(waxColor), roughness: 0.9, flatShading: true });\n    this.#materials.push(wax);\n\n    this.waxInstances = new InstancedMesh(waxGeometry, wax, presentCount);\n    this.waxInstances.castShadow = true;\n    placements.forEach((p, i) => {\n      this.#dummy.position.set(p.x, p.y, p.z);\n      this.#dummy.scale.set(1, p.height / waxHeight, 1);\n      this.#dummy.updateMatrix();\n      this.waxInstances.setMatrixAt(i, this.#dummy.matrix);\n    });\n    this.waxInstances.instanceMatrix.needsUpdate = true;\n    this.add(this.waxInstances);\n\n    // --- flame: one batch, the guttering stretch rides in the matrix ----------\n    const flameGeometry = new CylinderGeometry(0.002, 0.024, 0.08, 5);\n    flameGeometry.translate(0, 0.04, 0);\n    this.#geometries.push(flameGeometry);\n    const flameMaterial = new MeshBasicMaterial({ color: this.#tint, toneMapped: false, fog: false });\n    this.#materials.push(flameMaterial);\n\n    this.flameInstances = new InstancedMesh(flameGeometry, flameMaterial, litCount);\n    this.flameInstances.instanceMatrix.setUsage(DynamicDrawUsage);\n    this.add(this.flameInstances);\n\n    // --- halos: one batch, screen-aligned in the vertex shader ----------------\n    // Defaults to the library's canonical ramp — the same texture a single `GlowHalo` uses, so one\n    // candle and a rack of two hundred are identical by construction rather than by both restating the\n    // same stops. A supplied `haloMap` belongs to the caller and is never disposed here.\n    const haloTexture = haloMap ?? glowFalloffTexture();\n    const offsets = new Float32Array(litCount * 3);\n    this.#haloScales = new Float32Array(litCount);\n    this.#haloColors = new Float32Array(litCount * 3);\n    this.#lit.forEach((p, i) => {\n      offsets[i * 3] = p.x;\n      offsets[i * 3 + 1] = p.y + p.height + 0.035;\n      offsets[i * 3 + 2] = p.z;\n      this.#haloScales[i] = glowSize;\n      this.#haloColors[i * 3] = this.#tint.r;\n      this.#haloColors[i * 3 + 1] = this.#tint.g;\n      this.#haloColors[i * 3 + 2] = this.#tint.b;\n    });\n\n    this.#haloScaleAttribute = new InstancedBufferAttribute(this.#haloScales, 1);\n    this.#haloColorAttribute = new InstancedBufferAttribute(this.#haloColors, 3);\n    this.#haloScaleAttribute.setUsage(DynamicDrawUsage);\n    this.#haloColorAttribute.setUsage(DynamicDrawUsage);\n\n    const haloMaterial = new SpriteNodeMaterial({\n      blending: AdditiveBlending,\n      transparent: true,\n      depthWrite: false,\n      toneMapped: false,\n      fog: false,\n    });\n    haloMaterial.positionNode = instancedBufferAttribute(new InstancedBufferAttribute(offsets, 3), \"vec3\");\n    haloMaterial.scaleNode = instancedDynamicBufferAttribute(this.#haloScaleAttribute, \"float\");\n    haloMaterial.colorNode = instancedDynamicBufferAttribute(this.#haloColorAttribute, \"vec3\");\n    // The ramp is colorless, so only its alpha matters here; the tint arrives per instance.\n    haloMaterial.opacityNode = texture(haloTexture).a.mul(glowOpacity);\n    this.#materials.push(haloMaterial);\n\n    const haloGeometry = new PlaneGeometry(1, 1);\n    this.#geometries.push(haloGeometry);\n    this.haloInstances = new InstancedMesh(haloGeometry, haloMaterial, litCount);\n    // `positionNode` replaces the vertex position, so computed bounds are meaningless.\n    this.haloInstances.frustumCulled = false;\n    const identity = new Matrix4();\n    for (let i = 0; i < litCount; i++) this.haloInstances.setMatrixAt(i, identity);\n    this.haloInstances.instanceMatrix.needsUpdate = true;\n    this.add(this.haloInstances);\n\n    if (intensity > 0) {\n      this.light = new PointLight(this.#tint, intensity, width * 2, 2);\n      this.light.position.set(0, baseHeight + (rows - 1) * rowRise * 0.5, 0);\n      this.add(this.light);\n    }\n  }\n\n  /** Present candles, lit or spent. */\n  get candleCount(): number {\n    return this.#placements.length;\n  }\n\n  /** Lit candles — the flame and halo batch size. */\n  get litCount(): number {\n    return this.#lit.length;\n  }\n\n  /** `elapsed` in seconds — the same clock any other flame in the scene advances on. */\n  update(elapsed: number): void {\n    let sum = 0;\n\n    this.#lit.forEach((p, i) => {\n      const f = flameFlicker(elapsed, p.phase);\n      sum += f;\n\n      this.#haloScales[i] = this.#glowSize * (0.95 + f * 0.08);\n      this.#haloColors[i * 3] = this.#tint.r * f;\n      this.#haloColors[i * 3 + 1] = this.#tint.g * f;\n      this.#haloColors[i * 3 + 2] = this.#tint.b * f;\n\n      this.#dummy.position.set(p.x, p.y + p.height, p.z);\n      this.#dummy.scale.set(1, 0.88 + f * 0.18, 1);\n      this.#dummy.updateMatrix();\n      this.flameInstances.setMatrixAt(i, this.#dummy.matrix);\n    });\n\n    this.#haloScaleAttribute.needsUpdate = true;\n    this.#haloColorAttribute.needsUpdate = true;\n    this.flameInstances.instanceMatrix.needsUpdate = true;\n\n    if (this.light && this.#lit.length) {\n      this.light.intensity = this.#peakIntensity * (sum / this.#lit.length);\n    }\n  }\n\n  /** Release geometries and materials. The halo ramp is a shared singleton and is deliberately kept. */\n  dispose(): void {\n    this.#geometries.forEach((geometry) => geometry.dispose());\n    this.#materials.forEach((material) => material.dispose());\n    this.waxInstances.dispose();\n    this.flameInstances.dispose();\n    this.haloInstances.dispose();\n  }\n}\n","import { mulberry32 } from \"../../utils/Random\";\n\n/** A rectangle on the surface, from its lower-left corner. Used to keep stones off things. */\nexport interface SurfaceRect {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nexport interface ProudStoneOptions {\n  /** Extent of the surface being decorated. Defaults to `3.2`. */\n  width?: number;\n  /** Defaults to `2.6`. */\n  height?: number;\n\n  /**\n   * The course grid the stones must land on. Defaults to `0.26`.\n   *\n   * Not decoration — a proud stone has to sit ON a stone rather than across a joint, so it needs the same\n   * grid the wall was built to. Give it the wall's own numbers.\n   */\n  courseHeight?: number;\n  /** A whole stone's length, as a multiple of the course. Defaults to `2.2`. */\n  stoneAspect?: number;\n  /** How far alternate courses start along a stone. Defaults to `0.5`. */\n  bondOffset?: number;\n\n  /**\n   * Chance a cell carries a proud stone. Defaults to `0.14`.\n   *\n   * A chance PER CELL, not a count — the grid stays regular and the result does not clump the way sampling\n   * positions at random would.\n   */\n  density?: number;\n\n  /**\n   * Length range, as multiples of a whole stone. Defaults to `0.72`–`1.12`.\n   *\n   * **The brick/stone dial.** Collapse a range and every proud stone is the same unit that has popped,\n   * which is brick; open it and each came from its own mold, which is stone.\n   */\n  lengthMin?: number;\n  lengthMax?: number;\n  /** Height range, as multiples of the course. Defaults to `0.8`–`0.92`. */\n  heightMin?: number;\n  heightMax?: number;\n  /** How far it stands out of the surface, in world units. Defaults to `0.024`–`0.056`. */\n  depthMin?: number;\n  depthMax?: number;\n\n  /** Max roll, radians. Defaults to `0.025`. */\n  tilt?: number;\n  /**\n   * Rectangles nothing may be placed across — an opening, a quoin, a doorway.\n   *\n   * **This is what lets the scatter stay a surface operation.** Its only two wall-aware rules were \"not on\n   * a quoin\" and \"not across a slit\", and both are COMPOSITION rather than masonry. Handed in, the scatter\n   * composes with anything without ever learning what it is composing with.\n   */\n  exclusions?: SurfaceRect[];\n  /** Defaults to `0x2c1a`. */\n  seed?: number;\n}\n\n/** One stone standing proud. Where and how big — what it is made of is the caller's business. */\nexport interface ProudStone {\n  /** Center on the surface, from its lower-left corner. */\n  x: number;\n  y: number;\n  /** Along the course — the stretcher face. */\n  length: number;\n  /** Up — the course. */\n  height: number;\n  /** How far it stands out of the surface. */\n  depth: number;\n  /** Roll, radians. */\n  tilt: number;\n}\n\nexport interface ProudStoneScatter {\n  placements: ProudStone[];\n  /** Cells considered. `placements.length / candidates` is the density actually achieved. */\n  candidates: number;\n  /** Cells skipped for landing on an exclusion. */\n  excluded: number;\n}\n\n/**\n * Where stones stand proud of a surface.\n *\n * **Takes a rectangle, not a wall.** A width, a height and a course grid is everything it needs, so the\n * same call decorates a wall, a pier, a chimney, a plinth or an arched slab. Returns placements rather\n * than geometry, so the caller decides how far each block sinks and what it is made of.\n *\n * **The block must be half-embedded, not sat on the face.** Build each one deeper than its `depth` and\n * push it out by exactly `depth`, leaving the rest buried:\n *\n * ```ts\n * const solid = new BoxGeometry(length, height, stoneWidth);\n * solid.rotateZ(tilt);\n * solid.translate(x, y, surfaceThickness / 2 + depth - stoneWidth / 2);\n * ```\n *\n * Sunk further than it stands out, so the join at its foot is inside solid material rather than on it and\n * no two faces land coplanar. Flush-backed, it becomes a sticker: same silhouette, wrong shadow.\n *\n * Every dimension is a MULTIPLIER on the course rather than an absolute, so one set of numbers reads the\n * same on a garden wall and a bell tower.\n *\n * **Distinct from {@link StoneWall}'s own `proudChance`, and both are right.** The wall MOVES stones it\n * already has; this ADDS blocks to a surface. Use the wall's when the face is built from real stones, and\n * this when it is a slab you cannot take apart.\n *\n * @example\n * ```ts\n * const { placements } = scatterProudStones({\n *   width: 3.2,\n *   height: 2.6,\n *   density: 0.14,\n *   exclusions: [{ x: 1.1, y: 0.8, width: 0.9, height: 1.4 }], // a window\n * });\n * ```\n */\nexport function scatterProudStones({\n  width = 3.2,\n  height = 2.6,\n  courseHeight = 0.26,\n  stoneAspect = 2.2,\n  bondOffset = 0.5,\n  density = 0.14,\n  lengthMin = 0.72,\n  lengthMax = 1.12,\n  heightMin = 0.8,\n  heightMax = 0.92,\n  depthMin = 0.024,\n  depthMax = 0.056,\n  tilt = 0.025,\n  exclusions = [],\n  seed = 0x2c1a,\n}: ProudStoneOptions = {}): ProudStoneScatter {\n  const random = mulberry32(seed);\n  const signed = (amount: number) => (random() - 0.5) * 2 * amount;\n  // Ranges given either way round, because a slider pair will cross sooner or later.\n  const between = (min: number, max: number) =>\n    Math.min(min, max) + random() * Math.abs(max - min);\n\n  const courses = Math.max(1, Math.round(height / courseHeight));\n  const step = height / courses;\n  const nominal = step * stoneAspect;\n\n  const placements: ProudStone[] = [];\n  let candidates = 0;\n  let excluded = 0;\n\n  for (let c = 0; c < courses; c++) {\n    const y = (c + 0.5) * step;\n    // The same running bond the wall itself uses, so a proud stone lands ON a stone rather than across a\n    // perpend. This is the whole reason it needs the course grid and not merely the rectangle.\n    const offset = (c % 2) * nominal * bondOffset;\n\n    for (let s = 0; ; s++) {\n      const x = offset + s * nominal;\n      if (x + nominal > width) break;\n      candidates++;\n\n      // A stone hanging across an opening would float in the gap.\n      const clash = exclusions.some(\n        (rect) =>\n          x + nominal > rect.x &&\n          x < rect.x + rect.width &&\n          y + step / 2 > rect.y &&\n          y - step / 2 < rect.y + rect.height,\n      );\n      if (clash) {\n        excluded++;\n        continue;\n      }\n\n      if (random() > density) continue;\n\n      placements.push({\n        x: x + nominal / 2,\n        y: y + signed(step * 0.03),\n        length: nominal * between(lengthMin, lengthMax),\n        height: step * between(heightMin, heightMax),\n        depth: between(depthMin, depthMax),\n        tilt: signed(tilt),\n      });\n    }\n  }\n\n  return { placements, candidates, excluded };\n}\n","import {\n  BoxGeometry,\n  BufferAttribute,\n  Color,\n  Group,\n  Mesh,\n  MeshStandardMaterial,\n  type BufferGeometry,\n  type Material,\n} from \"three\";\nimport { mergeGeometries } from \"three/examples/jsm/utils/BufferGeometryUtils.js\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed, mulberry32 } from \"../../utils/Random\";\n\nexport interface StoneWallOptions {\n  /** Extent along X. Defaults to `3.2`. */\n  width?: number;\n  /** Extent along Y, from the ground up. Defaults to `3`. */\n  height?: number;\n  /** Extent along Z. Defaults to `0.34`. */\n  thickness?: number;\n\n  /**\n   * Target course height. Defaults to `0.26`.\n   *\n   * Courses are fitted to `height`, so this is a target and never leaves a sliver at the top. The number\n   * actually laid is reported as {@link StoneWall.courseHeight}.\n   */\n  courseHeight?: number;\n  /** A whole stone's length, as a multiple of the course. Defaults to `2.2`. */\n  stoneAspect?: number;\n  /**\n   * The mortar line. Defaults to `0.012`.\n   *\n   * **Taken OUT of the stone**, so the coursing keeps its pitch as the joint widens. That is the mason's\n   * convention — stone is cut to suit a course. Brick does the opposite, adding the joint to the pitch,\n   * because a brick arrives at a fixed size.\n   */\n  joint?: number;\n  /**\n   * How far alternate courses start along a stone. Defaults to `0.5` — a RUNNING BOND, so no vertical\n   * joint runs through. `0` is a STACK BOND: real, but nothing is bonded to anything.\n   */\n  bondOffset?: number;\n  /**\n   * The shortest stone worth cutting, as a fraction of a whole one. Defaults to `0.45`.\n   *\n   * **The only reason a course ends on anything but a whole stone.** Before laying, a stone that would\n   * strand an uncuttable remainder takes the remainder instead — so every course reaches the edge and no\n   * sliver is ever left. Below about `1 − lengthVariance` this governs only the closers; above it, it\n   * starts clipping every stone's low-side variance.\n   */\n  shortestStone?: number;\n\n  /**\n   * How much course heights differ from one another. Defaults to `0`.\n   *\n   * Per COURSE, never within one — a course that is not level is not a course. `0` is ASHLAR; above it is\n   * RANDOM COURSED. The courses are jittered and then normalized, so they still sum to `height` exactly.\n   */\n  courseVariance?: number;\n  /** How much stone lengths differ, as a fraction of a whole stone. Defaults to `0.22`. */\n  lengthVariance?: number;\n\n  /**\n   * Bed the stones in a mortar core. Defaults to `true`.\n   *\n   * Without it the joints are holes — at a hairline they read as shadow, but open the joint and you see\n   * daylight through the wall. `false` is a DRY STONE wall, which is a real thing and wants tight joints.\n   */\n  mortar?: boolean;\n  /**\n   * How far the core sits BEHIND the stonework, on EVERY axis. Defaults to `0.014`.\n   *\n   * Recessed rather than flush: a joint filled level with the face has no shadow and reads as a painted\n   * line. Raked back, it reads as a joint. It insets from the wall's ends and head as well as its faces,\n   * because the stones themselves stop `joint / 2` short of the nominal extent — a core built to full size\n   * would stand proud of the stonework there and ring the wall with a pale edge.\n   */\n  mortarRecess?: number;\n  /** Mortar tint. Defaults to `#b8b2a6`. */\n  mortarColor?: string;\n\n  /**\n   * How far each stone strays from its bed, in world units. Defaults to `0`.\n   *\n   * Displacement, not size. Together with {@link StoneWallOptions.tilt} this takes a wall from newly built\n   * to long-standing — a stylized read rather than masonry truth, which is why both default to nothing.\n   */\n  settle?: number;\n  /**\n   * Max roll per stone, radians, about its own center. Defaults to `0`.\n   *\n   * Past about `0.038` a stone's corner reaches through the mortar recess, which is the decrepit look and\n   * is allowed. Note only the Z component stays in the wall's plane; X and Y tip the stone out of it and\n   * are what drive it into the core.\n   */\n  tilt?: number;\n\n  /** How far each stone sits in or out of the face, in world units. Defaults to `0.006`. */\n  depthVariance?: number;\n  /** Chance a stone stands notably PROUD. Defaults to `0.12`. */\n  proudChance?: number;\n  /** How far a proud stone stands out. Defaults to `0.03`. */\n  proudDepth?: number;\n\n  /** Base stone tint. Defaults to `#6a6560`. */\n  color?: string;\n  /** Per-stone tint spread in HSL. Defaults to `0.07` — mostly lightness, barely any hue. */\n  colorVariance?: number;\n  /** Per-stone sampler; overrides color/colorVariance. Index counts laid stones, excluding mortar; seeded color draws do not alter geometry. */\n  colors?: ColorSampler;\n  /** Defaults to `0x2c1a`. */\n  seed?: number;\n  /** A material to use instead of the default. **Must set `vertexColors: true`**, or every stone goes white. */\n  material?: Material;\n}\n\n/**\n * A coursed stone wall — **ASHLAR**: squared, dressed stone laid in level courses. Centered on X, foot on\n * `y = 0`, faces on ±Z.\n *\n * The wall is built stone by stone rather than as a slab with lines drawn on it, and three rules make it\n * read as masonry:\n *\n * - **A RUNNING BOND.** Alternate courses start part-way along a stone, so no vertical joint (a PERPEND)\n *   runs through. `bondOffset: 0` gives a stack bond, which is not a bond at all.\n * - **No course ever gives up.** A stone that would strand an uncuttable remainder takes the remainder\n *   instead, so every course reaches the edge and no sliver appears. The same rule {@link layPlankFloor}\n *   lays floors by — it belongs to LAYING, not to floors.\n * - **The joint comes OUT of the stone.** Stone is cut to suit a course, so widening the mortar does not\n *   move the coursing.\n *\n * **Three axes of variance, and they are not interchangeable.** `lengthVariance` and `depthVariance` are\n * per STONE; `courseVariance` is per COURSE, because a course that is not level is not a course. Their\n * ceilings are set low deliberately: variance compounds and ceilings do not, so a wall with this many\n * controls needs each one reined in.\n *\n * `settle` and `tilt` are **displacement**, not size — where a stone ended up rather than how big it is —\n * and are a stylized, decrepit read rather than masonry truth. Both default to nothing.\n *\n * **One geometry, one material, one draw call** at any size. Every stone differs, so they merge; the tint\n * rides a vertex attribute rather than a material group, which is what keeps it to a single call.\n *\n * @example\n * ```ts\n * const wall = new StoneWall({ width: 6, height: 4, seed: 12 });\n * scene.add(wall);\n * wall.stoneCount;  // stones laid\n * wall.closerCount; // how many were cut short to finish a course\n * ```\n */\nexport class StoneWall extends Group {\n  readonly mesh: Mesh;\n\n  /** Stones laid. */\n  readonly stoneCount: number;\n  /** Courses laid — fitted to `height`, so not necessarily `height / courseHeight`. */\n  readonly courseCount: number;\n  /** The course height actually used. */\n  readonly courseHeight: number;\n  /** Stones cut short to finish a course. */\n  readonly closerCount: number;\n  /** Stones that came out standing proud. */\n  readonly proudCount: number;\n\n  readonly #geometry: BufferGeometry;\n  readonly #material: Material;\n  readonly #ownsMaterial: boolean;\n\n  constructor({\n    width = 3.2,\n    height = 3,\n    thickness = 0.34,\n    courseHeight = 0.26,\n    stoneAspect = 2.2,\n    joint = 0.012,\n    bondOffset = 0.5,\n    shortestStone = 0.45,\n    courseVariance = 0,\n    lengthVariance = 0.22,\n    mortar = true,\n    mortarRecess = 0.014,\n    mortarColor = \"#b8b2a6\",\n    settle = 0,\n    tilt = 0,\n    depthVariance = 0.006,\n    proudChance = 0.12,\n    proudDepth = 0.03,\n    color = \"#6a6560\",\n    colors,\n    colorVariance = 0.07,\n    seed = 0x2c1a,\n    material,\n  }: StoneWallOptions = {}) {\n    super();\n\n    const random = mulberry32(seed);\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x77616c6c)) };\n    const signed = (amount: number) => (random() - 0.5) * 2 * amount;\n    const base = new Color(color);\n    const tint = new Color();\n\n    const courses = Math.max(1, Math.round(height / courseHeight));\n\n    // Course heights vary ACROSS courses and never within one. They must still sum to the wall exactly, so\n    // this is slack absorption: jitter every course, then normalize the set. Jittering independently and\n    // hoping would strand a remainder at the top — the runt problem standing on its end.\n    const weights = Array.from({ length: courses }, () => 1 + signed(courseVariance));\n    const weightTotal = weights.reduce((sum, w) => sum + w, 0);\n    const heights = weights.map((w) => (w / weightTotal) * height);\n\n    const stones: BufferGeometry[] = [];\n    let closers = 0;\n    let proud = 0;\n\n    const paint = (geometry: BufferGeometry, tone: Color) => {\n      const count = geometry.attributes.position!.count;\n      const colors = new Float32Array(count * 3);\n      for (let i = 0; i < count; i++) {\n        colors[i * 3] = tone.r;\n        colors[i * 3 + 1] = tone.g;\n        colors[i * 3 + 2] = tone.b;\n      }\n      geometry.setAttribute(\"color\", new BufferAttribute(colors, 3));\n      stones.push(geometry);\n    };\n\n    let below = 0;\n    for (let c = 0; c < courses; c++) {\n      const course = heights[c]!;\n      // A taller course carries proportionally longer stones — the aspect belongs to the stone.\n      const nominal = course * stoneAspect;\n      const y = below + course / 2;\n      below += course;\n\n      const offset = (c % 2) * nominal * bondOffset;\n      const shortest = nominal * shortestStone;\n      let x = 0;\n\n      while (x < width - 1e-6) {\n        const remaining = width - x;\n        // A course opens with a CLOSER taking up the bond offset, rather than hanging a whole stone off\n        // the corner.\n        const wanted =\n          c % 2 === 1 && x === 0 && offset > 1e-6 ? Math.max(offset, shortest) : nominal * (1 + signed(lengthVariance));\n\n        let length = Math.min(Math.max(wanted, shortest), remaining);\n        // NO RUNT. If putting this stone in would strand a remainder too short to cut, take the remainder\n        // now. Without it the last stone is whatever is left — sometimes a sliver, and sometimes so little\n        // that the course gives up short of the edge.\n        if (remaining - length < shortest) length = remaining;\n        if (length < nominal * 0.75) closers++;\n\n        const cut = Math.max(length - joint, course * 0.15);\n\n        // Depth. Every stone sits a little in or out, and a few stand notably proud. The stone is GROWN or\n        // SHRUNK rather than slid, so its back stays flush — sliding would open a hole behind every proud\n        // stone, and the through-joints would show it.\n        let out = signed(depthVariance);\n        if (random() < proudChance) {\n          out += proudDepth * (0.7 + random() * 0.9);\n          proud++;\n        }\n        const depth = Math.max(course * 0.15, thickness + out);\n\n        const block = new BoxGeometry(cut, course - joint, depth);\n        // Rotate about the stone's own center first, then move it — the geometry is born centered, so this\n        // is a spin in place rather than a swing about the wall's origin.\n        if (tilt > 0) {\n          block.rotateX(signed(tilt));\n          block.rotateY(signed(tilt));\n          block.rotateZ(signed(tilt));\n        }\n        block.translate(x + length / 2 + signed(settle), y + signed(settle), (depth - thickness) / 2 + signed(settle));\n\n        tint.copy(base).offsetHSL(signed(colorVariance) / 4, signed(colorVariance) / 2, signed(colorVariance));\n        // Retain the legacy draws above; custom sampling cannot change subsequent stones.\n        if (colors) {\n          colorContext.index = stones.length;\n          colors(tint, colorContext);\n        }\n        paint(block, tint);\n\n        x += length;\n      }\n    }\n\n    // The mortar core: one box behind everything, recessed from EVERY face so each joint reads as a joint\n    // rather than a hole. Painted with the same vertex colors, so the wall is still one draw call.\n    //\n    // Recessed on all three axes, not just the thickness. The stones do not reach the wall's nominal\n    // extent — each is `length - joint` wide and `course - joint` tall, so the stonework stops `joint / 2`\n    // short at every edge. A core built to the full width and height therefore stands PROUD of the\n    // stonework at the ends and at the head, and its pale edge reads as a band round the wall. Which is\n    // exactly wrong: the core is meant to be the thing you glimpse BEHIND the stones, never past them.\n    if (mortar) {\n      const inset = (extent: number) => Math.max(extent * 0.15, extent - mortarRecess * 2);\n      const core = new BoxGeometry(inset(width), inset(height), inset(thickness));\n      core.translate(width / 2, height / 2, 0);\n      paint(core, new Color(mortarColor));\n    }\n\n    const merged = mergeGeometries(stones, false);\n    stones.forEach((part) => part.dispose());\n    if (!merged) throw new Error(\"StoneWall: merge failed — the wall may be smaller than one stone.\");\n    // Centered on X, foot on y = 0.\n    merged.translate(-width / 2, 0, 0);\n\n    this.#ownsMaterial = material === undefined;\n    this.#material =\n      material ??\n      // White, so the vertex color lands as the exact tint rather than multiplying into it.\n      new MeshStandardMaterial({\n        color: 0xffffff,\n        vertexColors: true,\n        roughness: 0.95,\n        metalness: 0,\n        flatShading: true,\n      });\n\n    this.#geometry = merged;\n    this.stoneCount = stones.length - (mortar ? 1 : 0);\n    this.courseCount = courses;\n    this.courseHeight = height / courses;\n    this.closerCount = closers;\n    this.proudCount = proud;\n\n    this.mesh = new Mesh(merged, this.#material);\n    this.mesh.castShadow = true;\n    this.mesh.receiveShadow = true;\n    this.add(this.mesh);\n  }\n\n  /** Releases the merged geometry, and the material when this wall made it. */\n  dispose(): void {\n    this.#geometry.dispose();\n    if (this.#ownsMaterial) this.#material.dispose();\n  }\n}\n","/**\n * Coherent value noise + fbm — the shared core for baking procedural detail into\n * real geometry. Coherence is the whole point: neighboring samples return nearby\n * values, so vertices displaced by these functions move together and faces never\n * tear the way independent per-vertex random offsets do.\n *\n * - {@link fbm2} — heightfields (terrain: `Y = f(x, z)`).\n * - {@link fbm3} — surfaces displaced in 3D (boulders: push along the normal).\n */\n\n/** 32-bit integer hash → [0, 1). Deterministic per lattice cell + seed. */\nfunction hash2(ix: number, iy: number, seed: number): number {\n  let h = Math.imul(ix | 0, 374761393) ^ Math.imul(iy | 0, 668265263) ^ Math.imul(seed | 0, 362437);\n  h = Math.imul(h ^ (h >>> 13), 1274126177);\n  h ^= h >>> 16;\n  return (h >>> 0) / 4294967296;\n}\n\nfunction hash3(ix: number, iy: number, iz: number, seed: number): number {\n  let h =\n    Math.imul(ix | 0, 374761393) ^\n    Math.imul(iy | 0, 668265263) ^\n    Math.imul(iz | 0, 1274126177) ^\n    Math.imul(seed | 0, 362437);\n  h = Math.imul(h ^ (h >>> 13), 1274126177);\n  h ^= h >>> 16;\n  return (h >>> 0) / 4294967296;\n}\n\nconst smooth = (t: number) => t * t * (3 - 2 * t);\n\n/** Smoothly interpolated 2D value noise on a unit lattice → [0, 1). */\nfunction valueNoise2(x: number, y: number, seed: number): number {\n  const x0 = Math.floor(x);\n  const y0 = Math.floor(y);\n  const u = smooth(x - x0);\n  const v = smooth(y - y0);\n  const n00 = hash2(x0, y0, seed);\n  const n10 = hash2(x0 + 1, y0, seed);\n  const n01 = hash2(x0, y0 + 1, seed);\n  const n11 = hash2(x0 + 1, y0 + 1, seed);\n  const nx0 = n00 + (n10 - n00) * u;\n  const nx1 = n01 + (n11 - n01) * u;\n  return nx0 + (nx1 - nx0) * v;\n}\n\n/** Smoothly interpolated 3D value noise on a unit lattice → [0, 1). */\nfunction valueNoise3(x: number, y: number, z: number, seed: number): number {\n  const x0 = Math.floor(x);\n  const y0 = Math.floor(y);\n  const z0 = Math.floor(z);\n  const u = smooth(x - x0);\n  const v = smooth(y - y0);\n  const w = smooth(z - z0);\n  const c000 = hash3(x0, y0, z0, seed);\n  const c100 = hash3(x0 + 1, y0, z0, seed);\n  const c010 = hash3(x0, y0 + 1, z0, seed);\n  const c110 = hash3(x0 + 1, y0 + 1, z0, seed);\n  const c001 = hash3(x0, y0, z0 + 1, seed);\n  const c101 = hash3(x0 + 1, y0, z0 + 1, seed);\n  const c011 = hash3(x0, y0 + 1, z0 + 1, seed);\n  const c111 = hash3(x0 + 1, y0 + 1, z0 + 1, seed);\n  const x00 = c000 + (c100 - c000) * u;\n  const x10 = c010 + (c110 - c010) * u;\n  const x01 = c001 + (c101 - c001) * u;\n  const x11 = c011 + (c111 - c011) * u;\n  const y0v = x00 + (x10 - x00) * v;\n  const y1v = x01 + (x11 - x01) * v;\n  return y0v + (y1v - y0v) * w;\n}\n\n/**\n * 2D fractal Brownian motion → roughly [-1, 1]. Each octave doubles frequency and\n * scales amplitude by `persistence`, with its own seed offset so layers don't\n * correlate. Normalized by total amplitude so the range is stable across octave counts.\n */\nexport function fbm2(x: number, y: number, seed: number, octaves: number, persistence: number): number {\n  let sum = 0;\n  let amplitude = 1;\n  let frequency = 1;\n  let norm = 0;\n  for (let o = 0; o < octaves; o++) {\n    sum += (valueNoise2(x * frequency, y * frequency, seed + o * 101) * 2 - 1) * amplitude;\n    norm += amplitude;\n    amplitude *= persistence;\n    frequency *= 2;\n  }\n  return norm > 0 ? sum / norm : 0;\n}\n\n/** 3D fractal Brownian motion → roughly [-1, 1]. The 3D counterpart to {@link fbm2}. */\nexport function fbm3(\n  x: number,\n  y: number,\n  z: number,\n  seed: number,\n  octaves: number,\n  persistence: number,\n): number {\n  let sum = 0;\n  let amplitude = 1;\n  let frequency = 1;\n  let norm = 0;\n  for (let o = 0; o < octaves; o++) {\n    sum +=\n      (valueNoise3(x * frequency, y * frequency, z * frequency, seed + o * 101) * 2 - 1) * amplitude;\n    norm += amplitude;\n    amplitude *= persistence;\n    frequency *= 2;\n  }\n  return norm > 0 ? sum / norm : 0;\n}\n","import { BufferGeometry, IcosahedronGeometry, Vector3 } from \"three\";\nimport { mergeVertices } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { fbm3 } from \"../../utils/CoherentNoise\";\n\nexport interface BoulderGeometryOptions {\n  /** Base radius before displacement (world units). Defaults to `1`. */\n  radius?: number;\n  /** Icosahedron subdivision — more detail = more, finer facets. Defaults to `2`. */\n  detail?: number;\n  /** Radial relief amplitude (world units, ±). Keep below `radius`. Defaults to `0.35`. */\n  noiseHeight?: number;\n  /** Noise frequency over the unit sphere — higher packs more, smaller lumps. Defaults to `1.6`. */\n  noiseScale?: number;\n  /** fbm octaves (detail layers). Defaults to `3`. */\n  octaves?: number;\n  /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */\n  persistence?: number;\n  /** Seed for reproducible shape. Defaults to `1`. */\n  seed?: number;\n}\n\n/**\n * Boulder — an icosphere lumped by coherent 3D fbm ({@link fbm3}) displaced along\n * each vertex's radial direction. The 3D counterpart to the terrain heightfields:\n * same noise strategy, applied over a closed surface instead of a height map.\n *\n * The icosphere is welded (`mergeVertices` after stripping seam UVs) so coincident\n * vertices share one displacement — the coherent noise then moves neighbors together,\n * so the surface stays watertight and never cracks (the failure mode of displacing a\n * non-indexed polyhedron along normals). Real baked geometry, so shadows, raycasts,\n * and physics colliders match what's drawn, on WebGL and WebGPU/TSL alike.\n *\n * Centered on the origin. Pair with a `flatShading` material for a faceted low-poly\n * look. Vary `seed` per instance to fill a field with unique boulders.\n */\nexport class BoulderGeometry extends BufferGeometry {\n  readonly radius: number;\n\n  constructor({\n    radius = 1,\n    detail = 2,\n    noiseHeight = 0.35,\n    noiseScale = 1.6,\n    octaves = 3,\n    persistence = 0.5,\n    seed = 1,\n  }: BoulderGeometryOptions = {}) {\n    super();\n\n    this.radius = radius;\n\n    // Strip seam UVs/normals before welding so coincident vertices actually merge —\n    // otherwise the icosphere stays split at UV seams and displacing along normals\n    // would pull those duplicates apart into cracks.\n    const base = new IcosahedronGeometry(radius, detail);\n    base.deleteAttribute(\"uv\");\n    base.deleteAttribute(\"normal\");\n    const merged = mergeVertices(base);\n    base.dispose();\n\n    const position = merged.getAttribute(\"position\");\n    const v = new Vector3();\n    // Sample noise on the unit sphere (position / radius) so lump count stays\n    // consistent regardless of radius; displace radially outward/inward.\n    const s = noiseScale / radius;\n    for (let i = 0; i < position.count; i++) {\n      v.fromBufferAttribute(position, i);\n      const n = fbm3(v.x * s, v.y * s, v.z * s, seed, octaves, persistence);\n      const length = v.length() || 1;\n      const scale = (length + n * noiseHeight) / length;\n      position.setXYZ(i, v.x * scale, v.y * scale, v.z * scale);\n    }\n    position.needsUpdate = true;\n\n    merged.computeVertexNormals();\n    merged.center();\n\n    this.copy(merged);\n    merged.dispose();\n  }\n}\n","import {\n  Color,\n  ColorRepresentation,\n  Euler,\n  Group,\n  InstancedMesh,\n  Material,\n  Matrix4,\n  MeshStandardMaterial,\n  Quaternion,\n  Vector3,\n} from \"three\";\nimport { BoulderGeometry } from \"../../geometry/rocks/BoulderGeometry\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\nimport type { RockScatterPlacementOptions } from \"./RockFactory\";\n\nexport interface ScatterBouldersOptions extends RockScatterPlacementOptions {\n  /** Base radius for each boulder geometry. Defaults to `1`. */\n  radius?: number;\n  /** Icosphere subdivision per boulder. Defaults to `2`. */\n  detail?: number;\n  /** Radial relief amplitude per boulder. Defaults to `0.35`. */\n  noiseHeight?: number;\n  /** Noise frequency per boulder. Defaults to `1.6`. */\n  noiseScale?: number;\n  /** fbm octaves per boulder. Defaults to `3`. */\n  octaves?: number;\n  /** fbm gain per octave. Defaults to `0.5`. */\n  persistence?: number;\n  /**\n   * Distinct boulder geometries generated and distributed across the field. Each is\n   * a real, unique lumped shape (unlike rotating one shared mesh); instances round-robin\n   * across them, so the field stays batch-friendly (one draw call per variant).\n   * Defaults to `4`.\n   */\n  variants?: number;\n  /** Override the default stone material (shared across all instances). */\n  material?: Material;\n  /** Stone tint when `material` is omitted. Defaults to `#6f6f6f`. */\n  color?: ColorRepresentation;\n  /** Per-boulder color overriding color. Index follows scatter order before variant batching. Custom material colors still multiply the tint. */\n  colors?: ColorSampler;\n}\n\n/**\n * Scatter instanced boulders through a horizontal bounds region — a \"created layer\" of the\n * noise-lumped {@link BoulderGeometry}. Unlike {@link scatterRocks}, which rotates a single\n * shared shape, this generates several distinct boulder geometries (see `variants`) and\n * distributes instances across them, so the group reads as unique rocks while staying to a\n * few draw calls.\n *\n * **Scatter, not field.** Placement is stochastic — a `count` distributed pseudo-randomly\n * within the bounds — so the name states the operation rather than one of its uses. A\n * \"field\" in this library means a laid-out grid (see {@link fieldOfHeadstones}, which walks\n * rows and columns at fixed spacing), and this is not that. The same scatter serves a\n * boulder field, stones set proud of a wall, rubble along a path, or rocks in a streambed;\n * naming it for any one of them would narrow it and misdescribe how it places.\n *\n * Returns a {@link Group} of {@link InstancedMesh}es (one per variant), each sharing the\n * stone material. Pass a `seed` to make a scatter reproducible. Dispose each child's\n * geometry and the shared material when removing it.\n *\n * @example\n * ```ts\n * const boulders = scatterBoulders({ count: 24, width: 12, depth: 12, seed: 1337 });\n * scene.add(boulders);\n * ```\n */\nexport function scatterBoulders({\n  count = 8,\n  width = 6,\n  depth = 6,\n  heightJitter = 0,\n  scaleMin = 0.8,\n  scaleMax = 1.2,\n  seed,\n  radius = 1,\n  detail = 2,\n  noiseHeight = 0.35,\n  noiseScale = 1.6,\n  octaves = 3,\n  persistence = 0.5,\n  variants = 4,\n  material,\n  color = \"#6f6f6f\",\n  colors,\n}: ScatterBouldersOptions = {}): Group {\n  const source = createRandom(seed);\n  const context = { index: 0, random: createRandom(seed === undefined ? undefined : deriveSubSeed(seed, 0x626f756c)) };\n  const tint = new Color();\n  const variantCount = Math.max(1, Math.min(Math.round(variants), Math.max(1, count)));\n\n  const boulderMaterial =\n    material ??\n    new MeshStandardMaterial({ color: new Color(colors ? 0xffffff : color), roughness: 1, metalness: 0, flatShading: true });\n\n  // One distinct geometry per variant, each seeded from the scatter stream so the whole\n  // field is reproducible when `seed` is given.\n  const geometries: BoulderGeometry[] = [];\n  for (let v = 0; v < variantCount; v++) {\n    geometries.push(\n      new BoulderGeometry({\n        radius,\n        detail,\n        noiseHeight,\n        noiseScale,\n        octaves,\n        persistence,\n        seed: source.int(0, 1_000_000),\n      }),\n    );\n  }\n\n  // Round-robin instance counts across variants.\n  const perVariant = new Array<number>(variantCount).fill(0);\n  for (let i = 0; i < count; i++) perVariant[i % variantCount]++;\n\n  const meshes = geometries.map((geometry, v) => new InstancedMesh(geometry, boulderMaterial, perVariant[v]));\n  const cursors = new Array<number>(variantCount).fill(0);\n\n  const matrix = new Matrix4();\n  const position = new Vector3();\n  const quaternion = new Quaternion();\n  const scale = new Vector3();\n  const rotation = new Euler();\n\n  for (let i = 0; i < count; i++) {\n    // Uniform scale keeps the lumped shape from stretching.\n    const s = source.float(scaleMin, scaleMax);\n    scale.set(s, s, s);\n    rotation.set(source.float(0, Math.PI * 2), source.float(0, Math.PI * 2), source.float(0, Math.PI * 2));\n    quaternion.setFromEuler(rotation);\n    position.set(source.float(-width / 2, width / 2), source.float(0, heightJitter), source.float(-depth / 2, depth / 2));\n    matrix.compose(position, quaternion, scale);\n\n    const v = i % variantCount;\n    const slot = cursors[v]++;\n    meshes[v].setMatrixAt(slot, matrix);\n    if (colors) {\n      context.index = i;\n      colors(tint, context);\n      meshes[v].setColorAt(slot, tint);\n    }\n  }\n\n  const group = new Group();\n  for (const mesh of meshes) {\n    mesh.instanceMatrix.needsUpdate = true;\n    if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;\n    mesh.castShadow = true;\n    mesh.receiveShadow = true;\n    group.add(mesh);\n  }\n  return group;\n}\n","import { BufferGeometry, DodecahedronGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface MossyRockGeometryOptions {\n  /** Rock dodecahedron radius. Defaults to `1`. */\n  radius?: number;\n  /** Dodecahedron detail level. Defaults to `0`. */\n  detail?: number;\n  /** Moss horizontal scale relative to the rock. Defaults to `0.9`. */\n  mossScaleXZ?: number;\n  /** Moss vertical scale relative to the rock. Defaults to `0.5`. */\n  mossScaleY?: number;\n  /** Moss center offset above the rock origin. Defaults to `0.3`. */\n  mossOffsetY?: number;\n}\n\n/**\n * Mossy rock — dodecahedron body with a smaller, flatter moss shell (group 1).\n *\n * Material groups: `0` rock, `1` moss.\n *\n * Local frame: centered on the rock body.\n */\nexport class MossyRockGeometry extends BufferGeometry {\n  readonly radius: number;\n  readonly detail: number;\n\n  constructor({\n    radius = 1,\n    detail = 0,\n    mossScaleXZ = 0.9,\n    mossScaleY = 0.5,\n    mossOffsetY = 0.3,\n  }: MossyRockGeometryOptions = {}) {\n    super();\n\n    this.radius = radius;\n    this.detail = detail;\n\n    const rock = new DodecahedronGeometry(radius, detail);\n\n    const moss = new DodecahedronGeometry(radius, detail);\n    moss.scale(mossScaleXZ, mossScaleY, mossScaleXZ);\n    moss.translate(0, mossOffsetY, 0);\n\n    this.copy(mergeGeometries([rock, moss], true) as BufferGeometry);\n    this.computeVertexNormals();\n  }\n}","import { Axis } from \"../../constants/Axis\";\nimport { BufferGeometry, Vector3 } from \"three\";\nimport { mergeVertices } from \"three/addons/utils/BufferGeometryUtils.js\";\n\n/** Deletes input UVs/normals, then returns a welded geometry displaced by axis × randomScale per vertex.\n * Recomputes output normals; the input attribute deletion is a side effect. */\nexport function randomTransformVertices<T extends BufferGeometry>(\n  geometry: T,\n  axis = Axis.XYZ,\n  minScale = 0.5,\n  maxScale = 2.0,\n  random: () => number = Math.random,\n) {\n  geometry.deleteAttribute(\"uv\");\n  geometry.deleteAttribute(\"normal\");\n  geometry = mergeVertices(geometry) as T;\n  geometry.computeVertexNormals();\n\n  const positionAttribute = geometry.getAttribute(\"position\");\n\n  for (let i = 0; i < positionAttribute.count; i++) {\n    const vertex = new Vector3().fromBufferAttribute(positionAttribute, i);\n\n    const randomScale = random() * (maxScale - minScale) + minScale;\n    const displacement = axis.clone().multiplyScalar(randomScale);\n\n    vertex.add(displacement);\n    positionAttribute.setXYZ(i, vertex.x, vertex.y, vertex.z);\n  }\n\n  positionAttribute.needsUpdate = true;\n  geometry.computeVertexNormals();\n\n  return geometry;\n}\n","import { BufferGeometry, SphereGeometry } from \"three\";\nimport { Axis } from \"../../constants/Axis\";\nimport { randomTransformVertices } from \"../../modeling/mesh/VertexUtils\";\n\nimport { createRandom } from \"../../utils/Random\";\n\nexport interface RockGeometryOptions {\n  /** Optional seed for repeatable vertex offsets. */\n  seed?: number;\n  /** Base sphere radius before vertex noise. Defaults to `1`. */\n  radius?: number;\n  /** Horizontal segments. Defaults to `4`. */\n  widthSegments?: number;\n  /** Vertical segments. Defaults to `4`. */\n  heightSegments?: number;\n}\n\n/**\n * Low-poly rock — sphere with randomized vertex offsets, then centered.\n */\nexport class RockGeometry extends BufferGeometry {\n  readonly radius: number;\n  readonly widthSegments: number;\n  readonly heightSegments: number;\n\n  constructor({ seed, radius = 1, widthSegments = 4, heightSegments = 4 }: RockGeometryOptions = {}) {\n    super();\n\n    this.radius = radius;\n    this.widthSegments = widthSegments;\n    this.heightSegments = heightSegments;\n\n    const sphere = new SphereGeometry(radius, widthSegments, heightSegments);\n    this.copy(randomTransformVertices(sphere, Axis.XYZ, 0.5, 1.0, createRandom(seed).next));\n    this.computeVertexNormals();\n    this.center();\n  }\n}\n","import {\n  Color,\n  ColorRepresentation,\n  Euler,\n  InstancedMesh,\n  Material,\n  Matrix4,\n  MeshStandardMaterial,\n  Quaternion,\n  Vector3,\n} from \"three\";\nimport { MossyRockGeometry } from \"../../geometry/rocks/MossyRockGeometry\";\nimport { RockGeometry } from \"../../geometry/rocks/RockGeometry\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\n\nexport interface RockScatterBounds {\n  /** Scatter extent along X (centered on origin). Defaults to `4`. */\n  width?: number;\n  /** Scatter extent along Z (centered on origin). Defaults to `4`. */\n  depth?: number;\n  /** Max random Y offset above the ground plane. Defaults to `0`. */\n  heightJitter?: number;\n}\n\nexport interface RockScatterPlacementOptions extends RockScatterBounds {\n  /** Number of instances. Defaults to `5`. */\n  count?: number;\n  /** Min per-axis instance scale. Defaults to `0.8`. */\n  scaleMin?: number;\n  /** Max per-axis instance scale. Defaults to `1.2`. */\n  scaleMax?: number;\n  /** Optional seed for reproducible scatter. Omit for unique runtime. */\n  seed?: number;\n}\n\nexport interface ScatterRocksOptions extends RockScatterPlacementOptions {\n  /** Per-instance color; overrides color with a white generated material. Custom materials still multiply it. */\n  colors?: ColorSampler;\n  /** Base sphere radius for each instance geometry. Defaults to `1`. */\n  radius?: number;\n  widthSegments?: number;\n  heightSegments?: number;\n  material?: Material;\n  /** Stone tint when `material` is omitted. Defaults to `#808080`. */\n  color?: ColorRepresentation;\n}\n\nexport interface ScatterMossyRocksOptions extends RockScatterPlacementOptions {\n  /** Per-instance multiplier applied to BOTH stone and moss materials. White is neutral; use gray endpoints for brightness variation. */\n  tints?: ColorSampler;\n  /** Dodecahedron radius for each instance geometry. Defaults to `1`. */\n  radius?: number;\n  detail?: number;\n  mossScaleXZ?: number;\n  mossScaleY?: number;\n  mossOffsetY?: number;\n  rockMaterial?: Material;\n  mossMaterial?: Material;\n  /** Rock tint when `rockMaterial` is omitted. Defaults to `#808080`. */\n  rockColor?: ColorRepresentation;\n  /** Moss tint when `mossMaterial` is omitted. Defaults to `#4b8b3b`. */\n  mossColor?: ColorRepresentation;\n  /** Moss opacity when `mossMaterial` is omitted. Defaults to `0.8`. */\n  mossOpacity?: number;\n}\n\nfunction colorInstances(mesh: InstancedMesh, sample: ColorSampler, seed?: number): void {\n  const context = { index: 0, random: createRandom(seed === undefined ? undefined : deriveSubSeed(seed, 0x726f636b)) };\n  const target = new Color();\n  for (let i = 0; i < mesh.count; i++) {\n    context.index = i;\n    sample(target, context);\n    mesh.setColorAt(i, target);\n  }\n  if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;\n}\n\nfunction placeInstances(\n  mesh: InstancedMesh,\n  { count = 5, width = 4, depth = 4, heightJitter = 0, scaleMin = 0.8, scaleMax = 1.2, seed }: RockScatterPlacementOptions,\n): void {\n  const source = createRandom(seed);\n  const matrix = new Matrix4();\n  const position = new Vector3();\n  const quaternion = new Quaternion();\n  const scale = new Vector3();\n  const rotation = new Euler();\n\n  for (let i = 0; i < count; i++) {\n    scale.set(source.float(scaleMin, scaleMax), source.float(scaleMin, scaleMax), source.float(scaleMin, scaleMax));\n    rotation.set(source.float(0, Math.PI), source.float(0, Math.PI), source.float(0, Math.PI));\n    quaternion.setFromEuler(rotation);\n    position.set(source.float(-width / 2, width / 2), source.float(0, heightJitter), source.float(-depth / 2, depth / 2));\n    matrix.compose(position, quaternion, scale);\n    mesh.setMatrixAt(i, matrix);\n  }\n\n  mesh.instanceMatrix.needsUpdate = true;\n}\n\n/**\n * Scatter instanced rocks inside a horizontal bounds region.\n *\n * @example\n * ```ts\n * const rocks = scatterRocks({ count: 12, width: 8, depth: 8, seed: 1337 });\n * scene.add(rocks);\n * ```\n */\nexport function scatterRocks({\n  count = 5,\n  width = 4,\n  depth = 4,\n  heightJitter = 0,\n  scaleMin = 0.8,\n  scaleMax = 1.2,\n  seed,\n  radius = 1,\n  widthSegments = 4,\n  heightSegments = 4,\n  material,\n  color = \"#808080\",\n  colors,\n}: ScatterRocksOptions = {}): InstancedMesh {\n  const rockMaterial = material ?? new MeshStandardMaterial({ color: new Color(colors ? 0xffffff : color), flatShading: true });\n\n  const geometry = new RockGeometry({\n    radius,\n    widthSegments,\n    heightSegments,\n    seed: seed === undefined ? undefined : deriveSubSeed(seed, 0x73686170),\n  });\n  const mesh = new InstancedMesh(geometry, rockMaterial, count);\n\n  placeInstances(mesh, { count, width, depth, heightJitter, scaleMin, scaleMax, seed });\n  if (colors) colorInstances(mesh, colors, seed);\n  return mesh;\n}\n\n/**\n * Scatter instanced mossy rocks inside a horizontal bounds region.\n *\n * Uses two material groups (rock + moss) on a shared {@link InstancedMesh}, so the moss can be\n * tinted and made translucent independently of the stone beneath it.\n *\n * @example\n * ```ts\n * const rocks = scatterMossyRocks({ count: 12, width: 8, depth: 8, seed: 1337 });\n * scene.add(rocks);\n * ```\n */\nexport function scatterMossyRocks({\n  count = 5,\n  width = 4,\n  depth = 4,\n  heightJitter = 0,\n  scaleMin = 0.8,\n  scaleMax = 1.2,\n  seed,\n  radius = 1,\n  detail = 0,\n  mossScaleXZ = 0.9,\n  mossScaleY = 0.5,\n  mossOffsetY = 0.3,\n  rockMaterial,\n  mossMaterial,\n  rockColor = \"#808080\",\n  mossColor = \"#4b8b3b\",\n  mossOpacity = 0.8,\n  tints,\n}: ScatterMossyRocksOptions = {}): InstancedMesh {\n  const materials: Material[] = [\n    rockMaterial ?? new MeshStandardMaterial({ color: new Color(rockColor), flatShading: true }),\n    mossMaterial ??\n      new MeshStandardMaterial({\n        color: new Color(mossColor),\n        flatShading: true,\n        opacity: mossOpacity,\n        transparent: mossOpacity < 1,\n      }),\n  ];\n\n  const geometry = new MossyRockGeometry({ radius, detail, mossScaleXZ, mossScaleY, mossOffsetY });\n  const mesh = new InstancedMesh(geometry, materials, count);\n\n  placeInstances(mesh, { count, width, depth, heightJitter, scaleMin, scaleMax, seed });\n  if (tints) colorInstances(mesh, tints, seed);\n  return mesh;\n}\n","import { Vector2 } from \"three\";\n\n// Lathe profiles for laboratory glassware, and the liquid that fills any of them.\n//\n// Pure geometry — numbers in, profile points out. A vessel's SILHOUETTE (its outer wall, bottom to rim) is\n// the source of truth: `vesselShell` thickens it into the renderable glass, `fillProfile` cuts the liquid\n// from it, and a caller can measure it. Keeping the silhouette first-class is what lets one set of\n// functions serve every vessel.\n\nconst clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v));\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\n// ---------------------------------------------------------------------------\n// Silhouettes — the outer wall, bottom to rim start. Exposed as a vessel's `.profile`.\n// ---------------------------------------------------------------------------\n\nexport interface FlorenceFlaskProfileOptions {\n  /** Bulb (sphere) radius. Defaults to `1`. */\n  bodyRadius?: number;\n  /** Neck radius — the straight tube above the bulb. Defaults to `0.2`. */\n  neckRadius?: number;\n  /** Neck height, above the bulb's shoulder. Defaults to `1.5`. */\n  neckHeight?: number;\n  /** Arc stations over the bulb — its smoothness. Defaults to `32`. */\n  profileSegments?: number;\n}\n\n/**\n * Florence flask silhouette — a sphere opened at the top into a straight neck.\n *\n * The arc runs from the south pole to the latitude where the sphere is exactly as wide as the neck\n * (`asin(neckRadius / bodyRadius)`), so the neck meets the bulb tangentially with no crease. Bulb bottom\n * on Y=0; ends at the rim.\n */\nexport function florenceFlaskProfile({\n  bodyRadius = 1,\n  neckRadius = 0.2,\n  neckHeight = 1.5,\n  profileSegments = 32,\n}: FlorenceFlaskProfileOptions = {}): Vector2[] {\n  const segments = Math.max(3, profileSegments);\n  const thetaTop = neckRadius > 0 ? Math.asin(clamp(neckRadius / bodyRadius, 0, 1)) : 0;\n  const points: Vector2[] = [];\n  for (let i = 0; i <= segments; i++) {\n    const theta = Math.PI - ((Math.PI - thetaTop) * i) / segments;\n    points.push(new Vector2(bodyRadius * Math.sin(theta), bodyRadius * Math.cos(theta) + bodyRadius));\n  }\n  const shoulderY = points[points.length - 1]!.y;\n  if (neckRadius > 0 && neckHeight > 0) points.push(new Vector2(neckRadius, shoulderY + neckHeight));\n  return points;\n}\n\nexport interface ErlenmeyerFlaskProfileOptions {\n  /** Body (base) radius — the widest point. Defaults to `1`. */\n  bodyRadius?: number;\n  /** Neck radius. Defaults to `0.3`. */\n  neckRadius?: number;\n  /** Body height, before the neck. Defaults to `2.5`. */\n  bodyHeight?: number;\n  /** Neck height. Defaults to `1`. */\n  neckHeight?: number;\n}\n\n/**\n * Erlenmeyer flask silhouette — a conical body rising to a straight neck. Base on Y=0; ends at the rim.\n *\n * The base is drawn a touch in from full radius with a small chamfer, so the wall turns up rather than\n * meeting the bottom at a hard rim that catches the light wrongly.\n */\nexport function erlenmeyerFlaskProfile({\n  bodyRadius = 1,\n  neckRadius = 0.3,\n  bodyHeight = 2.5,\n  neckHeight = 1,\n}: ErlenmeyerFlaskProfileOptions = {}): Vector2[] {\n  return [\n    new Vector2(0, 0),\n    new Vector2(bodyRadius * 0.875, 0),\n    new Vector2(bodyRadius, bodyHeight * 0.04),\n    new Vector2(neckRadius, bodyHeight),\n    new Vector2(neckRadius, bodyHeight + neckHeight),\n  ];\n}\n\nexport interface TestTubeProfileOptions {\n  /** Tube radius. Defaults to `0.2`. */\n  radius?: number;\n  /** Overall height, rounded bottom to rim. Defaults to `3`. */\n  height?: number;\n  /** Arc stations over the rounded bottom. Defaults to `16`. */\n  profileSegments?: number;\n}\n\n/**\n * Test tube silhouette — a cylinder closed by a hemisphere, as ONE curve.\n *\n * A single profile rather than a cylinder merged with half a sphere: a merge leaves two rings of vertices\n * at the join with different normals, so the seam shades as a crease on a tube meant to read as\n * continuous. The hemisphere's centre sits one radius up, so the tube rests on Y=0; ends at the rim.\n */\nexport function testTubeProfile({ radius = 0.2, height = 3, profileSegments = 16 }: TestTubeProfileOptions = {}): Vector2[] {\n  const segments = Math.max(3, profileSegments);\n  const points: Vector2[] = [];\n  for (let i = 0; i <= segments; i++) {\n    const theta = Math.PI - (Math.PI / 2) * (i / segments); // south pole up to the equator\n    points.push(new Vector2(radius * Math.sin(theta), radius * Math.cos(theta) + radius));\n  }\n  if (height > radius) points.push(new Vector2(radius, height));\n  return points;\n}\n\nexport interface GraduatedCylinderProfileOptions {\n  /** Body (bore) radius. Defaults to `0.35`. */\n  radius?: number;\n  /** Overall height. Defaults to `3`. */\n  height?: number;\n  /** Base-foot radius — the wider skirt for stability. Defaults to `1.5 ×` the body radius. */\n  footRadius?: number;\n  /** Foot height. Defaults to `0.08 ×` the height. */\n  footHeight?: number;\n}\n\n/**\n * Graduated cylinder silhouette — a straight bore rising from a flared base foot. Base on Y=0, ends at the\n * rim. A straight cylinder, not the Erlenmeyer's cone.\n */\nexport function graduatedCylinderProfile({\n  radius = 0.35,\n  height = 3,\n  footRadius,\n  footHeight,\n}: GraduatedCylinderProfileOptions = {}): Vector2[] {\n  const fr = footRadius ?? radius * 1.5;\n  const fh = footHeight ?? height * 0.08;\n  return [\n    new Vector2(0, 0),\n    new Vector2(fr, 0),\n    new Vector2(fr, fh * 0.5),\n    new Vector2(radius, fh),\n    new Vector2(radius, height),\n  ];\n}\n\nexport interface PipetteProfileOptions {\n  /** Tube radius. Defaults to `0.1`. */\n  radius?: number;\n  /** Overall height. Defaults to `3`. */\n  height?: number;\n  /** Length of the tapering cone tip at the base. Defaults to `0.22 ×` the height. */\n  tipLength?: number;\n}\n\n/**\n * Pipette silhouette — a very thin tube tapering through a cone to a point at the base. Tip on Y=0, ends at\n * the rim. Like a test tube, but with a conical instead of spherical base.\n */\nexport function pipetteProfile({ radius = 0.1, height = 3, tipLength }: PipetteProfileOptions = {}): Vector2[] {\n  const tl = tipLength ?? height * 0.22;\n  return [new Vector2(0, 0), new Vector2(radius, tl), new Vector2(radius, height)];\n}\n\nexport interface ApothecaryJarProfileOptions {\n  /** Widest body radius. Defaults to `1.5`. */\n  radius?: number;\n  /** Base (foot) radius. Defaults to `0.8 ×` the body radius. */\n  baseRadius?: number;\n  /** Neck (mouth) radius — where the cork seats. Defaults to `0.4 ×` the body radius. */\n  neckRadius?: number;\n  /** Overall height. Defaults to `3.5`. */\n  height?: number;\n}\n\n/**\n * Apothecary jar silhouette — a round, oblong body drawn in to a short neck. Base on Y=0, ends at the rim.\n */\nexport function apothecaryJarProfile({\n  radius = 1.5,\n  baseRadius,\n  neckRadius,\n  height = 3.5,\n}: ApothecaryJarProfileOptions = {}): Vector2[] {\n  const br = baseRadius ?? radius * 0.8;\n  const nr = neckRadius ?? radius * 0.4;\n  return [\n    new Vector2(0, 0),\n    new Vector2(br, 0),\n    new Vector2(radius * 0.98, height * 0.25),\n    new Vector2(radius, height * 0.5), // widest belly\n    new Vector2(radius * 0.74, height * 0.78), // shoulder\n    new Vector2(nr, height), // neck / rim\n  ];\n}\n\nexport interface PotionBottleProfileOptions {\n  /** Widest body radius. Defaults to `1`. */\n  radius?: number;\n  /** Base (foot) radius. Defaults to `0.7 ×` the body radius. */\n  baseRadius?: number;\n  /** Neck (mouth) radius — where the cork seats. Defaults to `0.4 ×` the body radius. */\n  neckRadius?: number;\n  /** Overall height. Defaults to `2.6`. */\n  height?: number;\n}\n\n/**\n * Potion bottle silhouette — a small, bulbous body drawn in to a narrow neck (a perfume-bottle shape).\n * Base on Y=0, ends at the rim.\n */\nexport function potionBottleProfile({\n  radius = 1,\n  baseRadius,\n  neckRadius,\n  height = 2.6,\n}: PotionBottleProfileOptions = {}): Vector2[] {\n  const br = baseRadius ?? radius * 0.7;\n  const nr = neckRadius ?? radius * 0.4;\n  return [\n    new Vector2(0, 0),\n    new Vector2(br, 0),\n    new Vector2(radius, height * 0.45), // belly (widest)\n    new Vector2(radius * 0.7, height * 0.64), // shoulder\n    new Vector2(nr, height * 0.8), // neck\n    new Vector2(nr, height), // rim\n  ];\n}\n\nexport interface WineBottleProfileOptions {\n  /** Body radius. Defaults to `0.5`. */\n  radius?: number;\n  /** Neck (mouth) radius — where the cork seats. Defaults to `0.18`. */\n  neckRadius?: number;\n  /** Overall height. Defaults to `3`. */\n  height?: number;\n  /** Straight neck height. Defaults to `0.9`. */\n  neckHeight?: number;\n  /** Shoulder height — the curve from body to neck, the bottle's classical tell. Defaults to `0.5`. */\n  shoulderHeight?: number;\n  /** Points sampling the shoulder curve. `1` is a single straight line (a hard `/`); more rounds it. Defaults to `6`. */\n  shoulderSegments?: number;\n}\n\n/**\n * Wine bottle silhouette — a straight cylindrical body, a shoulder, and a long neck. Base on Y=0, ends at\n * the rim.\n *\n * The shoulder is a quarter-ellipse sampled at `shoulderSegments` points: `1` collapses it to one straight\n * facet (a hard Bordeaux shoulder), more rounds it (a Burgundy/Champagne slope). That is the whole trick to\n * a lathe — roundness is point count, since the segments between points are straight.\n */\nexport function wineBottleProfile({\n  radius = 0.5,\n  neckRadius = 0.18,\n  height = 3,\n  neckHeight = 0.9,\n  shoulderHeight = 0.5,\n  shoulderSegments = 6,\n}: WineBottleProfileOptions = {}): Vector2[] {\n  const bodyTop = Math.max(0, height - neckHeight - shoulderHeight);\n  const seg = Math.max(1, Math.floor(shoulderSegments));\n  const points = [new Vector2(0, 0), new Vector2(radius, 0)];\n  // Shoulder as a quarter-ellipse from body (radius, bodyTop) up to neck (neckRadius, bodyTop + shoulderHeight).\n  for (let i = 0; i <= seg; i++) {\n    const a = (i / seg) * (Math.PI / 2);\n    points.push(new Vector2(neckRadius + (radius - neckRadius) * Math.cos(a), bodyTop + shoulderHeight * Math.sin(a)));\n  }\n  points.push(new Vector2(neckRadius, height)); // neck to rim\n  return points;\n}\n\n// ---------------------------------------------------------------------------\n// Shell — thicken a silhouette into the renderable glass.\n// ---------------------------------------------------------------------------\n\nexport interface VesselShellOptions {\n  /**\n   * Wall thickness, in world units. `> 0` builds a full double wall (up the outside, rolled over the rim,\n   * down a full inner wall, closed at the bottom) so the vessel reads solid under a single-sided material —\n   * right for OPAQUE vessels (a mortar). `0` (the default) leaves a single surface with just a rounded\n   * rolled rim — right for TRANSPARENT glass, where a double wall only multiplies the layers to sort. For\n   * glass, fake the wall with a fill gap instead ({@link fillProfile}'s `inset`).\n   */\n  thickness?: number;\n  /** Rolled-rim bead thickness, as a fraction of the rim radius — used only when `thickness` is `0`. Defaults to `0.1`. */\n  rim?: number;\n  /**\n   * Round the double wall's rim over a bead (a rolled lip). When `false`, the outer and inner walls meet\n   * the rim with a flat edge — right for a plain thick rim like a stone mortar. Only affects `thickness > 0`.\n   * Defaults to `true`.\n   */\n  roundedRim?: boolean;\n}\n\n/** Offset a profile inward along its own normal — the inner wall of a shell of the given thickness. */\nfunction offsetInward(profile: Vector2[], thickness: number): Vector2[] {\n  const n = profile.length;\n  const result: Vector2[] = [];\n  for (let i = 0; i < n; i++) {\n    const prev = profile[Math.max(0, i - 1)]!;\n    const next = profile[Math.min(n - 1, i + 1)]!;\n    let tx = next.x - prev.x;\n    let ty = next.y - prev.y;\n    const len = Math.hypot(tx, ty) || 1;\n    tx /= len;\n    ty /= len;\n    // Inward normal: the tangent turned +90°, which points toward the axis for a bottom-to-top profile.\n    result.push(new Vector2(Math.max(0.0005, profile[i]!.x - ty * thickness), profile[i]!.y + tx * thickness));\n  }\n  return result;\n}\n\n/** A small rounded bead rolling from the outer rim point over the top to the inner rim point. */\nfunction rimRoll(outer: Vector2, inner: Vector2, segments = 5): Vector2[] {\n  const cx = (outer.x + inner.x) / 2;\n  const cy = (outer.y + inner.y) / 2;\n  const r = Math.hypot(outer.x - inner.x, outer.y - inner.y) / 2 || 1e-4;\n  const a0 = Math.atan2(outer.y - cy, outer.x - cx);\n  const points: Vector2[] = [];\n  for (let i = 1; i < segments; i++) {\n    const a = a0 + Math.PI * (i / segments); // sweep π, bulging up over the rim\n    points.push(new Vector2(cx + r * Math.cos(a), cy + r * Math.sin(a)));\n  }\n  return points;\n}\n\n/**\n * A single-surface rolled rim — rounds the top edge over into a lip and STOPS at the inner rim, without\n * traversing down an inner wall. Just enough to give the opening a \"top\" and kill the hard cut edge.\n */\nfunction rolledRim(radius: number, topY: number, rim: number, segments = 6): Vector2[] {\n  if (rim <= 0) return [new Vector2(radius, topY)];\n  const thickness = Math.min(rim, 0.9) * radius;\n  const bead = thickness / 2;\n  const cx = radius - bead;\n  const points: Vector2[] = [];\n  for (let i = 0; i <= segments; i++) {\n    const a = (i / segments) * Math.PI; // outer rim (0) → rounded top (π/2) → inner rim (π), then stop\n    points.push(new Vector2(cx + bead * Math.cos(a), topY + bead * Math.sin(a)));\n  }\n  return points;\n}\n\n/**\n * Thicken a vessel silhouette into the profile actually lathed.\n *\n * With `thickness > 0` the profile winds up the outside, rolls over the rim, and comes back down a full\n * inner wall to a closed inner bottom — real glass under a single-sided material. With `thickness = 0` it\n * is the bare silhouette with a decorative rolled rim.\n */\nexport function vesselShell(\n  silhouette: Vector2[],\n  { thickness = 0, rim = 0.1, roundedRim = true }: VesselShellOptions = {},\n): Vector2[] {\n  if (silhouette.length < 2) return silhouette;\n  const rimTop = silhouette[silhouette.length - 1]!;\n\n  if (thickness > 1e-6) {\n    const t = Math.min(thickness, rimTop.x * 0.8);\n    const inner = offsetInward(silhouette, t);\n    // Rounded lip, or a flat edge straight across to the inner wall (a plain thick rim, e.g. a mortar).\n    const bead = roundedRim ? rimRoll(rimTop, inner[inner.length - 1]!) : [];\n    return [...silhouette, ...bead, ...inner.slice().reverse()];\n  }\n  return [...silhouette.slice(0, -1), ...rolledRim(rimTop.x, rimTop.y, rim)];\n}\n\n// ---------------------------------------------------------------------------\n// Fill — the liquid, cut from a vessel's own silhouette.\n// ---------------------------------------------------------------------------\n\n/**\n * The liquid that fills a vessel to a given fraction of its height — derived from the shell's OWN\n * silhouette rather than written per vessel.\n *\n * One function serves every vessel, and the liquid cannot disagree with the glass it sits in because it is\n * the same curve. `fill` is a fraction of the vessel's height, so it means the same on every shape.\n *\n * `inset` clears the liquid off the glass by ONE uniform gap — a fraction of the widest radius — applied\n * along the wall's own NORMAL, so the sides, the bottom AND the meniscus all pull in by the same amount and\n * nothing is coplanar with the glass to z-fight. (A radius-only shrink leaves zero gap at the axis, so the\n * flat bottom stays on the glass floor and fights it.) The whole silhouette is offset first, THEN cut at the\n * fill line, so every point keeps a clean normal and the rim never juts where the level meets a shell point.\n *\n * Returns `[]` when there is nothing to draw.\n */\nexport function fillProfile(shell: Vector2[], fill: number, inset = 0.03): Vector2[] {\n  if (shell.length < 2) return [];\n  const base = shell[0]!.y;\n  let top = base;\n  let maxRadius = 0;\n  for (const p of shell) {\n    top = Math.max(top, p.y);\n    maxRadius = Math.max(maxRadius, p.x);\n  }\n  const level = lerp(base, top, clamp(fill, 0, 1));\n  if (level <= base) return [];\n\n  const gap = clamp(inset, 0, 0.5) * (maxRadius || 1);\n  const inner = offsetInward(shell, gap);\n  if (level <= inner[0]!.y + 1e-6) return []; // fill shallower than the lifted floor — nothing to draw\n\n  const points: Vector2[] = [];\n  if (inner[0]!.x > 1e-6) points.push(new Vector2(0, inner[0]!.y)); // close the floor across the axis\n  for (let i = 0; i < inner.length; i++) {\n    const p = inner[i]!;\n    if (p.y <= level) {\n      points.push(p);\n      continue;\n    }\n    if (i > 0) {\n      const prev = inner[i - 1]!;\n      const span = p.y - prev.y;\n      if (span > 1e-6) {\n        const t = (level - prev.y) / span;\n        points.push(new Vector2(lerp(prev.x, p.x, t), level));\n      }\n    }\n    break;\n  }\n\n  const surface = points[points.length - 1]!;\n  if (surface.x > 1e-6) points.push(new Vector2(0, surface.y)); // flat meniscus, inset to match the sides\n  return points.length >= 2 ? points : [];\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  florenceFlaskProfile,\n  vesselShell,\n  type FlorenceFlaskProfileOptions,\n  type VesselShellOptions,\n} from \"./vesselProfiles\";\n\nexport interface FlorenceFlaskGeometryOptions extends FlorenceFlaskProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `32`. */\n  radialSegments?: number;\n}\n\n/**\n * Florence flask — a spherical bulb drawn out into a straight neck, walled to a real glass thickness.\n *\n * A lathe of {@link vesselShell} over {@link florenceFlaskProfile}. The outer silhouette is exposed as\n * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a\n * measurement. A round-bottom flask cannot stand on its own — see {@link FlorenceFlaskStand}. Local frame:\n * bulb bottom on Y=0, opening up +Y.\n */\nexport class FlorenceFlaskGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly bodyRadius: number;\n  readonly height: number;\n\n  constructor(options: FlorenceFlaskGeometryOptions = {}) {\n    const silhouette = florenceFlaskProfile(options);\n    super(vesselShell(silhouette, options), options.radialSegments ?? 32);\n    this.profile = silhouette;\n    this.bodyRadius = options.bodyRadius ?? 1;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { BufferGeometry, CylinderGeometry, TorusGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface RingStandGeometryOptions {\n  /** Ring radius. Defaults to `0.3`. */\n  radius?: number;\n  /** Leg height. Defaults to `0.4`. */\n  height?: number;\n  /** Number of legs. Defaults to `3`. */\n  count?: number;\n  /** Ring tube thickness. Defaults to `0.03`. */\n  thickness?: number;\n  /** Circumference segments. Defaults to `16`. */\n  radialSegments?: number;\n}\n\n/**\n * Ring stand — a torus ring on radial legs, for a round-bottom vessel to rest in.\n *\n * Local frame: legs on Y=0, ring at Y=height.\n */\nexport class RingStandGeometry extends BufferGeometry {\n  readonly radius: number;\n  readonly height: number;\n  readonly count: number;\n\n  constructor({\n    radius = 0.3,\n    height = 0.4,\n    count = 3,\n    thickness = 0.03,\n    radialSegments = 16,\n  }: RingStandGeometryOptions = {}) {\n    super();\n\n    this.radius = radius;\n    this.height = height;\n    this.count = count;\n\n    const ringGeometry = new TorusGeometry(radius, thickness, 8, radialSegments);\n    ringGeometry.rotateX(Math.PI / 2);\n    ringGeometry.translate(0, height, 0);\n\n    const legGeometry = new CylinderGeometry(thickness * 0.6, thickness * 0.6, height, radialSegments);\n    const legs = [];\n\n    for (let i = 0; i < count; i++) {\n      const angle = (i / count) * Math.PI * 2;\n      const leg = legGeometry.clone();\n      leg.translate(Math.cos(angle) * radius, height / 2, Math.sin(angle) * radius);\n      legs.push(leg);\n    }\n\n    this.copy(mergeGeometries([ringGeometry, ...legs], false) as BufferGeometry);\n  }\n}\n","import { BufferGeometry, LatheGeometry, Vector2 } from \"three\";\nimport { fillProfile } from \"./vesselProfiles\";\n\nexport interface LiquidFillGeometryOptions {\n  /** The vessel profile to fill — take it from a vessel geometry's `profile`. */\n  profile: Vector2[];\n  /** Fill level, as a fraction of the vessel's height. `0` is empty. Defaults to `0`. */\n  fill?: number;\n  /** Radius inset so the liquid wall isn't coplanar with the glass. Defaults to `0.02`. */\n  inset?: number;\n  /** Circumference segments — match the vessel's for a clean surface. Defaults to `32`. */\n  radialSegments?: number;\n}\n\n/**\n * The liquid inside a vessel, cut from the vessel's OWN profile (see {@link fillProfile}) and revolved.\n *\n * Pure geometry: the liquid's colour, opacity and glow are a material the caller supplies. Because it is\n * turned from the same curve as the glass, it can never clip through it. Comes back EMPTY (no attributes)\n * when the vessel is empty, so a caller can always build one and drive `fill` from a control.\n *\n * Draw the liquid BEFORE the glass (`liquid.renderOrder < shell.renderOrder`): their centres coincide, so\n * depth-sorting has nothing to say and the order must be stated.\n */\nexport class LiquidFillGeometry extends BufferGeometry {\n  readonly fillHeight: number;\n\n  constructor({ profile, fill = 0, inset = 0.02, radialSegments = 32 }: LiquidFillGeometryOptions) {\n    super();\n    const contents = fillProfile(profile, fill, inset);\n    if (contents.length >= 2) {\n      const lathe = new LatheGeometry(contents, radialSegments);\n      this.copy(lathe);\n      lathe.dispose();\n      this.fillHeight = contents[contents.length - 1]!.y - (profile[0]?.y ?? 0);\n    } else {\n      this.fillHeight = 0;\n    }\n  }\n}\n","import { Color, type ColorRepresentation, Mesh, MeshStandardMaterial, type Vector2 } from \"three\";\nimport { LiquidFillGeometry } from \"../../geometry/vessels/LiquidFillGeometry\";\n\n/**\n * The shared \"fill capability\" — the optional liquid any vessel can carry.\n *\n * Level is geometry; colour, opacity and glow are material. One interface covers both so a filled vessel\n * is described the same way wherever it is composed (a flask in a stand, a tube in a rack, an example).\n */\nexport interface FillOptions {\n  /** Fill level, as a fraction of the vessel's height. `0` (or omitted) is empty. */\n  fill?: number;\n  /** Liquid colour. Defaults to a pale green. */\n  color?: ColorRepresentation;\n  /** Liquid opacity. Defaults to `0.85`. */\n  opacity?: number;\n  /** Emissive glow, `0` for none. Defaults to `0`. */\n  glow?: number;\n  /** Radius inset from the glass wall, so the two surfaces don't z-fight. Defaults to `0.02`. */\n  inset?: number;\n}\n\n/**\n * Build the liquid mesh for a vessel from its `profile` and a {@link FillOptions}.\n *\n * The composition step: geometry from {@link LiquidFillGeometry}, appearance from the options. One function\n * fills any vessel. Returns `null` when the vessel is empty. The mesh is given `renderOrder = 0` so it\n * draws before the glass — give the glass `renderOrder = 1`, since their centres coincide and depth\n * sorting cannot order them.\n */\nexport function createLiquidFill(profile: Vector2[], options: FillOptions = {}, radialSegments = 32): Mesh | null {\n  const { fill = 0, color = 0x4bbfa0, opacity = 0.85, glow = 0, inset = 0.02 } = options;\n  if (fill <= 0) return null;\n\n  const geometry = new LiquidFillGeometry({ profile, fill, inset, radialSegments });\n  if (!geometry.getAttribute(\"position\")) {\n    geometry.dispose();\n    return null;\n  }\n\n  const material = new MeshStandardMaterial({\n    color,\n    transparent: opacity < 1,\n    opacity,\n    roughness: 0.25,\n    emissive: new Color(color),\n    emissiveIntensity: glow,\n  });\n\n  const mesh = new Mesh(geometry, material);\n  mesh.renderOrder = 0;\n  return mesh;\n}\n","import { Group, Mesh, MeshStandardMaterial } from \"three\";\nimport { FlorenceFlaskGeometry, type FlorenceFlaskGeometryOptions } from \"../../geometry/vessels/FlorenceFlaskGeometry\";\nimport { RingStandGeometry } from \"../../geometry/science/RingStandGeometry\";\nimport { createLiquidFill, type FillOptions } from \"../vessels/liquidFill\";\n\nexport interface FlorenceFlaskStandOptions {\n  /** Flask geometry — resize the bulb, neck, etc. The ring re-sizes and re-seats to whatever bulb results. */\n  flask?: FlorenceFlaskGeometryOptions;\n  /** Optional liquid inside the flask — colour, opacity, glow, fill level. */\n  fill?: FillOptions;\n  /**\n   * Ring radius as a fraction of the bulb radius — how deep the bulb sits. Defaults to `0.55`, which\n   * cradles the lower third. Must be below `1`, or the ring is wider than the bulb and it falls through.\n   */\n  seat?: number;\n  /** Ring tube thickness. Defaults to `0.03 ×` the bulb radius. */\n  ringThickness?: number;\n  /** Number of legs. Defaults to `3`. */\n  legs?: number;\n  /** Gap from the bulb's lowest point to the ground. Defaults to `0.15 ×` the bulb radius. */\n  clearance?: number;\n  /** Ring circumference segments. Defaults to `24`. */\n  radialSegments?: number;\n  /** Flask (glass) material. A translucent default is supplied. */\n  glassMaterial?: MeshStandardMaterial;\n  /** Stand (metal) material. A brushed-metal default is supplied. */\n  standMaterial?: MeshStandardMaterial;\n}\n\n/**\n * Florence flask resting in a ring stand.\n *\n * A round-bottom flask cannot stand on its own; the stand is what makes it a thing that sits on a table.\n * This composes {@link FlorenceFlaskGeometry} and {@link RingStandGeometry} into a `Group` resting on Y=0.\n *\n * **The bulb is seated by radius, not tessellation.** The ring is sized to the bulb, and the bulb settles\n * until its surface is tangent to the ring tube — in an axial cross-section, a bulb circle of radius `R`\n * tangent (from inside) to a tube circle of radius `t` at ring radius `ringRadius`:\n *\n * ```\n * riseAboveRing = √((R + t)² − ringRadius²)\n * ```\n *\n * The flask is MEASURED (bounding box) rather than assumed, so the seating stays correct if\n * {@link FlorenceFlaskGeometry} is parameterized later.\n *\n * **Glass and metal are SEPARATE meshes, not one merged geometry.** Transparency sorts per object, so a\n * glass group baked into the opaque stand would sort wrong; the parts must stay separate objects.\n */\nexport class FlorenceFlaskStand extends Group {\n  constructor({\n    flask,\n    fill,\n    seat = 0.55,\n    ringThickness,\n    legs = 3,\n    clearance,\n    radialSegments = 24,\n    glassMaterial,\n    standMaterial,\n  }: FlorenceFlaskStandOptions = {}) {\n    super();\n\n    // Measure the flask: the bulb is a sphere, so its widest ring is its equator.\n    const flaskGeometry = new FlorenceFlaskGeometry(flask);\n    flaskGeometry.computeBoundingBox();\n    const box = flaskGeometry.boundingBox!;\n    const bulbRadius = (box.max.x - box.min.x) / 2;\n    const bulbCenterLocal = box.min.y + bulbRadius;\n\n    const ringRadius = seat * bulbRadius;\n    const t = ringThickness ?? 0.03 * bulbRadius;\n    const clear = clearance ?? 0.15 * bulbRadius;\n\n    // Bulb-center height above the ring plane (radius-based; clamped so a too-wide ring degrades gracefully).\n    const rise = Math.sqrt(Math.max(0, (bulbRadius + t) ** 2 - ringRadius ** 2));\n    // Stand height so the bulb's lowest point clears the ground by `clear`:\n    //   bulbBottomWorld = standHeight + rise − bulbRadius  ⇒  set equal to clear.\n    const standHeight = clear + bulbRadius - rise;\n\n    const stand = new Mesh(\n      new RingStandGeometry({ radius: ringRadius, height: standHeight, count: legs, thickness: t, radialSegments }),\n      standMaterial ?? new MeshStandardMaterial({ color: 0x8a8f96, roughness: 0.6, metalness: 0.4, flatShading: true }),\n    );\n    stand.castShadow = true;\n\n    const flaskMesh = new Mesh(\n      flaskGeometry,\n      glassMaterial ??\n        new MeshStandardMaterial({ color: 0xbfe3e0, roughness: 0.15, transparent: true, opacity: 0.4 }),\n    );\n    // Seat the bulb center at (ring plane + rise); the ring plane is at standHeight.\n    flaskMesh.position.y = standHeight + rise - bulbCenterLocal;\n    flaskMesh.castShadow = true;\n    flaskMesh.renderOrder = 1; // glass after the liquid\n\n    this.add(stand, flaskMesh);\n\n    if (fill) {\n      const liquid = createLiquidFill(flaskGeometry.profile, fill, radialSegments);\n      if (liquid) {\n        liquid.position.y = flaskMesh.position.y;\n        this.add(liquid);\n      }\n    }\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport { testTubeProfile, vesselShell, type TestTubeProfileOptions, type VesselShellOptions } from \"./vesselProfiles\";\n\nexport interface TestTubeGeometryOptions extends TestTubeProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `32`. */\n  radialSegments?: number;\n}\n\n/**\n * Test tube — a cylinder closed by a hemisphere as ONE curve, walled to a real glass thickness.\n *\n * A lathe of {@link vesselShell} over {@link testTubeProfile}. The outer silhouette is exposed as\n * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a\n * measurement. Building the silhouette as a single curve rather than a merged cylinder + hemisphere avoids\n * the shading crease at the join. Local frame: rounded bottom on Y=0, rim up +Y.\n */\nexport class TestTubeGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: TestTubeGeometryOptions = {}) {\n    const silhouette = testTubeProfile(options);\n    super(vesselShell(silhouette, options), options.radialSegments ?? 32);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 0.2;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { BoxGeometry, BufferGeometry, ExtrudeGeometry, Group, Mesh, MeshStandardMaterial, Path, Shape } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { TestTubeGeometry, type TestTubeGeometryOptions } from \"../../geometry/vessels/TestTubeGeometry\";\nimport { createLiquidFill, type FillOptions } from \"../vessels/liquidFill\";\n\nexport interface TestTubeRackOptions {\n  /** Tubes per row. Defaults to `6`. */\n  columns?: number;\n  /** Rows of tubes. Defaults to `1`. */\n  rows?: number;\n  /** Tube geometry — its radius and height size the whole rack. */\n  tube?: TestTubeGeometryOptions;\n  /** Optional liquid in every tube — colour, opacity, glow, fill level. */\n  fill?: FillOptions;\n  /** Gap between neighbouring tubes, added to the diameter for the pitch. Defaults to `0.9 ×` the tube radius. */\n  gap?: number;\n  /**\n   * Height of the top plate — how high up the tube the rack holds it — as a fraction of the tube height.\n   * Defaults to `0.55`. Clamped to `[0.1, 0.9]` so the tube always seats on the base and protrudes above.\n   */\n  rise?: number;\n  /** Glass material for the tubes. A translucent default is supplied. */\n  glassMaterial?: MeshStandardMaterial;\n  /** Frame material for the rack. A wood default is supplied. */\n  rackMaterial?: MeshStandardMaterial;\n}\n\n/**\n * A rack of test tubes — a row or grid, each seated by its rounded bottom on the base and held through the\n * top plate. Nothing floats.\n *\n * A spatial factory: it sizes a two-plate frame (base + top plate on corner posts) to whatever tube it is\n * given, lays the tubes out on a pitch, and rests the whole `Group` on Y=0. Everything is proportional to\n * the tube radius, so one set of numbers holds across tube sizes.\n *\n * The frame is ONE merged opaque mesh; the tubes are SEPARATE glass meshes sharing a single geometry —\n * glass sorts per object, so it cannot be baked into the opaque frame.\n */\nexport class TestTubeRack extends Group {\n  constructor({ columns = 6, rows = 1, tube, fill, gap, rise = 0.55, glassMaterial, rackMaterial }: TestTubeRackOptions = {}) {\n    super();\n\n    const tubeGeometry = new TestTubeGeometry(tube);\n    const r = tubeGeometry.radius;\n    const h = tubeGeometry.height;\n\n    const pitch = 2 * r + (gap ?? 0.9 * r);\n\n    // Frame proportions, all relative to the tube radius.\n    const baseThickness = 0.4 * r;\n    const topThickness = 0.5 * r;\n    const postSize = 0.7 * r;\n    const holdHeight = baseThickness + Math.max(0.1, Math.min(0.9, rise)) * h; // underside of the top plate\n    const plateWidth = columns * pitch;\n    const plateDepth = rows * pitch;\n\n    // --- frame: base + top plate + four corner posts, merged into one opaque mesh ---\n    const parts: BufferGeometry[] = [];\n\n    const base = new BoxGeometry(plateWidth, baseThickness, plateDepth);\n    base.translate(0, baseThickness / 2, 0);\n    parts.push(base);\n\n    // Top plate — a board with a clean round hole per tube, cut the way a pipe-organ windchest drills pipe\n    // holes: one Shape, a Path hole each, one extrusion. No CSG. ExtrudeGeometry handles the hole winding;\n    // the holes never overlap or reach the edge (pitch/2 > holeRadius), so nothing triangulates to a knot.\n    const holeRadius = r * 1.12;\n    const holeSegments = 20;\n    const plate = new Shape();\n    plate.moveTo(-plateWidth / 2, -plateDepth / 2);\n    plate.lineTo(plateWidth / 2, -plateDepth / 2);\n    plate.lineTo(plateWidth / 2, plateDepth / 2);\n    plate.lineTo(-plateWidth / 2, plateDepth / 2);\n    plate.closePath();\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < columns; col++) {\n        const hx = (col - (columns - 1) / 2) * pitch;\n        const hz = -(row - (rows - 1) / 2) * pitch; // shape-Y maps to world −Z after the rotation below\n        const hole = new Path();\n        for (let i = 0; i <= holeSegments; i++) {\n          const a = (i / holeSegments) * Math.PI * 2;\n          const px = hx + Math.cos(a) * holeRadius;\n          const pz = hz + Math.sin(a) * holeRadius;\n          if (i === 0) hole.moveTo(px, pz);\n          else hole.lineTo(px, pz);\n        }\n        hole.closePath();\n        plate.holes.push(hole);\n      }\n    }\n    const top = new ExtrudeGeometry(plate, { depth: topThickness, bevelEnabled: false, curveSegments: 1 });\n    top.rotateX(-Math.PI / 2); // XY board → lies flat in XZ, thickness up +Y\n    top.translate(0, holdHeight, 0);\n    parts.push(top);\n\n    const postHeight = holdHeight - baseThickness;\n    const px = plateWidth / 2 - postSize / 2;\n    const pz = plateDepth / 2 - postSize / 2;\n    for (const sx of [-1, 1]) {\n      for (const sz of [-1, 1]) {\n        const post = new BoxGeometry(postSize, postHeight, postSize);\n        post.translate(sx * px, baseThickness + postHeight / 2, sz * pz);\n        parts.push(post);\n      }\n    }\n\n    // The extruded plate is non-indexed while the boxes are indexed; mergeGeometries needs them uniform.\n    const rack = new Mesh(\n      mergeGeometries(\n        parts.map((g) => (g.index ? g.toNonIndexed() : g)),\n        false,\n      ) as BufferGeometry,\n      rackMaterial ?? new MeshStandardMaterial({ color: 0x8a5a3b, roughness: 0.75, metalness: 0.05, flatShading: true }),\n    );\n    rack.castShadow = true;\n    rack.receiveShadow = true;\n    this.add(rack);\n\n    // --- tubes: separate glass meshes sharing one geometry, bottoms resting on the base plate ---\n    const glass =\n      glassMaterial ?? new MeshStandardMaterial({ color: 0xbfe3e0, roughness: 0.15, transparent: true, opacity: 0.4 });\n\n    // One liquid geometry + material for the whole rack; each tube gets a mesh sharing them.\n    const liquidTemplate = fill ? createLiquidFill(tubeGeometry.profile, fill, tube?.radialSegments ?? 32) : null;\n\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < columns; col++) {\n        const x = (col - (columns - 1) / 2) * pitch;\n        const z = (row - (rows - 1) / 2) * pitch;\n\n        const t = new Mesh(tubeGeometry, glass);\n        t.position.set(x, baseThickness, z);\n        t.castShadow = true;\n        t.renderOrder = 1; // glass after the liquid\n        this.add(t);\n\n        if (liquidTemplate) {\n          const liquid = new Mesh(liquidTemplate.geometry, liquidTemplate.material);\n          liquid.position.set(x, baseThickness, z);\n          liquid.renderOrder = 0;\n          this.add(liquid);\n        }\n      }\n    }\n  }\n}\n","import {\n  Color,\n  CylinderGeometry,\n  Group,\n  IcosahedronGeometry,\n  InstancedMesh,\n  Mesh,\n  MeshStandardMaterial,\n  Object3D,\n  Quaternion,\n  SphereGeometry,\n  Vector3,\n  type BufferGeometry,\n  type Material,\n} from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\n\nexport interface AppleTreeOptions {\n  /** Seed for the deterministic stream. Defaults to `0xa991`. */\n  seed?: number;\n  /** Overall tree height, which the branch reach and rise are derived from. Defaults to `3.4`. */\n  height?: number;\n  /** Horizontal reach of the crown. Defaults to `1.5`. */\n  crownRadius?: number;\n  /** Fraction of crown anchors that receive leaf clusters. Defaults to `0.82`. */\n  leafDensity?: number;\n  /** Overrides the built-in leaf palette. Index counts visible clusters; seeded color draws do not alter wood, foliage placement or apples. */\n  leafColors?: ColorSampler;\n  /** Number of apples scattered through the crown. Defaults to `18`. */\n  appleCount?: number;\n  /**\n   * Height of the straight vertical rise before the trunk leans. Defaults to `0.25`.\n   *\n   * The trunk's top carries a small random offset, so without a rise the trunk tilts from the ground up and\n   * its bottom face tilts with it, sinking the low edge below `y = 0`. One vertical segment makes the base\n   * tangent exactly UP so the face lies flat. Set `0` to see the original tilt.\n   */\n  baseRise?: number;\n}\n\nconst UP = /*@__PURE__*/ new Vector3(0, 1, 0);\n\n/** A tapered branch from `start` to `end`, oriented along the segment. */\nfunction branchGeometry(start: Vector3, end: Vector3, bottomRadius: number, topRadius: number): BufferGeometry {\n  const direction = new Vector3().subVectors(end, start);\n  const length = Math.max(direction.length(), 0.0001);\n  const geometry = new CylinderGeometry(topRadius, bottomRadius, length, 7, 1);\n  geometry.translate(0, length / 2, 0);\n  geometry.applyQuaternion(new Quaternion().setFromUnitVectors(UP, direction.normalize()));\n  geometry.translate(start.x, start.y, start.z);\n  return geometry;\n}\n\n/**\n * A compact cultivated apple tree with a low, rounded crown.\n *\n * Six primary branches leave a short trunk, each carrying a shoulder, a tip, and two twigs — orchard form\n * rather than the recursive gnarl of {@link DeciduousTree}. **Deliberately independent of it:** branching rules,\n * foliage, and fruit all live here, because a pruned orchard tree is a different thing from a wild one, not a\n * reparameterization of it.\n *\n * Three draw calls at any size — merged wood, one {@link InstancedMesh} of leaf clusters tinted per instance,\n * and one of apples. Exposed as {@link wood}, {@link leaves}, and {@link apples}.\n *\n * Local frame: **grows from the origin**, base flat on the `y = 0` plane, occupying `+Y`. See\n * {@link AppleTreeOptions.baseRise}.\n *\n * **This factory owns its materials**, faithful to the scene it came from, where bark, leaf, and apple colors\n * are part of the asset's identity. Call {@link dispose} to release them.\n *\n * @example\n * ```typescript\n * const tree = new AppleTree({ seed: 0xa991, appleCount: 24 });\n * scene.add(tree);\n * ```\n */\nexport class AppleTree extends Group {\n  /** The merged trunk, branches, and twigs — one draw call. */\n  readonly wood: Mesh<BufferGeometry, MeshStandardMaterial>;\n  /** Leaf clusters, tinted per instance. */\n  readonly leaves: InstancedMesh<BufferGeometry, MeshStandardMaterial>;\n  /** Apples scattered through the crown. */\n  readonly apples: InstancedMesh<BufferGeometry, MeshStandardMaterial>;\n  readonly #geometries: BufferGeometry[];\n  readonly #materials: Material[];\n\n  constructor({\n    seed = 0xa991,\n    height = 3.4,\n    crownRadius = 1.5,\n    leafDensity = 0.82,\n    leafColors,\n    appleCount = 18,\n    baseRise = 0.25,\n  }: AppleTreeOptions = {}) {\n    super();\n\n    // The library's seeded source rather than the scene's own linear congruential generator, so one random\n    // implementation serves the whole library. A given seed therefore grows a different — equally valid — tree\n    // than the website's.\n    const source = createRandom(seed);\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x1eaf)) };\n    const random = () => source.next();\n\n    const woodParts: BufferGeometry[] = [];\n    const crownAnchors: Vector3[] = [];\n\n    const trunkTop = new Vector3((random() - 0.5) * 0.18, height * 0.46, (random() - 0.5) * 0.18);\n\n    // Rise straight up before the trunk leans toward its offset top, so the bottom face lands flat.\n    let trunkBase = new Vector3();\n    if (baseRise > 0) {\n      const risen = new Vector3(0, baseRise, 0);\n      woodParts.push(branchGeometry(trunkBase, risen, 0.24, 0.24));\n      trunkBase = risen;\n    }\n    woodParts.push(branchGeometry(trunkBase, trunkTop, 0.24, 0.16));\n\n    const primaryCount = 6;\n    for (let branch = 0; branch < primaryCount; branch++) {\n      const angle = (branch / primaryCount) * Math.PI * 2 + (random() - 0.5) * 0.35;\n      const reach = crownRadius * (0.62 + random() * 0.24);\n      const shoulder = trunkTop\n        .clone()\n        .add(new Vector3(Math.cos(angle) * reach * 0.48, height * (0.15 + random() * 0.08), Math.sin(angle) * reach * 0.48));\n      const tip = trunkTop\n        .clone()\n        .add(new Vector3(Math.cos(angle) * reach, height * (0.28 + random() * 0.13), Math.sin(angle) * reach));\n      woodParts.push(branchGeometry(trunkTop, shoulder, 0.13, 0.085));\n      woodParts.push(branchGeometry(shoulder, tip, 0.085, 0.035));\n      crownAnchors.push(tip, shoulder.clone().lerp(tip, 0.55));\n\n      for (const side of [-1, 1]) {\n        const twigAngle = angle + side * (0.45 + random() * 0.28);\n        const twigTip = shoulder\n          .clone()\n          .add(\n            new Vector3(\n              Math.cos(twigAngle) * reach * 0.46,\n              height * (0.12 + random() * 0.12),\n              Math.sin(twigAngle) * reach * 0.46,\n            ),\n          );\n        woodParts.push(branchGeometry(shoulder, twigTip, 0.065, 0.025));\n        crownAnchors.push(twigTip);\n      }\n    }\n\n    const woodGeometry = mergeGeometries(woodParts);\n    if (!woodGeometry) throw new Error(\"AppleTree: wood parts failed to merge.\");\n    woodParts.forEach((geometry) => geometry.dispose());\n\n    const woodMaterial = new MeshStandardMaterial({\n      color: new Color(\"#493222\"),\n      roughness: 1,\n      metalness: 0,\n      flatShading: true,\n    });\n    const wood = new Mesh(woodGeometry, woodMaterial);\n    wood.castShadow = wood.receiveShadow = true;\n    this.wood = wood;\n    this.add(wood);\n\n    const visibleAnchors = crownAnchors.filter(() => random() < leafDensity);\n    const leafGeometry = new IcosahedronGeometry(0.43, 1);\n    const leafMaterial = new MeshStandardMaterial({\n      color: 0xffffff,\n      roughness: 1,\n      metalness: 0,\n      flatShading: true,\n    });\n    const leaves = new InstancedMesh(leafGeometry, leafMaterial, visibleAnchors.length * 2);\n    const placement = new Object3D();\n    const leafPalette = [\"#53602c\", \"#697438\", \"#7d7531\", \"#8a692b\", \"#465126\"];\n    const tint = new Color();\n    let leafIndex = 0;\n\n    for (const anchor of visibleAnchors) {\n      for (let cluster = 0; cluster < 2; cluster++) {\n        placement.position.set(\n          anchor.x + (random() - 0.5) * 0.62,\n          anchor.y + (random() - 0.5) * 0.48,\n          anchor.z + (random() - 0.5) * 0.62,\n        );\n        placement.rotation.set(random() * Math.PI, random() * Math.PI, random() * Math.PI);\n        const scale = 0.68 + random() * 0.46;\n        placement.scale.set(scale * 1.15, scale, scale * 1.05);\n        placement.updateMatrix();\n        leaves.setMatrixAt(leafIndex, placement.matrix);\n        tint.set(leafPalette[Math.floor(random() * leafPalette.length)]!);\n        // Reserve the legacy palette draw even when a custom sampler is supplied.\n        if (leafColors) {\n          colorContext.index = leafIndex;\n          leafColors(tint, colorContext);\n        }\n        leaves.setColorAt(leafIndex, tint);\n        leafIndex++;\n      }\n    }\n    leaves.instanceMatrix.needsUpdate = true;\n    if (leaves.instanceColor) leaves.instanceColor.needsUpdate = true;\n    leaves.castShadow = leaves.receiveShadow = true;\n    this.leaves = leaves;\n    this.add(leaves);\n\n    const appleGeometry = new SphereGeometry(0.105, 8, 6);\n    const appleMaterial = new MeshStandardMaterial({\n      color: new Color(\"#8f251b\"),\n      roughness: 0.82,\n      metalness: 0,\n      flatShading: true,\n    });\n    const apples = new InstancedMesh(appleGeometry, appleMaterial, appleCount);\n    for (let i = 0; i < appleCount; i++) {\n      // Every anchor is eligible, including ones the leaf pass skipped — fruit and foliage are scattered\n      // independently.\n      const anchor = crownAnchors[Math.floor(random() * crownAnchors.length)]!;\n      placement.position.set(\n        anchor.x + (random() - 0.5) * 0.72,\n        anchor.y - 0.18 - random() * 0.42,\n        anchor.z + (random() - 0.5) * 0.72,\n      );\n      placement.rotation.set(0, random() * Math.PI, 0);\n      placement.scale.setScalar(0.82 + random() * 0.35);\n      placement.updateMatrix();\n      apples.setMatrixAt(i, placement.matrix);\n    }\n    apples.instanceMatrix.needsUpdate = true;\n    apples.castShadow = true;\n    this.apples = apples;\n    this.add(apples);\n\n    this.#geometries = [woodGeometry, leafGeometry, appleGeometry];\n    this.#materials = [woodMaterial, leafMaterial, appleMaterial];\n  }\n\n  /** Release the geometries and materials this factory created. */\n  dispose(): void {\n    this.#geometries.forEach((geometry) => geometry.dispose());\n    this.#materials.forEach((material) => material.dispose());\n  }\n}\n","import {\n  Color,\n  CylinderGeometry,\n  DodecahedronGeometry,\n  Group,\n  InstancedMesh,\n  Matrix4,\n  Mesh,\n  MeshStandardMaterial,\n  Object3D,\n  Quaternion,\n  SphereGeometry,\n  Vector3,\n  type BufferGeometry,\n  type Material,\n} from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { createRandom, deriveSubSeed } from \"../../utils/Random\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\n\nexport interface DeciduousTreeOptions {\n  /** Seed for the deterministic stream. Defaults to `0xa711`. Shapes differ from the source scene's. */\n  seed?: number;\n  /** Trunk radius at the base. Defaults to `0.32`. */\n  trunkRadius?: number;\n  /** Length of one branch segment before taper. Defaults to `0.66`. */\n  segmentLength?: number;\n  /** Recursion limit for branching. Defaults to `4`. */\n  maxDepth?: number;\n  /** Fraction of crown points that receive leaf clusters. Defaults to `0.72`. */\n  leafDensity?: number;\n  /** Bark color. Defaults to `\"#332419\"`. */\n  barkColor?: string;\n  /**\n   * Colors sampled per leaf cluster. Defaults to a set of summer greens.\n   *\n   * **The palette is what makes this tree a season**, and nothing else does. Swapping in rust and ochre\n   * gives an autumn tree; swapping in pale pinks and raising `clustersPerPoint` gives a cherry in blossom.\n   * Both are studies rather than subclasses — see `Studies › Trees` — because a season is ten numbers, not\n   * a different tree. The default stays green so the class is not named for one family and dressed as one\n   * member of it.\n   */\n  leafPalette?: string[];\n  /**\n   * Optional per-cluster sampler, overriding leafPalette. Writes a working-space Color.\n   * Index follows visible crown-point order, then cluster order. The seeded color stream\n   * is independent of placement; sampling more random values does not move leaf clusters.\n   * Bark remains controlled by barkColor. Omit to preserve the original palette and seeded output.\n   */\n  leafColors?: ColorSampler;\n  /** Leaf cluster radius. Defaults to `0.38`. */\n  leafSize?: number;\n  /** Clusters placed at each crown point. Defaults to `2`. */\n  clustersPerPoint?: number;\n  /**\n   * Height of the straight vertical rise before the trunk starts leaning. Defaults to `0.35`.\n   *\n   * This is what lets the base sit FLAT. The trunk leans from its first segment, so without a rise the\n   * bottom face is tilted with it and its low edge sinks below `y = 0` by roughly\n   * `trunkRadius × sin(lean)` — measured at `-0.087` on the default seed. One vertical segment makes the\n   * tangent at the base exactly UP, so the face lies in the ground plane.\n   *\n   * A correction to the PATH, not to the geometry: a real trunk rises out of the earth before it does\n   * anything interesting. Same fix, same reasoning as {@link GnarledTreeGeometry}'s `baseRise`. Set `0` to\n   * see the original tilt.\n   */\n  baseRise?: number;\n}\n\nconst UP = /*@__PURE__*/ new Vector3(0, 1, 0);\n\nfunction perpendicular(direction: Vector3, random: () => number): Vector3 {\n  const reference = Math.abs(direction.y) < 0.98 ? UP : new Vector3(1, 0, 0);\n  return new Vector3()\n    .crossVectors(direction, reference)\n    .normalize()\n    .applyAxisAngle(direction, random() * Math.PI * 2);\n}\n\n/** A tapered cylinder from `start` to `end`, oriented along the segment. */\nfunction frustum(start: Vector3, end: Vector3, startRadius: number, endRadius: number): BufferGeometry {\n  const axis = new Vector3().subVectors(end, start);\n  const length = Math.max(axis.length(), 0.0001);\n  axis.normalize();\n  const geometry = new CylinderGeometry(endRadius, startRadius, length, 6, 1);\n  geometry.translate(0, length / 2, 0);\n  geometry.applyQuaternion(new Quaternion().setFromUnitVectors(UP, axis));\n  geometry.translate(start.x, start.y, start.z);\n  return geometry;\n}\n\n/**\n * Deterministic crooked broadleaf tree with a sparse, instanced crown.\n *\n * One merged low-poly branch skeleton plus an {@link InstancedMesh} of faceted leaf clusters tinted per\n * instance from {@link DeciduousTreeOptions.leafPalette}. The trunk's deliberate lean supplies the large\n * silhouette; recursive branching supplies the gnarl.\n *\n * Local frame: **grows from the origin**, so the base sits flat on the `y = 0` plane and the tree occupies\n * `+Y`. That flatness comes from {@link DeciduousTreeOptions.baseRise}; without it the leaning trunk's bottom\n * face tilts and sinks below the ground.\n *\n * Two draw calls regardless of crown size — one for the merged branches, one for every leaf cluster. Both are\n * exposed as {@link branches} and {@link leaves} rather than left to be dug out of `children`.\n *\n * **This factory owns its materials**, unlike the geometry classes. That is faithful to the scene it came\n * from, where bark color and leaf palette are part of the asset's identity rather than a consumer choice.\n * Call {@link dispose} to release them.\n *\n * @example\n * ```typescript\n * const tree = new DeciduousTree({ seed: 0xa711 });\n * scene.add(tree);\n * ```\n */\nexport class DeciduousTree extends Group {\n  /** The merged branch skeleton — one draw call however deep the branching goes. */\n  readonly branches: Mesh<BufferGeometry, MeshStandardMaterial>;\n  /** Every leaf cluster, tinted per instance. `count` is the cluster total. */\n  readonly leaves: InstancedMesh<BufferGeometry, MeshStandardMaterial>;\n  readonly #geometries: BufferGeometry[];\n  readonly #materials: Material[];\n\n  constructor({\n    seed = 0xa711,\n    trunkRadius = 0.32,\n    segmentLength = 0.66,\n    maxDepth = 4,\n    leafDensity = 0.72,\n    barkColor = \"#332419\",\n    leafPalette = [\"#4e5f33\", \"#5d7142\", \"#6b8150\", \"#455a2e\", \"#7a8a58\"],\n    leafColors,\n    leafSize = 0.38,\n    clustersPerPoint = 2,\n    baseRise = 0.35,\n  }: DeciduousTreeOptions = {}) {\n    super();\n\n    // The library's seeded source (mulberry32) rather than the scene's own linear congruential generator.\n    // A different algorithm means a different sequence, so a given seed grows a different — equally valid —\n    // tree than the website's. Deliberate: one random implementation across the library beats seed parity\n    // with the sketch it came from.\n    const source = createRandom(seed);\n    const random = () => source.next();\n    const branchParts: BufferGeometry[] = [];\n    const crownPoints: Vector3[] = [];\n\n    const grow = (origin: Vector3, initialDirection: Vector3, radius: number, length: number, depth: number): void => {\n      const steps = Math.max(2, 5 - depth);\n      const taper = 0.82;\n      let position = origin.clone();\n      const direction = initialDirection.clone().normalize();\n      let currentRadius = radius;\n\n      // Rise straight up before leaning, so the bottom face lands flat in the ground plane.\n      if (depth === 0 && baseRise > 0) {\n        const risen = position.clone().addScaledVector(UP, baseRise);\n        branchParts.push(frustum(position, risen, currentRadius, currentRadius));\n        position = risen;\n      }\n\n      for (let i = 0; i < steps; i++) {\n        const bend = depth === 0 ? 0.16 + random() * 0.18 : 0.2 + random() * 0.32;\n        direction.applyAxisAngle(perpendicular(direction, random), bend);\n        if (depth === 0) direction.lerp(UP, 0.23).normalize();\n\n        const nextRadius = currentRadius * taper;\n        const next = position.clone().addScaledVector(direction, length * (0.84 + random() * 0.3));\n        branchParts.push(frustum(position, next, currentRadius, nextRadius));\n        branchParts.push(new SphereGeometry(nextRadius * 1.04, 6, 4).translate(next.x, next.y, next.z));\n\n        if (depth === 0 && (i === 2 || i === 3)) {\n          const offshoot = direction.clone().applyAxisAngle(perpendicular(direction, random), 0.72 + random() * 0.42);\n          offshoot.lerp(UP, 0.08).normalize();\n          grow(next, offshoot, nextRadius * 0.57, length * 0.76, depth + 1);\n        }\n\n        position = next;\n        currentRadius = nextRadius;\n      }\n\n      // Continue into genuinely fine twigs. A 0.025 cutoff exhausted the taper near depth two, which made\n      // larger `maxDepth` values look identical.\n      if (depth < maxDepth && currentRadius > 0.006) {\n        const children = depth === 0 ? 4 : depth < 3 ? 2 : random() < 0.6 ? 2 : 1;\n        for (let i = 0; i < children; i++) {\n          const childDirection = direction.clone().applyAxisAngle(perpendicular(direction, random), 0.48 + random() * 0.7);\n          if (depth < 2) childDirection.lerp(UP, 0.12).normalize();\n          grow(position, childDirection, currentRadius * 0.69, length * 0.79, depth + 1);\n        }\n      } else {\n        crownPoints.push(position.clone());\n      }\n    };\n\n    // A purposeful lean gives the trunk its Sleepy Hollow silhouette before the recursive gnarl adds\n    // smaller-scale irregularity.\n    grow(new Vector3(), new Vector3(-0.16, 1, 0.08), trunkRadius, segmentLength, 0);\n\n    const branchGeometry = mergeGeometries(branchParts);\n    if (!branchGeometry) throw new Error(\"DeciduousTree: branch parts failed to merge.\");\n    branchParts.forEach((geometry) => geometry.dispose());\n\n    const barkMaterial = new MeshStandardMaterial({\n      color: new Color(barkColor),\n      roughness: 1,\n      metalness: 0,\n      flatShading: true,\n    });\n    const branches = new Mesh(branchGeometry, barkMaterial);\n    branches.castShadow = branches.receiveShadow = true;\n    this.branches = branches;\n    this.add(branches);\n\n    const visiblePoints = crownPoints.filter(() => random() < leafDensity);\n    const leafGeometry = new DodecahedronGeometry(leafSize, 0);\n    const leafMaterial = new MeshStandardMaterial({\n      color: 0xffffff,\n      roughness: 1,\n      metalness: 0,\n      flatShading: true,\n    });\n    const leaves = new InstancedMesh(leafGeometry, leafMaterial, visiblePoints.length * clustersPerPoint);\n    leaves.visible = leaves.count > 0;\n    const placement = new Object3D();\n    const tint = new Color();\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x1eaf)) };\n    const sampleColor: ColorSampler =\n      leafColors ??\n      ((target) => {\n        target.set(leafPalette[Math.floor(random() * leafPalette.length)]!);\n      });\n    let index = 0;\n\n    for (const point of visiblePoints) {\n      for (let cluster = 0; cluster < clustersPerPoint; cluster++) {\n        placement.position.copy(point).add(new Vector3((random() - 0.5) * 0.7, (random() - 0.5) * 0.55, (random() - 0.5) * 0.7));\n        placement.rotation.set(random() * Math.PI, random() * Math.PI, random() * Math.PI);\n        const scale = 0.65 + random() * 0.55;\n        placement.scale.set(scale * (0.8 + random() * 0.45), scale, scale * 0.8);\n        placement.updateMatrix();\n        leaves.setMatrixAt(index, new Matrix4().copy(placement.matrix));\n        // Preserve the legacy placement stream: palette selection used one draw between\n        // cluster transforms. Custom samplers use only the independent color stream.\n        if (leafColors) random();\n        colorContext.index = index;\n        sampleColor(tint, colorContext);\n        leaves.setColorAt(index, tint);\n        index++;\n      }\n    }\n    leaves.instanceMatrix.needsUpdate = true;\n    if (leaves.instanceColor) leaves.instanceColor.needsUpdate = true;\n    leaves.castShadow = leaves.receiveShadow = true;\n    this.leaves = leaves;\n    this.add(leaves);\n\n    this.#geometries = [branchGeometry, leafGeometry];\n    this.#materials = [barkMaterial, leafMaterial];\n  }\n\n  /** Release the geometries and materials this factory created. */\n  dispose(): void {\n    this.#geometries.forEach((geometry) => geometry.dispose());\n    this.#materials.forEach((material) => material.dispose());\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  apothecaryJarProfile,\n  vesselShell,\n  type ApothecaryJarProfileOptions,\n  type VesselShellOptions,\n} from \"./vesselProfiles\";\n\nexport interface ApothecaryJarGeometryOptions extends ApothecaryJarProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `20`. */\n  radialSegments?: number;\n}\n\n/**\n * Apothecary jar — a round, oblong glass jar with a rolled rim, corked by {@link ApothecaryJar}.\n *\n * A lathe of {@link vesselShell} over {@link apothecaryJarProfile}; the silhouette is exposed as `.profile`\n * for the fill and for seating a cork in the rim. Local frame: base on Y=0, opening up +Y.\n */\nexport class ApothecaryJarGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: ApothecaryJarGeometryOptions = {}) {\n    const silhouette = apothecaryJarProfile(options);\n    // A subtle rim by default — a jar mouth, not a fat rolled lip — so the opening stays close to the neck.\n    super(vesselShell(silhouette, { ...options, rim: options.rim ?? 0.15 }), options.radialSegments ?? 20);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 1.5;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\n\nexport interface CorkGeometryOptions {\n  /** Radius at the MIDDLE — the seal, where the cork meets the rim. Defaults to `0.5`. */\n  radius?: number;\n  /** Radius at the top (head). Wider than the middle flares a lipped head; narrower tapers it in. Defaults to `1.15 ×`. */\n  topRadius?: number;\n  /** Radius at the bottom (tip). Defaults to `0.7 ×` the middle radius. */\n  bottomRadius?: number;\n  /** Upper taper height, middle → top. `0` gives a flat-topped lid. Defaults to `0.18`. */\n  upperHeight?: number;\n  /** Lower taper height, middle → bottom (the part that goes into the neck). Defaults to `0.28`. */\n  lowerHeight?: number;\n  /** Circumference segments. Defaults to `16`. */\n  radialSegments?: number;\n}\n\n/**\n * Cork stopper — a bi-taper barrel referenced from its MIDDLE (the seal plane).\n *\n * Middle → top is the upper taper (sits above the vessel); middle → bottom is the lower taper (goes into the\n * neck). The head may flare *wider* than the middle (a lipped stopper, `\\===/`) or taper narrower (a barrel).\n * `upperHeight: 0` gives a flat-topped lid; equal upper/lower tapers make a wine cork.\n *\n * {@link ApothecaryJar} scales the cork uniformly so the lower taper's radius meets the opening exactly at\n * the chosen depth — a watertight seal at any height.\n *\n * Local frame: bottom on Y=0, seal middle at Y=`lowerHeight`.\n */\nexport class CorkGeometry extends LatheGeometry {\n  readonly radius: number;\n  readonly bottomRadius: number;\n  readonly middleY: number;\n  readonly height: number;\n\n  constructor({\n    radius = 0.5,\n    topRadius,\n    bottomRadius,\n    upperHeight = 0.18,\n    lowerHeight = 0.28,\n    radialSegments = 16,\n  }: CorkGeometryOptions = {}) {\n    const tr = topRadius ?? radius * 1.15;\n    const br = bottomRadius ?? radius * 0.7;\n    const topY = lowerHeight + upperHeight;\n    const points = [\n      new Vector2(0, 0),\n      new Vector2(br, 0), // bottom (tip)\n      new Vector2(radius, lowerHeight), // middle (seal)\n    ];\n    // A separate top ring only when there is an actual head; otherwise the middle IS the flat top (a lid),\n    // and a duplicate point would lathe a ring of zero-area quads with undefined normals.\n    if (upperHeight > 1e-4 || Math.abs(tr - radius) > 1e-4) points.push(new Vector2(tr, topY));\n    points.push(new Vector2(0, topY));\n    super(points, radialSegments);\n    this.radius = radius;\n    this.bottomRadius = br;\n    this.middleY = lowerHeight;\n    this.height = lowerHeight + upperHeight;\n  }\n}\n","import { Mesh, MeshStandardMaterial, type Vector2 } from \"three\";\nimport { CorkGeometry, type CorkGeometryOptions } from \"../../geometry/vessels/CorkGeometry\";\n\nexport interface CorkStopperOptions {\n  /** Cork shape — upper (vertical collar) and lower (plug) heights, and tip radius. Top is locked to the seal. */\n  cork?: CorkGeometryOptions;\n  /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */\n  corkDepth?: number;\n  /** Cork material. A cork-brown default is supplied. */\n  material?: MeshStandardMaterial;\n}\n\n/**\n * A cork stopper seated in a vessel's rim — the shared cork-fit for corked vessels (jar, potion bottle, …).\n *\n * A lid: the top radius equals the middle, so any `upperHeight` rises as a VERTICAL collar above the seal,\n * never a wider head that could intersect the rolled rim. The cork is scaled UNIFORMLY so that, at\n * `corkDepth` up its lower taper, its radius equals the vessel's opening — a watertight seal at any depth;\n * rise the cork and it scales up to keep it.\n *\n * Give it the vessel's `.profile`, its `rim` option (`rimRoll` — the rolled rim shrinks the opening), and\n * the circumference segments to match. Returns the cork mesh, seated; add it to the vessel's group.\n */\nexport function createCorkStopper(\n  profile: Vector2[],\n  rimRoll: number,\n  segments: number,\n  { cork, corkDepth = 0.6, material }: CorkStopperOptions = {},\n): Mesh {\n  const rim = profile[profile.length - 1]!;\n  const rimRadius = rim.x;\n  const rimY = rim.y; // the neck point — the lid's flat top meets the inner rim edge here\n  const opening = rimRadius * (1 - Math.min(0.9, Math.max(0, rimRoll)));\n\n  const corkGeometry = new CorkGeometry({\n    radius: 1,\n    // Default = the middle: a vertical cap rise, no head. A smaller value tapers the head in (top === bottom\n    // gives a symmetric barrel — a wine cork's bulge).\n    topRadius: cork?.topRadius ?? 1,\n    bottomRadius: cork?.bottomRadius ?? 0.72,\n    upperHeight: cork?.upperHeight ?? 0,\n    lowerHeight: cork?.lowerHeight ?? 0.7,\n    radialSegments: cork?.radialSegments ?? segments,\n  });\n  const depth = Math.min(1, Math.max(0, corkDepth));\n  const radiusAtRim = corkGeometry.bottomRadius + (corkGeometry.radius - corkGeometry.bottomRadius) * depth;\n  const corkScale = opening / radiusAtRim;\n\n  const mesh = new Mesh(\n    corkGeometry,\n    material ?? new MeshStandardMaterial({ color: 0x9a6a3c, roughness: 0.9, metalness: 0, flatShading: true }),\n  );\n  mesh.scale.setScalar(corkScale);\n  mesh.position.y = rimY - corkGeometry.middleY * depth * corkScale;\n  mesh.castShadow = true;\n  return mesh;\n}\n","import { Group, Mesh, MeshStandardMaterial } from \"three\";\nimport { ApothecaryJarGeometry, type ApothecaryJarGeometryOptions } from \"../../geometry/vessels/ApothecaryJarGeometry\";\nimport { type CorkGeometryOptions } from \"../../geometry/vessels/CorkGeometry\";\nimport { createCorkStopper } from \"./corkStopper\";\nimport { createLiquidFill, type FillOptions } from \"./liquidFill\";\n\nexport interface ApothecaryJarOptions {\n  /** Jar geometry — resize the body, neck, etc. The cork re-sizes and re-seats to the resulting rim. */\n  jar?: ApothecaryJarGeometryOptions;\n  /** Optional liquid inside the jar — colour, opacity, glow, fill level. */\n  fill?: FillOptions;\n  /** Cork shape — vertical cap height (`upperHeight`), plug depth (`lowerHeight`), tip radius. */\n  cork?: CorkGeometryOptions;\n  /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */\n  corkDepth?: number;\n  /** Jar (glass) material. A translucent default is supplied. */\n  glassMaterial?: MeshStandardMaterial;\n  /** Cork material. A cork-brown default is supplied. */\n  corkMaterial?: MeshStandardMaterial;\n}\n\n/**\n * Apothecary jar with a cork stopper — glass shell, a fitted cork lid, and an optional fill.\n *\n * A spatial factory, not a baked geometry: the glass is transparent, so shell, cork and liquid must be\n * SEPARATE meshes (transparency sorts per object). The cork is fitted to the jar's opening and sealed at\n * any depth by {@link createCorkStopper} — the same measured-seating idea as {@link FlorenceFlaskStand}.\n * Rests on Y=0.\n */\nexport class ApothecaryJar extends Group {\n  constructor({ jar, fill, cork, corkDepth, glassMaterial, corkMaterial }: ApothecaryJarOptions = {}) {\n    super();\n\n    const jarGeometry = new ApothecaryJarGeometry(jar);\n    const segments = jar?.radialSegments ?? 20;\n\n    const shell = new Mesh(\n      jarGeometry,\n      glassMaterial ?? new MeshStandardMaterial({ color: 0xbfe3e0, roughness: 0.15, transparent: true, opacity: 0.4 }),\n    );\n    shell.castShadow = true;\n    shell.renderOrder = 1; // glass after the liquid\n    this.add(shell);\n\n    if (fill) {\n      const liquid = createLiquidFill(jarGeometry.profile, fill, segments);\n      if (liquid) this.add(liquid);\n    }\n\n    this.add(createCorkStopper(jarGeometry.profile, jar?.rim ?? 0.15, segments, { cork, corkDepth, material: corkMaterial }));\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  potionBottleProfile,\n  vesselShell,\n  type PotionBottleProfileOptions,\n  type VesselShellOptions,\n} from \"./vesselProfiles\";\n\nexport interface PotionBottleGeometryOptions extends PotionBottleProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `20`. */\n  radialSegments?: number;\n}\n\n/**\n * Potion bottle — a small, bulbous glass bottle with a narrow neck and a rolled rim, corked by\n * {@link PotionBottle}.\n *\n * A lathe of {@link vesselShell} over {@link potionBottleProfile}; the silhouette is exposed as `.profile`\n * for the fill and for seating a cork. Local frame: base on Y=0, opening up +Y.\n */\nexport class PotionBottleGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: PotionBottleGeometryOptions = {}) {\n    const silhouette = potionBottleProfile(options);\n    // A subtle rim so the opening stays close to the neck.\n    super(vesselShell(silhouette, { ...options, rim: options.rim ?? 0.15 }), options.radialSegments ?? 20);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 1;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { Group, Mesh, MeshStandardMaterial } from \"three\";\nimport { type CorkGeometryOptions } from \"../../geometry/vessels/CorkGeometry\";\nimport { PotionBottleGeometry, type PotionBottleGeometryOptions } from \"../../geometry/vessels/PotionBottleGeometry\";\nimport { createCorkStopper } from \"./corkStopper\";\nimport { createLiquidFill, type FillOptions } from \"./liquidFill\";\n\nexport interface PotionBottleOptions {\n  /** Bottle geometry — resize the body, neck, etc. The cork re-sizes and re-seats to the resulting rim. */\n  bottle?: PotionBottleGeometryOptions;\n  /** Optional liquid inside the bottle — colour, opacity, glow, fill level. */\n  fill?: FillOptions;\n  /** Cork shape — vertical cap height (`upperHeight`), plug depth (`lowerHeight`), tip radius. */\n  cork?: CorkGeometryOptions;\n  /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */\n  corkDepth?: number;\n  /** Bottle (glass) material. A translucent default is supplied. */\n  glassMaterial?: MeshStandardMaterial;\n  /** Cork material. A cork-brown default is supplied. */\n  corkMaterial?: MeshStandardMaterial;\n}\n\n/**\n * Stoppered potion bottle — glass shell, a fitted cork, and an optional bright fill.\n *\n * The same spatial factory as {@link ApothecaryJar}: transparent glass, so shell, cork and liquid are\n * separate meshes, and the cork is fitted and sealed at any depth by {@link createCorkStopper}. Rests on\n * Y=0.\n */\nexport class PotionBottle extends Group {\n  constructor({ bottle, fill, cork, corkDepth, glassMaterial, corkMaterial }: PotionBottleOptions = {}) {\n    super();\n\n    const bottleGeometry = new PotionBottleGeometry(bottle);\n    const segments = bottle?.radialSegments ?? 20;\n\n    const shell = new Mesh(\n      bottleGeometry,\n      glassMaterial ?? new MeshStandardMaterial({ color: 0xc7bce0, roughness: 0.15, transparent: true, opacity: 0.4 }),\n    );\n    shell.castShadow = true;\n    shell.renderOrder = 1;\n    this.add(shell);\n\n    if (fill) {\n      const liquid = createLiquidFill(bottleGeometry.profile, fill, segments);\n      if (liquid) this.add(liquid);\n    }\n\n    this.add(createCorkStopper(bottleGeometry.profile, bottle?.rim ?? 0.15, segments, { cork, corkDepth, material: corkMaterial }));\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  vesselShell,\n  wineBottleProfile,\n  type VesselShellOptions,\n  type WineBottleProfileOptions,\n} from \"./vesselProfiles\";\n\nexport interface WineBottleGeometryOptions extends WineBottleProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `20`. */\n  radialSegments?: number;\n}\n\n/**\n * Wine bottle — a straight body, rounded shoulder, and long neck, as glass with a rolled rim; corked by\n * {@link WineBottle}.\n *\n * A lathe of {@link vesselShell} over {@link wineBottleProfile}; the silhouette is exposed as `.profile`\n * for the fill and for seating a cork. Local frame: base on Y=0, opening up +Y.\n */\nexport class WineBottleGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: WineBottleGeometryOptions = {}) {\n    const silhouette = wineBottleProfile(options);\n    super(vesselShell(silhouette, { ...options, rim: options.rim ?? 0.12 }), options.radialSegments ?? 20);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 0.5;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { Group, Mesh, MeshStandardMaterial } from \"three\";\nimport { type CorkGeometryOptions } from \"../../geometry/vessels/CorkGeometry\";\nimport { WineBottleGeometry, type WineBottleGeometryOptions } from \"../../geometry/vessels/WineBottleGeometry\";\nimport { createCorkStopper } from \"./corkStopper\";\nimport { createLiquidFill, type FillOptions } from \"./liquidFill\";\n\nexport interface WineBottleOptions {\n  /** Bottle geometry — resize the body, neck, shoulder, etc. The cork re-sizes and re-seats to the rim. */\n  bottle?: WineBottleGeometryOptions;\n  /** Optional liquid inside the bottle — colour, opacity, glow, fill level. */\n  fill?: FillOptions;\n  /** Cork shape. Defaults to a long wine cork — a tall vertical body over a deep plug. */\n  cork?: CorkGeometryOptions;\n  /** How deep the cork sits: `0` = tip at the rim, `1` = the flat top flush. Defaults to `0.6`. */\n  corkDepth?: number;\n  /** Bottle (glass) material. A green-glass default is supplied. */\n  glassMaterial?: MeshStandardMaterial;\n  /** Cork material. A cork-brown default is supplied. */\n  corkMaterial?: MeshStandardMaterial;\n}\n\n/**\n * Corked wine bottle — glass shell, a long wine cork, and an optional fill.\n *\n * The same spatial factory as {@link ApothecaryJar} and {@link PotionBottle}: transparent glass, so shell,\n * cork and liquid are separate meshes, and the cork is fitted and sealed by {@link createCorkStopper}. The\n * default cork is longer here — a tall vertical body over a deep plug, the way a wine cork actually is.\n * Rests on Y=0.\n */\nexport class WineBottle extends Group {\n  constructor({ bottle, fill, cork, corkDepth, glassMaterial, corkMaterial }: WineBottleOptions = {}) {\n    super();\n\n    const bottleGeometry = new WineBottleGeometry(bottle);\n    const segments = bottle?.radialSegments ?? 20;\n\n    const shell = new Mesh(\n      bottleGeometry,\n      glassMaterial ?? new MeshStandardMaterial({ color: 0x3f6b4a, roughness: 0.18, transparent: true, opacity: 0.5 }),\n    );\n    shell.castShadow = true;\n    shell.renderOrder = 1;\n    this.add(shell);\n\n    if (fill) {\n      const liquid = createLiquidFill(bottleGeometry.profile, fill, segments);\n      if (liquid) this.add(liquid);\n    }\n\n    // A long classical wine cork by default: a tall symmetric barrel (top === bottom, the `<>` bulge),\n    // seated flush (depth 1).\n    const wineCork: CorkGeometryOptions = { upperHeight: 1.2, lowerHeight: 1.2, topRadius: 0.72, bottomRadius: 0.72, ...cork };\n    this.add(\n      createCorkStopper(bottleGeometry.profile, bottle?.rim ?? 0.12, segments, {\n        cork: wineCork,\n        corkDepth: corkDepth ?? 1,\n        material: corkMaterial,\n      }),\n    );\n  }\n}\n","import { BufferGeometry, Quaternion, ShapeUtils, Vector2, Vector3 } from \"three\";\nimport type { PathPoint } from \"../paths/PathPoint\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type Vec2,\n  type Vec3,\n} from \"../mesh/GeometryBuffers\";\n\n/**\n * Profile placement basis: position + normal * px + binormal * py, scaled by scale.\n * Miter stations can carry nonunit normal/binormal vectors.\n */\nexport interface Station {\n  position: Vector3;\n  tangent: Vector3;\n  normal: Vector3;\n  binormal: Vector3;\n  scale?: number;\n}\n\n/**\n * Parallel-transport frames from path tangents, removing adjacent coincident positions.\n * Provide a nonempty path with nonzero tangents and a reference that yields a nonzero perpendicular seed.\n *\n * ```ts\n * const stations = transportFrames(arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 }));\n * const geometry = sweep(circleProfile(0.08, 8), stations);\n * ```\n */\nexport function transportFrames(path: PathPoint[], reference = new Vector3(0, 0, 1)): Station[] {\n  // Remove duplicate joints before constructing frames.\n  const points = path.filter(\n    (p, i) => i === 0 || p.position.distanceToSquared(path[i - 1]!.position) > 1e-12,\n  );\n\n  const tangents = points.map((p) => p.tangent.clone().normalize());\n\n  // Seed the first normal from a reference direction, projected perpendicular to the tangent.\n  let normal = reference.clone().sub(tangents[0]!.clone().multiplyScalar(reference.dot(tangents[0]!)));\n  if (normal.lengthSq() < 1e-8) {\n    // The reference happened to lie along the path. Any other axis will do.\n    normal = new Vector3(1, 0, 0).sub(tangents[0]!.clone().multiplyScalar(tangents[0]!.x));\n  }\n  normal.normalize();\n\n  const stations: Station[] = [];\n  const axis = new Vector3();\n  const rotation = new Quaternion();\n\n  for (let i = 0; i < points.length; i++) {\n    if (i > 0) {\n      axis.crossVectors(tangents[i - 1]!, tangents[i]!);\n\n      if (axis.lengthSq() > 1e-12) {\n        const angle = Math.acos(Math.min(1, Math.max(-1, tangents[i - 1]!.dot(tangents[i]!))));\n        rotation.setFromAxisAngle(axis.normalize(), angle);\n        normal.applyQuaternion(rotation);\n      }\n\n      normal.sub(tangents[i]!.clone().multiplyScalar(normal.dot(tangents[i]!))).normalize();\n    }\n\n    stations.push({\n      position: points[i]!.position.clone(),\n      tangent: tangents[i]!.clone(),\n      normal: normal.clone(),\n      binormal: new Vector3().crossVectors(tangents[i]!, normal).normalize(),\n      scale: points[i]!.scale,\n    });\n  }\n\n  return stations;\n}\n\nexport interface SweepOptions {\n  /** Section scale at t ∈ [0, 1] by station index; a station scale takes precedence, including zero. */\n  scale?: (t: number) => number;\n  /** Triangulate open-end profiles; a failed ear-clipping result falls back to a fan. */\n  cap?: boolean;\n  /**\n * Stitch the last ring to the first and omit caps; the start station must not be repeated.\n * Spatial closed loops can retain parallel-transport twist (holonomy); no seam correction is applied.\n */\n  closed?: boolean;\n}\n\n/**\n * Sweep a closed CCW profile in each station’s (normal, binormal) basis.\n * Use at least two stations; tight curvature and collapsed scales can create invalid geometry.\n *\n * ```ts\n * // A wrought iron tube arching over a gate — swap the profile for a rectangle and it is masonry.\n * const path = joinPaths(\n *   linePath(new Vector3(-2, 0, 0), new Vector3(-2, 2, 0), 2),\n *   transformPath(arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 }), translate),\n *   linePath(new Vector3(2, 2, 0), new Vector3(2, 0, 0), 2),\n * );\n *\n * const geometry = sweep(circleProfile(0.08, 8), transportFrames(path));\n * ```\n */\nexport function sweep(\n  profile: Vec2[],\n  stations: Station[],\n  { scale = () => 1, cap = true, closed = false }: SweepOptions = {},\n): BufferGeometry {\n  const buffers = createGeometryBuffers();\n  const sides = profile.length;\n  const last = stations.length - 1;\n\n  // Preserve a station scale of zero when selecting between the two scale sources.\n  const rings: Vec3[][] = stations.map((s, i) => {\n    const k = s.scale ?? scale(last === 0 ? 0 : i / last);\n    return profile.map(([px, py]) => {\n      const p = s.position\n        .clone()\n        .addScaledVector(s.normal, px * k)\n        .addScaledVector(s.binormal, py * k);\n      return [p.x, p.y, p.z] as Vec3;\n    });\n  });\n\n  // Stitch ring i to the next. A closed loop wraps the final ring back onto the first.\n  const bands = closed ? rings.length : last;\n\n  for (let i = 0; i < bands; i++) {\n    const a = rings[i]!;\n    const b = rings[(i + 1) % rings.length]!;\n\n    for (let j = 0; j < sides; j++) {\n      const k = (j + 1) % sides;\n      // u runs around the profile, v runs along the path — the classic tube layout.\n      pushQuad(\n        buffers,\n        [a[j]!, a[k]!, b[k]!, b[j]!],\n        undefined, // swept faces are slanted — derive the normal from the winding\n        [\n          [j / sides, i / last],\n          [(j + 1) / sides, i / last],\n          [(j + 1) / sides, (i + 1) / last],\n          [j / sides, (i + 1) / last],\n        ],\n      );\n    }\n  }\n\n  // Ear clipping supports concave simple profiles; a corner fan requires visibility from its anchor.\n  if (cap && !closed) {\n    const first = rings[0]!;\n    const end = rings[last]!;\n\n    const contour = profile.map(([px, py]) => new Vector2(px, py));\n    const faces = ShapeUtils.triangulateShape(contour, []);\n\n    // Orient cap triangles to match the authored profile winding.\n    let twice = 0;\n    for (let j = 0; j < sides; j++) {\n      const a = profile[j]!;\n      const b = profile[(j + 1) % sides]!;\n      twice += a[0] * b[1] - b[0] * a[1];\n    }\n    const flip = twice < 0;\n\n    if (faces.length > 0) {\n      for (const [a, b, c] of faces) {\n        const [i0, i1, i2] = flip ? [a!, c!, b!] : [a!, b!, c!];\n        pushTriangle(buffers, [first[i0]!, first[i2]!, first[i1]!], undefined); // reversed: faces back\n        pushTriangle(buffers, [end[i0]!, end[i1]!, end[i2]!], undefined);\n      }\n    } else {\n      // Fan fallback can cross a concave outline; callers must validate the resulting cap.\n      for (let j = 1; j < sides - 1; j++) {\n        pushTriangle(buffers, [first[0]!, first[j + 1]!, first[j]!], undefined);\n        pushTriangle(buffers, [end[0]!, end[j]!, end[j + 1]!], undefined);\n      }\n    }\n  }\n\n  return toBufferGeometry(buffers);\n}\n","import { BufferGeometry, Path, Vector3 } from \"three\";\nimport type { PathPoint } from \"../../modeling/paths/PathPoint\";\nimport { ArchStyle, archRise, traceArch } from \"../../modeling/profiles/ArchProfile\";\nimport { circleProfile, rectProfile } from \"../../modeling/profiles/Profiles\";\nimport { sweep, transportFrames } from \"../../modeling/surfaces/Sweep\";\nimport type { Vec2 } from \"../../modeling/mesh/GeometryBuffers\";\n\nexport interface ArchGeometryOptions {\n  /** Opening width, outer leg to outer leg. Defaults to `4`. */\n  span?: number;\n  /** Straight rise before the arc springs. Defaults to `2`. */\n  legHeight?: number;\n  /**\n   * Which arch the legs rise into. Defaults to `semicircle`. See {@link ArchStyle}.\n   *\n   * **The same seven names a doorway takes** — so an archway you walk through and the opening it frames\n   * can be drawn from one curve. `square` gives a flat lintel on two posts, which is still a portal.\n   *\n   * Note what each style does where it MEETS the legs. `semicircle`, `elliptical`, `pointed` and `ogee`\n   * all spring VERTICALLY, so they flow out of the legs with no corner. `segmental` and `horseshoe` do\n   * not — they arrive at an angle and leave a visible break at the springing. That break is not an\n   * artifact: it is the impost, and a real segmental arch has one.\n   */\n  arch?: ArchStyle;\n  /**\n   * Rise of the arch above the springing. Defaults to `span / 2` — a semicircle.\n   *\n   * A radius, not an angle, and it does not follow the span. Some styles override it: `square` has no\n   * rise, `semicircle` forces `span / 2`.\n   */\n  archHeight?: number;\n  /**\n   * Cross-section carried around the arch. Defaults to `\"bar\"`.\n   *\n   * The path does not care. A rectangle gives a masonry band; a circle gives wrought iron tubing\n   * arching over a gate. Same arc, same frames, same flat base caps — nothing else changes.\n   */\n  profile?: \"bar\" | \"tube\";\n  /** Radial depth of the band. Bar only. Defaults to `0.4`. */\n  thickness?: number;\n  /** Depth out of the arch's plane. Bar only. Defaults to `0.5`. */\n  depth?: number;\n  /** Radius of the tube. Tube only. Defaults to `0.08`. */\n  tubeRadius?: number;\n  /** Sides of the tube. `4` gives square tubing, which is what wrought iron actually is. Defaults to `8`. */\n  tubeSides?: number;\n  /**\n   * Smoothness of the arc — the low-poly knob. Defaults to `24`.\n   *\n   * Rounded UP to an even number for `pointed` and `ogee`, whose crown is a point sitting exactly\n   * halfway along the arc: an odd count never samples it, and the tip gets chamfered off.\n   */\n  segments?: number;\n  /** Stations along each straight leg. Defaults to `2`. */\n  legSegments?: number;\n}\n\n/**\n * An archway — two straight legs rising into any of the named {@link ArchStyle} arches.\n *\n * A swept band, not a filled outline: this is the arch you walk *through*. (For an arched door or a\n * headstone — the same silhouette, filled — see `ArchedSlabGeometry`.) **Both draw from the same\n * `traceArch` curve**, so an archway and the doorway it frames agree by construction.\n *\n * Every station states its TANGENT, taken from the curve's own derivative rather than guessed from the\n * chord between neighbors. That is what lets the base caps sit perfectly flat on the floor. Estimate\n * them from chords instead and the first cap tilts by half a segment angle — the sort of error you fix\n * by nudging a parameter until it looks right without ever learning what was wrong.\n *\n * **A swept band tolerates corners; it tears only at reversals.** Nothing in this vocabulary reverses, so\n * every style sweeps — but two of them put a corner where the arc meets the leg (`segmental`,\n * `horseshoe`, which do not spring vertically), and two put one at the crown (`pointed`, `ogee`, which\n * come to a point). Those corners are joints, not bugs: an impost and a miter.\n *\n * Set `legHeight: 0` and the arch stands on the floor by itself — still flat, because a `semicircle`,\n * `elliptical`, `pointed` or `ogee` arch all supply a vertical tangent at their springing.\n *\n * Local frame: spans X, rises +Y, and lies in the XY plane.\n *\n * @example\n * ```ts\n * const masonry = new ArchGeometry({ span: 4, legHeight: 2 });\n * const gateway = new ArchGeometry({ profile: \"tube\", tubeRadius: 0.06, tubeSides: 4 });\n * const persian = new ArchGeometry({ arch: \"ogee\", archHeight: 3 });\n * const moorish = new ArchGeometry({ arch: \"horseshoe\", archHeight: 2.6 });\n * ```\n */\n/** The curve's own derivative at `u`, lifted into the arch's XY plane. */\nfunction tangentAt(curve: Path, u: number): Vector3 {\n  const tangent = curve.getTangentAt(u);\n  return new Vector3(tangent.x, tangent.y, 0).normalize();\n}\n\nexport class ArchGeometry extends BufferGeometry {\n  readonly span: number;\n  readonly legHeight: number;\n  /** The arch drawn over the legs. */\n  readonly arch: ArchStyle;\n  /** Overall height, crown included. */\n  readonly totalHeight: number;\n\n  constructor({\n    span = 4,\n    legHeight = 2,\n    arch = \"semicircle\",\n    archHeight,\n    profile = \"bar\",\n    thickness = 0.4,\n    depth = 0.5,\n    tubeRadius = 0.08,\n    tubeSides = 8,\n    segments = 24,\n    legSegments = 2,\n  }: ArchGeometryOptions = {}) {\n    super();\n\n    const r = span / 2;\n    // Ask the arch what it will ACTUALLY rise — a semicircle forces its own, a square head has none.\n    const profileOptions = { style: arch, x: 0, y: legHeight, halfSpan: r, rise: archHeight ?? r };\n    const rise = archRise(profileOptions);\n\n    this.span = span;\n    this.legHeight = legHeight;\n    this.arch = arch;\n    this.totalHeight = legHeight + rise;\n\n    const path: PathPoint[] = [];\n\n    const UP = new Vector3(0, 1, 0);\n    const DOWN = new Vector3(0, -1, 0);\n\n    // The arc: springs from the left leg, over the crown, and down to the right. Exactly the curve a\n    // doorway of the same span and rise would be cut from — one vocabulary, two operations.\n    const curve = new Path();\n    curve.moveTo(-r, legHeight);\n    traceArch(curve, { ...profileOptions, from: \"left\", to: \"right\" });\n\n    const springTangent = tangentAt(curve, 0);\n    const landTangent = tangentAt(curve, 1);\n\n    // Does the arch actually leave the leg going straight up? A semicircle, an ellipse, a point and an\n    // ogee all do. A segmental or horseshoe arch does NOT — it springs at an angle, and a square head\n    // leaves horizontally.\n    const springsVertically = springTangent.dot(UP) > 1 - 1e-6;\n    const landsVertically = landTangent.dot(DOWN) > 1 - 1e-6;\n\n    // Left leg: straight up. Zero curvature — where Frenet frames are undefined and transport is not.\n    for (let i = 0; i < legSegments; i++) {\n      path.push({\n        position: new Vector3(-r, (i / legSegments) * legHeight, 0),\n        tangent: UP.clone(),\n      });\n    }\n\n    // When the arch does NOT spring vertically, pin one more station AT the springing still holding the\n    // LEG's tangent. Without it the frame has to swing from vertical to the arc's angle across the whole\n    // gap between the leg's last station and the arc's first — so the leg bends and creases inward over\n    // its entire upper length instead of standing straight. With it, the whole turn happens in one place,\n    // as a sharp joint. Which is what an impost IS: the block a segmental arch springs from.\n    if (!springsVertically) {\n      path.push({ position: new Vector3(-r, legHeight, 0), tangent: UP.clone() });\n    }\n\n    // `pointed` and `ogee` come to a POINT at the crown, and by symmetry that crown sits exactly halfway\n    // along the arc. An ODD segment count never samples `u = 0.5`, so the tip is never visited and the\n    // sweep chamfers flat across it — the point just disappears. Round up to even and it survives.\n    const pointy = arch === \"pointed\" || arch === \"ogee\";\n    const arcSegments = pointy && segments % 2 === 1 ? segments + 1 : segments;\n\n    // Space the stations by ARC LENGTH, so an ogee's long flanks do not starve its point of segments.\n    const stations = curve.getSpacedPoints(arcSegments);\n\n    // A joint station STANDS ON the springing, so skip the arc sample that lands on the same point.\n    //\n    // This must be explicit. `transportFrames` silently drops coincident stations — and it drops the\n    // LATER of the pair — so leaving both in place gives two different joints from one construction: at\n    // the start the joint wins and the arc sample is dropped, at the end the arc sample wins and the\n    // joint is dropped. The left leg comes out straight and the right one creases, which is exactly the\n    // bug this fixes.\n    const from = springsVertically ? 0 : 1;\n    const to = landsVertically ? arcSegments : arcSegments - 1;\n\n    for (let i = from; i <= to; i++) {\n      const point = stations[i];\n      path.push({\n        position: new Vector3(point.x, point.y, 0),\n        tangent: tangentAt(curve, i / arcSegments),\n      });\n    }\n\n    // The same joint on the way down.\n    if (!landsVertically) {\n      path.push({ position: new Vector3(r, legHeight, 0), tangent: DOWN.clone() });\n    }\n\n    // Right leg: straight back down.\n    for (let i = 1; i <= legSegments; i++) {\n      path.push({\n        position: new Vector3(r, legHeight - (i / legSegments) * legHeight, 0),\n        tangent: DOWN.clone(),\n      });\n    }\n\n    // For this planar path the normal comes out as +Z (out of plane) and the binormal as radial, so a\n    // rectangle's first axis is the wall's depth and its second is the band's thickness.\n    const section: Vec2[] =\n      profile === \"tube\" ? circleProfile(tubeRadius, tubeSides) : rectProfile(thickness, depth);\n\n    this.copy(sweep(section, transportFrames(path)));\n  }\n}\n","import type { Vec2 } from \"../mesh/GeometryBuffers\";\n\n/**\n * Solid-backed corner-section styles: cove/scotia hollows, ovolo convex quarter, ogee/cyma S-curves,\n * chamfer splay, fillet rectangle, and step polygon.\n */\nexport type MoldingStyle = \"cove\" | \"ovolo\" | \"chamfer\" | \"ogee\" | \"cyma\" | \"scotia\" | \"fillet\" | \"step\";\n\nexport interface MoldingProfileOptions {\n  /** Exposed contour between the wall and ceiling or floor backs. */\n  style?: MoldingStyle;\n  /** Distance along the wall from the corner. */\n  drop?: number;\n  /** Distance along the ceiling or floor from the wall. */\n  projection?: number;\n  /** Curve subdivisions; endpoints remain at drop and projection. Chamfer, fillet and step use fixed polygons. */\n  segments?: number;\n}\n\n/**\n * Closed CCW corner profile in (normal, binormal) coordinates, with backs meeting at (0, 0).\n * The x extent is drop; the y extent is projection.\n *\n * ```\n *          ceiling\n *     ────┬──────────────────►  projection   (the profile's `y`, and the sweep's binormal)\n *         │╲\n *    wall │ ╲___\n *         │      ╲\n *         ▼        ╵\n *        drop  (the profile's `x`, and the sweep's normal)\n * ```\n *\n * ```ts\n * const cornice = sweep(moldingProfile({ style: \"ogee\", drop: 0.12, projection: 0.09 }), stations, {\n *   closed: true,\n * });\n * ```\n */\nexport function moldingProfile({\n  style = \"cove\",\n  drop = 0.09,\n  projection = 0.09,\n  segments = 6,\n}: MoldingProfileOptions = {}): Vec2[] {\n  const steps = Math.max(1, Math.round(segments));\n  // Start at the intersection of the two backs.\n  const points: Vec2[] = [[0, 0]];\n\n  switch (style) {\n    case \"chamfer\":\n      points.push([drop, 0], [0, projection]);\n      break;\n\n    case \"ovolo\":\n      // A convex quarter about the corner: the face bulges into the room.\n      for (let i = 0; i <= steps; i++) {\n        const t = (i / steps) * (Math.PI / 2);\n        points.push([drop * Math.cos(t), projection * Math.sin(t)]);\n      }\n      break;\n\n    case \"cove\":\n      // A concave quarter about the OUTER corner, so the face falls back toward the wall line.\n      for (let i = 0; i <= steps; i++) {\n        const t = (i / steps) * (Math.PI / 2);\n        points.push([drop * (1 - Math.sin(t)), projection * (1 - Math.cos(t))]);\n      }\n      break;\n\n    case \"ogee\": {\n      // Two quarters of half size meeting at the diagonal's midpoint — convex nearest the wall, concave\n      // nearest the ceiling. That order is what makes it a cyma RECTA; swapping them gives the reversa.\n      const half = Math.max(1, Math.round(steps / 2));\n      const hx = drop / 2;\n      const hy = projection / 2;\n      for (let i = 0; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([hx + hx * Math.cos(t), hy * Math.sin(t)]);\n      }\n      for (let i = 1; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([hx - hx * Math.sin(t), projection - hy * Math.cos(t)]);\n      }\n      break;\n    }\n\n    case \"cyma\": {\n      // The reversa: the same two quarters, swapped. Hollow nearest the wall, bulge nearest the ceiling.\n      const half = Math.max(1, Math.round(steps / 2));\n      const hx = drop / 2;\n      const hy = projection / 2;\n      for (let i = 0; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([drop - hx * Math.sin(t), hy * (1 - Math.cos(t))]);\n      }\n      for (let i = 1; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([hx * Math.cos(t), hy + hy * Math.sin(t)]);\n      }\n      break;\n    }\n\n    case \"scotia\": {\n      // A cubic with control points on the backs preserves endpoint tangents and an asymmetric hollow.\n      // The Bézier control hull bounds it within drop × projection.\n      const p0: Vec2 = [drop, 0];\n      const p1: Vec2 = [drop * (1 - SCOTIA_WALL_PULL), 0];\n      const p2: Vec2 = [0, projection * (1 - SCOTIA_CEILING_PULL)];\n      const p3: Vec2 = [0, projection];\n      for (let i = 0; i <= steps; i++) {\n        const t = i / steps;\n        const u = 1 - t;\n        const a = u * u * u;\n        const b = 3 * u * u * t;\n        const c = 3 * u * t * t;\n        const d = t * t * t;\n        points.push([\n          a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0],\n          a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1],\n        ]);\n      }\n      break;\n    }\n\n    case \"fillet\":\n      points.push([drop, 0], [drop, projection], [0, projection]);\n      break;\n\n    case \"step\":\n      // Two steps; segments does not alter this polygon.\n      points.push(\n        [drop, 0],\n        [drop, projection * STEP_FRACTION],\n        [drop * STEP_FRACTION, projection * STEP_FRACTION],\n        [drop * STEP_FRACTION, projection],\n        [0, projection],\n      );\n      break;\n  }\n\n  return points;\n}\n\n/** Step riser position as a fraction of each dimension. */\nconst STEP_FRACTION = 0.45;\n\n/** Unequal Bézier control-point fractions for the scotia hollow. */\nconst SCOTIA_WALL_PULL = 0.85;\nconst SCOTIA_CEILING_PULL = 0.35;\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { miterFrames } from \"../../modeling/surfaces/MiterFrames\";\nimport { moldingProfile, type MoldingStyle } from \"../../modeling/profiles/MoldingProfiles\";\nimport { sweep } from \"../../modeling/surfaces/Sweep\";\nimport type { Vec2 } from \"../../modeling/mesh/GeometryBuffers\";\n\n/** Which corner the molding sits in, and therefore which way its face runs. */\nexport type MoldingRun = \"crown\" | \"base\";\n\n/** Which side of the run the molding stands on. */\nexport type MoldingFacing = \"inward\" | \"outward\";\n\nexport interface MoldingGeometryOptions {\n  /**\n   * The CORNER LINE the molding follows — where wall meets ceiling for a crown, wall meets floor for a\n   * base. One point per corner, in order. Two points is a single length; three is one corner; a whole\n   * room is the footprint with `closed`.\n   *\n   * Lift a plan straight into one: `footprint.map((p) => new Vector3(p.x, ceilingY, p.y))`.\n   */\n  points: Vector3[];\n  /** Close the run back onto its first point — a room, rather than a wall. Defaults to `false`. */\n  closed?: boolean;\n  /** Which section. Defaults to `\"cove\"`. See {@link MoldingStyle}. */\n  style?: MoldingStyle;\n  /**\n   * A section of your own, overriding `style`. Any closed profile works — the corners never see it.\n   *\n   * Author it in the same corner axes {@link moldingProfile} uses: `x` runs along the wall, `y` out from\n   * it, with the corner at the origin.\n   */\n  profile?: Vec2[];\n  /** How far the molding runs along the wall. Defaults to `0.09`. */\n  drop?: number;\n  /** How far it stands out from the wall. Defaults to `0.09`. */\n  projection?: number;\n  /** How finely the section's face is cut — the low-poly knob. Defaults to `6`. */\n  segments?: number;\n  /**\n   * Which corner this is. Defaults to `\"crown\"`.\n   *\n   * - `\"crown\"` — the corner line is at the CEILING and the molding hangs down from it.\n   * - `\"base\"` — the corner line is at the FLOOR and the molding stands up from it. A baseboard, or a\n   *   plinth. The identical section, flipped.\n   *\n   * Both take the same profile, because a molding's two backs do not care which surface is which.\n   */\n  run?: MoldingRun;\n  /**\n   * Which side of the run the molding stands on. Defaults to `\"inward\"` — a room, seen from inside.\n   *\n   * Honored **regardless of how the points are wound**: the run is measured against its own center and\n   * reversed if it came out facing the wrong way. A winding rule the caller has to remember is a rule\n   * that silently produces molding facing into the wall.\n   *\n   * A perfectly straight run has no inside, so nothing is flipped there — reverse the points, or swap\n   * this, if it lands on the wrong face.\n   */\n  facing?: MoldingFacing;\n}\n\n/**\n * Molding run along a wall line — crown at the ceiling, base at the floor.\n *\n * This is a sweep of a {@link moldingProfile} along the corner line, and the only interesting part is\n * what happens where two walls meet: the run is framed with {@link miterFrames}, so every corner is cut\n * on the plane bisecting it and the two lengths share one ring. The joint closes exactly, at any angle,\n * for any section — **the miter never sees the profile, because the corner is a property of the path.**\n *\n * A carpenter *copes* an inside corner rather than mitering it, but that is a tolerance trick for walls\n * that are not truly square. These walls are square, so the miter is exact.\n *\n * Real crown molding cut on a saw needs a COMPOUND miter — two settings, because the stock lies tilted\n * against the fence. That is an artifact of cutting flat: in world space the corner is a single vertical\n * plane through the bisector, which is what this builds and why nothing extra is needed for it.\n *\n * **A short run between two CONCAVE corners has a floor.** Both miters carry material inward along the\n * segment between them, so a face narrower than `2 · projection · tan(turn / 2)` — 2× the projection at\n * right angles — has its two ends overlapping. Nothing is wrong when that happens: both miters are exact,\n * and the request simply does not fit, the way molding too deep for a narrow alcove does not fit on a real\n * wall. A CONVEX pair has no such limit, because there the miters spread apart instead — molding wraps a\n * chimney breast at any width.\n *\n * An open run gets a square cut at each end, which is a length dying into a doorway. A closed run has no\n * ends at all, and no caps.\n *\n * No origin of its own: it is drawn where its `points` are, so a run built from a room's footprint lands\n * in that room. Material groups: none — pass one material, not an array.\n *\n * @example\n * ```ts\n * const room = [\n *   new Vector3(-2, 2.4, -1.5),\n *   new Vector3(2, 2.4, -1.5),\n *   new Vector3(2, 2.4, 1.5),\n *   new Vector3(-2, 2.4, 1.5),\n * ];\n *\n * const cornice = new Mesh(new MoldingGeometry({ points: room, closed: true, style: \"ogee\" }), plaster);\n * ```\n */\nexport class MoldingGeometry extends BufferGeometry {\n  constructor({\n    points,\n    closed = false,\n    style = \"cove\",\n    profile,\n    drop = 0.09,\n    projection = 0.09,\n    segments = 6,\n    run = \"crown\",\n    facing = \"inward\",\n  }: MoldingGeometryOptions) {\n    super();\n\n    if (points.length < 2) throw new Error(\"MoldingGeometry: a run needs at least two points.\");\n\n    const section = profile ?? moldingProfile({ style, drop, projection, segments });\n    // The profile's `x` runs along the wall AWAY from the corner line, and `sweep` puts that on the\n    // station's normal — so seeding the frame with DOWN hangs a crown, and with UP stands a base.\n    const reference = new Vector3(0, run === \"crown\" ? -1 : 1, 0);\n\n    const frame = (ordered: Vector3[]) =>\n      miterFrames(\n        ordered.map((position) => ({ position: position.clone(), tangent: new Vector3() })),\n        { closed, reference },\n      );\n\n    // A crown seeds its frame with DOWN where a base seeds it with UP, and the binormal is `cut × normal`\n    // — so flipping the reference also flips WHICH SIDE of the path the section projects to. Where the run\n    // has an inside (a room, an L) the test below judges that and corrects it. A STRAIGHT run has no\n    // inside, nothing is corrected, and the identical points would put a base in the room and a crown\n    // inside the wall. Pre-flipping the traversal keeps the two agreeing.\n    //\n    // Reversing turns the frame 180° about its normal — a rotation, not a reflection — so the swept\n    // surface keeps its winding either way.\n    let ordered = run === \"crown\" ? [...points].reverse() : [...points];\n    let stations = frame(ordered);\n\n    // Which side the face lands on falls out of the traversal direction, which callers should not have\n    // to reason about. Measure it instead: the binormal either points away from the run's own center or\n    // toward it, and `facing` says which is wanted. Reversing the point list turns the frame 180° about\n    // its normal — a rotation, not a reflection, so the swept surface keeps its winding.\n    const center = ordered\n      .reduce((sum, point) => sum.add(point), new Vector3())\n      .divideScalar(ordered.length);\n    const first = stations[0];\n    if (first) {\n      const away = first.binormal.dot(first.position.clone().sub(center));\n      // A straight run has no inside — its center lies on the line, so the test is meaningless and\n      // nothing is flipped.\n      if (Math.abs(away) > 1e-6 && away > 0 === (facing === \"inward\")) {\n        ordered = ordered.reverse();\n        stations = frame(ordered);\n      }\n    }\n\n    const geometry = sweep(section, stations, { closed });\n    this.copy(geometry);\n    geometry.dispose();\n    this.computeBoundingSphere();\n  }\n}\n","import { BoxGeometry, BufferGeometry, Vector2, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { miterFrames } from \"../../modeling/surfaces/MiterFrames\";\nimport { sweep } from \"../../modeling/surfaces/Sweep\";\nimport { createGeometryBuffers, pushQuad, pushTriangle, toBufferGeometry, type Vec2, type Vec3 } from \"../../modeling/mesh/GeometryBuffers\";\nimport { offsetLoop } from \"../../modeling/profiles/OffsetLoop\";\n\n/** One panel's aperture in the frame, before the groove is taken into account. */\ninterface Opening {\n  x0: number;\n  x1: number;\n  y0: number;\n  y1: number;\n}\n\nexport interface PanelDoorGeometryOptions {\n  /** Width of the door leaf. Defaults to `0.813` — a 32 inch door. */\n  width?: number;\n  /** Height of the leaf. Defaults to `2.032` — 80 inches, the standard door height. */\n  height?: number;\n  /** Thickness of the leaf. Defaults to `0.045`. */\n  thickness?: number;\n  /**\n   * Width of each stile — the two vertical members. Defaults to `0.115`.\n   *\n   * The stiles run the full height and everything else lands on them, so this also sets how far the\n   * panels are held in from the door's edges.\n   */\n  stileWidth?: number;\n  /** Height of the top rail. Defaults to `0.115`, matching the stiles. */\n  topRail?: number;\n  /**\n   * Height of the lock rail — the middle one, named for the lockset it carries. Defaults to `0.2`.\n   *\n   * Deeper than the others because it is bored through for a latch, and because it is the rail a hand\n   * meets.\n   */\n  lockRail?: number;\n  /**\n   * Height of the bottom rail. Defaults to `0.235`.\n   *\n   * Traditionally the deepest member: it is the one that gets kicked, and a taller rail reads as a base\n   * the door stands on rather than a border around it.\n   */\n  bottomRail?: number;\n  /**\n   * Height of the lock rail's CENTER, as a fraction of the door's height. Defaults to `0.44`.\n   *\n   * A fraction rather than a distance, deliberately. Given in world units it would stay put while the\n   * door grew around it, so resizing would quietly change the door's character instead of scaling it —\n   * a tall door would end up with a lock rail down by its knees. As a fraction the proportions hold, and\n   * {@link PanelDoorGeometry.lockRailY} reports where it actually landed.\n   */\n  lockRailPosition?: number;\n  /** Width of the muntin — the short vertical divider between the panels. Defaults to `0.1`. */\n  muntinWidth?: number;\n  /**\n   * How the panels are worked. Defaults to `\"raised\"`.\n   *\n   * - `\"raised\"` — a flat FIELD in the middle, a BEVEL sloping down to a thin edge. The classical panel,\n   *   and what casts the shadow line that makes a paneled door read as paneled.\n   * - `\"flat\"` — a plain board of `panelThickness` throughout. The Shaker door.\n   */\n  panel?: \"raised\" | \"flat\";\n  /** Thickness of the panel at its field. Defaults to `0.018`. */\n  panelThickness?: number;\n  /** Width of the bevel around a raised panel — the slope from field to tongue. Defaults to `0.055`. */\n  bevelWidth?: number;\n  /**\n   * Thickness of the panel's TONGUE, the thinned edge that sits in the frame's groove. Defaults to\n   * `0.008`. Ignored by a flat panel, which is one thickness throughout.\n   */\n  tongueThickness?: number;\n  /**\n   * How far the panel runs into the frame's groove on every side. Defaults to `0.012`.\n   *\n   * A panel is never cut to its opening — a panel the size of the opening falls out of it. It is cut\n   * oversize and held in a groove, loose, so it can move with the season without splitting the frame.\n   */\n  grooveDepth?: number;\n  /** Add planted molding around each panel, on both faces. Defaults to `false`. */\n  molding?: boolean;\n  /** How far the molding lies across the frame, measured out from the opening's edge. Defaults to `0.022`. */\n  moldingWidth?: number;\n  /** How far the molding stands proud of the door's face. Defaults to `0.012`. */\n  moldingHeight?: number;\n  /**\n   * How finely the molding's quarter-round is cut — the low-poly knob. Defaults to `4`.\n   *\n   * `1` is a plain chamfer, `12` reads as turned.\n   */\n  moldingSegments?: number;\n}\n\n/**\n * A four-panel door, built the way a joiner builds one: **frame and panel**.\n *\n * Two STILES run the full height, and the RAILS — top, lock, bottom — butt into them, with a MUNTIN\n * butting between the rails to split each row in two. That is a T-junction at every joint, and it is\n * deliberate rather than a simplification: the hinges screw into the stile and the whole leaf hangs off\n * it, so the stile has to be one continuous member. Mitering those corners would trade the door's\n * strongest member for four end-grain joints. (Mitered frames are a real style, but a cabinet-door one —\n * they cannot carry a door's weight, and a miter cannot join unequal stock, so the deep bottom rail that\n * gives a door its stance would be impossible.)\n *\n * The panels FLOAT. Each one is cut oversize and runs into a groove in the surrounding members, never\n * glued, so it can move with the season without splitting the frame. A raised panel is a flat field with\n * a bevel sloping down to a thin tongue — and its four bevels meet at the corners in a 45° hip, which\n * comes free because the surface is lofted between two loops rather than swept along one.\n *\n * With `molding` on, an ovolo section wraps each opening as one closed **mitered** loop. That is the\n * only miter on the door, and it is the one a joiner cuts too.\n *\n * Stands on the `y = 0` plane, centered on X, with its faces at `±thickness / 2`. To hang it, move the\n * origin onto the hinge stile first — `geometry.translate(width / 2, 0, 0)` puts it on the left edge, so\n * rotating the mesh about Y swings the door.\n *\n * Material groups: none. A door is one piece of joinery in one material, so this is a single geometry\n * with a single group — pass one material, not an array.\n *\n * @example\n * ```ts\n * const door = new Mesh(new PanelDoorGeometry({ molding: true }), paint);\n * ```\n */\nexport class PanelDoorGeometry extends BufferGeometry {\n  /**\n   * Height of the lock rail's center, in world units — where a knob, a latch, or a letter plate mounts.\n   *\n   * Reported rather than assumed, because it follows `lockRailPosition` and the door's height.\n   */\n  readonly lockRailY: number;\n\n  constructor({\n    width = 0.813,\n    height = 2.032,\n    thickness = 0.045,\n    stileWidth = 0.115,\n    topRail = 0.115,\n    lockRail = 0.2,\n    bottomRail = 0.235,\n    lockRailPosition = 0.44,\n    muntinWidth = 0.1,\n    panel = \"raised\",\n    panelThickness = 0.018,\n    bevelWidth = 0.055,\n    tongueThickness = 0.008,\n    grooveDepth = 0.012,\n    molding = false,\n    moldingWidth = 0.022,\n    moldingHeight = 0.012,\n    moldingSegments = 4,\n  }: PanelDoorGeometryOptions = {}) {\n    super();\n\n    const halfWidth = width / 2;\n    const front = thickness / 2;\n    const innerLeft = -halfWidth + stileWidth;\n    const innerRight = halfWidth - stileWidth;\n    const lockBottom = height * lockRailPosition - lockRail / 2;\n    const lockTop = lockBottom + lockRail;\n    const topRailBottom = height - topRail;\n\n    this.lockRailY = lockBottom + lockRail / 2;\n\n    const parts: BufferGeometry[] = [\n      // The stiles, full height. Everything else lands on them.\n      box(-halfWidth, innerLeft, 0, height, -front, front),\n      box(innerRight, halfWidth, 0, height, -front, front),\n      // The rails, butting between the stiles.\n      box(innerLeft, innerRight, topRailBottom, height, -front, front),\n      box(innerLeft, innerRight, lockBottom, lockTop, -front, front),\n      box(innerLeft, innerRight, 0, bottomRail, -front, front),\n      // The muntins, butting between the rails.\n      box(-muntinWidth / 2, muntinWidth / 2, lockTop, topRailBottom, -front, front),\n      box(-muntinWidth / 2, muntinWidth / 2, bottomRail, lockBottom, -front, front),\n    ];\n\n    const openings: Opening[] = [\n      { x0: innerLeft, x1: -muntinWidth / 2, y0: lockTop, y1: topRailBottom },\n      { x0: muntinWidth / 2, x1: innerRight, y0: lockTop, y1: topRailBottom },\n      { x0: innerLeft, x1: -muntinWidth / 2, y0: bottomRail, y1: lockBottom },\n      { x0: muntinWidth / 2, x1: innerRight, y0: bottomRail, y1: lockBottom },\n    ];\n\n    for (const opening of openings) {\n      // Members can be given widths that leave no opening at all. A degenerate panel is worse than a\n      // missing one — it inverts, and the bevel folds through itself.\n      if (opening.x1 - opening.x0 <= 0 || opening.y1 - opening.y0 <= 0) continue;\n\n      parts.push(\n        buildPanel(opening, { panel, panelThickness, bevelWidth, tongueThickness, grooveDepth }),\n      );\n\n      if (molding) {\n        parts.push(\n          ...buildMolding(opening, {\n            thickness,\n            moldingWidth,\n            moldingHeight,\n            moldingSegments,\n          }),\n        );\n      }\n    }\n\n    // Not cast — `mergeGeometries` returns null on mismatched attributes, and a cast turns that into an\n    // unreadable \"cannot read properties of null\" three frames later. Every part here is indexed and\n    // carries position, normal, and uv, which is what it requires.\n    const merged = mergeGeometries(parts, false);\n    if (!merged) throw new Error(\"PanelDoorGeometry: parts have incompatible attributes.\");\n\n    this.copy(merged);\n    merged.dispose();\n    parts.forEach((part) => part.dispose());\n    this.computeBoundingSphere();\n  }\n}\n\n/** A rectangular member, given by the two corners it spans. */\nfunction box(x0: number, x1: number, y0: number, y1: number, z0: number, z1: number): BufferGeometry {\n  return new BoxGeometry(x1 - x0, y1 - y0, z1 - z0).translate(\n    (x0 + x1) / 2,\n    (y0 + y1) / 2,\n    (z0 + z1) / 2,\n  );\n}\n\n/** A flat cap over a closed loop at height `z`, facing `+Z` when `outward` is positive. */\nfunction pushCap(\n  buffers: ReturnType<typeof createGeometryBuffers>,\n  loop: Vector2[],\n  z: number,\n  outward: number,\n): void {\n  const normal: Vec3 = [0, 0, Math.sign(outward)];\n  const at = (i: number): Vec3 => [loop[i]!.x, loop[i]!.y, z];\n  const order = outward > 0 ? loop.map((_, i) => i) : loop.map((_, i) => loop.length - 1 - i);\n\n  if (loop.length === 4) {\n    pushQuad(buffers, [at(order[0]!), at(order[1]!), at(order[2]!), at(order[3]!)], normal);\n    return;\n  }\n  // A fan, for a field that is not a rectangle.\n  for (let i = 1; i < loop.length - 1; i++) {\n    pushTriangle(buffers, [at(order[0]!), at(order[i]!), at(order[i + 1]!)], normal);\n  }\n}\n\n/**\n * One panel: a loft between the outline and the field inset inside it, mirrored on both faces, closed\n * by the tongue's own edge.\n *\n * The inset comes from {@link offsetLoop} rather than from shrinking the rectangle. The two agree only\n * on a square — and only a real offset survives the day this outline stops being a rectangle.\n */\nfunction buildPanel(\n  opening: Opening,\n  {\n    panel,\n    panelThickness,\n    bevelWidth,\n    tongueThickness,\n    grooveDepth,\n  }: Required<Pick<PanelDoorGeometryOptions, \"panel\" | \"panelThickness\" | \"bevelWidth\" | \"tongueThickness\" | \"grooveDepth\">>,\n): BufferGeometry {\n  // Cut oversize on every side, to sit in the groove.\n  const outline = [\n    new Vector2(opening.x0 - grooveDepth, opening.y0 - grooveDepth),\n    new Vector2(opening.x1 + grooveDepth, opening.y0 - grooveDepth),\n    new Vector2(opening.x1 + grooveDepth, opening.y1 + grooveDepth),\n    new Vector2(opening.x0 - grooveDepth, opening.y1 + grooveDepth),\n  ];\n\n  const buffers = createGeometryBuffers();\n  const flat = panel === \"flat\";\n  const edge = flat ? panelThickness / 2 : tongueThickness / 2;\n  const field = panelThickness / 2;\n  // Never past the middle: a bevel wider than half the panel has no field left to slope down to.\n  const span = Math.min(opening.x1 - opening.x0, opening.y1 - opening.y0) / 2;\n  const inset = Math.max(0, Math.min(bevelWidth, span - 0.005));\n  const inner = flat || inset === 0 ? outline : offsetLoop(outline, -inset);\n\n  for (const side of [1, -1]) {\n    pushCap(buffers, inner, side * field, side);\n    if (flat || inner === outline) continue;\n\n    // The bevel. Each quad carries its own slanted normal, so it facets under flat shading — and the\n    // four bands meet at the corners in a 45° hip, which is free here only because this is a loft\n    // between two loops rather than a sweep along one.\n    for (let i = 0; i < outline.length; i++) {\n      const j = (i + 1) % outline.length;\n      const o0: Vec3 = [outline[i]!.x, outline[i]!.y, side * edge];\n      const o1: Vec3 = [outline[j]!.x, outline[j]!.y, side * edge];\n      const f1: Vec3 = [inner[j]!.x, inner[j]!.y, side * field];\n      const f0: Vec3 = [inner[i]!.x, inner[i]!.y, side * field];\n      pushQuad(buffers, side > 0 ? [o0, o1, f1, f0] : [o1, o0, f0, f1], undefined);\n    }\n  }\n\n  // The tongue's edge — the sliver that disappears into the groove.\n  for (let i = 0; i < outline.length; i++) {\n    const j = (i + 1) % outline.length;\n    pushQuad(\n      buffers,\n      [\n        [outline[i]!.x, outline[i]!.y, -edge],\n        [outline[j]!.x, outline[j]!.y, -edge],\n        [outline[j]!.x, outline[j]!.y, edge],\n        [outline[i]!.x, outline[i]!.y, edge],\n      ],\n      undefined,\n    );\n  }\n\n  return toBufferGeometry(buffers);\n}\n\n/**\n * A quarter-round (ovolo) section, in the sweep station's own axes.\n *\n * `px` runs along the frame's normal — proud of the door's face — and `py` along its binormal, which on a\n * loop wound counter-clockwise points radially outward, away from the opening. So the section is a lip\n * standing at the opening's edge, curving down onto the frame.\n */\nfunction ovoloProfile(width: number, height: number, segments: number): Vec2[] {\n  const points: Vec2[] = [[0, 0]];\n  for (let i = 0; i <= segments; i++) {\n    const angle = (i / segments) * (Math.PI / 2);\n    points.push([height * Math.cos(angle), width * Math.sin(angle)]);\n  }\n  return points;\n}\n\n/**\n * Planted molding around one opening, on both faces — a closed mitered loop each.\n *\n * The section never has to know about the corners: the miter is a property of the PATH, so an arbitrary\n * routed profile wraps the opening exactly as a plain bar would. The back face runs the loop reversed\n * against a `-Z` reference, which lands the same section proud of the back and still facing outward.\n */\nfunction buildMolding(\n  opening: Opening,\n  {\n    thickness,\n    moldingWidth,\n    moldingHeight,\n    moldingSegments,\n  }: Required<Pick<PanelDoorGeometryOptions, \"thickness\" | \"moldingWidth\" | \"moldingHeight\" | \"moldingSegments\">>,\n): BufferGeometry[] {\n  const profile = ovoloProfile(moldingWidth, moldingHeight, Math.max(1, Math.round(moldingSegments)));\n  const front = thickness / 2;\n\n  return [1, -1].map((side) => {\n    const loop = [\n      new Vector3(opening.x0, opening.y0, side * front),\n      new Vector3(opening.x1, opening.y0, side * front),\n      new Vector3(opening.x1, opening.y1, side * front),\n      new Vector3(opening.x0, opening.y1, side * front),\n    ];\n    if (side < 0) loop.reverse();\n\n    return sweep(\n      profile,\n      miterFrames(\n        loop.map((position) => ({ position, tangent: new Vector3() })),\n        { closed: true, reference: new Vector3(0, 0, side) },\n      ),\n      { closed: true },\n    );\n  });\n}\n","import { BufferAttribute, BufferGeometry } from \"three\";\nimport { pushSpiralRiser, pushSpiralTread } from \"./staircaseQuad\";\n\nexport interface SpiralStaircaseGeometryOptions {\n  /** Newel / center-hole radius (inner edge of every tread). Defaults to `0.45`. */\n  innerRadius?: number;\n  /** Radial tread width (outer − inner radius). Defaults to `1.95`. */\n  width?: number;\n  /** Arc run per step at the walking line (mid-radius). Defaults to `0.45`. */\n  treadDepth?: number;\n  /** Vertical rise per step (riser). Defaults to `0.2`. */\n  riserHeight?: number;\n  /** Number of steps. Defaults to `20`. */\n  stepCount?: number;\n  /** Spiral start angle in radians (+X = 0, CCW). Defaults to `0`. */\n  startAngle?: number;\n  /** Override step angle (radians). When omitted, derived from `treadDepth`. */\n  stepAngle?: number;\n}\n\n/**\n * Turret-style spiral staircase — trapezoidal treads between an inner newel radius\n * and an outer wall radius, ascending counter-clockwise when viewed from above.\n *\n * Each step is a four-sided tread (no pinched center point). Step angle is\n * derived from tread depth at the mid-radius so treads meet without overlapping.\n */\nexport class SpiralStaircaseGeometry extends BufferGeometry {\n  readonly innerRadius: number;\n  readonly width: number;\n  readonly outerRadius: number;\n  readonly treadDepth: number;\n  readonly riserHeight: number;\n  readonly stepCount: number;\n  readonly startAngle: number;\n  readonly stepAngle: number;\n  readonly totalHeight: number;\n  readonly totalTurn: number;\n\n  constructor({\n    innerRadius = 0.45,\n    width = 1.95,\n    treadDepth = 0.45,\n    riserHeight = 0.2,\n    stepCount = 20,\n    startAngle = 0,\n    stepAngle: stepAngleOption,\n  }: SpiralStaircaseGeometryOptions = {}) {\n    super();\n\n    this.innerRadius = Math.max(0.01, innerRadius);\n    this.width = Math.max(0.05, width);\n    this.outerRadius = this.innerRadius + this.width;\n    this.treadDepth = treadDepth;\n    this.riserHeight = riserHeight;\n    this.stepCount = Math.max(1, Math.round(stepCount));\n    this.startAngle = startAngle;\n\n    const walkRadius = (this.innerRadius + this.outerRadius) * 0.5;\n    this.stepAngle =\n      stepAngleOption ?? Math.max(treadDepth / walkRadius, Math.PI / 180);\n    this.totalHeight = this.stepCount * this.riserHeight;\n    this.totalTurn = this.stepCount * this.stepAngle;\n\n    const positions: number[] = [];\n    const normals: number[] = [];\n    const uvs: number[] = [];\n    const indices: number[] = [];\n    const buffers = { positions, normals, uvs, indices };\n\n    for (let i = 0; i < this.stepCount; i++) {\n      const angleStart = this.startAngle + i * this.stepAngle;\n      const angleEnd = angleStart + this.stepAngle;\n      const yBottom = i * this.riserHeight;\n      const yTop = yBottom + this.riserHeight;\n\n      pushSpiralRiser(buffers, this.innerRadius, this.outerRadius, yBottom, yTop, angleStart);\n      pushSpiralTread(buffers, this.innerRadius, this.outerRadius, yTop, angleStart, angleEnd);\n    }\n\n    this.setIndex(indices);\n    this.setAttribute(\"position\", new BufferAttribute(new Float32Array(positions), 3));\n    this.setAttribute(\"normal\", new BufferAttribute(new Float32Array(normals), 3));\n    this.setAttribute(\"uv\", new BufferAttribute(new Float32Array(uvs), 2));\n    this.computeBoundingSphere();\n  }\n}","import { BufferGeometry, Vector3 } from \"three\";\nimport type { PathPoint } from \"../../modeling/paths/PathPoint\";\nimport { circleProfile } from \"../../modeling/profiles/Profiles\";\nimport { sweep, transportFrames } from \"../../modeling/surfaces/Sweep\";\n\nexport interface SmokeCurlGeometryOptions {\n  /** How far the curl swings out from its axis by the top. Defaults to `0.7`. */\n  swirl?: number;\n  /** How high it rises. Defaults to `3`. */\n  height?: number;\n  /** How many times it wraps around as it climbs. Defaults to `1.25`. */\n  turns?: number;\n  /** Thickness at the root. Defaults to `0.22`. */\n  radius?: number;\n  /** 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`. */\n  taper?: number;\n  /** Stations along the path — the smoothness of the curl. Defaults to `80`. */\n  segments?: number;\n  /** Sides of the cross-section. `4` gives a hard-edged ribbon, `16` a round wisp. Defaults to `8`. */\n  sides?: number;\n}\n\n/**\n * A rising, wrapping curl — the stylized steam trailing a chimney, or the smoke off a snuffed candle.\n *\n * This is the library's first path that LEAVES ITS PLANE. The arch and the scroll are both flat, so\n * their frames only ever had to survive straight runs and changing curvature. A curl has **torsion**:\n * it twists out of any plane you could draw through it, which is precisely the case where a Frenet\n * frame spins the cross-section like a corkscrew for no reason at all. Parallel transport carries the\n * section along without ever spinning it, and this is the shape where you can see the difference.\n *\n * The tangent is analytic, not estimated from the chords. For `r(t)·(cos θ, ·, sin θ)` climbing in Y:\n *\n * ```text\n *   dx/dt = r'·cos θ − r·sin θ·θ'\n *   dy/dt = height\n *   dz/dt = r'·sin θ + r·cos θ·θ'\n * ```\n *\n * Local frame: root at the origin, rising +Y.\n *\n * @example\n * ```ts\n * const geometry = new SmokeCurlGeometry({ turns: 2, taper: 0.02 });\n * ```\n */\nexport class SmokeCurlGeometry extends BufferGeometry {\n  readonly height: number;\n\n  constructor({\n    swirl = 0.7,\n    height = 3,\n    turns = 1.25,\n    radius = 0.22,\n    taper = 0.04,\n    segments = 80,\n    sides = 8,\n  }: SmokeCurlGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n\n    const dTheta = turns * Math.PI * 2; // θ'(t)\n    const dR = swirl; //                   r'(t) — the curl widens linearly as it rises\n\n    const path: PathPoint[] = Array.from({ length: segments + 1 }, (_, i) => {\n      const t = i / segments;\n      const theta = dTheta * t;\n      const r = swirl * t;\n\n      return {\n        position: new Vector3(r * Math.cos(theta), height * t, r * Math.sin(theta)),\n        tangent: new Vector3(\n          dR * Math.cos(theta) - r * Math.sin(theta) * dTheta,\n          height,\n          dR * Math.sin(theta) + r * Math.cos(theta) * dTheta,\n        ),\n      };\n    });\n\n    const geometry = sweep(circleProfile(radius, sides), transportFrames(path), {\n      // Ease the taper rather than running it linearly — smoke thins slowly, then vanishes fast.\n      scale: (t) => 1 - (1 - taper) * t * t,\n      cap: false, // the root emerges from something; the tip is already a point\n    });\n\n    this.copy(geometry);\n    geometry.dispose();\n  }\n}\n","import { BoxGeometry, BufferGeometry, ConeGeometry, CylinderGeometry, ExtrudeGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { WallShape } from \"../../shapes/WallShape\";\n\n/** Where a door hangs in this mausoleum, in the mausoleum's own coordinates. */\nexport interface MausoleumDoorway {\n  /** Width of the opening. A door should be built slightly narrower, for clearance. */\n  width: number;\n  /** Height of the opening's straight sides, to the springing. */\n  height: number;\n  /** Rise of the arch. Equal to `width / 2` — a perfect semicircle. */\n  archHeight: number;\n  /** Centerline of the opening. */\n  x: number;\n  /** The sill. A door's `y = 0` sits here. */\n  y: number;\n  /**\n   * The hinge plane — the wall's OUTER face. A door's `z = 0` (its front face, where the straps are\n   * bolted and the pin stands) sits here.\n   *\n   * Not set back into the reveal. Strap hinges are mounted on the face you can reach, so the pin is\n   * flush with the facade and the slab hangs behind it, filling the reveal. Sink the hinge plane into\n   * the opening instead and the ironwork disappears into the jamb's shadow — which is exactly what a\n   * real door does not do.\n   */\n  z: number;\n}\n\nconst BUILDING_WIDTH = 4;\nconst BUILDING_HEIGHT = 3;\nconst BUILDING_DEPTH = 4;\nconst BASE_HEIGHT = 1;\n\n/** Real walls, with real thickness — so the inside is a place you can look into, not a hollow illusion. */\nconst WALL = 0.3;\nconst CEILING = 0.2;\n\nconst HW = BUILDING_WIDTH / 2;\nconst HD = BUILDING_DEPTH / 2;\n/** The interior clear span, wall face to wall face. */\nconst INNER_W = BUILDING_WIDTH - WALL * 2;\nconst INNER_D = BUILDING_DEPTH - WALL * 2;\n\n/** The opening. `archHeight === width / 2`, so the arch is a true semicircle. */\nconst DOORWAY = { width: 1.24, height: 1.42, archHeight: 0.62 };\n\n/**\n * A mausoleum — four stone walls, a peaked roof, and an arched doorway you can walk through.\n *\n * **The building is a SHELL, not a block.** Each wall is a slab with real thickness, so the interior is\n * genuine space with genuine inward-facing surfaces: open the doors and you look into a room, not at a\n * backface. That is the whole reason the walls cost four boxes instead of one.\n *\n * **The doorway is carved out of the front wall's OUTLINE, not punched through it as a hole.** A void\n * that reaches the floor is not a hole — `ExtrudeGeometry` would run a side wall along its bottom edge\n * and hand you a face lying across the threshold. Drawing the wall *around* the opening means that face\n * never exists, and the notch's side walls become the reveals: the jambs and the arch soffit. See\n * {@link WallShape}, which owns this distinction.\n *\n * The doorway's dimensions are published on {@link MausoleumGeometry.doorway} rather than left for a\n * caller to rediscover — hang a door by asking the building where its hinges go, the same way a fence\n * run asks a post how wide it is.\n *\n * Group indices:\n * 0. Base\n * 1. Building — walls and pillars\n * 2. Roof\n * 3. Interior — the floor and ceiling of the room inside\n *\n * @example\n * ```ts\n * const mausoleum = new Mausoleum();\n * const { width, height, archHeight, x, y, z } = mausoleum.geometry.doorway;\n *\n * const doors = createDoubleDoor({ width: width - 0.04, height: height - 0.02, archHeight: archHeight - 0.02 });\n * doors.position.set(x, y, z);\n * mausoleum.add(doors);\n * ```\n */\nexport class MausoleumGeometry extends BufferGeometry {\n  /** Where a door hangs. See {@link MausoleumDoorway}. */\n  readonly doorway: MausoleumDoorway = {\n    ...DOORWAY,\n    x: 0,\n    y: BASE_HEIGHT,\n    z: HD, // the facade — the hinges are bolted to the OUTSIDE of the wall\n  };\n\n  constructor() {\n    super();\n\n    // Base of the Mausoleum\n    const baseGeometry = new BoxGeometry(5, BASE_HEIGHT, 5);\n    baseGeometry.translate(0, BASE_HEIGHT / 2, 0);\n\n    // The front wall carries the doorway. The opening is part of this outline — walk the floor, up the\n    // jamb, over the arch, down the far jamb, on along the floor.\n    const frontWall = new ExtrudeGeometry(\n      new WallShape({ width: BUILDING_WIDTH, height: BUILDING_HEIGHT, doorway: DOORWAY }),\n      { depth: WALL, bevelEnabled: false, curveSegments: 16 },\n    );\n    frontWall.translate(0, BASE_HEIGHT, HD - WALL);\n    // ExtrudeGeometry is non-indexed and everything else here is indexed; mergeGeometries will not mix\n    // the two, so give it a trivial index rather than flattening every box.\n    frontWall.setIndex([...Array(frontWall.attributes.position.count).keys()]);\n\n    // The other three walls are solid slabs. Left and right are inset to butt against front and back.\n    const backWall = new BoxGeometry(BUILDING_WIDTH, BUILDING_HEIGHT, WALL);\n    backWall.translate(0, BASE_HEIGHT + BUILDING_HEIGHT / 2, -HD + WALL / 2);\n\n    const leftWall = new BoxGeometry(WALL, BUILDING_HEIGHT, INNER_D);\n    leftWall.translate(-HW + WALL / 2, BASE_HEIGHT + BUILDING_HEIGHT / 2, 0);\n\n    const rightWall = new BoxGeometry(WALL, BUILDING_HEIGHT, INNER_D);\n    rightWall.translate(HW - WALL / 2, BASE_HEIGHT + BUILDING_HEIGHT / 2, 0);\n\n    // Pillars\n    const pillarPositions = [\n      [-1.8, 2.3, -2.2],\n      [1.8, 2.3, -2.2],\n      [-1.8, 2.3, 2.2],\n      [1.8, 2.3, 2.2],\n    ];\n\n    const pillars: BufferGeometry[] = [];\n    pillarPositions.forEach((position) => {\n      const pillar = new CylinderGeometry(0.2, 0.2, 3.5, 16);\n      pillar.translate(position[0], position[1], position[2]);\n      pillars.push(pillar);\n    });\n\n    // Roof (Peaked)\n    const roofGeometry = new ConeGeometry(3.5, 2, 4);\n    roofGeometry.rotateY(Math.PI / 4);\n    roofGeometry.translate(0, 5, 0);\n\n    // The room inside. The floor sits a hair proud of the base's top face rather than flush with it —\n    // two coplanar faces at the same depth is a z-fight, and a millimeter is cheaper than a fix.\n    const floor = new BoxGeometry(INNER_W, 0.1, INNER_D);\n    floor.translate(0, BASE_HEIGHT - 0.05 + 0.001, 0);\n\n    const ceiling = new BoxGeometry(INNER_W, CEILING, INNER_D);\n    ceiling.translate(0, BASE_HEIGHT + BUILDING_HEIGHT - CEILING / 2, 0);\n\n    this.copy(\n      mergeGeometries(\n        [\n          baseGeometry,\n          mergeGeometries(\n            [frontWall, backWall, leftWall, rightWall, ...pillars],\n            false,\n          ) as BufferGeometry,\n          roofGeometry,\n          mergeGeometries([floor, ceiling], false) as BufferGeometry,\n        ],\n        true,\n      ) as BufferGeometry,\n    );\n    this.computeVertexNormals();\n  }\n}\n","import { BoxGeometry, BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface StoneFencePostGeometryOptions {\n  /** Main column height, excluding base and cap. Defaults to `2.25`. */\n  height?: number;\n  /** Column width and depth. Defaults to `1`. */\n  columnWidth?: number;\n  /** Base width and depth. Defaults to `1.2`. */\n  baseWidth?: number;\n  /** Base height. Defaults to `0.5`. */\n  baseHeight?: number;\n  /** Cap width and depth. Defaults to `1.4`. */\n  capWidth?: number;\n  /** Cap height. Defaults to `0.3`. */\n  capHeight?: number;\n}\n\n/**\n * Stone fence post — wide base, column, and cap.\n *\n * The stepped profile is the whole point, and the whole difficulty: a fence run meeting this post\n * must clear the widest step at bar height while its rails reach the narrower column. Use\n * {@link widthAt} and {@link maxWidthBetween} to size the run rather than hardcoding the steps.\n *\n * Local frame: base on Y=0.\n *\n * @example\n * ```ts\n * const geometry = new StoneFencePostGeometry({ height: 2.6 });\n * const post = new Mesh(geometry, stoneMaterial);\n * scene.add(post);\n * ```\n */\nexport class StoneFencePostGeometry extends BufferGeometry {\n  /** Column height, excluding base and cap. */\n  readonly height: number;\n  readonly columnWidth: number;\n  readonly baseWidth: number;\n  readonly baseHeight: number;\n  readonly capWidth: number;\n  readonly capHeight: number;\n\n  /** Overall height, base and cap included. */\n  get totalHeight(): number {\n    return this.baseHeight + this.height + this.capHeight;\n  }\n\n  constructor({\n    height = 2.25,\n    columnWidth = 1,\n    baseWidth = 1.2,\n    baseHeight = 0.5,\n    capWidth = 1.4,\n    capHeight = 0.3,\n  }: StoneFencePostGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n    this.columnWidth = columnWidth;\n    this.baseWidth = baseWidth;\n    this.baseHeight = baseHeight;\n    this.capWidth = capWidth;\n    this.capHeight = capHeight;\n\n    const base = new BoxGeometry(baseWidth, baseHeight, baseWidth);\n    base.translate(0, baseHeight / 2, 0);\n\n    const column = new BoxGeometry(columnWidth, height, columnWidth);\n    column.translate(0, baseHeight + height / 2, 0);\n\n    const cap = new BoxGeometry(capWidth, capHeight, capWidth);\n    cap.translate(0, baseHeight + height + capHeight / 2, 0);\n\n    this.copy(mergeGeometries([base, column, cap], false) as BufferGeometry);\n  }\n\n  /**\n   * Post width at height `y` — what a fence run asks to size itself against.\n   *\n   * Zero above and below the post. The profile steps between base, column, and cap.\n   *\n   * This is the face-to-face width, which is what a run meeting the post square-on needs. A run\n   * approaching a corner diagonally would face the wider diagonal instead — worth revisiting when\n   * fences follow arbitrary paths.\n   */\n  widthAt(y: number): number {\n    if (y < 0 || y > this.totalHeight) return 0;\n    if (y <= this.baseHeight) return this.baseWidth;\n    if (y <= this.baseHeight + this.height) return this.columnWidth;\n    return this.capWidth;\n  }\n\n  /**\n   * Widest the post gets between two heights — what bars must clear to avoid burying themselves\n   * in the stonework.\n   */\n  maxWidthBetween(y0: number, y1: number): number {\n    const lo = Math.min(y0, y1);\n    const hi = Math.max(y0, y1);\n\n    // Piecewise-constant profile: the max can only occur at a step boundary or an endpoint.\n    const boundaries = [lo, this.baseHeight, this.baseHeight + this.height, hi];\n\n    return Math.max(\n      ...boundaries.filter((y) => y >= lo && y <= hi).map((y) => this.widthAt(y)),\n    );\n  }\n}\n","import { BoxGeometry, BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface WoodPostGeometryOptions {\n  /** Post width and depth — a square section. Defaults to `0.12`. */\n  width?: number;\n  /** Shaft height, excluding the cap. Defaults to `1.5`. */\n  height?: number;\n  /** Cap width and depth. Overhangs the shaft. Defaults to `0.18`. */\n  capWidth?: number;\n  /** Cap height. `0` gives a bare post. Defaults to `0.05`. */\n  capHeight?: number;\n}\n\n/**\n * Wooden fence post — a square shaft under an overhanging cap board.\n *\n * Its stepped profile is why {@link widthAt} exists: a run's stringers meet the shaft, but its\n * pickets must clear whatever is widest at picket height.\n *\n * Local frame: base at Y=0.\n *\n * @example\n * ```ts\n * const geometry = new WoodPostGeometry({ height: 1.5 });\n * const post = new Mesh(geometry, woodMaterial);\n * scene.add(post);\n * ```\n */\nexport class WoodPostGeometry extends BufferGeometry {\n  /** Shaft height, excluding the cap. */\n  readonly height: number;\n  readonly width: number;\n  readonly capWidth: number;\n  readonly capHeight: number;\n\n  constructor({\n    width = 0.12,\n    height = 1.5,\n    capWidth = 0.18,\n    capHeight = 0.05,\n  }: WoodPostGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.height = height;\n    this.capWidth = capWidth;\n    this.capHeight = capHeight;\n\n    const parts: BufferGeometry[] = [];\n\n    const shaft = new BoxGeometry(width, height, width);\n    shaft.translate(0, height / 2, 0);\n    parts.push(shaft);\n\n    if (capHeight > 0) {\n      const cap = new BoxGeometry(capWidth, capHeight, capWidth);\n      cap.translate(0, height + capHeight / 2, 0);\n      parts.push(cap);\n    }\n\n    this.copy(mergeGeometries(parts, false) as BufferGeometry);\n  }\n\n  /** Overall height, cap included. */\n  get totalHeight(): number {\n    return this.height + this.capHeight;\n  }\n\n  /**\n   * Post width at height `y` — what a fence run asks to size itself against. Zero above and below\n   * the post; steps out at the cap.\n   */\n  widthAt(y: number): number {\n    if (y < 0 || y > this.totalHeight) return 0;\n    return y <= this.height ? this.width : this.capWidth;\n  }\n\n  /** Widest the post gets between two heights — what pickets must clear. */\n  maxWidthBetween(y0: number, y1: number): number {\n    const lo = Math.min(y0, y1);\n    const hi = Math.max(y0, y1);\n\n    return Math.max(this.widthAt(lo), this.widthAt(hi), this.widthAt(Math.min(hi, Math.max(lo, this.height))));\n  }\n}\n","import { BufferGeometry, CylinderGeometry, SphereGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface WroughtIronPostGeometryOptions {\n  /** Shaft height, excluding the ball. Defaults to `1.1`. */\n  height?: number;\n  /** Shaft radius. Defaults to `0.06`. */\n  radius?: number;\n  /** Ball finial radius. Defaults to `0.1`. */\n  ballRadius?: number;\n  /**\n   * How far the ball's center sits above the shaft top — it settles onto the shaft rather than\n   * balancing on it. Defaults to `ballRadius * 0.6`.\n   */\n  ballOffset?: number;\n  /** Shaft circumference segments. Defaults to `6`. */\n  radialSegments?: number;\n  /** Ball circumference segments. Defaults to `8`. */\n  ballWidthSegments?: number;\n  /** Ball vertical segments. Defaults to `6`. */\n  ballHeightSegments?: number;\n}\n\n/**\n * Wrought-iron fence post — a slim shaft under a ball finial, standing a little proud of the\n * pickets it supports. The post to a {@link WroughtIronPicketGeometry}'s infill, and the right\n * weight for a small fenced plot where a stone pier would be far too heavy.\n *\n * Local frame: base at Y=0.\n *\n * @example\n * ```ts\n * const geometry = new WroughtIronPostGeometry({ height: 1.25 });\n * const post = new Mesh(geometry, ironMaterial);\n * scene.add(post);\n * ```\n */\nexport class WroughtIronPostGeometry extends BufferGeometry {\n  /** Shaft height, excluding the ball. */\n  readonly height: number;\n  readonly radius: number;\n  readonly ballRadius: number;\n  /** Y of the ball's center. */\n  readonly ballCenterY: number;\n\n  constructor({\n    height = 1.1,\n    radius = 0.06,\n    ballRadius = 0.1,\n    ballOffset = ballRadius * 0.6,\n    radialSegments = 6,\n    ballWidthSegments = 8,\n    ballHeightSegments = 6,\n  }: WroughtIronPostGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n    this.radius = radius;\n    this.ballRadius = ballRadius;\n    this.ballCenterY = height + ballOffset;\n\n    const shaft = new CylinderGeometry(radius, radius, height, radialSegments);\n    shaft.translate(0, height / 2, 0);\n\n    const ball = new SphereGeometry(ballRadius, ballWidthSegments, ballHeightSegments);\n    ball.translate(0, this.ballCenterY, 0);\n\n    this.copy(mergeGeometries([shaft, ball], false) as BufferGeometry);\n  }\n\n  /** Overall height, ball included. */\n  get totalHeight(): number {\n    return this.ballCenterY + this.ballRadius;\n  }\n\n  /**\n   * Post width at height `y` — what a fence run asks to size itself against.\n   *\n   * Zero above and below the post. Through the ball this is the sphere's chord, so it tapers\n   * rather than stepping.\n   *\n   * Widths are circumscribed, not face-to-face: a low-poly shaft is a hexagon, so its true width\n   * varies with the angle you approach from. Reporting the widest case keeps a run clear of the\n   * post no matter how it meets it.\n   */\n  widthAt(y: number): number {\n    const shaft = y >= 0 && y <= this.height ? this.radius * 2 : 0;\n\n    const dy = y - this.ballCenterY;\n    const ball =\n      Math.abs(dy) <= this.ballRadius\n        ? 2 * Math.sqrt(this.ballRadius * this.ballRadius - dy * dy)\n        : 0;\n\n    return Math.max(shaft, ball);\n  }\n\n  /**\n   * Widest the post gets between two heights — what pickets must clear to avoid burying themselves\n   * in the post.\n   */\n  maxWidthBetween(y0: number, y1: number): number {\n    const lo = Math.min(y0, y1);\n    const hi = Math.max(y0, y1);\n\n    // The ball is widest at its equator; clamp that into the range to find the local maximum.\n    const equator = Math.min(hi, Math.max(lo, this.ballCenterY));\n\n    return Math.max(this.widthAt(lo), this.widthAt(hi), this.widthAt(equator));\n  }\n}\n","import { Vector3 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\nexport interface SpiralPathOptions {\n  /** Radius at the first point. */\n  startRadius?: number;\n  /** Number of turns. */\n  turns?: number;\n  /** Exponential decay coefficient; zero gives a circle. */\n  tightness?: number;\n  /** Number of spiral intervals. */\n  segments?: number;\n}\n\n/** Logarithmic spiral r = r₀·e^(−kθ) in XY. Tangents carry derivative direction, with the positive r factor omitted. */\nexport function spiralPath({\n  startRadius = 1,\n  turns = 1.5,\n  tightness = 0.22,\n  segments = 96,\n}: SpiralPathOptions = {}): PathPoint[] {\n  const total = turns * Math.PI * 2;\n\n  return Array.from({ length: segments + 1 }, (_, i) => {\n    const theta = (i / segments) * total;\n    const r = startRadius * Math.exp(-tightness * theta);\n\n    return {\n      position: new Vector3(r * Math.cos(theta), r * Math.sin(theta), 0),\n      // r' = −k·r, so d/dθ of r·(cosθ, sinθ) is r·(−k·cosθ − sinθ, −k·sinθ + cosθ)\n      tangent: new Vector3(\n        -tightness * Math.cos(theta) - Math.sin(theta),\n        -tightness * Math.sin(theta) + Math.cos(theta),\n        0,\n      ),\n    };\n  });\n}\n","import { BufferGeometry } from \"three\";\nimport { spiralPath } from \"../../modeling/paths/SpiralPath\";\nimport { rectProfile } from \"../../modeling/profiles/Profiles\";\nimport { sweep, transportFrames } from \"../../modeling/surfaces/Sweep\";\n\nexport interface WroughtIronScrollGeometryOptions {\n  /** Radius at the open end, before the bar winds in. Defaults to `1.4`. */\n  startRadius?: number;\n  /** How many turns it makes. Defaults to `1.6`. */\n  turns?: number;\n  /** How tightly it winds. Higher closes the curl faster. Defaults to `0.22`. */\n  tightness?: number;\n  /** Bar width — the wide face, lying in the plane of the scroll. Defaults to `0.16`. */\n  barWidth?: number;\n  /** Bar thickness, out of the plane. Defaults to `0.05`. */\n  barThickness?: number;\n  /** How far the bar draws down by the curl. `1` is no taper. Defaults to `0.45`. */\n  taper?: number;\n  /** Smoothness of the spiral — the low-poly knob. Defaults to `96`. */\n  segments?: number;\n}\n\n/**\n * A wrought iron scroll — a flat bar drawn out and curled into a spiral.\n *\n * The path is a LOGARITHMIC spiral, `r = r₀·e^(−kθ)`, which is what a real scroll follows. An\n * Archimedean spiral (constant spacing) reads as mechanical; a logarithmic one tightens as it winds,\n * the way hot iron actually curls under a scroll jig.\n *\n * And the bar TAPERS as it curls, because a smith draws the metal out toward the tip. That per-station\n * taper is the thing that makes a scroll read as *forged* rather than bent from pipe — and it is the\n * one thing Three's own sweep cannot do at all.\n *\n * Local frame: the scroll lies in the XY plane, winding inward from `startRadius`.\n *\n * @example\n * ```ts\n * const scroll = new WroughtIronScrollGeometry({ turns: 1.6, taper: 0.35 });\n * ```\n */\nexport class WroughtIronScrollGeometry extends BufferGeometry {\n  readonly startRadius: number;\n  readonly turns: number;\n\n  constructor({\n    startRadius = 1.4,\n    turns = 1.6,\n    tightness = 0.22,\n    barWidth = 0.16,\n    barThickness = 0.05,\n    taper = 0.45,\n    segments = 96,\n  }: WroughtIronScrollGeometryOptions = {}) {\n    super();\n\n    this.startRadius = startRadius;\n    this.turns = turns;\n\n    const path = spiralPath({ startRadius, turns, tightness, segments });\n\n    this.copy(\n      sweep(rectProfile(barWidth, barThickness), transportFrames(path), {\n        // t runs 0 at the open end to 1 at the curl.\n        scale: (t) => 1 - (1 - taper) * t,\n      }),\n    );\n  }\n}\n","import { ShapeUtils, Vector2 } from \"three\";\n\nexport interface TriangulateRegionOptions {\n  /** Uniform midpoint subdivisions, 0–5. Triangle count grows by four per level. Default 0. */\n  subdivisions?: number;\n  /** Improve interior edges with up to 40 Delaunay-flip passes. Boundary edges stay fixed. Default true. */\n  improveTriangles?: boolean;\n}\n\nexport interface TriangulatedRegion {\n  /** Owned points in the same coordinate system as the input. */\n  points: Vector2[];\n  /** Counter-clockwise triangles referencing points. */\n  triangles: [number, number, number][];\n  /** Counter-clockwise outer boundary, without a repeated closing index. */\n  outline: number[];\n  /** Clockwise hole boundaries, in input hole order, without repeated closing indices. */\n  holes: number[][];\n}\n\ntype Triangle = [number, number, number];\n\n/**\n * Triangulate a planar region with holes, refine its triangles and retain corresponding boundary\n * loops. Map these points onto a curved surface, or use the loops to attach thickness or frames.\n * Input loops are cloned. Either winding and an optional repeated closing point are accepted.\n *\n * Loops must be simple, nondegenerate and disjoint, with holes strictly inside the outline and no\n * nested holes. Redundant collinear corners are rejected. This is region meshing, not a boolean\n * operation or a general constrained-Delaunay solver; the bounded improvement pass is heuristic.\n * Coordinates are normalized internally so geometric tolerances are independent of model scale.\n */\nexport function triangulateRegion(\n  contour: readonly Vector2[],\n  cutouts: readonly (readonly Vector2[])[] = [],\n  { subdivisions = 0, improveTriangles = true }: TriangulateRegionOptions = {},\n): TriangulatedRegion {\n  if (!Number.isInteger(subdivisions) || subdivisions < 0 || subdivisions > 5) {\n    throw new RangeError(\"triangulateRegion: subdivisions must be an integer from 0 to 5.\");\n  }\n  const loops = [contour, ...cutouts].map((loop) => {\n    const copy = loop.map((p) => p.clone());\n    if (copy.length > 1 && copy[0].equals(copy[copy.length - 1])) copy.pop();\n    if (copy.length < 3 || copy.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) {\n      throw new RangeError(\"triangulateRegion: each loop needs at least three finite points.\");\n    }\n    return copy;\n  });\n  const min = new Vector2(Infinity, Infinity),\n    max = new Vector2(-Infinity, -Infinity);\n  for (const loop of loops)\n    for (const p of loop) {\n      min.min(p);\n      max.max(p);\n    }\n  const scale = Math.max(max.x - min.x, max.y - min.y);\n  if (!(scale > 0) || !Number.isFinite(scale)) throw new RangeError(\"triangulateRegion: invalid extent.\");\n  for (const loop of loops) for (const p of loop) p.sub(min).divideScalar(scale);\n  validateLoops(loops);\n  // Establish an explicit winding contract for both surfaces and attached walls.\n  loops.forEach((loop, i) => {\n    if (ShapeUtils.isClockWise(loop) !== i > 0) loop.reverse();\n  });\n  const [outline, ...holes] = loops;\n  const points = [...outline, ...holes.flat()];\n  let triangles = ShapeUtils.triangulateShape(outline, holes) as Triangle[];\n  let offset = 0;\n  let boundaries = [outline, ...holes].map((hole) => {\n    const ring = hole.map((_, i) => offset + i);\n    offset += hole.length;\n    return ring;\n  });\n\n  for (let level = 0; level < subdivisions; level++) {\n    const midpoints = new Map<string, number>();\n    const midpoint = (a: number, b: number): number => {\n      const key = a < b ? `${a}:${b}` : `${b}:${a}`;\n      let index = midpoints.get(key);\n      if (index === undefined) {\n        index = points.length;\n        points.push(points[a].clone().add(points[b]).multiplyScalar(0.5));\n        midpoints.set(key, index);\n      }\n      return index;\n    };\n    triangles = triangles.flatMap(([a, b, c]): Triangle[] => {\n      const ab = midpoint(a, b),\n        bc = midpoint(b, c),\n        ca = midpoint(c, a);\n      return [\n        [a, ab, ca],\n        [ab, b, bc],\n        [ca, bc, c],\n        [ab, bc, ca],\n      ];\n    });\n    boundaries = boundaries.map((ring) => ring.flatMap((a, i) => [a, midpoint(a, ring[(i + 1) % ring.length])]));\n  }\n\n  // Ear clipping creates long diagonals around the holes. After subdivision, flip interior\n  // edges toward a Delaunay triangulation in parameter space: skinny triangles otherwise fold\n  // visibly across ribs when wrapped. Boundary edges (including holes) never move.\n  const orient = (a: number, b: number, c: number) =>\n    (points[b].x - points[a].x) * (points[c].y - points[a].y) - (points[b].y - points[a].y) * (points[c].x - points[a].x);\n  for (let pass = 0; pass < (improveTriangles ? 40 : 0); pass++) {\n    const edges = new Map<string, { triangle: number; c: number }>();\n    const changed = new Set<number>();\n    for (let t = 0; t < triangles.length; t++) {\n      if (changed.has(t)) continue;\n      const triangle = triangles[t];\n      for (let e = 0; e < 3; e++) {\n        const a = triangle[e],\n          b = triangle[(e + 1) % 3],\n          c = triangle[(e + 2) % 3];\n        const key = a < b ? `${a}:${b}` : `${b}:${a}`;\n        const other = edges.get(key);\n        if (!other) {\n          edges.set(key, { triangle: t, c });\n          continue;\n        }\n        if (changed.has(other.triangle)) continue;\n        const d = other.c;\n        if (orient(c, d, b) <= 1e-12 || orient(d, c, a) <= 1e-12) continue;\n        const ax = points[a].x - points[d].x,\n          ay = points[a].y - points[d].y;\n        const bx = points[b].x - points[d].x,\n          by = points[b].y - points[d].y;\n        const cx = points[c].x - points[d].x,\n          cy = points[c].y - points[d].y;\n        const inCircle =\n          (ax * ax + ay * ay) * (bx * cy - by * cx) -\n          (bx * bx + by * by) * (ax * cy - ay * cx) +\n          (cx * cx + cy * cy) * (ax * by - ay * bx);\n        if (inCircle <= 1e-12) continue;\n        triangles[t] = [c, d, b];\n        triangles[other.triangle] = [d, c, a];\n        changed.add(t);\n        changed.add(other.triangle);\n        break;\n      }\n    }\n    if (changed.size === 0) break;\n  }\n\n  for (const p of points) p.multiplyScalar(scale).add(min);\n  return { points, triangles, outline: boundaries[0], holes: boundaries.slice(1) };\n}\n\n/** Validation happens before subdivision, while the user-authored boundaries are small. */\nfunction validateLoops(loops: Vector2[][]): void {\n  const epsilon = 1e-12;\n  const turn = (a: Vector2, b: Vector2, c: Vector2) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);\n  const onSegment = (a: Vector2, b: Vector2, p: Vector2) =>\n    Math.abs(turn(a, b, p)) <= epsilon &&\n    p.x >= Math.min(a.x, b.x) - epsilon &&\n    p.x <= Math.max(a.x, b.x) + epsilon &&\n    p.y >= Math.min(a.y, b.y) - epsilon &&\n    p.y <= Math.max(a.y, b.y) + epsilon;\n  const intersects = (a: Vector2, b: Vector2, c: Vector2, d: Vector2) => {\n    const abC = turn(a, b, c),\n      abD = turn(a, b, d),\n      cdA = turn(c, d, a),\n      cdB = turn(c, d, b);\n    return (\n      (((abC > epsilon && abD < -epsilon) || (abC < -epsilon && abD > epsilon)) &&\n        ((cdA > epsilon && cdB < -epsilon) || (cdA < -epsilon && cdB > epsilon))) ||\n      onSegment(a, b, c) ||\n      onSegment(a, b, d) ||\n      onSegment(c, d, a) ||\n      onSegment(c, d, b)\n    );\n  };\n  const inside = (p: Vector2, loop: Vector2[]) => {\n    let result = false;\n    for (let i = 0, j = loop.length - 1; i < loop.length; j = i++) {\n      const a = loop[i],\n        b = loop[j];\n      if (a.y > p.y !== b.y > p.y && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x) result = !result;\n    }\n    return result;\n  };\n  for (let l = 0; l < loops.length; l++) {\n    const loop = loops[l];\n    if (Math.abs(ShapeUtils.area(loop)) <= epsilon) throw new RangeError(\"triangulateRegion: degenerate loop.\");\n    for (let i = 0; i < loop.length; i++) {\n      const a = loop[i],\n        b = loop[(i + 1) % loop.length];\n      if (Math.abs(turn(a, b, loop[(i + 2) % loop.length])) <= epsilon) {\n        throw new RangeError(\"triangulateRegion: duplicate or collinear corners.\");\n      }\n      for (let m = l; m < loops.length; m++) {\n        const other = loops[m];\n        for (let j = m === l ? i + 1 : 0; j < other.length; j++) {\n          if (m === l && (j === (i + 1) % loop.length || (j + 1) % loop.length === i)) continue;\n          if (intersects(a, b, other[j], other[(j + 1) % other.length])) {\n            throw new RangeError(\"triangulateRegion: boundaries must not cross or touch.\");\n          }\n        }\n      }\n    }\n    if (\n      l > 0 &&\n      (!inside(loop[0], loops[0]) || loops.slice(1, l).some((other) => inside(loop[0], other) || inside(other[0], loop)))\n    ) {\n      throw new RangeError(\"triangulateRegion: holes must lie inside the outline without nesting.\");\n    }\n  }\n}\n","import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { triangulateRegion } from \"../../modeling/mesh/TriangulateRegion\";\nimport {\n  createPumpkinStemGeometry,\n  pumpkinStemMatrix,\n  type PumpkinStemGeometryOptions,\n  type PumpkinAssemblyOptions,\n} from \"./PumpkinGeometry\";\n\nexport interface JackOLanternRindGeometryOptions {\n  /** Overall radius. Positive; defaults to 1. The rind rests on y = 0. */\n  rindRadius?: number;\n  /** Vertical radius divided by horizontal radius. Positive; defaults to 0.82. */\n  rindSquash?: number;\n  /** Integer rib count, 0–16. Defaults to 8. */\n  rindRibs?: number;\n  /** Radial rib amplitude, 0–0.2. Defaults to 0.075. */\n  rindRibDepth?: number;\n  /** Fractional concentric inset, strictly between 0 and 1. Default 0.13.\n   * Inner scale = 1 - rindThickness; this is NOT a constant normal offset or a world-unit distance. */\n  rindThickness?: number;\n  /** Uniform parameter-space subdivisions, 2–4. Default 4; each level quadruples skin triangles. */\n  rindSubdivisions?: number;\n  /** Scale of the fixed face in longitude/latitude space, 0.1–1.5. Default 1. */\n  faceScale?: number;\n}\n\nexport interface JackOLanternGeometryOptions\n  extends JackOLanternRindGeometryOptions, PumpkinStemGeometryOptions, PumpkinAssemblyOptions {}\n\n/**\n * A hollow ribbed rind with triangular eyes/nose and a toothed grin facing +Z.\n * Material groups: 0 outer skin, 1 inner skin, 2 cut walls. Returned geometry is non-indexed,\n * with flat normals. Skin UVs are spherical; each opening wall has its own unwrapped 0–1 strip.\n *\n * The face is triangulated in longitude/latitude space before wrapping. Corresponding inner and\n * outer boundaries are bridged to close the rind. This constructs openings; it does not subtract\n * from an existing mesh. The back seam and poles are closed explicitly. Caller owns the result.\n */\nexport function createJackOLanternRindGeometry({\n  rindRadius = 1,\n  rindSquash: squash = 0.82,\n  rindRibs: ribs = 8,\n  rindRibDepth: ribDepth = 0.075,\n  rindThickness: thickness = 0.13,\n  rindSubdivisions: detail = 4,\n  faceScale = 1,\n}: JackOLanternRindGeometryOptions = {}): BufferGeometry {\n  if (!Number.isFinite(rindRadius) || rindRadius <= 0 || !Number.isFinite(squash) || squash <= 0) {\n    throw new RangeError(\"JackOLanternGeometry: rindRadius and rindSquash must be finite and positive.\");\n  }\n  if (!Number.isFinite(thickness) || thickness <= 0 || thickness >= 1)\n    throw new RangeError(\"JackOLanternGeometry: rindThickness must be between 0 and 1, exclusive.\");\n  if (!Number.isInteger(ribs) || ribs < 0 || ribs > 16)\n    throw new RangeError(\"JackOLanternGeometry: rindRibs must be an integer from 0 to 16.\");\n  if (!Number.isFinite(ribDepth) || ribDepth < 0 || ribDepth > 0.2)\n    throw new RangeError(\"JackOLanternGeometry: rindRibDepth must be from 0 to 0.2.\");\n  if (!Number.isInteger(detail) || detail < 2 || detail > 4)\n    throw new RangeError(\"JackOLanternGeometry: rindSubdivisions must be an integer from 2 to 4.\");\n  if (!Number.isFinite(faceScale) || faceScale < 0.1 || faceScale > 1.5)\n    throw new RangeError(\"JackOLanternGeometry: faceScale must be from 0.1 to 1.5.\");\n  const outline = [\n    new Vector2(-Math.PI, -Math.PI / 2),\n    new Vector2(Math.PI, -Math.PI / 2),\n    new Vector2(Math.PI, Math.PI / 2),\n    new Vector2(-Math.PI, Math.PI / 2),\n  ];\n  const face = (points: number[][]) => points.map(([u, v]) => new Vector2(u * faceScale, v * faceScale));\n  const holes = [\n    face([\n      [-0.67, 0.14],\n      [-0.19, 0.17],\n      [-0.4, 0.56],\n    ]),\n    face([\n      [0.19, 0.17],\n      [0.67, 0.14],\n      [0.4, 0.56],\n    ]),\n    face([\n      [-0.12, -0.13],\n      [0.12, -0.13],\n      [0, 0.09],\n    ]),\n    // A concave grin, with two teeth left attached to the upper lip.\n    face([\n      [-0.72, -0.23],\n      [-0.39, -0.26],\n      [-0.36, -0.39],\n      [-0.23, -0.4],\n      [-0.22, -0.28],\n      [0.22, -0.28],\n      [0.23, -0.4],\n      [0.36, -0.39],\n      [0.39, -0.26],\n      [0.72, -0.23],\n      [0.51, -0.55],\n      [0.26, -0.65],\n      [-0.26, -0.65],\n      [-0.51, -0.55],\n    ]),\n  ];\n  const { points, triangles, holes: rims } = triangulateRegion(outline, holes, { subdivisions: detail });\n  const surface = (p: Vector2, scale: number): Vector3 => {\n    const radial = Math.cos(p.y) * (1 + Math.cos((Math.PI / 2 - p.x) * ribs) * ribDepth);\n    return new Vector3(\n      Math.abs(p.x) === Math.PI || Math.abs(p.y) === Math.PI / 2 ? 0 : Math.sin(p.x) * radial * scale,\n      squash + Math.sin(p.y) * squash * scale,\n      Math.abs(p.y) === Math.PI / 2 ? 0 : Math.cos(p.x) * radial * scale,\n    );\n  };\n  const outer = points.map((p) => surface(p, 1));\n  const inner = points.map((p) => surface(p, 1 - thickness));\n  const positions: number[] = [],\n    uvs: number[] = [];\n  const geometry = new BufferGeometry();\n  const edge = new Vector3(),\n    cross = new Vector3();\n  const emit = (a: Vector3, b: Vector3, c: Vector3, uv: Vector2[]) => {\n    // Pole boundaries collapse; omit their zero-area triangles rather than emit invalid normals.\n    cross.subVectors(b, a).cross(edge.subVectors(c, a));\n    if (cross.lengthSq() < 1e-20) return;\n    positions.push(...a.toArray(), ...b.toArray(), ...c.toArray());\n    for (const p of uv) uvs.push(p.x, p.y);\n  };\n  const group = (material: number, build: () => void) => {\n    const start = positions.length / 3;\n    build();\n    geometry.addGroup(start, positions.length / 3 - start, material);\n  };\n  const skinUvs = points.map((p) => new Vector2(p.x / (2 * Math.PI) + 0.5, p.y / Math.PI + 0.5));\n  group(0, () => {\n    for (const [a, b, c] of triangles) emit(outer[a], outer[b], outer[c], [skinUvs[a], skinUvs[b], skinUvs[c]]);\n  });\n  group(1, () => {\n    for (const [a, b, c] of triangles) emit(inner[c], inner[b], inner[a], [skinUvs[c], skinUvs[b], skinUvs[a]]);\n  });\n  group(2, () => {\n    for (const ring of rims) {\n      // Hole loops must run clockwise in parameter space: the surviving surface is to their left.\n      const ordered = ShapeUtils.isClockWise(ring.map((i) => points[i])) ? ring : [...ring].reverse();\n      const lengths = ordered.map((a, i) => outer[a].distanceTo(outer[ordered[(i + 1) % ordered.length]]));\n      const perimeter = lengths.reduce((sum, length) => sum + length, 0);\n      let distance = 0;\n      for (let i = 0; i < ordered.length; i++) {\n        const a = ordered[i],\n          b = ordered[(i + 1) % ordered.length];\n        // U follows arc length around this opening; V crosses the rind from outer to inner.\n        const u0 = distance / perimeter,\n          u1 = (distance + lengths[i]) / perimeter;\n        emit(outer[b], outer[a], inner[a], [new Vector2(u1, 0), new Vector2(u0, 0), new Vector2(u0, 1)]);\n        emit(outer[b], inner[a], inner[b], [new Vector2(u1, 0), new Vector2(u0, 1), new Vector2(u1, 1)]);\n        distance += lengths[i];\n      }\n    }\n  });\n  geometry.setAttribute(\"position\", new Float32BufferAttribute(positions, 3));\n  geometry.setAttribute(\"uv\", new Float32BufferAttribute(uvs, 2));\n  geometry.computeVertexNormals();\n  geometry.scale(rindRadius, rindRadius, rindRadius);\n  geometry.computeBoundingBox();\n  geometry.computeBoundingSphere();\n  return geometry;\n}\n\n/** One geometry with material groups 0 outer rind, 1 inner rind, 2 cut walls, 3 stem.\n * Stem dimensions are world units, matching PumpkinGeometry; they do not scale with rindRadius. */\nexport function createJackOLanternGeometry(options: JackOLanternGeometryOptions = {}): BufferGeometry {\n  for (const name of [\n    \"stemTopRadius\",\n    \"stemBottomRadius\",\n    \"stemHeight\",\n    \"stemSegments\",\n    \"stemSink\",\n    \"stemLean\",\n    \"stemTwist\",\n  ] as const) {\n    const value = options[name];\n    if (value !== undefined && !Number.isFinite(value)) throw new RangeError(`JackOLanternGeometry: ${name} must be finite.`);\n  }\n  if (\n    (options.stemTopRadius ?? 0.1) < 0 ||\n    (options.stemBottomRadius ?? 0.14) <= 0 ||\n    (options.stemHeight ?? 0.38) <= 0 ||\n    !Number.isInteger(options.stemSegments ?? 5) ||\n    (options.stemSegments ?? 5) < 3\n  ) {\n    throw new RangeError(\"JackOLanternGeometry: invalid stem dimensions or segment count.\");\n  }\n  const rind = createJackOLanternRindGeometry(options);\n  const sourceStem = createPumpkinStemGeometry(options);\n  const stem = sourceStem.toNonIndexed();\n  sourceStem.dispose();\n  try {\n    stem.applyMatrix4(pumpkinStemMatrix(options));\n    const merged = mergeGeometries([rind, stem], false);\n    if (!merged) throw new Error(\"JackOLanternGeometry: incompatible component attributes.\");\n    // mergeGeometries does not carry nested groups forward. Preserve the rind's three slots.\n    for (const group of rind.groups) merged.addGroup(group.start, group.count, group.materialIndex);\n    merged.addGroup(rind.getAttribute(\"position\").count, stem.getAttribute(\"position\").count, 3);\n    merged.computeBoundingBox();\n    merged.computeBoundingSphere();\n    return merged;\n  } finally {\n    rind.dispose();\n    stem.dispose();\n  }\n}\n\n/** A reusable geometry; lighting and materials belong to the consuming scene. */\nexport class JackOLanternGeometry extends BufferGeometry {\n  readonly type = \"JackOLanternGeometry\";\n\n  constructor(options: JackOLanternGeometryOptions = {}) {\n    super();\n    const geometry = createJackOLanternGeometry(options);\n    this.copy(geometry);\n    geometry.dispose();\n    this.userData.parameters = { ...options };\n  }\n}\n","import { BufferGeometry, Float32BufferAttribute } from \"three\";\n\nexport interface LeafGeometryOptions {\n  /** Overall leaf scale. Defaults to `0.13`. */\n  size?: number;\n  /** Midrib rise above the rim as a fraction of size. Defaults to `0.22`. */\n  lift?: number;\n}\n\n/**\n * Low-poly folded leaf — a pointed ellipse with a gently raised midrib so it\n * catches rim light instead of reading as a flat sliver. Spine vertices sit\n * slightly above the mirrored rim outline, giving a soft V cross-section under\n * flat shading.\n *\n * Local frame: tip at +Y, base at −Y, fold rises along +Z.\n */\nexport class LeafGeometry extends BufferGeometry {\n  readonly size: number;\n  readonly lift: number;\n\n  constructor({ size = 0.13, lift = 0.22 }: LeafGeometryOptions = {}) {\n    super();\n\n    this.size = size;\n    this.lift = lift;\n\n    // Outline pairs (rim) running tip → base down each side, plus a raised spine.\n    const half: [number, number][] = [\n      [0.0, 1.0],\n      [0.34, 0.55],\n      [0.42, 0.05],\n      [0.3, -0.45],\n      [0.0, -1.0],\n    ];\n\n    const verts: number[] = [];\n    const idx: number[] = [];\n\n    const spine: number[] = [];\n    for (let i = 0; i < half.length; i++) {\n      const [, y] = half[i];\n      spine.push(verts.length / 3);\n      verts.push(0, y * size, lift * size * (1 - Math.abs(y)));\n    }\n\n    const left: number[] = [];\n    const right: number[] = [];\n    for (let i = 0; i < half.length; i++) {\n      const [x, y] = half[i];\n      right.push(verts.length / 3);\n      verts.push(x * size, y * size, 0);\n      left.push(verts.length / 3);\n      verts.push(-x * size, y * size, 0);\n    }\n\n    for (let i = 0; i < half.length - 1; i++) {\n      idx.push(spine[i], right[i], right[i + 1], spine[i], right[i + 1], spine[i + 1]);\n      idx.push(spine[i], spine[i + 1], left[i + 1], spine[i], left[i + 1], left[i]);\n    }\n\n    this.setAttribute(\"position\", new Float32BufferAttribute(verts, 3));\n    this.setIndex(idx);\n    this.computeVertexNormals();\n    this.computeBoundingSphere();\n  }\n}","import { BoxGeometry, BufferGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface BookshelfGeometryOptions {\n  /** Overall width. Defaults to `5`. */\n  width?: number;\n  /** Overall height. Defaults to `8`. */\n  height?: number;\n  /** Shelf depth. Defaults to `1`. */\n  depth?: number;\n  /** Number of interior shelves. Defaults to `4`. */\n  shelves?: number;\n  /** Frame board thickness. Defaults to `0.1`. */\n  frameThickness?: number;\n  /** Omit the back panel when `true`. Defaults to `false`. */\n  open?: boolean;\n}\n\n/**\n * Bookshelf frame with optional back panel and evenly spaced shelves.\n *\n * Local frame: sits on the Y=0 plane, centered on X/Z.\n */\nexport class BookshelfGeometry extends BufferGeometry {\n  readonly width: number;\n  readonly height: number;\n  readonly depth: number;\n  readonly shelves: number;\n\n  constructor({\n    width = 5,\n    height = 8,\n    depth = 1,\n    shelves = 4,\n    frameThickness = 0.1,\n    open = false,\n  }: BookshelfGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.height = height;\n    this.depth = depth;\n    this.shelves = shelves;\n\n    const sidePanelGeometry = new BoxGeometry(frameThickness, height, depth);\n    const shelfGeometry = new BoxGeometry(width - 2 * frameThickness, frameThickness, depth);\n\n    const leftPanel = sidePanelGeometry.clone();\n    leftPanel.translate(-width / 2 + frameThickness / 2, height / 2, 0);\n\n    const rightPanel = sidePanelGeometry.clone();\n    rightPanel.translate(width / 2 - frameThickness / 2, height / 2, 0);\n\n    const topPanel = shelfGeometry.clone();\n    topPanel.translate(0, height - frameThickness / 2, 0);\n\n    const bottomPanel = shelfGeometry.clone();\n    bottomPanel.translate(0, frameThickness / 2, 0);\n\n    const backPanel = new BoxGeometry(width, height, frameThickness);\n    backPanel.translate(0, height / 2, -depth / 2 + frameThickness / 2);\n\n    const shelfPanels = [];\n    const shelfSpacing = (height - frameThickness) / (shelves + 1);\n    for (let i = 1; i <= shelves; i++) {\n      const shelfPanel = shelfGeometry.clone();\n      shelfPanel.translate(0, frameThickness / 2 + i * shelfSpacing, 0);\n      shelfPanels.push(shelfPanel);\n    }\n\n    this.copy(\n      mergeGeometries(\n        [leftPanel, rightPanel, topPanel, bottomPanel, ...(open ? [] : [backPanel]), ...shelfPanels],\n        false,\n      ) as BufferGeometry,\n    );\n  }\n}","import { BoxGeometry, BufferGeometry, LatheGeometry, Vector2 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\n/**\n * Group indices:\n * 0. Desk surface\n * 1. Desk legs\n */\nexport class DeskGeometry extends BufferGeometry {\n  constructor() {\n    super();\n\n    // Desk Surface\n    const surfaceGeometry = new BoxGeometry(5, 0.3, 3);\n    surfaceGeometry.translate(0, 3.15, 0);\n\n    // Desk Legs (using lathe geometry)\n    const points = [\n      new Vector2(0.2, 0), //\n      new Vector2(0.25, 0.5),\n      new Vector2(0.15, 1.5),\n      new Vector2(0.3, 3),\n    ];\n\n    const legLatheGeometry = new LatheGeometry(points, 32);\n\n    // Create four legs for the desk\n    const legPositions = [\n      [2.1, 0, 1.1],\n      [-2.1, 0, 1.1],\n      [2.1, 0, -1.1],\n      [-2.1, 0, -1.1],\n    ];\n\n    const legGeometry = mergeGeometries(\n      legPositions.map((position) => {\n        const leg = legLatheGeometry.clone();\n        leg.translate(position[0], position[1], position[2]);\n        return leg;\n      }),\n    ) as BufferGeometry;\n\n    this.copy(mergeGeometries([surfaceGeometry, legGeometry], true) as BufferGeometry);\n  }\n}\n","import { Path, Shape, Vector2 } from \"three\";\n\nexport interface GearShapeOptions {\n  /** Number of gear teeth. Defaults to `5`. */\n  teeth?: number;\n  /** Tooth valley radius. Defaults to `0.5`. */\n  innerRadius?: number;\n  /** Tooth tip radius. Defaults to `1`. */\n  outerRadius?: number;\n  /**\n   * Width of the flat at the tooth tip, as a fraction of one tooth period.\n   * `0` brings the tooth to a point. Defaults to `0.25`.\n   */\n  tipWidth?: number;\n  /**\n   * Width of the flat at the valley floor, as a fraction of one tooth period.\n   * `0` brings the valley to a point. Defaults to `0.25`.\n   */\n  valleyWidth?: number;\n  /**\n   * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal. At `1` the\n   * rising flank vanishes and the tooth's trailing face drops radially — a\n   * ratchet or escapement wheel rather than a gear. Defaults to `0`.\n   */\n  lean?: number;\n  /** Number of sides on the center bore. Defaults to `5`. */\n  holeSides?: number;\n  /**\n   * Center bore radius. Clamped to stay strictly inside the tooth profile — a bore that\n   * reaches the outline would punch through the gear and cannot be triangulated. Set to\n   * `0` to omit the bore. Defaults to `0.25`.\n   */\n  holeRadius?: number;\n  /** Rotation in radians from the resting state. Defaults to `0`. */\n  rotation?: number;\n  /**\n   * Rotation of the bore in radians, **relative to the wheel**. Defaults to `0`.\n   *\n   * Only visible on a low {@link GearShapeOptions.holeSides} count: at `4` the bore rests as a diamond, points\n   * at north, south, east and west, and `Math.PI / 4` turns it into a square with flat sides. A round bore has\n   * no orientation to set.\n   *\n   * Relative rather than absolute, so turning the wheel carries the bore with it — the shaft does not slip.\n   */\n  holeRotation?: number;\n}\n\n/** Distance from the origin to segment `ab`. */\nfunction distanceToSegment(a: Vector2, b: Vector2): number {\n  const dx = b.x - a.x;\n  const dy = b.y - a.y;\n  const lengthSq = dx * dx + dy * dy;\n\n  if (lengthSq === 0) return a.length();\n\n  // Project the origin onto the segment, clamped to its endpoints.\n  const t = Math.max(0, Math.min(1, -(a.x * dx + a.y * dy) / lengthSq));\n\n  return Math.hypot(a.x + t * dx, a.y + t * dy);\n}\n\n/**\n * Gear profile — teeth around a polygonal center bore. Rests with a tooth up.\n *\n * One tooth period runs tip, falling flank, valley, rising flank. The two flats\n * are sized independently and the rest of the period is split between the\n * flanks, so the same profile spans a blunt trapezoidal gear, a spiked one, and\n * an asymmetric ratchet wheel. A flat given zero width collapses to a single\n * point rather than a doubled vertex.\n */\nexport class GearShape extends Shape {\n  /** The bore radius actually used, after clamping to fit inside the tooth profile. */\n  readonly holeRadius: number;\n\n  constructor({\n    teeth = 5,\n    innerRadius = 0.5,\n    outerRadius = 1,\n    tipWidth = 0.25,\n    valleyWidth = 0.25,\n    lean = 0,\n    holeSides = 5,\n    holeRadius = 0.25,\n    rotation = 0,\n    holeRotation = 0,\n  }: GearShapeOptions = {}) {\n    super();\n\n    const step = (Math.PI * 2) / teeth;\n    const start = Math.PI / 2 + rotation;\n\n    // The two flats share the period with the two flanks; keep a little room for\n    // the flanks so a tooth can never become a plain cylinder wall.\n    const tip = Math.max(0, Math.min(tipWidth, 1));\n    const valley = Math.max(0, Math.min(valleyWidth, 1 - tip));\n    const flanks = 1 - tip - valley;\n    const bias = Math.max(-1, Math.min(lean, 1));\n    const falling = (flanks * (1 + bias)) / 2;\n\n    const outline: Vector2[] = [];\n\n    for (let n = 0; n < teeth; ++n) {\n      // Each tooth is centered on its own angle and walks forward from there.\n      const center = start + step * n;\n\n      const at = (fraction: number, radius: number) => {\n        const angle = center + fraction * step;\n        outline.push(new Vector2(Math.cos(angle) * radius, Math.sin(angle) * radius));\n      };\n\n      if (tip > 0) {\n        at(-tip / 2, outerRadius); // tip start\n        at(tip / 2, outerRadius); // tip end\n      } else {\n        at(0, outerRadius); // sharp tooth\n      }\n\n      if (valley > 0) {\n        at(tip / 2 + falling, innerRadius); // valley start\n        at(tip / 2 + falling + valley, innerRadius); // valley end\n      } else {\n        at(tip / 2 + falling, innerRadius); // sharp valley\n      }\n    }\n\n    this.setFromPoints(outline);\n    this.closePath();\n\n    // The bore must sit strictly inside the outline. Its closest approach to the center is\n    // NOT innerRadius — a flank chord running from a valley out to the next tip passes\n    // nearer the origin than either endpoint, and at low tooth counts it cuts well inside\n    // the valley. Measure the outline instead of assuming.\n    let limit = Infinity;\n    for (let n = 0; n < outline.length; ++n) {\n      limit = Math.min(limit, distanceToSegment(outline[n], outline[(n + 1) % outline.length]));\n    }\n\n    const bore = Math.min(holeRadius, limit * 0.99);\n    this.holeRadius = bore;\n\n    if (bore > 0 && holeSides > 2) {\n      const hole = new Path();\n      const holeStep = (Math.PI * 2) / holeSides;\n\n      for (let n = 0; n < holeSides; ++n) {\n        // Offset from the wheel's own phase, so the bore turns with the teeth and `holeRotation` is the\n        // difference between them.\n        const angle = start + holeRotation + holeStep * n;\n        const x = Math.cos(angle) * bore;\n        const y = Math.sin(angle) * bore;\n\n        if (n === 0) hole.moveTo(x, y);\n        else hole.lineTo(x, y);\n      }\n\n      hole.closePath();\n      this.holes.push(hole);\n    }\n  }\n}\n","import { BufferGeometry, Vector2 } from \"three\";\nimport { GearShape, type GearShapeOptions } from \"../../shapes/GearShape\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type Vec3,\n} from \"../../modeling/mesh/GeometryBuffers\";\n\nexport interface BevelGearGeometryOptions extends GearShapeOptions {\n  /**\n   * Half-angle of the pitch cone, measured from the axis, in radians. Defaults to `Math.PI / 4` (45°).\n   *\n   * A 45° pair of equal wheels meshes at a right angle with a 1:1 ratio — a **miter gear**. Approaching `90°`\n   * flattens the cone into a **crown wheel**, its teeth standing on the face; approaching `0` stretches it into\n   * a long thin cone, which is a plain spur gear.\n   */\n  pitchAngle?: number;\n  /**\n   * Tooth length measured **along the cone element**, not along the axis. Defaults to `0.35`.\n   *\n   * Clamped so the teeth cannot run past the cone's apex.\n   */\n  faceWidth?: number;\n}\n\n/** Points of a closed 2D contour, with the duplicated closing point dropped. */\nfunction contour(points: Vector2[]): Vector2[] {\n  if (points.length > 1 && points[0]!.distanceToSquared(points[points.length - 1]!) < 1e-12) {\n    return points.slice(0, -1);\n  }\n  return points;\n}\n\n/**\n * Bevel gear — teeth cut on a **pitch cone**, for shafts whose axes intersect.\n *\n * Where {@link GearGeometry} extrudes a fixed profile into a cylinder, a bevel gear's teeth **taper toward the\n * cone's apex**. That convergence is the whole signature, and it is why this cannot be an extrusion: it is a\n * **loft** between the full profile at the back face and the same profile uniformly scaled at the front. Every\n * tooth flank therefore lies on a plane through the apex, correct by construction rather than by adjustment.\n *\n * A 45° pair of equal wheels is a **miter gear** — right angle, 1:1. Change\n * {@link BevelGearGeometryOptions.pitchAngle} and the same construction spans a spur-like cone near `0` through\n * to a flat crown wheel near `90°`. This is the wheel a gearbox or differential is built from; the\n * differential's crown wheel, pinion, and spider gears are all bevels.\n *\n * A pair meshes when their pitch cones share an apex, which makes the shaft angle the **sum** of the two cone\n * angles — so 45° + 45° is the right-angle case, and unequal angles give unequal ratios.\n *\n * Not a **worm** gear, which is a different mechanism: a helical screw driving a wheel, thread-based rather than\n * conical.\n *\n * Local frame: the **back face** (largest teeth) sits on `z = 0`, and the gear tapers toward `+Z`, with the cone's\n * apex on the axis at {@link apexZ}. The bore runs straight through — it is cylindrical, not tapered, because a\n * shaft is.\n *\n * Material groups: **none** — one material for the whole wheel.\n *\n * @example\n * ```typescript\n * // A miter pair: two identical 45° wheels meshing at a right angle.\n * const wheel = new Mesh(new BevelGearGeometry({ teeth: 16 }), steel);\n * const mate = new Mesh(new BevelGearGeometry({ teeth: 16 }), steel);\n * mate.rotation.x = Math.PI / 2;\n * ```\n */\nexport class BevelGearGeometry extends BufferGeometry {\n  /** The bore radius actually used, after clamping inside the smaller front outline. */\n  readonly holeRadius: number;\n  /** Uniform scale of the front outline against the back — how far the teeth converge. */\n  readonly frontScale: number;\n  /** Z of the front face, where the teeth are smallest. */\n  readonly frontZ: number;\n  /** Z of the pitch cone's apex on the axis. Teeth stop short of it by construction. */\n  readonly apexZ: number;\n  /** The face width actually used, after clamping short of the apex. */\n  readonly faceWidth: number;\n\n  constructor({\n    pitchAngle = Math.PI / 4,\n    faceWidth = 0.35,\n    ...gearOptions\n  }: BevelGearGeometryOptions = {}) {\n    super();\n\n    const outerRadius = gearOptions.outerRadius ?? 1;\n    const innerRadius = gearOptions.innerRadius ?? 0.5;\n    const angle = Math.min(Math.max(pitchAngle, 0.05), Math.PI / 2 - 0.05);\n\n    // Element length from the apex to the back face. Everything else is measured along this line, which is\n    // what makes the taper a single uniform scale rather than a per-radius adjustment.\n    const element = outerRadius / Math.sin(angle);\n    // Leave material short of the apex; teeth converging to a literal point cannot be triangulated.\n    const width = Math.min(Math.max(faceWidth, 1e-3), element * 0.95);\n    const scale = (element - width) / element;\n\n    this.faceWidth = width;\n    this.frontScale = scale;\n    this.frontZ = width * Math.cos(angle);\n    this.apexZ = element * Math.cos(angle);\n\n    // The front outline is smaller, so IT sets the bore limit. Take the clamp from the front shape and give the\n    // same bore to both, or the two contours would stop corresponding and the loft would shear.\n    const front = new GearShape({\n      ...gearOptions,\n      outerRadius: outerRadius * scale,\n      innerRadius: innerRadius * scale,\n    });\n    const bore = front.holeRadius;\n    this.holeRadius = bore;\n\n    const back = new GearShape({ ...gearOptions, outerRadius, innerRadius, holeRadius: bore });\n\n    const backOutline = contour(back.getPoints(0));\n    const frontOutline = contour(front.getPoints(0));\n    const count = Math.min(backOutline.length, frontOutline.length);\n\n    const buffers = createGeometryBuffers();\n    const at = (p: Vector2, z: number): Vec3 => [p.x, p.y, z];\n\n    // --- the toothed cone: a quad per outline edge, spanning back face to front ---\n    for (let i = 0; i < count; i++) {\n      const j = (i + 1) % count;\n      pushQuad(\n        buffers,\n        [\n          at(backOutline[i]!, 0),\n          at(backOutline[j]!, 0),\n          at(frontOutline[j]!, this.frontZ),\n          at(frontOutline[i]!, this.frontZ),\n        ],\n        undefined,\n      );\n    }\n\n    // --- bore wall, and the two end faces ---\n    const boreRing: Vector2[] = [];\n    if (bore > 0) {\n      const sides = Math.max(3, Math.round(gearOptions.holeSides ?? 5));\n      // Same offset convention as `GearShape`: relative to the wheel's phase.\n      const start = Math.PI / 2 + (gearOptions.rotation ?? 0) + (gearOptions.holeRotation ?? 0);\n      for (let i = 0; i < sides; i++) {\n        const a = start + (Math.PI * 2 * i) / sides;\n        boreRing.push(new Vector2(Math.cos(a) * bore, Math.sin(a) * bore));\n      }\n\n      // Cylindrical, not tapered — a shaft does not taper. Wound to face inward, into the hole.\n      for (let i = 0; i < boreRing.length; i++) {\n        const j = (i + 1) % boreRing.length;\n        pushQuad(\n          buffers,\n          [\n            at(boreRing[j]!, 0),\n            at(boreRing[i]!, 0),\n            at(boreRing[i]!, this.frontZ),\n            at(boreRing[j]!, this.frontZ),\n          ],\n          undefined,\n        );\n      }\n    }\n\n    /**\n     * Close one end by stitching its outline to the bore ring.\n     *\n     * The two loops carry different vertex counts, so each outline edge takes the bore vertex nearest it in\n     * angle — giving a quad where the bore advances and a triangle where it does not.\n     */\n    const cap = (outline: Vector2[], z: number, normal: Vec3, flip: boolean) => {\n      const n = outline.length;\n      const m = boreRing.length;\n      for (let i = 0; i < n; i++) {\n        const i2 = (i + 1) % n;\n        const j = Math.floor((i * m) / n) % m;\n        const j2 = Math.floor((i2 * m) / n) % m;\n        const a = at(outline[i]!, z);\n        const b = at(outline[i2]!, z);\n        const c = at(boreRing[j2]!, z);\n        const d = at(boreRing[j]!, z);\n        if (j === j2) {\n          pushTriangle(buffers, flip ? [b, a, d] : [a, b, d], normal);\n        } else {\n          pushQuad(buffers, flip ? [b, a, d, c] : [a, b, c, d], normal);\n        }\n      }\n    };\n\n    if (bore > 0) {\n      // Back face looks away from the apex; front face looks toward it.\n      cap(backOutline, 0, [0, 0, -1], true);\n      cap(frontOutline, this.frontZ, [0, 0, 1], false);\n    }\n\n    const geometry = toBufferGeometry(buffers);\n    this.copy(geometry);\n    geometry.dispose();\n    this.computeBoundingSphere();\n  }\n}\n","import { Path } from \"three\";\nimport { GearShape, type GearShapeOptions } from \"./GearShape\";\n\nexport interface CrossedWheelShapeOptions extends GearShapeOptions {\n  /**\n   * Number of crossings — the radial spokes. Defaults to `5`.\n   *\n   * Fewer than `2` leaves the web solid, which is what {@link GearShape} already is.\n   */\n  crossings?: number;\n  /** Tangential thickness of each crossing, in world units. Defaults to `0.08`. */\n  crossingWidth?: number;\n  /** Outer radius of the hub — the disc the crossings spring from. Defaults to `0.3`. */\n  hubRadius?: number;\n  /**\n   * Material kept inward of the tooth valleys, holding the teeth onto the rim. Defaults to `0.1`.\n   *\n   * Take this to zero and the teeth have nothing behind them: the cut-outs would reach the valley floor and\n   * the rim would fall apart into loose teeth.\n   */\n  rimWidth?: number;\n  /** Segments along each cut-out's inner and outer arcs. Defaults to `6`. */\n  crossingSegments?: number;\n}\n\n/**\n * A **crossed-out wheel** — a gear whose web has been cut away, leaving radial spokes.\n *\n * In horology the spokes are **crossings** and the operation is *crossing out*: clock and watch wheels were\n * crossed out to shed weight and brass, so the train had less inertia to drive. A wheel is described by the\n * count — a *five-crossing wheel*. Engineering calls the same thing spokes or arms, and the solid disc version\n * a web.\n *\n * The anatomy, and the parameters that control it:\n *\n * - **rim** — the toothed outer ring. Its depth inward of the valleys is {@link CrossedWheelShapeOptions.rimWidth},\n *   and it is what the teeth are attached to.\n * - **crossings** — the spokes, {@link CrossedWheelShapeOptions.crossings} of them at\n *   {@link CrossedWheelShapeOptions.crossingWidth} thick.\n * - **hub** — the center disc, out to {@link CrossedWheelShapeOptions.hubRadius}.\n * - **bore** — the hole for the arbor, inherited from {@link GearShape}.\n *\n * Every tooth option is inherited, so a crossed wheel can also be spiked or leaning — a crossed-out ratchet is\n * `{ crossings: 5, tipWidth: 0, lean: 1 }`.\n *\n * **Crossings are constant width, not constant angle.** The half-angle a spoke subtends is `asin(w / 2r)`,\n * which narrows as the radius grows — so the spoke reads as a straight bar rather than a wedge that fattens\n * toward the rim.\n */\nexport class CrossedWheelShape extends GearShape {\n  /** Crossings actually cut, after clamping. `0` means the web was left solid. */\n  readonly crossings: number;\n  /** The hub radius actually used, after clamping clear of the bore. */\n  readonly hubRadius: number;\n  /** Inner edge of the rim — where the cut-outs stop and tooth backing begins. */\n  readonly rimInnerRadius: number;\n  /** The crossing width actually used, after clamping so spokes cannot overlap at the hub. */\n  readonly crossingWidth: number;\n\n  constructor({\n    crossings = 5,\n    crossingWidth = 0.08,\n    hubRadius = 0.3,\n    rimWidth = 0.1,\n    crossingSegments = 6,\n    ...gearOptions\n  }: CrossedWheelShapeOptions = {}) {\n    super(gearOptions);\n\n    const innerRadius = gearOptions.innerRadius ?? 0.5;\n    const spokes = Math.max(0, Math.round(crossings));\n\n    // The hub must enclose the bore with real metal between them, and the rim must keep backing behind the\n    // teeth. Both are clamps, not suggestions — either one violated produces a wheel that falls apart.\n    const hub = Math.max(hubRadius, this.holeRadius * 1.15);\n    const rimInner = Math.max(innerRadius - Math.max(rimWidth, 0), hub);\n\n    this.hubRadius = hub;\n    this.rimInnerRadius = rimInner;\n\n    // No annulus left between hub and rim, or too few spokes to define gaps: leave the web solid.\n    if (spokes < 2 || rimInner - hub < 1e-6) {\n      this.crossings = 0;\n      this.crossingWidth = 0;\n      return;\n    }\n\n    const step = (Math.PI * 2) / spokes;\n    // Two spoke half-widths plus a sliver of gap must fit inside one step at the hub, where the angle a given\n    // width subtends is largest.\n    const maxWidth = 2 * hub * Math.sin(step / 2) * 0.9;\n    const width = Math.min(Math.max(crossingWidth, 1e-4), maxWidth);\n\n    this.crossings = spokes;\n    this.crossingWidth = width;\n\n    const segments = Math.max(1, Math.round(crossingSegments));\n    // Spoke centers share the outline's phase so a crossing lines up under a tooth rather than a valley.\n    const start = Math.PI / 2 + (gearOptions.rotation ?? 0);\n    /** Half-angle the spoke occupies at radius `r` — shrinking with radius keeps the spoke a straight bar. */\n    const half = (r: number) => Math.asin(Math.min(1, width / 2 / r));\n\n    for (let n = 0; n < spokes; n++) {\n      const from = start + step * n + half(hub);\n      const to = start + step * (n + 1) - half(hub);\n      const fromRim = start + step * n + half(rimInner);\n      const toRim = start + step * (n + 1) - half(rimInner);\n      if (to <= from || toRim <= fromRim) continue;\n\n      const cut = new Path();\n      // Out along the hub arc, up the trailing spoke edge, back along the rim arc, down the leading edge.\n      for (let i = 0; i <= segments; i++) {\n        const a = from + ((to - from) * i) / segments;\n        const x = Math.cos(a) * hub;\n        const y = Math.sin(a) * hub;\n        if (i === 0) cut.moveTo(x, y);\n        else cut.lineTo(x, y);\n      }\n      for (let i = 0; i <= segments; i++) {\n        const a = toRim - ((toRim - fromRim) * i) / segments;\n        cut.lineTo(Math.cos(a) * rimInner, Math.sin(a) * rimInner);\n      }\n      cut.closePath();\n      this.holes.push(cut);\n    }\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { CrossedWheelShape, type CrossedWheelShapeOptions } from \"../../shapes/CrossedWheelShape\";\n\nexport interface CrossedWheelGeometryOptions extends CrossedWheelShapeOptions {\n  /** Extrusion depth. Defaults to `0.06` — clock wheels are thin brass. */\n  depth?: number;\n}\n\n/**\n * Extruded **crossed-out wheel** — a clock or watch wheel: a toothed rim carried on radial spokes. See\n * {@link CrossedWheelShape} for the anatomy and the horological vocabulary.\n *\n * Where {@link GearGeometry} is a solid disc with a bore, this removes the web between hub and rim. Every tooth\n * option is inherited, so the crossings compose with the tooth profile — a crossed-out escapement wheel is\n * `{ crossings: 5, tipWidth: 0, lean: 1 }`.\n *\n * The clamps are load-bearing rather than defensive: the hub is held clear of the bore and the rim keeps\n * backing inward of the tooth valleys, since without either the cut-outs reach through and the teeth come away\n * from the wheel. {@link crossings} reports `0` when no annulus was left to cut, leaving the web solid.\n *\n * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, matching\n * {@link GearGeometry} so a rank of wheels on one arbor lines up on the plane they turn in.\n *\n * Material groups: **none** — one material for the whole wheel.\n *\n * @example\n * ```typescript\n * const wheel = new Mesh(new CrossedWheelGeometry({ teeth: 60, crossings: 5 }), brass);\n * ```\n */\nexport class CrossedWheelGeometry extends ExtrudeGeometry {\n  /** The bore radius actually used, after clamping inside the tooth profile. */\n  readonly holeRadius: number;\n  /** Crossings actually cut. `0` means the web was left solid. */\n  readonly crossings: number;\n  /** The hub radius actually used, after clamping clear of the bore. */\n  readonly hubRadius: number;\n  /** Inner edge of the rim — where the cut-outs stop and tooth backing begins. */\n  readonly rimInnerRadius: number;\n  /** The crossing width actually used, after clamping so spokes cannot overlap at the hub. */\n  readonly crossingWidth: number;\n\n  constructor({ depth = 0.06, ...shapeOptions }: CrossedWheelGeometryOptions = {}) {\n    const shape = new CrossedWheelShape(shapeOptions);\n\n    super(shape, { depth, bevelEnabled: false });\n\n    // Center on the thickness rather than extruding forward from zero.\n    this.translate(0, 0, -depth / 2);\n\n    this.holeRadius = shape.holeRadius;\n    this.crossings = shape.crossings;\n    this.hubRadius = shape.hubRadius;\n    this.rimInnerRadius = shape.rimInnerRadius;\n    this.crossingWidth = shape.crossingWidth;\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { GearShape, type GearShapeOptions } from \"../../shapes/GearShape\";\n\nexport interface GearGeometryOptions extends GearShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded gear profile with a center bore. See {@link GearShape} for the tooth period and how the two flats\n * divide it.\n *\n * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, so a rank of gears sharing an\n * arbor lines up on the plane they turn in rather than each one starting where the last began.\n *\n * Material groups: **none** — one material for the whole wheel.\n *\n * @example\n * ```typescript\n * const gear = new Mesh(new GearGeometry({ teeth: 12, tipWidth: 0.1 }), brass);\n * const ratchet = new Mesh(new GearGeometry({ teeth: 16, lean: 1, tipWidth: 0 }), steel);\n * ```\n */\nexport class GearGeometry extends ExtrudeGeometry {\n  /** The bore radius actually used, after clamping to fit inside the tooth profile. */\n  readonly holeRadius: number;\n\n  constructor({ depth = 0.25, ...shapeOptions }: GearGeometryOptions = {}) {\n    const shape = new GearShape(shapeOptions);\n\n    super(shape, { depth, bevelEnabled: false });\n\n    // Center on the thickness rather than extruding forward from zero.\n    this.translate(0, 0, -depth / 2);\n    this.holeRadius = shape.holeRadius;\n  }\n}\n","import { Path, Shape, Vector2 } from \"three\";\n\nexport interface InternalGearShapeOptions {\n  /** Number of teeth. Defaults to `36`. */\n  teeth?: number;\n  /** Radius the tooth tips reach. Defaults to `0.72`. */\n  tipRadius?: number;\n  /** Radius the valley floors sit at. Defaults to `0.85`. */\n  valleyRadius?: number;\n  /** Outside radius of the ring. Clamped to stay outside the toothed opening. Defaults to `1`. */\n  rimRadius?: number;\n  /** Sides on the outer rim. Defaults to `48`. */\n  rimSides?: number;\n  /**\n   * Width of the flat at the tooth tip, as a fraction of one tooth period. `0` brings the tooth to a point.\n   * Defaults to `0.25`.\n   */\n  tipWidth?: number;\n  /**\n   * Width of the flat at the valley floor, as a fraction of one tooth period. `0` brings the valley to a point.\n   * Defaults to `0.25`.\n   */\n  valleyWidth?: number;\n  /**\n   * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal; at `1` the rising flank vanishes. Defaults to\n   * `0`.\n   */\n  lean?: number;\n  /** Rotation in radians from the resting state. Defaults to `0`. */\n  rotation?: number;\n}\n\n/**\n * Internal gear profile — a plain ring whose **opening is toothed**, teeth pointing inward.\n *\n * Where {@link GearShape} makes the teeth its outer contour and cuts a bore, this inverts the roles: the outer\n * contour is a plain circle and the teeth are the hole. The tooth period is the external gear's, unchanged.\n *\n * **Three radii, all absolute from the center.** {@link InternalGearShapeOptions.tipRadius} and\n * {@link InternalGearShapeOptions.valleyRadius} are the two extremes of the toothing; their order is not\n * enforced, so a valley inside the tip inverts it. {@link InternalGearShapeOptions.rimRadius} is the third\n * because the teeth do not define the outer edge here — the opening is a hole, so the ring needs its own\n * outside dimension.\n */\nexport class InternalGearShape extends Shape {\n  /** The tip radius actually used. */\n  readonly tipRadius: number;\n  /** The valley radius actually used. */\n  readonly valleyRadius: number;\n  /** The rim radius actually used, after clamping outside the toothed opening. */\n  readonly rimRadius: number;\n\n  constructor({\n    teeth = 36,\n    tipRadius = 0.72,\n    valleyRadius = 0.85,\n    rimRadius = 1,\n    rimSides = 48,\n    tipWidth = 0.25,\n    valleyWidth = 0.25,\n    lean = 0,\n    rotation = 0,\n  }: InternalGearShapeOptions = {}) {\n    super();\n\n    const count = Math.max(3, Math.round(teeth));\n    const tip = Math.max(tipRadius, 1e-3);\n    const valley = Math.max(valleyRadius, 1e-3);\n\n    this.tipRadius = tip;\n    this.valleyRadius = valley;\n\n    const step = (Math.PI * 2) / count;\n    const start = Math.PI / 2 + rotation;\n\n    const sides = Math.max(3, Math.round(rimSides));\n    const rimStep = (Math.PI * 2) / sides;\n    // The opening's farthest point is always a vertex, so the larger extreme is what the rim must clear.\n    const rim = Math.max(rimRadius, Math.max(tip, valley) * 1.02);\n    this.rimRadius = rim;\n\n    for (let n = 0; n < sides; n++) {\n      const angle = start + rimStep * n;\n      const x = Math.cos(angle) * rim;\n      const y = Math.sin(angle) * rim;\n      if (n === 0) this.moveTo(x, y);\n      else this.lineTo(x, y);\n    }\n    this.closePath();\n\n    const flatTip = Math.max(0, Math.min(tipWidth, 1));\n    const flatValley = Math.max(0, Math.min(valleyWidth, 1 - flatTip));\n    const flanks = 1 - flatTip - flatValley;\n    const bias = Math.max(-1, Math.min(lean, 1));\n    const falling = (flanks * (1 + bias)) / 2;\n\n    const opening = new Path();\n    const outline: Vector2[] = [];\n\n    for (let n = 0; n < count; n++) {\n      const center = start + step * n;\n      const at = (fraction: number, radius: number) => {\n        const angle = center + fraction * step;\n        outline.push(new Vector2(Math.cos(angle) * radius, Math.sin(angle) * radius));\n      };\n\n      if (flatTip > 0) {\n        at(-flatTip / 2, tip);\n        at(flatTip / 2, tip);\n      } else {\n        at(0, tip);\n      }\n\n      if (flatValley > 0) {\n        at(flatTip / 2 + falling, valley);\n        at(flatTip / 2 + falling + flatValley, valley);\n      } else {\n        at(flatTip / 2 + falling, valley);\n      }\n    }\n\n    opening.setFromPoints(outline);\n    opening.closePath();\n    this.holes.push(opening);\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { InternalGearShape, type InternalGearShapeOptions } from \"../../shapes/InternalGearShape\";\n\nexport interface InternalGearGeometryOptions extends InternalGearShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded **internal gear** — a ring whose opening is toothed, teeth pointing inward. See\n * {@link InternalGearShape} for the profile and its three radii.\n *\n * This is the ring of a planetary gearset and the mating half of an internal pair. Note that an *externally*\n * toothed ring — a flywheel starter ring — needs nothing new: it is {@link GearGeometry} with a bore set just\n * inside the valley radius. \"Ring gear\" names the form, not the tooth direction.\n *\n * Local frame: **centered on its own thickness**, spanning `±depth / 2` in Z, matching {@link GearGeometry} so\n * meshing wheels share the plane they turn in.\n *\n * Material groups: **none** — one material for the whole ring.\n *\n * @example\n * ```typescript\n * const ring = new Mesh(new InternalGearGeometry({ teeth: 36 }), steel);\n * ```\n */\nexport class InternalGearGeometry extends ExtrudeGeometry {\n  /** The tip radius actually used. */\n  readonly tipRadius: number;\n  /** The valley radius actually used. */\n  readonly valleyRadius: number;\n  /** The rim radius actually used, after clamping outside the toothed opening. */\n  readonly rimRadius: number;\n\n  constructor({ depth = 0.25, ...shapeOptions }: InternalGearGeometryOptions = {}) {\n    const shape = new InternalGearShape(shapeOptions);\n\n    super(shape, { depth, bevelEnabled: false });\n\n    this.translate(0, 0, -depth / 2);\n\n    this.tipRadius = shape.tipRadius;\n    this.valleyRadius = shape.valleyRadius;\n    this.rimRadius = shape.rimRadius;\n  }\n}\n","import { Shape, Vector2 } from \"three\";\n\nexport interface RackShapeOptions {\n  /** Overall length of the bar, end to end. Defaults to `3`. */\n  length?: number;\n  /** Number of teeth. Defaults to `12`. */\n  teeth?: number;\n  /**\n   * Height the tooth tips reach, measured from the underside. Defaults to `0.38`.\n   *\n   * Absolute, like the radii on the circular gears — and paired with\n   * {@link RackShapeOptions.valleyHeight} the same way {@link RackShapeOptions.tipWidth} is paired with\n   * {@link RackShapeOptions.valleyWidth}.\n   */\n  tipHeight?: number;\n  /**\n   * Height the valley floors sit at, measured from the underside. Defaults to `0.2`.\n   *\n   * Absolute from the same datum as {@link RackShapeOptions.tipHeight}, so the two are directly comparable.\n   *\n   * **Their order is not enforced.** Set the valley above the tip and the teeth invert into channels cut down\n   * into the bar — a legitimate shape, and the caller's business.\n   */\n  valleyHeight?: number;\n  /**\n   * Flat carved out of **each** end before the toothed run begins. Defaults to `0`.\n   *\n   * Taken out of {@link RackShapeOptions.length}, never added to it: the bar measures `length` whatever this is\n   * set to, and the teeth crowd into what is left.\n   *\n   * **Any nonzero inset destroys tileability** — hence the default of `0`. At `0` each end carries exactly half\n   * a valley, so two racks butted end to end form a seam valley identical to an interior one and a pinion rolls\n   * across the join without a hitch. An inset adds `inset × 2` to that seam and the gap becomes visible. Use it\n   * for a standalone bar that wants plain material at its ends, not for a run.\n   */\n  inset?: number;\n  /**\n   * Width of the flat at the tooth tip, as a fraction of one period. `0` brings the tooth to a point. Defaults\n   * to `0.25`.\n   */\n  tipWidth?: number;\n  /**\n   * Width of the flat at the valley floor, as a fraction of one period. `0` brings the valley to a point.\n   * Defaults to `0.25`.\n   */\n  valleyWidth?: number;\n  /**\n   * Tooth asymmetry, `-1` to `1`. At `0` both flanks are equal; at `1` the rising flank vanishes and the tooth's\n   * trailing face drops vertically — a linear ratchet. Defaults to `0`.\n   */\n  lean?: number;\n}\n\n/**\n * Rack profile — the straight counterpart of a gear, as in rack and pinion.\n *\n * **A rack is a gear of infinite radius.** The teeth no longer converge on a center, so they stand parallel and\n * the period advances along a line rather than around a circle. That is why the tooth fractions are identical to\n * {@link GearShape}'s — tip, falling flank, valley, rising flank, with the two flats sized independently and the\n * flanks taking the remainder — and why there is no polar arithmetic here at all.\n *\n * **{@link pitch} is an output, not an input** — `(length − inset × 2) / teeth`. Size the bar, then choose how\n * finely to divide it: teeth subdivide a fixed run instead of extending it, so every tooth is whole by\n * construction and adding teeth never moves the ends. A circular gear divides its circumference the same way,\n * `2π × outerRadius / teeth`, though it does not publish the result.\n *\n * **{@link RackShapeOptions.tipHeight} and {@link RackShapeOptions.valleyHeight} are absolute**, both measured\n * from the underside, exactly as the circular gears measure both their radii from the center. That completes a\n * grid with the tooth flats — `tipWidth`/`tipHeight`, `valleyWidth`/`valleyHeight` — and their order is not\n * enforced: put the valley above the tip and the teeth invert into channels.\n *\n * Rests with its underside on `y = 0`, teeth pointing up, running along `+X` from the origin.\n */\nexport class RackShape extends Shape {\n  /** Overall length of the bar, after clamping. */\n  readonly length: number;\n  /** Tooth period, center to center — `(length − inset × 2) / teeth`. */\n  readonly pitch: number;\n  /** Height the tooth tips reach, after clamping. */\n  readonly tipHeight: number;\n  /** Height the valley floors sit at, after clamping. */\n  readonly valleyHeight: number;\n  /** Tip flat as a fraction of the period, after clamping. */\n  readonly tipWidth: number;\n  /** Valley flat as a fraction of the period, after clamping. */\n  readonly valleyWidth: number;\n\n  constructor({\n    length = 3,\n    teeth = 12,\n    tipHeight = 0.38,\n    valleyHeight = 0.2,\n    inset = 0,\n    tipWidth = 0.25,\n    valleyWidth = 0.25,\n    lean = 0,\n  }: RackShapeOptions = {}) {\n    super();\n\n    const span = Math.max(length, 1e-4);\n    const count = Math.max(1, Math.round(teeth));\n    // The insets are carved out of the span, so they can never consume the whole of it.\n    const margin = Math.min(Math.max(inset, 0), span / 2 - 1e-5);\n    const period = (span - margin * 2) / count;\n    // Both clamped only off the floor: at or below y=0 the outline would cross its own underside and leave no\n    // polygon to triangulate. Their ORDER is deliberately free — valley above tip inverts the teeth.\n    const tipY = Math.max(tipHeight, 1e-4);\n    const valleyY = Math.max(valleyHeight, 1e-4);\n\n    this.tipHeight = tipY;\n    this.valleyHeight = valleyY;\n    this.length = span;\n    this.pitch = period;\n\n    // Identical period split to the circular gears: the two flats are sized independently and whatever is left\n    // of the period is divided between the flanks, biased by `lean`.\n    //\n    // NOTE the priority, which is shared with `GearShape` and is undecided rather than designed: the TIP takes\n    // what it asks for and the VALLEY absorbs the whole overflow, so tip 0.8 / valley 0.4 resolves to 0.8 / 0.2\n    // and `tipWidth: 1` erases the valley entirely.\n    const tip = Math.max(0, Math.min(tipWidth, 1));\n    const valley = Math.max(0, Math.min(valleyWidth, 1 - tip));\n    const flanks = 1 - tip - valley;\n\n    this.tipWidth = tip;\n    this.valleyWidth = valley;\n    const bias = Math.max(-1, Math.min(lean, 1));\n    const falling = (flanks * (1 + bias)) / 2;\n    const rising = flanks - falling;\n\n    // The toothed top, traced left to right; it is reversed below so the whole outline winds counter-clockwise.\n    //\n    // The period is split so HALF the valley flat sits at each end of it: half-valley, rising, tip, falling,\n    // half-valley. Both alternatives are visible defects — emitting the tip first leaves the end inset with no\n    // valley-level run to sit on, so the first flank stretches across it as one long shallow ramp; emitting a\n    // whole valley flat at the END of each period makes the two ends of the bar carry different amounts of\n    // plain material.\n    //\n    // The half at each end is also what makes racks TILE. At `inset: 0` a trailing half meets the next bar's\n    // leading half to form a seam valley identical to an interior one.\n    const half = valley / 2;\n    const top: Vector2[] = [new Vector2(0, valleyY)];\n\n    for (let n = 0; n < count; n++) {\n      const start = margin + period * n;\n      const at = (fraction: number, y: number) => top.push(new Vector2(start + fraction * period, y));\n\n      // Bottom of the rising flank, at the end of the leading half valley.\n      at(half, valleyY);\n\n      if (tip > 0) {\n        at(half + rising, tipY);\n        at(half + rising + tip, tipY);\n      } else {\n        at(half + rising, tipY);\n      }\n\n      // Bottom of the falling flank. From here to the period's end is the trailing half valley, which meets\n      // the next period's leading half to form one whole valley.\n      at(1 - half, valleyY);\n    }\n\n    top.push(new Vector2(span, valleyY));\n\n    // Counter-clockwise: along the underside, up the right end, back across the teeth, down the left end.\n    this.moveTo(0, 0);\n    this.lineTo(span, 0);\n    for (let i = top.length - 1; i >= 0; i--) this.lineTo(top[i]!.x, top[i]!.y);\n    this.closePath();\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { RackShape, type RackShapeOptions } from \"../../shapes/RackShape\";\n\nexport interface RackGeometryOptions extends RackShapeOptions {\n  /** Extrusion depth — the rack's thickness across its run. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded **rack** — the straight member of a rack and pinion. See {@link RackShape} for the profile.\n *\n * To mesh with a pinion, size the bar so its derived {@link pitch} lands on the pinion's: a run of `n` teeth\n * against a `pinionTeeth` pinion of pitch radius `r` wants `length = n × 2π × r / pinionTeeth + inset × 2`.\n *\n * Local frame: **rests on `y = 0`** with teeth pointing up, running along `+X` from the origin and extruded\n * across `+Z`. Ground contact, like the rest of the library — no translate needed to lay it on a surface.\n *\n * Material groups: **none** — one material for the whole rack.\n *\n * @example\n * ```typescript\n * // A 24-tooth rack cut to mesh with a 20-tooth pinion of pitch radius 0.8.\n * const length = (24 * 2 * Math.PI * 0.8) / 20;\n * const rack = new Mesh(new RackGeometry({ teeth: 24, length }), steel);\n * ```\n */\nexport class RackGeometry extends ExtrudeGeometry {\n  /** Overall length of the bar, after clamping. */\n  readonly length: number;\n  /** Tooth period, center to center — `(length − inset × 2) / teeth`. */\n  readonly pitch: number;\n  /** Height the tooth tips reach, after clamping. */\n  readonly tipHeight: number;\n  /** Height the valley floors sit at, after clamping. */\n  readonly valleyHeight: number;\n  /** Tip flat as a fraction of the period, after clamping. */\n  readonly tipWidth: number;\n  /** Valley flat as a fraction of the period, after clamping. */\n  readonly valleyWidth: number;\n\n  constructor({ depth = 0.25, ...shapeOptions }: RackGeometryOptions = {}) {\n    const shape = new RackShape(shapeOptions);\n\n    super(shape, { depth, bevelEnabled: false });\n\n    this.length = shape.length;\n    this.pitch = shape.pitch;\n    this.tipHeight = shape.tipHeight;\n    this.valleyHeight = shape.valleyHeight;\n    this.tipWidth = shape.tipWidth;\n    this.valleyWidth = shape.valleyWidth;\n  }\n}\n","import { BufferGeometry, ConeGeometry, CylinderGeometry, TorusGeometry, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { linePath } from \"../../modeling/paths/LinePath\";\nimport { circleProfile } from \"../../modeling/profiles/Profiles\";\nimport { miterFrames } from \"../../modeling/surfaces/MiterFrames\";\nimport { sweep } from \"../../modeling/surfaces/Sweep\";\nimport { createGeometryBuffers, pushQuad, toBufferGeometry, type Vec3 } from \"../../modeling/mesh/GeometryBuffers\";\n\nconst UP = /*@__PURE__*/ new Vector3(0, 1, 0);\n/** The cage's own axis — the glass panes face ±X and ±Z, so a square bar squares up to this. */\nconst CAGE_AXIS = /*@__PURE__*/ new Vector3(1, 0, 0);\n\nexport interface CoachLanternGeometryOptions {\n  /** Distance from the hang point down to the top of the cage. Defaults to `0.42`. */\n  drop?: number;\n  /** Half-width of the cage at its base. Defaults to `0.15`. */\n  width?: number;\n  /** Cage height, cap underside to floor plate. Defaults to `0.4`. */\n  height?: number;\n  /**\n   * How far the cage narrows toward the top, as a fraction of `width`. Defaults to `0.72`.\n   *\n   * `1` gives straight sides; smaller values rake the posts inward, which is what reads as a coach\n   * lantern rather than a box.\n   */\n  taper?: number;\n  /** Post bar radius. Defaults to `0.015`. */\n  barWidth?: number;\n  /** Pyramidal cap height. Defaults to `0.15`. */\n  capHeight?: number;\n  /**\n   * Pyramid cap size as a multiple of the **roof plate**. Defaults to `1.4`.\n   *\n   * Measured against the plate rather than `width` so the two are directly comparable, which makes `1` the\n   * boundary between the lantern's two roof styles:\n   *\n   * - **above `1`** — the cap oversails the plate and reads as a **roof** over the whole lantern. The plate\n   *   vanishes beneath it, and a consumer need not know it is there.\n   * - **below `1`** — the cap sits inset on a flat roof and reads as a centered **gable**. This is the\n   *   country-lantern look.\n   *\n   * The cap always stands *on* the plate, so it cannot intersect or pass through it at any value.\n   */\n  capSpread?: number;\n  /**\n   * Roof plate size as a multiple of the cage's **top** corner distance. Defaults to `1.05` — just proud of\n   * the top corners, so the plate closes the cage rather than leaving a gap you can see through.\n   *\n   * Measured at the top so it tracks {@link CoachLanternGeometryOptions.taper} automatically and does not\n   * need re-tuning whenever the cage is re-raked.\n   */\n  roofSpread?: number;\n  /** Roof plate thickness as a multiple of `barWidth`. Defaults to `2`. */\n  roofThickness?: number;\n  /** The ring at the hang point that a chain or hook passes through. Defaults to `true`. */\n  bail?: boolean;\n  /**\n   * Bail ring radius as a multiple of `barWidth`. Defaults to `3`.\n   *\n   * This is what a chain or hook has to fit through, so it is the one dimension a consumer may need to match\n   * against something else. Raising it also lowers the cap, since the rod starts below the ring.\n   */\n  bailRadius?: number;\n  /** Bail wire thickness as a multiple of `barWidth`. Defaults to `0.8` — slightly lighter than the bars. */\n  bailThickness?: number;\n  /**\n   * Segments around the bail's ring — its roundness. Defaults to `10`. Minimum `3`.\n   *\n   * **The bail is the only round part of this geometry**, so it is the only place a segment count changes\n   * anything. The cap, roof plate, and floor plate are 4-sided because 4 *is* the square they are meant to\n   * be, not because they are coarse approximations of a circle.\n   */\n  bailSegments?: number;\n  /** Sides on the bail's wire cross-section. Defaults to `6`. Minimum `3`. */\n  bailSides?: number;\n  /**\n   * Floor plate size, as a multiple of the distance from center to the cage's bottom corners.\n   * Defaults to `1.15` — just proud of the posts, so the plate closes the cage rather than leaving a\n   * gap you can see the interior through.\n   *\n   * `1` lands exactly on the corner centerlines (the bars' outer halves stay proud). Raise past `1.3`\n   * for a plate that oversails the cage like a country lantern's tray.\n   */\n  plateSpread?: number;\n  /**\n   * Floor plate thickness as a multiple of `barWidth`. Defaults to `2`, matching\n   * {@link CoachLanternGeometryOptions.roofThickness}.\n   *\n   * The plate **stacks below** the lower rail, mirroring the roof plate stacking above the upper rail, so no\n   * thickness can bury the cage — both plates sit outside it and the frame stays fully visible. Thickening\n   * this one grows it downward and leaves the candle where it is.\n   */\n  plateThickness?: number;\n  /** The dropped spike beneath the floor plate. Defaults to `true`. */\n  finial?: boolean;\n  /** A candle standing on the floor plate. Defaults to `true`. */\n  candle?: boolean;\n  /** Candle height as a fraction of `height`. Defaults to `0.5`. */\n  candleHeight?: number;\n}\n\n/**\n * Wrought-iron coach lantern — a tapered four-sided cage under a pyramidal cap, glazed on all four\n * faces, with a candle standing on the floor plate.\n *\n * Material groups: `0` iron (bail, rod, cap, roof plate, posts, rails, floor plate, finial), `1` glass\n * (four panes), `2` wax (the candle). Group `2` is absent when `candle` is `false`.\n *\n * Local frame: **origin at the hang point** — the topmost metal of the bail, so the lantern hangs into −Y\n * and `drop` lengthens the rod without moving where it attaches. That is the point a consumer positions\n * against a ceiling or a bracket, and it is also the natural pivot if the lantern swings.\n *\n * {@link wickY} is where a flame, glow, and light belong. The flame is deliberately **not** part of this\n * geometry: welded into the vertices it could not move, and a flame that cannot move is not a flame.\n * Position it at `wickY` and let it pivot there.\n *\n * @example\n * ```typescript\n * const lantern = new Mesh(new CoachLanternGeometry({ drop: 0.6 }), [iron, glass, wax]);\n * ```\n */\nexport class CoachLanternGeometry extends BufferGeometry {\n  readonly drop: number;\n  readonly width: number;\n  readonly height: number;\n  /**\n   * Y of the cage top — the upper rail's centerline. The roof plate rests on that rail and the pyramid cap\n   * rests on the plate, so both sit *above* this.\n   */\n  readonly capY: number;\n  /** Y of the cage bottom — the lower rail's centerline, and the floor plate's centerline. */\n  readonly baseY: number;\n  /**\n   * Y of the floor plate's upper face — the surface the candle stands on. The plate stacks below the lower\n   * rail, so this is flush with that rail's underside and does **not** move with\n   * {@link CoachLanternGeometryOptions.plateThickness}.\n   */\n  readonly trayY: number;\n  /** Y of the candle's wick. Attach the flame, glow, and light here. */\n  readonly wickY: number;\n\n  constructor({\n    drop = 0.42,\n    width = 0.15,\n    height = 0.4,\n    taper = 0.72,\n    barWidth = 0.015,\n    capHeight = 0.15,\n    capSpread = 1.4,\n    roofSpread = 1.05,\n    roofThickness = 2,\n    plateSpread = 1.15,\n    plateThickness = 2,\n    bail = true,\n    bailRadius = 3,\n    bailThickness = 0.8,\n    bailSegments = 10,\n    bailSides = 6,\n    finial = true,\n    candle = true,\n    candleHeight = 0.5,\n  }: CoachLanternGeometryOptions = {}) {\n    super();\n\n    this.drop = drop;\n    this.width = width;\n    this.height = height;\n\n    const capY = -drop;\n    const baseY = capY - height;\n    const top = width * taper;\n    const bottom = width;\n\n    const floorPlate = barWidth * plateThickness;\n\n    this.capY = capY;\n    this.baseY = baseY;\n    // `trayY` and `wickY` depend on where the lower rail's underside actually is, so they are assigned once\n    // the rails exist rather than predicted from `baseY`.\n\n    const iron: BufferGeometry[] = [];\n\n    // --- bail and rod ---------------------------------------------------------\n    // The bail is a real ring rather than an implied hole, so a chain or hook has something to pass\n    // through and the joint is visible instead of assumed.\n    const ringRadius = barWidth * bailRadius;\n    const ringTube = barWidth * bailThickness;\n    let rodTop = 0;\n    if (bail) {\n      const ring = new TorusGeometry(\n        ringRadius,\n        ringTube,\n        Math.max(3, bailSides),\n        Math.max(3, bailSegments),\n      ).rotateY(Math.PI / 2);\n\n      // Drop the ring so its topmost metal lands exactly on the origin, which is what the anchor claims and\n      // what `bail: false` already does. Tessellation decides where that is: a 10-segment ring's highest\n      // VERTEX sits below the analytic `radius + tube` because the polygon cuts the corner, so lifting by\n      // `radius + tube` leaves it hanging a few thousandths low — and the error changes with `bailSegments`.\n      ring.computeBoundingBox();\n      const lift = -ring.boundingBox!.max.y;\n      ring.translate(0, lift, 0);\n      iron.push(ring);\n\n      // The rod starts at the ring's centerline bottom so the two overlap rather than butt.\n      rodTop = ringRadius - lift;\n    }\n    // The rod is pushed further down, once the roof it disappears into has been placed.\n\n    // --- posts and the rails closing the cage top and bottom ------------------\n    // Every bar is a profile swept along a line — the same kernel the arches and scrollwork use, which\n    // is why the corners meet cleanly instead of interpenetrating.\n    const cornerAt = (radius: number, y: number, index: number) => {\n      const angle = (index / 4) * Math.PI * 2 + Math.PI / 4;\n      return new Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius);\n    };\n    const corner = (halfWidth: number, y: number, index: number) =>\n      cornerAt(halfWidth * Math.SQRT2, y, index);\n    // A post is raked, so a frame perpendicular to its own axis cuts its ends on a slant: one lip buries\n    // itself in the rail while the opposite lip lifts clear and punches out through it. Both ends are\n    // SEAT CUT instead — cut to the horizontal plane they land on, so the end face is flat against it.\n    //\n    // `circleProfile(r, 4)` puts its faces perpendicular to the frame's axes, so `reference` decides which\n    // way a square bar presents. It must be perpendicular to the cut plane's normal, so it cannot be UP\n    // here — and it must be a CAGE axis, not the corner's radial direction. Radial looks like the\n    // symmetric choice and is the wrong symmetry: it turns the post 45° and points an edge at each pane\n    // instead of a face.\n    // ONE segment, not two. An intermediate station on a straight bar is unstretched while both seat-cut\n    // ends widen by `1 / cos φ`, so it pinches the middle — and a straight run has nothing to interpolate.\n    const post = (from: Vector3, to: Vector3, radius: number) =>\n      sweep(circleProfile(radius, 4), miterFrames(linePath(from, to, 1), { startCut: UP, endCut: UP, reference: CAGE_AXIS }));\n\n    // A rail loop is ONE closed sweep with mitered corners: every ring sits in the plane bisecting its\n    // joint, so consecutive segments share the identical ring and the corner closes exactly.\n    const railLoop = (halfWidth: number, y: number, radius: number) =>\n      sweep(\n        circleProfile(radius, 4),\n        miterFrames(\n          [0, 1, 2, 3].map((i) => ({ position: corner(halfWidth, y, i), tangent: new Vector3() })),\n          { closed: true, reference: new Vector3(0, 1, 0) },\n        ),\n        { closed: true },\n      );\n\n    // Rails and posts are the SAME stock, and that is not a stylistic default — it is the condition that\n    // makes the post's faces continue the rails' rather than overhanging them. There is deliberately no\n    // option to vary it: thinning the rails while leaving their centerline in place oversizes the post on\n    // all four faces at once, and it cannot be tuned back, because moving a rail's outer face toward the\n    // post moves its inner face away by the same amount. Lighter rails would need them INSET so their outer\n    // faces stay flush — a different construction, not a scalar.\n    const upperRail = railLoop(top, capY, barWidth);\n    const lowerRail = railLoop(bottom, baseY, barWidth);\n\n    // The posts SPAN BETWEEN the rails' facing surfaces rather than running into them. A miter aligns\n    // surfaces so members meet; burying one member inside another is not a joint, it is a hidden defect —\n    // and it was hiding one, with the lower rail dipping below the post's end where the floor plate\n    // happened to cover it. Spanning means nothing in the cage interpenetrates.\n    //\n    // The rails' surfaces are read off their own bounding boxes, not recomputed. The profile decides where\n    // a ring's extremes land — `circleProfile(r, 4)` reaches `r / √2` along a frame axis, not `r` — so\n    // asking the geometry is right and assuming is a bug waiting for the profile to change.\n    upperRail.computeBoundingBox();\n    lowerRail.computeBoundingBox();\n    const footY = lowerRail.boundingBox!.max.y;\n    const headY = upperRail.boundingBox!.min.y;\n\n    // The post's center must land on the RAIL's center radius at the contact plane, which is not the same\n    // as the nominal corner line evaluated there: the corner line rakes inward as it rises, so by `footY`\n    // it has already drifted in by the rake, and the post's faces miss the rail's by that drift. Take the\n    // rails' own radii at the rails' own faces instead of interpolating a line between their centerlines.\n    for (let i = 0; i < 4; i++) {\n      iron.push(post(cornerAt(bottom * Math.SQRT2, footY, i), cornerAt(top * Math.SQRT2, headY, i), barWidth));\n    }\n    iron.push(upperRail, lowerRail);\n\n    // --- roof: the plate on the cage, the pyramid cap on the plate -------------\n    // Three parts STACKED, each resting on the one below rather than passing through it: top rail → roof\n    // plate → pyramid cap. Stacking is what makes both usages fall out of a single construction, with no\n    // mode switch:\n    //\n    //   `capSpread > 1` — the cap oversails the plate and reads as a ROOF over the whole lantern; the plate\n    //                     disappears beneath it and a consumer need not know it is there.\n    //   `capSpread < 1` — the cap sits inset on a flat roof and reads as a centered GABLE, which is the\n    //                     country-lantern look.\n    //\n    // The cap previously had its base ON `capY` while the plate was CENTERED there, so the cap's base plane\n    // was buried in the plate's slab. A wide cap hid that; a narrow one emerged from the middle of the\n    // plate and eventually passed through it entirely.\n    const roofY = upperRail.boundingBox!.max.y;\n    const roofPlate = barWidth * roofThickness;\n    // Measured against the cage's TOP corner distance, so the plate tracks `taper` instead of needing to be\n    // re-tuned whenever the cage is re-raked. The old `top * 1.18` could never reach the corners at\n    // `top * √2`, which is where the gap between plate and bars came from.\n    const roofRadius = top * Math.SQRT2 * roofSpread;\n    iron.push(\n      new CylinderGeometry(roofRadius, roofRadius, roofPlate, 4)\n        .rotateY(Math.PI / 4)\n        .translate(0, roofY + roofPlate / 2, 0),\n    );\n\n    // `capSpread` is a multiple of the ROOF PLATE, not of `width`, so the two are directly comparable and\n    // `1` is the meaningful boundary between a roof and a gable.\n    const capBaseY = roofY + roofPlate;\n    iron.push(\n      new ConeGeometry(roofRadius * capSpread, capHeight, 4)\n        .rotateY(Math.PI / 4)\n        .translate(0, capBaseY + capHeight / 2, 0),\n    );\n\n    // The rod runs from the bail down into the cap, stopping partway up the cone so it is enclosed rather\n    // than emerging through the apex.\n    const rodEnd = capBaseY + capHeight * 0.35;\n    const rodLength = Math.max(-rodTop - rodEnd, 0.001);\n    iron.push(\n      new CylinderGeometry(barWidth * 0.8, barWidth * 0.8, rodLength, 5).translate(0, -rodTop - rodLength / 2, 0),\n    );\n\n    // --- floor plate, and the finial hanging under it -------------------------\n    // STACKS BELOW the lower rail, mirroring the roof plate stacking above the upper rail. Centering it on\n    // `baseY` instead let it swallow the rail — and once the thickness became adjustable, a thick enough\n    // plate made the bottom rail disappear entirely. Stacked, the cage reads as a complete frame at every\n    // setting, with both plates outside it.\n    //\n    // Sized off the corner distance, not the half-width: `corner()` places corners at `width * √2`, so a\n    // plate of radius `width * 1.2` could never reach them.\n    const soffitY = lowerRail.boundingBox!.min.y;\n\n    // With the plate stacked below the rail, the surface a candle stands on IS the rail's underside — the\n    // plate's upper face is flush with it. Read off the geometry, so it stays right if the profile changes.\n    const trayY = soffitY;\n    this.trayY = trayY;\n    this.wickY = trayY + height * candleHeight;\n\n    const plateRadius = bottom * Math.SQRT2 * plateSpread;\n    iron.push(\n      new CylinderGeometry(plateRadius, plateRadius, floorPlate, 4)\n        .rotateY(Math.PI / 4)\n        .translate(0, soffitY - floorPlate / 2, 0),\n    );\n    if (finial) {\n      // Base parked on the plate's CENTERLINE, so it stays buried in the plate at any thickness. Its old\n      // fixed offset sat just inside a 0.03 plate and would have floated free of a thinner one, leaving a\n      // gap between spike and tray.\n      const spikeHeight = height * 0.3;\n      const plateMidY = soffitY - floorPlate / 2;\n      iron.push(\n        new ConeGeometry(width / 3, spikeHeight, 5).rotateX(Math.PI).translate(0, plateMidY - spikeHeight / 2, 0),\n      );\n    }\n\n    // --- glazing --------------------------------------------------------------\n    // Four panes spanning the posts, raked with them. Quads rather than planes, because a tapered face\n    // is a trapezoid and a `PlaneGeometry` cannot be one.\n    const buffers = createGeometryBuffers();\n    const point = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n    for (let i = 0; i < 4; i++) {\n      pushQuad(\n        buffers,\n        [\n          point(corner(bottom, baseY, i)),\n          point(corner(bottom, baseY, i + 1)),\n          point(corner(top, capY, i + 1)),\n          point(corner(top, capY, i)),\n        ],\n        undefined,\n      );\n    }\n\n    // Every part must agree on whether it carries an index — `mergeGeometries` requires it present in\n    // all or none. `toBufferGeometry` sets one, the swept bars set one, the primitives do not, so\n    // everything is flattened to non-indexed before either merge.\n    const glazing = toBufferGeometry(buffers);\n    const glass = glazing.toNonIndexed();\n    glazing.dispose();\n\n    const ironMerged = mergeGeometries(\n      iron.map((part) => (part.index ? part.toNonIndexed() : part)),\n      false,\n    );\n    if (!ironMerged) throw new Error(\"CoachLanternGeometry: iron parts failed to merge.\");\n\n    const parts: BufferGeometry[] = [ironMerged, glass];\n\n    if (candle) {\n      const waxHeight = height * candleHeight;\n      parts.push(\n        new CylinderGeometry(width * 0.22, width * 0.24, waxHeight, 8)\n          .translate(0, trayY + waxHeight / 2, 0)\n          .toNonIndexed(),\n      );\n    }\n\n    // Not cast — `mergeGeometries` returns null on mismatched attributes, and a cast turns that into an\n    // unreadable \"cannot read properties of null\" three frames later.\n    const merged = mergeGeometries(parts, true);\n    if (!merged) throw new Error(\"CoachLanternGeometry: parts have incompatible attributes.\");\n\n    this.copy(merged);\n    merged.dispose();\n    iron.forEach((part) => part.dispose());\n    parts.forEach((part) => part.dispose());\n    this.computeBoundingSphere();\n  }\n}\n","import { BoxGeometry, Quaternion, Vector3 } from \"three\";\n\nconst Y_AXIS = new Vector3(0, 1, 0);\n\n/** Thin box aligned between two points — for open iron lantern cages. */\nexport function barBetween(from: Vector3, to: Vector3, thickness: number): BoxGeometry {\n  const direction = to.clone().sub(from);\n  const length = direction.length();\n  const geometry = new BoxGeometry(thickness, length, thickness);\n  geometry.applyQuaternion(new Quaternion().setFromUnitVectors(Y_AXIS, direction.normalize()));\n  const midpoint = from.clone().add(to).multiplyScalar(0.5);\n  geometry.translate(midpoint.x, midpoint.y, midpoint.z);\n  return geometry;\n}","import { BoxGeometry, BufferGeometry, OctahedronGeometry, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { barBetween } from \"./barBetween\";\n\nexport interface HangingLanternGeometryOptions {\n  /** Chain length from the hang point. Defaults to `3`. */\n  drop?: number;\n  /** Chain link cross-section. Defaults to `0.05`. */\n  chainWidth?: number;\n  /** Cap width (X). Defaults to `0.18`. */\n  capWidth?: number;\n  /** Cap height (Y). Defaults to `0.16`. */\n  capHeight?: number;\n  /** Cap depth (Z). Defaults to `0.18`. */\n  capDepth?: number;\n  /** Cap center offset below the chain bottom. Defaults to `0.02`. */\n  capOffset?: number;\n  /** Cage vertex radius before stretch. Defaults to `0.42`. */\n  cageRadius?: number;\n  /** Vertical stretch on the cage. Defaults to `1.4`. */\n  cageStretch?: number;\n  /** Extra downward offset below the cap-center cage attach. Defaults to `0`. */\n  cageGap?: number;\n  /** Cage strut thickness. Defaults to `0.03`. */\n  cageBarWidth?: number;\n  /** Inner lamp scale relative to the cage (inset to sit inside struts). Defaults to `0.96`. */\n  innerScale?: number;\n  /** Include the solid inner octahedron lamp volume. Defaults to `true`. */\n  inner?: boolean;\n}\n\n/**\n * Wrought-iron hanging lantern frame — chain, cap, and open octahedron cage\n * built from edge struts.\n *\n * Material groups: `0` mount (chain + cap), `1` cage struts, `2` inner lamp\n * (solid octahedron).\n *\n * Local frame: origin at the chain top (hang point). The cage top vertex\n * attaches at the cap center, optionally lowered by `cageGap`.\n */\nexport class HangingLanternGeometry extends BufferGeometry {\n  readonly drop: number;\n  readonly chainWidth: number;\n  readonly capWidth: number;\n  readonly capHeight: number;\n  readonly capDepth: number;\n  readonly capOffset: number;\n  readonly cageRadius: number;\n  readonly cageStretch: number;\n  readonly cageGap: number;\n  readonly cageBarWidth: number;\n  readonly innerScale: number;\n  readonly inner: boolean;\n  /** Y of the cage center in local space (negative, below the hang point). */\n  readonly cageCenterY: number;\n\n  constructor({\n    drop = 3,\n    chainWidth = 0.05,\n    capWidth = 0.18,\n    capHeight = 0.16,\n    capDepth = 0.18,\n    capOffset = 0.02,\n    cageRadius = 0.42,\n    cageStretch = 1.4,\n    cageGap = 0,\n    cageBarWidth = 0.03,\n    innerScale = 0.96,\n    inner = true,\n  }: HangingLanternGeometryOptions = {}) {\n    super();\n\n    this.drop = drop;\n    this.chainWidth = chainWidth;\n    this.capWidth = capWidth;\n    this.capHeight = capHeight;\n    this.capDepth = capDepth;\n    this.capOffset = capOffset;\n    this.cageRadius = cageRadius;\n    this.cageStretch = cageStretch;\n    this.cageGap = cageGap;\n    this.cageBarWidth = cageBarWidth;\n    this.innerScale = innerScale;\n    this.inner = inner;\n\n    const capCenterY = -drop + capOffset;\n    this.cageCenterY = capCenterY - cageRadius * cageStretch - cageGap;\n\n    const mount: BufferGeometry[] = [];\n\n    const chain = new BoxGeometry(chainWidth, drop, chainWidth);\n    chain.translate(0, -drop / 2, 0);\n    mount.push(chain);\n\n    const cap = new BoxGeometry(capWidth, capHeight, capDepth);\n    cap.translate(0, -drop + capOffset, 0);\n    mount.push(cap);\n\n    const cy = this.cageCenterY;\n    const r = cageRadius;\n    const top = new Vector3(0, cy + r * cageStretch, 0);\n    const bottom = new Vector3(0, cy - r * cageStretch, 0);\n    const px = new Vector3(r, cy, 0);\n    const nx = new Vector3(-r, cy, 0);\n    const pz = new Vector3(0, cy, r);\n    const nz = new Vector3(0, cy, -r);\n\n    const cageEdges: [Vector3, Vector3][] = [\n      [top, px],\n      [top, nx],\n      [top, pz],\n      [top, nz],\n      [bottom, px],\n      [bottom, nx],\n      [bottom, pz],\n      [bottom, nz],\n      [px, pz],\n      [pz, nx],\n      [nx, nz],\n      [nz, px],\n    ];\n\n    const cage = cageEdges.map(([from, to]) => barBetween(from, to, cageBarWidth));\n\n    const mountMerged = mergeGeometries(\n      mount.map((part) => part.toNonIndexed()),\n      false,\n    ) as BufferGeometry;\n    const cageMerged = mergeGeometries(\n      cage.map((part) => part.toNonIndexed()),\n      false,\n    ) as BufferGeometry;\n\n    const parts = [mountMerged, cageMerged];\n\n    if (inner) {\n      const lamp = new OctahedronGeometry(cageRadius * innerScale, 0);\n      lamp.scale(1, cageStretch, 1);\n      lamp.translate(0, cy, 0);\n      parts.push(lamp.toNonIndexed());\n    }\n\n    this.copy(mergeGeometries(parts, true) as BufferGeometry);\n    this.computeBoundingSphere();\n  }\n}","import { BoxGeometry, BufferGeometry, CylinderGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\nexport interface WallSconceGeometryOptions {\n  /** Wall-plate thickness (X). Defaults to `0.05`. */\n  plateThickness?: number;\n  /** Wall-plate height (Y). Defaults to `0.22`. */\n  plateHeight?: number;\n  /** Wall-plate depth (Z). Defaults to `0.28`. */\n  plateDepth?: number;\n  /** Wall-plate center X (negative = into the wall). Defaults to `-0.055`. */\n  plateOffsetX?: number;\n  /** Bracket length into the room (X). Defaults to `0.1`. */\n  bracketLength?: number;\n  /** Bracket height (Y). Defaults to `0.05`. */\n  bracketHeight?: number;\n  /** Bracket depth (Z). Defaults to `0.07`. */\n  bracketDepth?: number;\n  /** Bracket center X. Defaults to `-0.005`. */\n  bracketOffsetX?: number;\n  /** Bracket center Y. Defaults to `0.1`. */\n  bracketOffsetY?: number;\n  /** Chimney / lamp body center X. Defaults to `0.06`. */\n  bodyOffsetX?: number;\n  /** Chimney height. Defaults to `0.3`. */\n  chimneyHeight?: number;\n  /** Chimney top radius. Defaults to `0.1`. */\n  chimneyTopRadius?: number;\n  /** Chimney bottom radius. Defaults to `0.105`. */\n  chimneyBottomRadius?: number;\n  /** Chimney center Y. Defaults to `-0.05`. */\n  chimneyCenterY?: number;\n  /** Cap radius. Defaults to `0.115`. */\n  capRadius?: number;\n  /** Cap height. Defaults to `0.05`. */\n  capHeight?: number;\n  /** Cap center Y. Defaults to `0.12`. */\n  capCenterY?: number;\n  /** Bowl top radius. Defaults to `0.09`. */\n  bowlTopRadius?: number;\n  /** Bowl bottom radius. Defaults to `0.11`. */\n  bowlBottomRadius?: number;\n  /** Bowl height. Defaults to `0.05`. */\n  bowlHeight?: number;\n  /** Bowl center Y. Defaults to `-0.22`. */\n  bowlCenterY?: number;\n  /** Radial segments on cylinders. Defaults to `8`. */\n  radialSegments?: number;\n  /** Glass chimney scale relative to the frame opening. Defaults to `0.96`. */\n  innerScale?: number;\n  /** Include the emissive glass chimney. Defaults to `true`. */\n  inner?: boolean;\n}\n\n/**\n * Wall-mounted oil-lamp sconce — iron mount, cap, and bowl framing an emissive\n * glass chimney.\n *\n * Material groups: `0` mount (plate + bracket), `1` iron frame (cap + bowl),\n * `2` glass chimney.\n *\n * Local frame: faces +X from a −X wall; lamp center at\n * `(bodyOffsetX, chimneyCenterY, 0)`.\n */\nexport class WallSconceGeometry extends BufferGeometry {\n  readonly bodyOffsetX: number;\n  readonly chimneyCenterY: number;\n  readonly innerScale: number;\n  readonly inner: boolean;\n  readonly lightCenterX: number;\n  readonly lightCenterY: number;\n  readonly lightCenterZ: number;\n\n  constructor({\n    plateThickness = 0.05,\n    plateHeight = 0.22,\n    plateDepth = 0.28,\n    plateOffsetX = -0.055,\n    bracketLength = 0.1,\n    bracketHeight = 0.05,\n    bracketDepth = 0.07,\n    bracketOffsetX = -0.005,\n    bracketOffsetY = 0.1,\n    bodyOffsetX = 0.06,\n    chimneyHeight = 0.3,\n    chimneyTopRadius = 0.1,\n    chimneyBottomRadius = 0.105,\n    chimneyCenterY = -0.05,\n    capRadius = 0.115,\n    capHeight = 0.05,\n    capCenterY = 0.12,\n    bowlTopRadius = 0.09,\n    bowlBottomRadius = 0.11,\n    bowlHeight = 0.05,\n    bowlCenterY = -0.22,\n    radialSegments = 8,\n    innerScale = 0.96,\n    inner = true,\n  }: WallSconceGeometryOptions = {}) {\n    super();\n\n    this.bodyOffsetX = bodyOffsetX;\n    this.chimneyCenterY = chimneyCenterY;\n    this.innerScale = innerScale;\n    this.inner = inner;\n    this.lightCenterX = bodyOffsetX;\n    this.lightCenterY = chimneyCenterY;\n    this.lightCenterZ = 0;\n\n    const mount: BufferGeometry[] = [];\n\n    const plate = new BoxGeometry(plateThickness, plateHeight, plateDepth);\n    plate.translate(plateOffsetX, 0, 0);\n    mount.push(plate);\n\n    const bracket = new BoxGeometry(bracketLength, bracketHeight, bracketDepth);\n    bracket.translate(bracketOffsetX, bracketOffsetY, 0);\n    mount.push(bracket);\n\n    const frame: BufferGeometry[] = [];\n\n    const cap = new CylinderGeometry(capRadius, capRadius, capHeight, radialSegments);\n    cap.translate(bodyOffsetX, capCenterY, 0);\n    frame.push(cap);\n\n    const bowl = new CylinderGeometry(bowlTopRadius, bowlBottomRadius, bowlHeight, radialSegments);\n    bowl.translate(bodyOffsetX, bowlCenterY, 0);\n    frame.push(bowl);\n\n    const mountMerged = mergeGeometries(\n      mount.map((part) => part.toNonIndexed()),\n      false,\n    ) as BufferGeometry;\n    const frameMerged = mergeGeometries(\n      frame.map((part) => part.toNonIndexed()),\n      false,\n    ) as BufferGeometry;\n\n    const parts = [mountMerged, frameMerged];\n\n    if (inner) {\n      const glass = new CylinderGeometry(\n        chimneyTopRadius * innerScale,\n        chimneyBottomRadius * innerScale,\n        chimneyHeight * innerScale,\n        radialSegments,\n      );\n      glass.translate(bodyOffsetX, chimneyCenterY, 0);\n      parts.push(glass.toNonIndexed());\n    }\n\n    this.copy(mergeGeometries(parts, true) as BufferGeometry);\n    this.computeBoundingSphere();\n  }\n}","import { BoxGeometry, BufferAttribute, BufferGeometry, Color } from \"three\";\nimport { mergeGeometries } from \"three/examples/jsm/utils/BufferGeometryUtils.js\";\nimport type { ColorSampler } from \"../../utils/RandomColor\";\nimport { createRandom, deriveSubSeed, mulberry32 } from \"../../utils/Random\";\n\n/**\n * How the two returns vary from course to course. Every pattern in the catalog is a rule for two\n * numbers, which is why one construction covers them all.\n *\n * - `\"straight\"` — equal returns, every course the same. Reads as a plain stacked column.\n * - `\"alternating\"` — the long face swaps walls each course. This is TOOTHING: it reads as though the two\n *   walls are bonded into one another rather than merely meeting, which is the classic quoin.\n * - `\"staggered\"` — one leg varies and the other holds. A softer step that keeps a clean line on one wall.\n */\nexport type QuoinPattern = \"straight\" | \"alternating\" | \"staggered\";\n\nexport interface QuoinStackGeometryOptions {\n  /** How tall the stack runs. Defaults to `2.8`. */\n  height?: number;\n  /**\n   * Target course height. Defaults to `0.26`.\n   *\n   * Fitted to `height`, so it never leaves a sliver at the top. Give it the wall's own course height and\n   * the quoins line up with the coursing.\n   */\n  courseHeight?: number;\n  /** See {@link QuoinPattern}. Defaults to `\"alternating\"`. */\n  pattern?: QuoinPattern;\n  /** The longer return. Defaults to `0.44`. */\n  longLeg?: number;\n  /** The shorter return. Defaults to `0.22`. Ignored by `\"straight\"`, which uses `longLeg` for both. */\n  shortLeg?: number;\n  /**\n   * Lay a quoin on every other course, leaving the wall showing between — \"teeth of a comb\". Defaults to\n   * `false`. The pattern still advances per quoin LAID, so gapping and alternating compose rather than\n   * canceling.\n   */\n  everyOther?: boolean;\n  /**\n   * Which phase the pattern starts on, `0` or `1`. Defaults to `0`.\n   *\n   * Two corners of one building want opposite phases, or the pattern mirrors instead of continuing round.\n   */\n  phase?: number;\n  /**\n   * The wall's thickness — what the stack is standing at the corner of. Defaults to `0.34`.\n   *\n   * Together with `proud` this places the stack's outer corner. See the note on the origin below.\n   */\n  wallThickness?: number;\n  /**\n   * How far the stack stands out of BOTH wall faces. Defaults to `0.032`.\n   *\n   * Most of why a corner reads as dressed rather than merely turned. On a 340mm wall: under 0.02 is a\n   * shadow line, 0.02–0.045 is clearly proud, past that is RUSTICATED. **Not optional at 0** — flush would\n   * land the quoin's end exactly coplanar with the other wall's face, and two coplanar surfaces fight.\n   */\n  proud?: number;\n  /** Base stone tint. Defaults to `#d6ccb6` — dressed limestone, paler than the wall it turns. */\n  color?: string;\n  /** Per-quoin tint spread in HSL. Defaults to `0.025`. A delivery of dressed stone is fairly uniform. */\n  colorVariance?: number;\n  /** Per-stone sampler; overrides color/colorVariance/alternateTint. Index counts laid stones, excluding mortar; seeded color draws do not alter geometry. */\n  colors?: ColorSampler;\n  /**\n   * Shade alternate courses light and dark. Defaults to `false`.\n   *\n   * **Only correct because ONE stack owns the corner.** A real corner is built by two walls contributing\n   * alternate courses; were this two stacks, each would need a UNIFORM tint opposite its neighbor, since\n   * both alternating in step gives light, light, dark, dark. Ownership decides the rule.\n   */\n  alternateTint?: boolean;\n  /** Defaults to `0x2c1a`. */\n  seed?: number;\n}\n\n/**\n * The dressed stones at a building's external corner.\n *\n * **A quoin is not an L-shaped block.** It is a rectangular stone laid so it shows a LONG face on one wall\n * and a SHORT end on the other, and every pattern in the catalog is just a rule for those two returns\n * per course. That is why one construction covers `straight`, `alternating` and `staggered` — nothing\n * differs but two numbers.\n *\n * **The origin is the corner LINE**, where the two walls' center planes cross — not the stack's own outer\n * corner. So placing it is one line: put it where the walls meet, and `wallThickness` and `proud` carry it\n * out to where a quoin actually sits. The stack runs UP from `y = 0` and its returns run along `−X` and\n * `−Z`, so the outside corner it dresses faces `+X +Z`.\n *\n * ```ts\n * const quoins = new Mesh(new QuoinStackGeometry({ height: 2.8, wallThickness: 0.34 }), stone);\n * quoins.position.set(cornerX, 0, cornerZ);   // where the two walls cross\n * ```\n *\n * Per-course tint rides a **vertex attribute**, so the whole stack is one geometry and one draw call and\n * still varies stone to stone. Give it a material with `vertexColors: true`, or every quoin comes out white.\n *\n * Material groups: none.\n */\nexport class QuoinStackGeometry extends BufferGeometry {\n  /** Quoins laid. */\n  readonly quoinCount: number;\n  /** Courses the stack was divided into — fitted to `height`. */\n  readonly courseCount: number;\n  /** The course height actually used. */\n  readonly courseHeight: number;\n\n  constructor({\n    height = 2.8,\n    courseHeight = 0.26,\n    pattern = \"alternating\",\n    longLeg = 0.44,\n    shortLeg = 0.22,\n    everyOther = false,\n    phase = 0,\n    wallThickness = 0.34,\n    proud = 0.032,\n    color = \"#d6ccb6\",\n    colors,\n    colorVariance = 0.025,\n    alternateTint = false,\n    seed = 0x2c1a,\n  }: QuoinStackGeometryOptions = {}) {\n    super();\n\n    const random = mulberry32(seed);\n    const colorContext = { index: 0, random: createRandom(deriveSubSeed(seed, 0x71756f69)) };\n    const signed = (amount: number) => (random() - 0.5) * 2 * amount;\n    const base = new Color(color);\n    const tint = new Color();\n\n    const courses = Math.max(1, Math.round(height / courseHeight));\n    const step = height / courses;\n    const outer = wallThickness / 2 + proud;\n    const start = Math.round(phase) % 2;\n\n    const parts: BufferGeometry[] = [];\n\n    for (let c = 0; c < courses; c++) {\n      // \"Teeth of a comb\" — the wall shows between quoins, an accent rather than a structural tie.\n      if (everyOther && (c + start) % 2 !== 0) continue;\n\n      // The pattern advances per QUOIN LAID, not per course. Keying it to the course makes gapping and\n      // alternating cancel: taking every second course only ever lands on one phase, so the long face\n      // stops swapping and the corner silently reverts to straight.\n      const swap = (parts.length + start) % 2 === 1;\n\n      let legA = longLeg;\n      let legB = shortLeg;\n      if (pattern === \"straight\") {\n        legA = longLeg;\n        legB = longLeg;\n      } else if (pattern === \"alternating\") {\n        legA = swap ? shortLeg : longLeg;\n        legB = swap ? longLeg : shortLeg;\n      } else {\n        legA = swap ? shortLeg : longLeg;\n        legB = longLeg;\n      }\n\n      const block = new BoxGeometry(legA, step * 0.96, legB);\n      block.translate(outer - legA / 2, (c + 0.5) * step, outer - legB / 2);\n\n      const shade = alternateTint && parts.length % 2 === 1 ? -colorVariance : colorVariance;\n      tint.copy(base).offsetHSL(signed(colorVariance) / 4, 0, shade * 0.5 + signed(colorVariance) / 2);\n\n      if (colors) {\n        colorContext.index = parts.length;\n        colors(tint, colorContext);\n      }\n      const count = block.attributes.position!.count;\n      const vertexColors = new Float32Array(count * 3);\n      for (let i = 0; i < count; i++) {\n        vertexColors[i * 3] = tint.r;\n        vertexColors[i * 3 + 1] = tint.g;\n        vertexColors[i * 3 + 2] = tint.b;\n      }\n      block.setAttribute(\"color\", new BufferAttribute(vertexColors, 3));\n      parts.push(block);\n    }\n\n    const merged = mergeGeometries(parts, false);\n    parts.forEach((part) => part.dispose());\n    if (!merged) throw new Error(\"QuoinStackGeometry: no quoins were laid.\");\n\n    this.copy(merged);\n    merged.dispose();\n    this.computeBoundingSphere();\n\n    this.quoinCount = parts.length;\n    this.courseCount = courses;\n    this.courseHeight = step;\n  }\n}\n","import { BufferGeometry, Vector2 } from \"three\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type Vec3,\n} from \"../../modeling/mesh/GeometryBuffers\";\nimport { offsetLoop } from \"../../modeling/profiles/OffsetLoop\";\n\n/**\n * How an edge is worked.\n *\n * - `sharp` — left alone. The box, unmodified.\n * - `chamfer` — a flat splay. One facet, whatever `segments` says.\n * - `round` — convex, a bullnose. The solid reaches nearly full size at once, then flattens.\n * - `cove` — concave. It stays pulled in and flares late.\n *\n * `round` and `cove` are the same construction with the curve bowed the other way, which is why an inside\n * and an outside edge are one option here rather than two geometries.\n */\nexport type EdgeStyle = \"sharp\" | \"chamfer\" | \"round\" | \"cove\";\n\n/** Which pair of faces is worked — the axis the treatment is measured along. */\nexport type EdgeAxis = \"x\" | \"y\" | \"z\";\n\n/** Which of the two faces on {@link EdgeAxis} is worked. `low` and `high` are the −/+ ends of that axis. */\nexport type EdgeEnds = \"both\" | \"low\" | \"high\" | \"none\";\n\nexport interface EdgedBoxGeometryOptions {\n  /** Extent on X. Defaults to `1`. */\n  width?: number;\n  /** Extent on Y. Defaults to `1`. */\n  height?: number;\n  /** Extent on Z. Defaults to `1`. */\n  depth?: number;\n  /** How the edge is worked. Defaults to `\"chamfer\"`. See {@link EdgeStyle}. */\n  edge?: EdgeStyle;\n  /**\n   * How deep the treatment runs, both inward from the sides and along the axis. Defaults to `0.1`.\n   *\n   * Clamped so the solid can never fold: it will not exceed half the smaller cross-section dimension, nor\n   * half the length when both ends are worked.\n   */\n  radius?: number;\n  /**\n   * How finely a `round` or `cove` is cut — the low-poly knob. Defaults to `4`.\n   *\n   * `1` collapses either onto its own chord, which is a chamfer. Like `segments` everywhere else here it\n   * changes TESSELLATION only: the solid fills `width × height × depth` exactly at every setting.\n   */\n  segments?: number;\n  /**\n   * Which pair of faces is worked. Defaults to `\"y\"` — the top and bottom.\n   *\n   * The dimensions do not rotate with it: `width` is always the extent on X. Only the treatment moves.\n   */\n  axis?: EdgeAxis;\n  /** Which of that pair. Defaults to `\"both\"`. `\"low\"` alone is a plinth; `\"both\"` is a raised panel. */\n  ends?: EdgeEnds;\n}\n\n/** One cross-section of the stack: how far along the axis, and how far in from the outline. */\ninterface Level {\n  rise: number;\n  inset: number;\n}\n\n/**\n * A box with its edges chamfered, rounded, or coved along one axis.\n *\n * **Built as a LOFT, not by rounding edges.** The solid is a stack of cross-sections — each one the base\n * rectangle pushed inward by however much the edge profile says at that height — with the bands between\n * them stitched. Three treatments come out of one mechanism, differing only in how the inset falls off:\n * a straight line, a convex quarter, a concave quarter.\n *\n * Two things fall out for free, and they are the reason this construction was worth finding:\n *\n * - **No corner logic.** There is none in this file. A corner is only ever where two bands of the loft\n *   meet, and each band brings its own plane, so the four corners of the treatment resolve themselves.\n * - **Nothing is trimmed.** Edge rounding wants a real trimming capability; lofting between sections\n *   wants nothing but {@link offsetLoop}.\n *\n * The inset is a true offset rather than a scale, so a long thin box keeps a constant edge all the way\n * round instead of a wider one on its long sides.\n *\n * Sits on the `y = 0` plane, centered on X and Z — whichever `axis` is worked. Material groups: none; pass\n * one material, not an array.\n *\n * @example\n * ```ts\n * // A shelf with a bullnose front edge.\n * const shelf = new Mesh(\n *   new EdgedBoxGeometry({ width: 1.2, height: 0.04, depth: 0.3, edge: \"round\", radius: 0.02, axis: \"z\", ends: \"high\" }),\n *   oak,\n * );\n * ```\n */\nexport class EdgedBoxGeometry extends BufferGeometry {\n  constructor({\n    width = 1,\n    height = 1,\n    depth = 1,\n    edge = \"chamfer\",\n    radius = 0.1,\n    segments = 4,\n    axis = \"y\",\n    ends = \"both\",\n  }: EdgedBoxGeometryOptions = {}) {\n    super();\n\n    // The stack is always built along +Y and then turned. Rotating is a PROPER rotation, so the swept\n    // winding survives it — which mirroring the coordinates would not do.\n    const [across, through, along] =\n      axis === \"y\" ? [width, depth, height] : axis === \"x\" ? [height, depth, width] : [width, height, depth];\n\n    const geometry = buildStack(across, through, along, edge, radius, segments, ends);\n    if (axis === \"x\") geometry.rotateZ(-Math.PI / 2);\n    else if (axis === \"z\") geometry.rotateX(Math.PI / 2);\n\n    // Stated anchor: standing on y = 0, centered on X and Z, whichever axis was worked.\n    geometry.computeBoundingBox();\n    const box = geometry.boundingBox!;\n    geometry.translate(-(box.min.x + box.max.x) / 2, -box.min.y, -(box.min.z + box.max.z) / 2);\n\n    this.copy(geometry);\n    geometry.dispose();\n    this.computeBoundingSphere();\n  }\n}\n\n/**\n * The edge's own profile, from the face inward.\n *\n * All three run from `(rise 0, inset radius)` — the face, pulled fully in — to `(rise radius, inset 0)`,\n * where the solid reaches full size. Parametrized by ANGLE rather than by rise, because sampling rise\n * uniformly would bunch a round's points where the curve is flat and starve it where it turns.\n */\nfunction edgeProfile(style: EdgeStyle, radius: number, segments: number): Level[] {\n  if (style === \"sharp\" || radius <= 0) return [{ rise: 0, inset: 0 }];\n  // A chamfer is one flat facet, so `segments` must not reach it — a chamfer that got smoother would not\n  // be a chamfer.\n  if (style === \"chamfer\") {\n    return [\n      { rise: 0, inset: radius },\n      { rise: radius, inset: 0 },\n    ];\n  }\n\n  const steps = Math.max(1, Math.round(segments));\n  return Array.from({ length: steps + 1 }, (_, i) => {\n    const t = (i / steps) * (Math.PI / 2);\n    return style === \"round\"\n      ? { rise: radius * (1 - Math.cos(t)), inset: radius * (1 - Math.sin(t)) }\n      : { rise: radius * Math.sin(t), inset: radius * Math.cos(t) };\n  });\n}\n\n/** The solid, built along +Y: a stack of offset loops with the bands between them stitched. */\nfunction buildStack(\n  across: number,\n  through: number,\n  along: number,\n  style: EdgeStyle,\n  radius: number,\n  segments: number,\n  ends: EdgeEnds,\n): BufferGeometry {\n  const ha = across / 2;\n  const ht = through / 2;\n  // Counter-clockwise, as `offsetLoop` requires.\n  const outline = [\n    new Vector2(-ha, -ht),\n    new Vector2(ha, -ht),\n    new Vector2(ha, ht),\n    new Vector2(-ha, ht),\n  ];\n\n  const low = ends === \"both\" || ends === \"low\";\n  const high = ends === \"both\" || ends === \"high\";\n  // A treatment deeper than the solid would meet itself in the middle. Clamp rather than fold.\n  const reach = Math.max(\n    0,\n    Math.min(radius, Math.min(across, through) / 2 - 1e-4, (low && high ? along / 2 : along) - 1e-4),\n  );\n  const profile = edgeProfile(style, reach, segments);\n\n  const sections: { loop: Vector2[]; y: number }[] = [];\n  const push = (y: number, inset: number) => {\n    // `offsetLoop` at zero distance still walks and rebuilds the loop; skip it so an untreated section is\n    // literally the outline.\n    sections.push({ loop: inset < 1e-9 ? outline : offsetLoop(outline, -inset), y });\n  };\n\n  if (low) for (const level of profile) push(level.rise, level.inset);\n  else push(0, 0);\n\n  if (high) for (const level of [...profile].reverse()) push(along - level.rise, level.inset);\n  else push(along, 0);\n\n  const buffers = createGeometryBuffers();\n  const at = (p: Vector2, y: number): Vec3 => [p.x, y, p.y];\n\n  for (let s = 0; s < sections.length - 1; s++) {\n    const lower = sections[s]!;\n    const upper = sections[s + 1]!;\n    // Coincident sections happen wherever a profile starts or ends flat, and a zero-height band has no\n    // normal to compute from.\n    if (Math.abs(upper.y - lower.y) < 1e-12) continue;\n\n    for (let i = 0; i < lower.loop.length; i++) {\n      const j = (i + 1) % lower.loop.length;\n      pushQuad(\n        buffers,\n        [\n          at(lower.loop[j]!, lower.y),\n          at(lower.loop[i]!, lower.y),\n          at(upper.loop[i]!, upper.y),\n          at(upper.loop[j]!, upper.y),\n        ],\n        undefined,\n      );\n    }\n  }\n\n  const cap = (loop: Vector2[], y: number, normal: Vec3, reverse: boolean) => {\n    const order = reverse ? loop.map((_, i) => loop.length - 1 - i) : loop.map((_, i) => i);\n    for (let i = 1; i < loop.length - 1; i++) {\n      pushTriangle(\n        buffers,\n        [at(loop[order[0]!]!, y), at(loop[order[i]!]!, y), at(loop[order[i + 1]!]!, y)],\n        normal,\n      );\n    }\n  };\n  const first = sections[0]!;\n  const last = sections[sections.length - 1]!;\n  cap(first.loop, first.y, [0, -1, 0], false);\n  cap(last.loop, last.y, [0, 1, 0], true);\n\n  return toBufferGeometry(buffers);\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport { vesselShell } from \"../vessels/vesselProfiles\";\n\nexport interface MortarGeometryOptions {\n  /** Outer radius at the widest point. Defaults to `1.4`. */\n  radius?: number;\n  /** Overall height, base to rim. Defaults to `1.8`. */\n  height?: number;\n  /** Base (foot) radius. Defaults to `1`. */\n  baseRadius?: number;\n  /** Wall thickness — a mortar is thick-walled. Defaults to `0.45`. */\n  wallThickness?: number;\n  /** Circumference segments — the low-poly knob. Defaults to `16`. */\n  radialSegments?: number;\n}\n\n/**\n * Mortar — the thick-walled bowl a {@link PestleGeometry} grinds in.\n *\n * A lathe of {@link vesselShell} over the bowl silhouette: the profile climbs the outside, rolls over the\n * rim, and comes back DOWN a real inner wall to an inner floor above the base, closing a solid shell. So\n * the interior normals face inward and the material can be single-sided — `DoubleSide` is no longer\n * load-bearing. The outer silhouette is exposed as `.profile`.\n *\n * Local frame: base on Y=0, centered on X/Z.\n *\n * TODO: a `mortarAndPestle()` factory is the home for the assembled pair — seating the pestle head against\n * the bowl's interior is arithmetic against both profiles, which is a factory's job, not either geometry's.\n */\nexport class MortarGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor({\n    radius = 1.4,\n    height = 1.8,\n    baseRadius = 1,\n    wallThickness = 0.45,\n    radialSegments = 16,\n  }: MortarGeometryOptions = {}) {\n    const silhouette = [\n      new Vector2(0, 0),\n      new Vector2(baseRadius, 0),\n      new Vector2(radius * 0.857, height * 0.278), // base flare\n      new Vector2(radius, height * 0.833), // widest wall\n      new Vector2(radius * 0.929, height), // rim\n    ];\n    super(vesselShell(silhouette, { thickness: wallThickness, roundedRim: false }), radialSegments);\n    this.profile = silhouette;\n    this.radius = radius;\n    this.height = height;\n  }\n}\n","import { BufferGeometry, CylinderGeometry } from \"three\";\n\nexport interface PestleGeometryOptions {\n  /** Overall length, head to grip. Defaults to `1.5`. */\n  height?: number;\n  /** Radius of the grinding head — the fat end that meets the mortar. Defaults to `0.3`. */\n  headRadius?: number;\n  /** Radius of the grip — the end you hold. Defaults to `0.2`. */\n  gripRadius?: number;\n  /** Sides around the shaft. `6` reads as hand-cut stone. Defaults to `8`. */\n  radialSegments?: number;\n}\n\n/**\n * Pestle — the grinding tool that works a {@link MortarGeometry}.\n *\n * The **head is the wide end and it is at the bottom**, because that is how a pestle rests when it is\n * not in your hand: standing on the part that does the work. A pestle used in anger sits head-down in\n * the bowl, so this frame is also the one an assembly wants to place from.\n *\n * Local frame: head at Y=0, grip at `+height`, centered on X and Z.\n *\n * The assembled pair — a pestle seated and leaning in the bowl — belongs to a future\n * `mortarAndPestle()` factory, not to either geometry. See the TODO on {@link MortarGeometry}.\n *\n * @example\n * ```ts\n * const geometry = new PestleGeometry({ height: 1.5, headRadius: 0.3 });\n * const pestle = new Mesh(geometry, stoneMaterial);\n * scene.add(pestle);\n * ```\n */\nexport class PestleGeometry extends BufferGeometry {\n  readonly height: number;\n  readonly headRadius: number;\n  readonly gripRadius: number;\n\n  constructor({\n    height = 1.5,\n    headRadius = 0.3,\n    gripRadius = 0.2,\n    radialSegments = 8,\n  }: PestleGeometryOptions = {}) {\n    super();\n\n    this.height = height;\n    this.headRadius = headRadius;\n    this.gripRadius = gripRadius;\n\n    // TODO: THE HEAD SHOULD BE ROUNDED, not a flat-cut cone. A pestle grinds with a domed face that\n    // matches the bowl's curve; a flat disc only ever contacts at its rim. Two candidate approaches:\n    //\n    //   1. A LATHE PROFILE — the same tool MortarGeometry uses, and the one that generalizes. It also\n    //      gets the club silhouette for free: a slim grip swelling into the head, which is what a real\n    //      pestle looks like and what a cone cannot express at all.\n    //   2. A CAPSULE with the grip end cut flat — cheaper, but it only buys the dome, not the club,\n    //      and the flat cut has to be authored anyway.\n    //\n    // The lathe is the better answer for the same reason it is in the mortar: the profile IS the\n    // parameterization. Kept as a cone for now purely so the split from the old prefab changed nothing\n    // visually.\n    const shaft = new CylinderGeometry(gripRadius, headRadius, height, radialSegments);\n    shaft.translate(0, height / 2, 0);\n\n    this.copy(shaft);\n    shaft.dispose();\n  }\n}\n","import { BufferGeometry, CylinderGeometry, SphereGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\n/**\n * Group indices\n * 0: Base\n * 1: Coil\n */\nexport class TeslaCoilGeometry extends BufferGeometry {\n  constructor() {\n    super();\n\n    // Base geometry\n    const baseGeometry = new CylinderGeometry(0.5, 0.6, 0.3, 16);\n    baseGeometry.translate(0, 0.15, 0);\n\n    // Coil geometry\n    const coilGeometry = new CylinderGeometry(0.15, 0.15, 2, 12, 1, true);\n    coilGeometry.translate(0, 1.3, 0);\n    const coilTopGeometry = new SphereGeometry(0.3, 16, 16);\n    coilTopGeometry.translate(0, 2.4, 0);\n\n    this.copy(\n      mergeGeometries(\n        [baseGeometry, mergeGeometries([coilGeometry, coilTopGeometry]) as BufferGeometry],\n        true,\n      ) as BufferGeometry,\n    );\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\n\nexport interface AnnulusGeometryOptions {\n  /** Outer radius — center to the outer wall. Defaults to `1`. */\n  radius?: number;\n  /**\n   * Radius of the bore. Defaults to `0.5`.\n   *\n   * Clamped to stay strictly inside `radius`, so a bore wider than the ring cannot invert the wall. A value\n   * of `0` or less is clamped to a hair above zero rather than producing a solid disc — a solid disc is a\n   * different shape, and {@link PolygonGeometry} says it more plainly.\n   */\n  holeRadius?: number;\n  /** Thickness, along `+Y` from the resting plane. Defaults to `0.15`. */\n  depth?: number;\n  /**\n   * Sides around the ring. Defaults to `24`.\n   *\n   * **This is the low-poly dial.** `24` reads as smooth, `8` is visibly faceted, and `4` is a genuine square\n   * washer — the same construction throughout, so faceting is a parameter rather than a different shape.\n   */\n  sides?: number;\n  /** Rotation about `+Y` in radians. Defaults to `0`. Only visible on a low `sides` count. */\n  rotation?: number;\n}\n\n/**\n * A flat ring with a bore, square in section — a washer, a pipe collar, a well rim, a coin blank.\n *\n * Built as a **surface of revolution**: the four sides you see are four points of a rectangular profile spun\n * around `+Y`. That is why it costs so little — at 8 sides it is 64 triangles across 45 shared vertices,\n * where the same ring extruded from a 2D shape with a hole runs to 384 unshared vertices for no visual gain.\n *\n * Local frame: **rests on the `y = 0` plane**, occupying `+Y` up to `depth`, centered on the origin in XZ.\n * Ground contact, like the rest of the library — no translate needed to stand it on a floor.\n *\n * Material groups: **none** — one continuous surface, one material.\n *\n * **On shading:** the profile's corner vertices are shared between the faces that meet there, so under\n * *smooth* shading those edges soften. With `flatShading: true` — which this library uses throughout — normals\n * are computed per face and the edges are hard, which is what a washer wants. If you ever need smooth shading\n * elsewhere on the mesh, duplicate the corner profile points to split the rings; it costs 72 vertices instead\n * of 45.\n *\n * @example\n * ```typescript\n * const washer = new Mesh(new AnnulusGeometry({ radius: 0.5, holeRadius: 0.2, depth: 0.06 }), iron);\n * ```\n */\nexport class AnnulusGeometry extends LatheGeometry {\n  readonly radius: number;\n  /** The bore radius actually used, after clamping inside `radius`. */\n  readonly holeRadius: number;\n  readonly depth: number;\n  readonly sides: number;\n\n  constructor({\n    radius = 1,\n    holeRadius = 0.5,\n    depth = 0.15,\n    sides = 24,\n    rotation = 0,\n  }: AnnulusGeometryOptions = {}) {\n    const segments = Math.max(3, Math.round(sides));\n    // A bore at or past the outer wall would invert the section. Leave a sliver of material rather than\n    // silently collapsing to a tube with no wall.\n    const bore = Math.min(Math.max(holeRadius, radius * 1e-4), radius * 0.999);\n\n    // Inner→outer along the bottom, up the outer wall, back across the top, and closed by the repeat. The\n    // order is what orients the faces: reversed, every normal points inward and the ring renders inside-out.\n    // The repeated first point is what closes the section into four walls — drop it and the bore has no wall.\n    super(\n      [\n        new Vector2(bore, 0),\n        new Vector2(radius, 0),\n        new Vector2(radius, depth),\n        new Vector2(bore, depth),\n        new Vector2(bore, 0),\n      ],\n      segments,\n      rotation,\n    );\n\n    this.radius = radius;\n    this.holeRadius = bore;\n    this.depth = depth;\n    this.sides = segments;\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface BurstShapeOptions {\n  /** Number of burst points. Defaults to `5`. */\n  points?: number;\n  /** Inner vertex radius. Defaults to `0.5`. */\n  innerRadius?: number;\n  /** Outer vertex radius. Defaults to `1`. */\n  outerRadius?: number;\n  /** Rotation in radians from the resting state. Defaults to `0`. */\n  rotation?: number;\n}\n\n/**\n * Starburst profile — radial points joined by concave quadratic curves.\n *\n * Rests with a point up.\n */\nexport class BurstShape extends Shape {\n  constructor({ points = 5, innerRadius = 0.5, outerRadius = 1.0, rotation = 0 }: BurstShapeOptions = {}) {\n    super();\n\n    const step = (Math.PI * 2) / points;\n    const halfStep = step / 2;\n    const qtrStep = step / 4;\n    const start = Math.PI / 2 + rotation;\n\n    // Control points ride a circle secant to the inner radius, bowing each edge inward.\n    const controlRadius = innerRadius / Math.cos(qtrStep);\n\n    this.moveTo(Math.cos(start) * outerRadius, Math.sin(start) * outerRadius);\n\n    for (let n = 1; n <= points; ++n) {\n      const tip = start + step * n;\n\n      const inControl = tip - qtrStep * 3;\n      const valley = tip - halfStep;\n      this.quadraticCurveTo(\n        Math.cos(inControl) * controlRadius,\n        Math.sin(inControl) * controlRadius,\n        Math.cos(valley) * innerRadius,\n        Math.sin(valley) * innerRadius,\n      );\n\n      const outControl = tip - qtrStep;\n      this.quadraticCurveTo(\n        Math.cos(outControl) * controlRadius,\n        Math.sin(outControl) * controlRadius,\n        Math.cos(tip) * outerRadius,\n        Math.sin(tip) * outerRadius,\n      );\n    }\n\n    this.closePath();\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { BurstShape, type BurstShapeOptions } from \"../../shapes/BurstShape\";\n\nexport interface BurstGeometryOptions extends BurstShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded burst / starburst prism.\n */\nexport class BurstGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, ...shapeOptions }: BurstGeometryOptions = {}) {\n    super(new BurstShape(shapeOptions), { depth, bevelEnabled: false });\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { ClubShape, type ClubShapeOptions } from \"../../shapes/ClubShape\";\n\nexport interface ClubGeometryOptions extends ClubShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded club prism.\n */\nexport class ClubGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, ...shapeOptions }: ClubGeometryOptions = {}) {\n    super(new ClubShape(shapeOptions), { depth, bevelEnabled: false });\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface DiamondShapeOptions {\n  /** Overall scale factor. Defaults to `1`. */\n  size?: number;\n  /** Diamond width, point to point across. Defaults to `1.6`. */\n  width?: number;\n  /** Diamond height, point to point. Defaults to `2.2`. */\n  height?: number;\n  /**\n   * How far the four sides bow INWARD, as a fraction of the way to the center. Defaults to `0.15`.\n   *\n   * `0` is a plain rhombus with straight `/` sides. Above it the sides pull in toward the middle — the\n   * `)` curve a printed card diamond actually has, which keeps it from reading as a kite.\n   */\n  concavity?: number;\n}\n\n/**\n * Diamond profile — the fourth card suit, with gently concave sides.\n *\n * Four points (top, right, bottom, left) joined by quadratic curves that bow toward the center. A rhombus\n * with straight edges reads as flat and kite-like; the inward `)` sweep is what makes it a *card*\n * diamond. Drop `concavity` to `0` for the plain rhombus.\n *\n * Drawn counter-clockwise from the top point, centered on the origin — the family convention shared with\n * {@link SpadeShape}, {@link HeartShape}, {@link ClubShape}.\n */\nexport class DiamondShape extends Shape {\n  constructor({ size = 1, width = 1.6, height = 2.2, concavity = 0.15 }: DiamondShapeOptions = {}) {\n    super();\n\n    const hx = (width / 2) * size;\n    const hy = (height / 2) * size;\n    const k = 1 - concavity; // pulls each side's control point in toward the center\n\n    // Top → left → bottom → right, each edge's control point on the line to the center so the side bows in.\n    this.moveTo(0, hy);\n    this.quadraticCurveTo(-hx * 0.5 * k, hy * 0.5 * k, -hx, 0);\n    this.quadraticCurveTo(-hx * 0.5 * k, -hy * 0.5 * k, 0, -hy);\n    this.quadraticCurveTo(hx * 0.5 * k, -hy * 0.5 * k, hx, 0);\n    this.quadraticCurveTo(hx * 0.5 * k, hy * 0.5 * k, 0, hy);\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { DiamondShape, type DiamondShapeOptions } from \"../../shapes/DiamondShape\";\n\nexport interface DiamondGeometryOptions extends DiamondShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n  /** Curve resolution of the concave sides — the low-poly knob. Defaults to `16`. */\n  curveSegments?: number;\n}\n\n/**\n * Extruded diamond prism — the card suit. See {@link DiamondShape}.\n */\nexport class DiamondGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, curveSegments = 16, ...shapeOptions }: DiamondGeometryOptions = {}) {\n    super(new DiamondShape(shapeOptions), { depth, bevelEnabled: false, curveSegments });\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface HeartShapeOptions {\n  /** Overall scale factor. Defaults to `1`. */\n  size?: number;\n  /** Heart width across the lobes. Defaults to `1.8`. */\n  width?: number;\n  /** Heart height, lobe tops to tip. Defaults to `1.7`. */\n  height?: number;\n}\n\n/**\n * Heart profile — two bulbous circular lobes sweeping down to a sharp tip.\n *\n * **The lobes are real circles, not cubics pretending to be round.** A heart is two discs set side by\n * side, met at a cleft, with the outer edges sweeping in to a point — so that is exactly how it is drawn\n * here: two half-circle arcs of radius `width / 4`, joined tip-ward by concave curves. Faking the lobes\n * with beziers is what leaves them flat and lopsided.\n *\n * Because the lobe radius is set by `width` alone, **stretching `height` lengthens the point without\n * deflating the lobes** — a tall heart stays round on top, which a single width/height scale of a bezier\n * heart never manages.\n *\n * Centered on the origin; `width` / `height` are its real extents. The card suit, sibling to\n * {@link SpadeShape}, {@link ClubShape}, {@link DiamondShape}.\n */\nexport class HeartShape extends Shape {\n  constructor({ size = 1, width = 1.8, height = 1.7 }: HeartShapeOptions = {}) {\n    super();\n\n    const r = (width / 4) * size; // lobe radius — the widest point is ±2r\n    const cy = (height / 2) * size - r; // lobe centers, placed so the bbox centers on the origin\n    const tip = -(height / 2) * size;\n\n    // Cleft, around the left lobe to the widest point, sweep down to the tip, up the right, around the\n    // right lobe back to the cleft.\n    this.moveTo(0, cy);\n    this.absarc(-r, cy, r, 0, Math.PI, false);\n    this.bezierCurveTo(-2 * r, cy - r * 1.2, -r * 0.9, tip * 0.5, 0, tip);\n    this.bezierCurveTo(r * 0.9, tip * 0.5, 2 * r, cy - r * 1.2, 2 * r, cy);\n    this.absarc(r, cy, r, 0, Math.PI, false);\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { HeartShape, type HeartShapeOptions } from \"../../shapes/HeartShape\";\n\nexport interface HeartGeometryOptions extends HeartShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded heart prism.\n */\nexport class HeartGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, ...shapeOptions }: HeartGeometryOptions = {}) {\n    super(new HeartShape(shapeOptions), { depth, bevelEnabled: false });\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { SpadeShape, type SpadeShapeOptions } from \"../../shapes/SpadeShape\";\n\nexport interface SpadeGeometryOptions extends SpadeShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded spade prism.\n */\nexport class SpadeGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, ...shapeOptions }: SpadeGeometryOptions = {}) {\n    super(new SpadeShape(shapeOptions), { depth, bevelEnabled: false });\n  }\n}\n","import { Shape } from \"three\";\n\nexport interface StarShapeOptions {\n  /** Number of star points. Defaults to `5`. */\n  points?: number;\n  /** Inner vertex radius. Defaults to `0.5`. */\n  innerRadius?: number;\n  /** Outer vertex radius. Defaults to `1`. */\n  outerRadius?: number;\n  /** Rotation in radians from the resting state. Defaults to `0`. */\n  rotation?: number;\n}\n\n/**\n * Star profile — radial points joined by straight edges.\n *\n * Rests with a point up.\n */\nexport class StarShape extends Shape {\n  constructor({ points = 5, innerRadius = 0.5, outerRadius = 1.0, rotation = 0 }: StarShapeOptions = {}) {\n    super();\n\n    const step = (Math.PI * 2) / points;\n    const halfStep = step / 2;\n    const start = Math.PI / 2 + rotation;\n\n    this.moveTo(Math.cos(start) * outerRadius, Math.sin(start) * outerRadius);\n\n    for (let n = 1; n <= points; ++n) {\n      const valley = start + step * n - halfStep;\n      const tip = start + step * n;\n      this.lineTo(Math.cos(valley) * innerRadius, Math.sin(valley) * innerRadius);\n      this.lineTo(Math.cos(tip) * outerRadius, Math.sin(tip) * outerRadius);\n    }\n\n    this.closePath();\n  }\n}\n","import { ExtrudeGeometry } from \"three\";\nimport { StarShape, type StarShapeOptions } from \"../../shapes/StarShape\";\n\nexport interface StarGeometryOptions extends StarShapeOptions {\n  /** Extrusion depth. Defaults to `0.25`. */\n  depth?: number;\n}\n\n/**\n * Extruded star prism.\n */\nexport class StarGeometry extends ExtrudeGeometry {\n  constructor({ depth = 0.25, ...shapeOptions }: StarGeometryOptions = {}) {\n    super(new StarShape(shapeOptions), { depth, bevelEnabled: false });\n  }\n}\n","import { BufferGeometry, CylinderGeometry, SphereGeometry } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\n\n/**\n * Bone Geometry, a simple bone shape\n * @extends BufferGeometry\n *\n * @example\n * // Create a bone\n * const boneGeometry = new BoneGeometry();\n * const boneMaterial = new MeshStandardMaterial({ color: 0xffffff });\n * const bone = new Mesh(boneGeometry, boneMaterial);\n * scene.add(bone);\n */\nclass BoneGeometry extends BufferGeometry {\n  constructor(radiusTop = 0.1, radiusBottom = 0.1, height = 0.4, radialSegments = 8) {\n    super();\n\n    // Create the cylinder (shaft of the bone)\n    const cylinderGeometry = new CylinderGeometry(radiusTop * 0.6, radiusBottom * 0.6, height, radialSegments);\n    cylinderGeometry.translate(0, 0, 0);\n\n    // Create the spheres (ends of the bone)\n    const sphereGeometry = new SphereGeometry(radiusTop, radialSegments, radialSegments);\n    const topSphere1 = sphereGeometry.clone();\n    const topSphere2 = sphereGeometry.clone();\n    const bottomSphere1 = sphereGeometry.clone();\n    const bottomSphere2 = sphereGeometry.clone();\n\n    // Position the spheres at each end of the cylinder\n    topSphere1.translate(0, height / 2 + radiusTop * 0.6, -radiusTop * 0.6);\n    topSphere2.translate(0, height / 2 + radiusTop * 0.6, radiusTop * 0.6);\n    bottomSphere1.translate(0, -height / 2 - radiusBottom * 0.6, -radiusBottom * 0.6);\n    bottomSphere2.translate(0, -height / 2 - radiusBottom * 0.6, radiusBottom * 0.6);\n\n    // Merge the parts\n    this.copy(mergeGeometries([cylinderGeometry, topSphere1, topSphere2, bottomSphere1, bottomSphere2], false) as BufferGeometry);\n  }\n}\n\nexport { BoneGeometry };\n","import { BufferAttribute, BufferGeometry } from \"three\";\nimport { fbm2 } from \"../../utils/CoherentNoise\";\n\nexport interface TerrainMoundGeometryOptions {\n  /** Footprint radius (world units). Defaults to `8`. */\n  radius?: number;\n  /** Dome peak height at the center. Defaults to `1.2`. */\n  height?: number;\n  /** Concentric rings from center to rim. Defaults to `40`. */\n  radialSegments?: number;\n  /** Segments around the circumference. Defaults to `64`. */\n  angularSegments?: number;\n  /** Amplitude of the terrain relief added on top of the dome. Defaults to `0.5`. */\n  noiseHeight?: number;\n  /** Noise frequency — higher packs more, smaller bumps into the footprint. Defaults to `0.35`. */\n  noiseScale?: number;\n  /** fbm octaves (detail layers). Defaults to `4`. */\n  octaves?: number;\n  /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */\n  persistence?: number;\n  /** Normalized radius (0–1) where the rim begins fading relief to a flat edge. Defaults to `0.82`. */\n  rim?: number;\n  /** Seed for reproducible terrain. Defaults to `1`. */\n  seed?: number;\n}\n\n/**\n * Rounded terrain cap — a circular disc bulged into a gentle dome, then broken up\n * with coherent fbm noise so it reads as rolling terrain rather than a smooth lens.\n * The relief tapers to a clean circular rim seated on the Y=0 plane.\n *\n * Displacement is baked into real vertices (no shader), so shadows, raycasts, and\n * any physics collider derived from the mesh match exactly what's drawn — and it\n * renders identically on WebGL and WebGPU/TSL. Pair with a `flatShading` material\n * for a faceted low-poly look; the coherent noise keeps neighboring vertices moving\n * together, so faces never tear.\n *\n * Local frame: base on Y=0, peak toward +Y, centered on the origin.\n */\nexport class TerrainMoundGeometry extends BufferGeometry {\n  readonly radius: number;\n  readonly height: number;\n\n  constructor({\n    radius = 8,\n    height = 1.2,\n    radialSegments = 40,\n    angularSegments = 64,\n    noiseHeight = 0.5,\n    noiseScale = 0.35,\n    octaves = 4,\n    persistence = 0.5,\n    rim = 0.82,\n    seed = 1,\n  }: TerrainMoundGeometryOptions = {}) {\n    super();\n\n    this.radius = radius;\n    this.height = height;\n\n    const rings = Math.max(1, Math.round(radialSegments));\n    const segs = Math.max(3, Math.round(angularSegments));\n\n    // Height at normalized radius r ∈ [0, 1]: raised cosine — 1 at center, 0 at the\n    // rim, with zero slope at both ends so the peak is rounded and the edge melts\n    // flush into Y=0. Relief is added on top and faded out over the rim band.\n    const domeAt = (r: number) => 0.5 + 0.5 * Math.cos(Math.PI * r);\n    const rimFade = (r: number) => {\n      if (r <= rim) return 1;\n      const t = (r - rim) / (1 - rim);\n      return 1 - t * t * (3 - 2 * t);\n    };\n    const yAt = (r: number, x: number, z: number) =>\n      domeAt(r) * height + fbm2(x * noiseScale, z * noiseScale, seed, octaves, persistence) * noiseHeight * rimFade(r);\n\n    const vertexCount = 1 + rings * segs;\n    const positions = new Float32Array(vertexCount * 3);\n    const uvs = new Float32Array(vertexCount * 2);\n\n    // Center vertex.\n    positions[1] = yAt(0, 0, 0);\n    uvs[0] = 0.5;\n    uvs[1] = 0.5;\n\n    // Ring vertices: ring j ∈ [1, rings], each with `segs` points.\n    for (let j = 1; j <= rings; j++) {\n      const r = j / rings;\n      const ringRadius = r * radius;\n      for (let k = 0; k < segs; k++) {\n        const angle = (k / segs) * Math.PI * 2;\n        const x = Math.cos(angle) * ringRadius;\n        const z = Math.sin(angle) * ringRadius;\n        const vi = 1 + (j - 1) * segs + k;\n        positions[vi * 3] = x;\n        positions[vi * 3 + 1] = yAt(r, x, z);\n        positions[vi * 3 + 2] = z;\n        uvs[vi * 2] = x / (2 * radius) + 0.5;\n        uvs[vi * 2 + 1] = z / (2 * radius) + 0.5;\n      }\n    }\n\n    const ringVertex = (j: number, k: number) => 1 + (j - 1) * segs + (k % segs);\n    const indices: number[] = [];\n\n    // Center fan (center → ring 1). Wound so the surface faces +Y.\n    for (let k = 0; k < segs; k++) {\n      indices.push(0, ringVertex(1, k + 1), ringVertex(1, k));\n    }\n\n    // Concentric quad strips between successive rings.\n    for (let j = 1; j < rings; j++) {\n      for (let k = 0; k < segs; k++) {\n        const inner0 = ringVertex(j, k);\n        const inner1 = ringVertex(j, k + 1);\n        const outer0 = ringVertex(j + 1, k);\n        const outer1 = ringVertex(j + 1, k + 1);\n        indices.push(inner0, outer1, outer0);\n        indices.push(inner0, inner1, outer1);\n      }\n    }\n\n    this.setAttribute(\"position\", new BufferAttribute(positions, 3));\n    this.setAttribute(\"uv\", new BufferAttribute(uvs, 2));\n    this.setIndex(indices);\n    this.computeVertexNormals();\n  }\n}\n","import { BufferAttribute, BufferGeometry } from \"three\";\nimport { fbm2 } from \"../../utils/CoherentNoise\";\n\nexport interface TerrainPlaneGeometryOptions {\n  /** Extent along X (world units). Defaults to `16`. */\n  width?: number;\n  /** Extent along Z (world units). Defaults to `16`. */\n  depth?: number;\n  /** Grid segments along X. Defaults to `48`. */\n  widthSegments?: number;\n  /** Grid segments along Z. Defaults to `48`. */\n  depthSegments?: number;\n  /** Amplitude of the terrain relief (world units, ±). Defaults to `0.8`. */\n  noiseHeight?: number;\n  /** Noise frequency — higher packs more, smaller features into the footprint. Defaults to `0.35`. */\n  noiseScale?: number;\n  /** fbm octaves (detail layers). Defaults to `4`. */\n  octaves?: number;\n  /** fbm gain per octave (0–1); lower is smoother, higher is rougher. Defaults to `0.5`. */\n  persistence?: number;\n  /**\n   * Border band (0–1 fraction of the half-extent) over which relief fades to a flat\n   * edge at Y=0. `0` leaves a raw, seamless heightfield (tileable); higher values\n   * seat the slab like a contained diorama patch. Defaults to `0`.\n   */\n  edgeFalloff?: number;\n  /** Seed for reproducible terrain. Defaults to `1`. */\n  seed?: number;\n}\n\n/**\n * Rectangular terrain patch — a flat grid displaced on Y by the shared coherent fbm\n * sampler ({@link fbm2}). The rectangular counterpart to {@link TerrainMoundGeometry}:\n * same noise strategy, grid layout instead of a radial disc.\n *\n * A pure heightfield (Y is single-valued per XZ) so faces can never fold, and it's\n * baked into real vertices — shadows, raycasts, and physics colliders match what's\n * drawn, on WebGL and WebGPU/TSL alike. Pair with a `flatShading` material for a\n * faceted low-poly look. Leave `edgeFalloff` at `0` for a tileable field; raise it to\n * seat the edges flat at Y=0.\n *\n * Local frame: base grid on Y=0, relief toward ±Y, centered on the origin.\n */\nexport class TerrainPlaneGeometry extends BufferGeometry {\n  readonly width: number;\n  readonly depth: number;\n\n  constructor({\n    width = 16,\n    depth = 16,\n    widthSegments = 48,\n    depthSegments = 48,\n    noiseHeight = 0.8,\n    noiseScale = 0.35,\n    octaves = 4,\n    persistence = 0.5,\n    edgeFalloff = 0,\n    seed = 1,\n  }: TerrainPlaneGeometryOptions = {}) {\n    super();\n\n    this.width = width;\n    this.depth = depth;\n\n    const cols = Math.max(1, Math.round(widthSegments));\n    const rows = Math.max(1, Math.round(depthSegments));\n    const halfW = width / 2;\n    const halfD = depth / 2;\n\n    // Fade relief to zero within `edgeFalloff` of the border so the rim seats flat.\n    const fade = Math.min(Math.max(edgeFalloff, 0), 1);\n    const edgeFade = (x: number, z: number) => {\n      if (fade <= 0) return 1;\n      const nx = Math.min(x + halfW, halfW - x) / halfW; // 0 at X edges → 1 at center line\n      const nz = Math.min(z + halfD, halfD - z) / halfD;\n      const d = Math.min(nx, nz);\n      if (d >= fade) return 1;\n      const t = d / fade;\n      return t * t * (3 - 2 * t);\n    };\n\n    const vertexCount = (cols + 1) * (rows + 1);\n    const positions = new Float32Array(vertexCount * 3);\n    const uvs = new Float32Array(vertexCount * 2);\n\n    for (let iz = 0; iz <= rows; iz++) {\n      const z = -halfD + (iz / rows) * depth;\n      for (let ix = 0; ix <= cols; ix++) {\n        const x = -halfW + (ix / cols) * width;\n        const y =\n          fbm2(x * noiseScale, z * noiseScale, seed, octaves, persistence) * noiseHeight * edgeFade(x, z);\n        const vi = iz * (cols + 1) + ix;\n        positions[vi * 3] = x;\n        positions[vi * 3 + 1] = y;\n        positions[vi * 3 + 2] = z;\n        uvs[vi * 2] = ix / cols;\n        uvs[vi * 2 + 1] = iz / rows;\n      }\n    }\n\n    const indices: number[] = [];\n    const vertex = (ix: number, iz: number) => iz * (cols + 1) + ix;\n    for (let iz = 0; iz < rows; iz++) {\n      for (let ix = 0; ix < cols; ix++) {\n        const a = vertex(ix, iz);\n        const b = vertex(ix + 1, iz);\n        const c = vertex(ix, iz + 1);\n        const d = vertex(ix + 1, iz + 1);\n        // Wound so the surface faces +Y.\n        indices.push(a, c, b);\n        indices.push(b, c, d);\n      }\n    }\n\n    this.setAttribute(\"position\", new BufferAttribute(positions, 3));\n    this.setAttribute(\"uv\", new BufferAttribute(uvs, 2));\n    this.setIndex(indices);\n    this.computeVertexNormals();\n  }\n}\n","import { BufferGeometry, Vector3 } from \"three\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type Vec2,\n  type Vec3,\n} from \"../mesh/GeometryBuffers\";\n\nexport interface SurfaceGridOptions {\n  /** Reverse winding and normals; u along +X and v along +Y ordinarily faces +Z. */\n  flip?: boolean;\n}\n\n/**\n * Skin rectangular grid[v][u] without wrapping, using whole-sheet UVs.\n * Fewer than two rows or columns returns empty geometry; otherwise ragged rows throw.\n *\n * ```ts\n * // Any f(u, v). Here, a hanging sheet.\n * const grid = Array.from({ length: rows + 1 }, (_, j) =>\n *   Array.from({ length: columns + 1 }, (_, i) => surfacePoint(i / columns, j / rows)),\n * );\n * const geometry = surfaceGrid(grid);\n * ```\n */\nexport function surfaceGrid(grid: Vector3[][], { flip = false }: SurfaceGridOptions = {}): BufferGeometry {\n  const buffers = createGeometryBuffers();\n  const rows = grid.length;\n  if (rows < 2) return toBufferGeometry(buffers);\n\n  const columns = grid[0]!.length;\n  if (columns < 2) return toBufferGeometry(buffers);\n\n  for (let j = 0; j < rows; j++) {\n    if (grid[j]!.length !== columns) {\n      throw new Error(\n        `surfaceGrid() requires a rectangular grid: row 0 has ${columns} points, row ${j} has ${grid[j]!.length}.`,\n      );\n    }\n  }\n\n  const xyz = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n  const edge = new Vector3();\n  const other = new Vector3();\n  const normal = new Vector3();\n\n  for (let j = 0; j < rows - 1; j++) {\n    for (let i = 0; i < columns - 1; i++) {\n      const a = grid[j]![i]!;\n      const b = grid[j]![i + 1]!;\n      const c = grid[j + 1]![i + 1]!;\n      const d = grid[j + 1]![i]!;\n\n      // Diagonal normal (c − a) × (d − b) remains usable when three corners are collinear.\n      normal.copy(edge.subVectors(c, a).cross(other.subVectors(d, b)));\n\n      // Genuinely no area: the tip of the pinch, where every row has arrived at one point.\n      if (normal.lengthSq() < 1e-20) continue;\n      normal.normalize();\n      if (flip) normal.negate();\n\n      const face: Vec3 = [normal.x, normal.y, normal.z];\n\n      const u0 = i / (columns - 1);\n      const u1 = (i + 1) / (columns - 1);\n      const v0 = j / (rows - 1);\n      const v1 = (j + 1) / (rows - 1);\n\n      // Emit one triangle when either u-boundary edge collapses.\n      const ad = a.distanceToSquared(d) < 1e-14;\n      const bc = b.distanceToSquared(c) < 1e-14;\n\n      if (ad) {\n        const corners: [Vec3, Vec3, Vec3] = flip ? [xyz(a), xyz(c), xyz(b)] : [xyz(a), xyz(b), xyz(c)];\n        const uvs: [Vec2, Vec2, Vec2] = flip\n          ? [[u0, v0], [u1, v1], [u1, v0]]\n          : [[u0, v0], [u1, v0], [u1, v1]];\n        pushTriangle(buffers, corners, face, uvs);\n      } else if (bc) {\n        const corners: [Vec3, Vec3, Vec3] = flip ? [xyz(a), xyz(d), xyz(b)] : [xyz(a), xyz(b), xyz(d)];\n        const uvs: [Vec2, Vec2, Vec2] = flip\n          ? [[u0, v0], [u0, v1], [u1, v0]]\n          : [[u0, v0], [u1, v0], [u0, v1]];\n        pushTriangle(buffers, corners, face, uvs);\n      } else {\n        const corners: [Vec3, Vec3, Vec3, Vec3] = flip\n          ? [xyz(a), xyz(d), xyz(c), xyz(b)]\n          : [xyz(a), xyz(b), xyz(c), xyz(d)];\n        const uvs: [Vec2, Vec2, Vec2, Vec2] = flip\n          ? [[u0, v0], [u0, v1], [u1, v1], [u1, v0]]\n          : [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];\n        pushQuad(buffers, corners, face, uvs);\n      }\n    }\n  }\n\n  return toBufferGeometry(buffers);\n}\n","/**\n * The plan section of a pleat, normalized to ±1, as a function of phase.\n *\n * `phase` runs 0 → 1 across one pleat and is not pre-wrapped, so an implementation takes its own\n * fractional part. Every pleat plan really is a function of one number — a knife, a sine, a box, a\n * pinch — which is what makes this a legitimate interface rather than an abstraction fitted to whatever\n * was convenient. It is the same argument that earns `Easing` its type and denies one to a surface,\n * whose real cases need state a pointwise function cannot carry.\n */\nexport type PleatShape = (phase: number) => number;\n\n/** Arc length of one pleat relative to its projected width — the fullness a given amplitude buys. */\nfunction arcRatio(shape: PleatShape, amplitude: number, pitch: number, samples = 240): number {\n  let length = 0;\n  let previousX = 0;\n  let previousZ = shape(0) * amplitude;\n\n  for (let i = 1; i <= samples; i++) {\n    const t = i / samples;\n    const x = t * pitch;\n    const z = shape(t) * amplitude;\n    length += Math.hypot(x - previousX, z - previousZ);\n    previousX = x;\n    previousZ = z;\n  }\n\n  return length / pitch;\n}\n\n/**\n * Amplitude for a required fullness — the inversion every pleated thing needs.\n *\n * **Fullness is the input and amplitude is the output**, which is the whole relationship: cloth is cut\n * once, and how deep its folds run is whatever fitting that fixed length into the available width\n * demands. Narrow the width and the folds deepen; widen it and they let themselves out. Nothing has to\n * push any fabric.\n *\n * Bisected, because a sine plan's arc length is an elliptic integral with no elementary inverse. The\n * ratio is monotonic in amplitude, so bisection is exact to machine precision in 60 steps. A triangular\n * plan does invert in closed form as `(pitch/4)·√(f²−1)` and is solved numerically anyway, so there is\n * one path — verified against the formula to 1.1e-16 rather than assumed.\n *\n * Solved against the CONTINUOUS arc length and never against a built polyline. Solving against the\n * polyline would make the fabric come out exact and would make a segment count move the SILHOUETTE,\n * since a coarser sampling would need a deeper wave to reach the same length.\n */\nexport function solveAmplitude(shape: PleatShape, fullness: number, pitch: number): number {\n  if (fullness <= 1.0000001) return 0;\n\n  let low = 0;\n  // THE SEARCH BOUND HAS TO SCALE WITH WHAT IS BEING ASKED, or the solve fails silently.\n  //\n  // A fixed ceiling of `pitch * 4` looks generous and is not: a triangular plan needs\n  // `A = (pitch/4)·√(f²−1)`, which is about `14.5 · pitch` at 58× fullness. Past the ceiling the\n  // bisection simply converges on the ceiling and returns it, and the caller gets a panel holding a\n  // third of its fabric with nothing reporting a problem. Measured at 31%, 22% and 18% before this.\n  //\n  // `pitch * fullness` is comfortably above the requirement for every shape here — the triangle is the\n  // hungriest, at roughly a quarter of it — and bisection over a wider range costs nothing, since 60\n  // halvings reach machine precision from any bound.\n  let high = pitch * Math.max(4, fullness);\n  for (let i = 0; i < 60; i++) {\n    const mid = (low + high) / 2;\n    if (arcRatio(shape, mid, pitch) < fullness) low = mid;\n    else high = mid;\n  }\n\n  return (low + high) / 2;\n}\n\n/**\n * Samples per pleat, rounded up to a MULTIPLE OF FOUR so every extremum lands on the grid.\n *\n * Four, not one: a knife wave turns at phases 0 and 0.5, a sine at 0.25 and 0.75, and only a multiple of\n * four puts a sample on all of them. A whole number per pleat is not enough — 40 requested across 6\n * pleats gives 7, which is odd, steps over the crease and clips the fold depth by about 11%. Snapped,\n * the silhouette is identical to nine digits at every count, which is what `segments` changes\n * tessellation, never silhouette demands of a shape with features at known parameters.\n */\nexport function samplesPerPleat(requested: number, pleats: number): number {\n  return Math.max(1, Math.ceil(Math.max(4, Math.floor(requested)) / pleats / 4)) * 4;\n}\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { surfaceGrid } from \"../../modeling/surfaces/SurfaceGrid\";\nimport { samplesPerPleat, solveAmplitude, type PleatShape } from \"./pleatWave\";\n\n/**\n * The plan section of the accordion.\n *\n * - `knife` — a triangle. Every fold leans the same way, which is what the name says. Constant slope,\n *   so its arc length is proportional to its projected width.\n * - `sine` — a soft, rounded pleat.\n */\nexport type CascadePleat = \"knife\" | \"sine\";\n\nexport interface CascadeGeometryOptions {\n  /**\n   * Width of the flat cloth before it is folded. Defaults to `2.4`.\n   *\n   * **This is the conserved quantity and the reason nothing else has to be told what to do.** The cloth\n   * is cut once; every fold depth below is solved so the accordion's arc length comes back to it.\n   */\n  fabricWidth?: number;\n  /** Finished width where it is stapled to the board. Defaults to `0.34`. */\n  topWidth?: number;\n  /**\n   * Finished width at the hem. Defaults to `0.62`.\n   *\n   * Wider than {@link topWidth} is the flare. Since the cloth is fixed, opening the flare LOWERS the\n   * local fullness and the folds shallow out on their own — you never set a fold depth.\n   */\n  bottomWidth?: number;\n  /** Number of pleats. Defaults to `6`. Each forward-facing crease becomes one step of the hem. */\n  pleats?: number;\n  /** The plan section. Defaults to `\"knife\"`. See {@link CascadePleat}. */\n  pleat?: CascadePleat;\n  /** Drop at the short (inner) edge. Defaults to `0.55`. */\n  shortDrop?: number;\n  /** Drop at the long (outer) tail. Defaults to `1.8`. The bias is the difference between the two. */\n  longDrop?: number;\n  /**\n   * How far the stack tips into a cone — inner pleats tucking back, leading edge throwing forward.\n   * Defaults to `0.06`.\n   *\n   * Zero at the board and growing with the drop, because at the board the cloth is stapled flat to a\n   * straight piece of timber and cannot lean. Applied at full strength throughout, it shears the whole\n   * panel and its top edge tilts about 10° away from the board it is fixed to.\n   */\n  roll?: number;\n  /**\n   * Samples across the width. Defaults to `240`.\n   *\n   * **Rounded UP so each pleat gets a multiple of four samples**, which puts one on every extremum of\n   * 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\n   * known parameters, and a sampling that steps over them clips the fold rather than approximating it:\n   * unsnapped, the fold depth wanders with the phase instead of holding still. Snapped, it is identical\n   * to nine digits at every count, which is what `segments` changes tessellation, never silhouette\n   * actually demands of a shape with features in it.\n   */\n  widthSegments?: number;\n  /** Samples down the drop. Defaults to `40`. */\n  heightSegments?: number;\n}\n\n/** The accordion's plan section, normalized to ±1. The one part that is this shape's own vocabulary. */\nfunction planShape(pleat: CascadePleat): PleatShape {\n  if (pleat === \"sine\") return (phase) => Math.sin((phase - Math.floor(phase)) * Math.PI * 2);\n  return (phase) => {\n    const t = phase - Math.floor(phase);\n    return t < 0.5 ? 4 * t - 1 : 3 - 4 * t;\n  };\n}\n\n/**\n * A cascade — the pleated tail hanging beside a swag. Also called a jabot.\n *\n * An accordion of cloth hung vertically and trimmed along ONE STRAIGHT DIAGONAL. The sawtooth hem is not\n * modelled and no step is placed: only forward-facing creases show, each meets the diagonal at a\n * different place along the cloth, and so each terminates at a different height. There are exactly as\n * many steps as there are pleats, because a step IS a fold.\n *\n * The bias is straight in the FABRIC — in arc length along the pleat wave — because a jabot is cut flat\n * and folded afterwards. Cutting straight in the projected width instead moves the hem by under 1% of\n * the bias and does not change the treads at all, since the deviation between the two is periodic with\n * the pleats and every crease samples it at the same phase.\n *\n * **Origin is the board**, at `y = 0`, with the cloth hanging to negative Y — the same convention as\n * {@link SwagGeometry}. A hanging thing is anchored where it is fixed.\n *\n * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`.\n *\n * @example\n * ```ts\n * const cascade = new Mesh(\n *   new CascadeGeometry({ pleats: 6, longDrop: 1.8 }),\n *   new MeshStandardMaterial({ color: 0x1f5b45, roughness: 0.95, side: DoubleSide, flatShading: true }),\n * );\n * ```\n */\nexport class CascadeGeometry extends BufferGeometry {\n  constructor({\n    fabricWidth = 2.4,\n    topWidth = 0.34,\n    bottomWidth = 0.62,\n    pleats = 6,\n    pleat = \"knife\",\n    shortDrop = 0.55,\n    longDrop = 1.8,\n    roll = 0.06,\n    widthSegments = 240,\n    heightSegments = 40,\n  }: CascadeGeometryOptions = {}) {\n    super();\n\n    const across = samplesPerPleat(widthSegments, pleats) * pleats;\n    const shape = planShape(pleat);\n    const down = Math.max(1, Math.floor(heightSegments));\n    const grid: Vector3[][] = [];\n\n    for (let j = 0; j <= down; j++) {\n      const v = j / down;\n      const width = topWidth + v * (bottomWidth - topWidth);\n      const pitch = width / pleats;\n      // Same cloth, more width: the local fullness is whatever the flare leaves it.\n      const amplitude = solveAmplitude(shape, fabricWidth / Math.max(1e-6, width), pitch);\n\n      // Arc length along this tier, so the diagonal can be straight in the CLOTH rather than in the\n      // picture of it. An accordion compresses cloth unevenly, so the two are not the same parameter.\n      const arc: number[] = [0];\n      let previous = new Vector3(0, 0, shape(0) * amplitude);\n      for (let i = 1; i <= across; i++) {\n        const u = i / across;\n        const point = new Vector3(u * width, 0, shape(u * pleats) * amplitude);\n        arc.push(arc[i - 1]! + point.distanceTo(previous));\n        previous = point;\n      }\n      const total = arc[across]!;\n\n      const row: Vector3[] = [];\n      for (let i = 0; i <= across; i++) {\n        const u = i / across;\n        const along = total < 1e-12 ? u : arc[i]! / total;\n        const drop = shortDrop + along * (longDrop - shortDrop);\n\n        row.push(\n          new Vector3(\n            u * width - width / 2,\n            -v * drop,\n            // `roll` carries `v`: at the board the cloth is stapled flat and cannot lean.\n            shape(u * pleats) * amplitude + roll * (u - 0.5) * v,\n          ),\n        );\n      }\n\n      grid.push(row);\n    }\n\n    const geometry = surfaceGrid(grid);\n    this.setIndex(geometry.getIndex());\n    for (const name of Object.keys(geometry.attributes)) {\n      this.setAttribute(name, geometry.attributes[name]!);\n    }\n    geometry.dispose();\n  }\n}\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { surfaceGrid } from \"../../modeling/surfaces/SurfaceGrid\";\nimport { samplesPerPleat, solveAmplitude, type PleatShape } from \"./pleatWave\";\n\n/**\n * The heading — how the fullness is taken up where the panel meets the rod.\n *\n * - `pinch` — the French pleat. Flat spans lying BACK, with the fullness pinched into tight groups that\n *   stand proud toward the room. The flat is the feature: it is what makes a pinch pleat read as\n *   tailored rather than gathered.\n * - `pencil` — continuous rounded gathers, very close to a true sinusoid in plan.\n * - `box` — flat front and back, square in plan, with the folds turned at the corners.\n * - `knife` — every fold leaning one way. Triangular in plan.\n */\nexport type CurtainPleat = \"pinch\" | \"pencil\" | \"box\" | \"knife\";\n\nexport interface CurtainPanelGeometryOptions {\n  /** Finished width of the panel at the rod. Defaults to `1.4`. */\n  width?: number;\n  /** How far the panel drops from the rod. Defaults to `3.2`. */\n  drop?: number;\n  /**\n   * Fabric width ÷ rod width. Defaults to `2.5`.\n   *\n   * **The design input the trade actually uses** — 2× is skimpy, 2.5× standard, 3× luxurious. Fold depth\n   * is not an option anywhere on this class because it is an OUTPUT of this: the cloth is a fixed length,\n   * and how deep its folds run is whatever fitting that length into the available width demands.\n   */\n  fullness?: number;\n  /** Number of pleats across the heading. Defaults to `9`. */\n  pleats?: number;\n  /** The heading. Defaults to `\"pinch\"`. See {@link CurtainPleat}. */\n  pleat?: CurtainPleat;\n  /**\n   * How far the plan section relaxes toward a sine as it descends. Defaults to `0.55`.\n   *\n   * A heading is stitched and holds whatever shape the pleat gives it; a hem is free, and free cloth\n   * takes the smooth shape. At `0` the panel keeps its heading's crispness all the way to the floor,\n   * which reads immediately as wrong.\n   */\n  relax?: number;\n  /**\n   * Where the tieback cinches, `0` at the rod and `1` at the hem. Defaults to `0.62`.\n   */\n  tiebackHeight?: number;\n  /**\n   * How far the leading edge is drawn in AT THE ROD, as a fraction of {@link width}. Defaults to `0`.\n   *\n   * Zero puts the panel at full width where it is hung, so a pair very nearly meets in the middle. Raise\n   * it to start the pair already parted at the top.\n   */\n  topPull?: number;\n  /**\n   * How far the leading edge is drawn in AT THE TIEBACK. Defaults to `0.42`.\n   *\n   * **This is a constraint on the panel's WIDTH, not a force on the cloth.** Narrowing the span the fixed\n   * fabric has to cross raises the local fullness, and the folds deepen because they cannot do anything\n   * else. Nothing here pushes any fabric sideways.\n   */\n  pull?: number;\n  /**\n   * How far the leading edge is drawn in AT THE HEM. Defaults to `0.12`.\n   *\n   * This is the dial that decides what a panel does BELOW its tieback, and the useful answers span its\n   * whole range. Set it to `0` and the panel flares fully back out to its rod width — the widest\n   * hourglass, which is a heavy curtain with plenty of material in the base. Set it equal to\n   * {@link pull} and the leading edge falls straight from the tie, a vertical drop parallel to the outer\n   * edge, which is what a thin curtain does because it has no material to flare with. Above `pull` it\n   * keeps narrowing, tapering to the floor.\n   *\n   * The default sits a little off zero deliberately. A tieback holds some cloth back permanently, so a\n   * real panel rarely recovers its full width at the floor — and a default of exactly `0` would leave\n   * the hem looking like a fixed consequence of the tie rather than something the caller controls.\n   */\n  hemPull?: number;\n  /**\n   * How much the OUTER edge follows the leading edge, `0` to `1`. Defaults to `0`.\n   *\n   * At `0` the outer edge is pinned at {@link width} and never moves, which is right for a rod-hung\n   * curtain: that edge is the RETURN, wrapping back to the wall where a bracket holds it. One panel is\n   * then an L — one edge curved, one straight — and the hourglass you see in photographs belongs to the\n   * PAIR, each half contributing one curve.\n   *\n   * At `1` the outer edge draws in by exactly as much as the leading edge, and a single panel becomes a\n   * symmetric hourglass on its own. Between the two it follows partway. This is not a stylistic dial: a\n   * panel with no return is a real thing — a stage curtain cinched at its middle, a portière in a\n   * doorway with no wall to return to, a free-hanging banner tied in the centre — and none of those can\n   * be built with the outer edge pinned.\n   *\n   * The two draws are scaled down together if they would close the panel, so the ratio between them\n   * survives and the cloth stays centred rather than one edge overrunning the other.\n   */\n  outerPull?: number;\n  /**\n   * Mirror the panel about `x = 0`, for the other half of a pair. Defaults to `false`.\n   *\n   * **Use this rather than rotating or negatively scaling a second copy in the scene.** Turning a panel\n   * through 180° about Y maps `(x, y, z)` to `(−x, y, −z)`, which flips the DEPTH as well as the width —\n   * so one panel's pleats face the room and the other's face the wall. On a heading that is symmetric\n   * about zero, `pencil`, `box` and `knife`, that is invisible — measured, their depth ranges really are\n   * symmetric, so rotating happened to be harmless. On `pinch` it is not: its section runs from −1 to\n   * +1.9, so negating the depth buries its pleats behind the flats and the pair stops matching.\n   * A negative scale would keep the depth but invert the winding instead.\n   *\n   * This reflects only `x` and re-winds the surface to suit, so both halves of a pair present the same\n   * face to the room.\n   */\n  mirror?: boolean;\n  /**\n   * How the leading edge curves between its three anchors. Defaults to `0.7`.\n   *\n   * `0` runs straight lines from rod to tie to hem, giving a hard V at the tieback. `1` eases into and\n   * out of every anchor, which bows each half. The default leans toward the curve, because hung cloth\n   * bows rather than creasing into a straight line between its anchors.\n   */\n  slack?: number;\n  /**\n   * Samples across the width. Defaults to `160`.\n   *\n   * Rounded up so each pleat gets a multiple of four, putting a sample on every extremum of the wave.\n   * Tessellation only — the silhouette does not move with it.\n   */\n  widthSegments?: number;\n  /** Samples down the drop. Defaults to `40`. */\n  heightSegments?: number;\n}\n\n/** The pleat's plan section — the one part that is this shape's own vocabulary. */\nfunction planShape(pleat: CurtainPleat): PleatShape {\n  return (phase) => {\n    const t = phase - Math.floor(phase);\n\n    if (pleat === \"pencil\") return Math.sin(t * Math.PI * 2);\n    if (pleat === \"knife\") return t < 0.5 ? 4 * t - 1 : 3 - 4 * t;\n\n    if (pleat === \"box\") {\n      // Softened at the turns, because a real box pleat is folded cloth and has a finite radius there.\n      const k = 0.06;\n      if (t < k) return t / k;\n      if (t < 0.5 - k) return 1;\n      if (t < 0.5 + k) return -(t - 0.5) / k;\n      if (t < 1 - k) return -1;\n      return (t - 1) / k;\n    }\n\n    // PINCH: the flat span sits BACK, and the pleat group projects FORWARD into the room.\n    //\n    // That direction is the whole character of the heading and it is easy to get backwards. A pinch\n    // pleat is made by folding cloth into a group and stitching it a few inches below the top; the group\n    // then stands proud of the curtain toward the room, while the fabric between two pleats bows away\n    // behind it. So the section is `−1` across the flat and reaches `+1.9` at the pleat, not the other\n    // way round. Built inverted it reads as wide flat panels with the pinches tucked behind them, which\n    // is a heading nobody makes.\n    //\n    // The flat is exactly HALF the pitch, and the half is deliberate rather than a round number. The\n    // excursion's extremum sits at its own midpoint, so a flat of 0.55 — which is what the study uses —\n    // puts it at phase 0.775, or 3.1 quarters, which no multiple-of-four sampling ever lands on. At 0.5\n    // it falls on 0.75 and the pinch obeys the same silhouette guarantee the other three headings do:\n    // measured spread 3.16e-3 before, exactly 0 after.\n    if (t < 0.5) return -1;\n    const s = (t - 0.5) / 0.5;\n    return (1 - Math.cos(s * Math.PI * 2)) + Math.sin(s * Math.PI) * 0.9 - 1;\n  };\n}\n\n/** Blend between straight and eased, so one dial covers a hard V and a bowed curve. */\nfunction ease(t: number, slack: number): number {\n  return t * (1 - slack) + t * t * (3 - 2 * t) * slack;\n}\n\n/**\n * A curtain panel — a pleat wave lofted downward, with a tieback cinching it.\n *\n * Look DOWN on a hanging panel and its plan section is a periodic wave; the whole thing is that wave\n * carried down the drop. It is a LOFT and not a sweep, because the section does not keep its shape: its\n * amplitude is re-solved at every height as the leading edge moves, and its profile relaxes toward a\n * sine as the cloth gets further from the stitched heading.\n *\n * **The fabric length is the conserved quantity and everything else follows from it.** The panel is cut\n * once at `fullness × width`, and no dial here is allowed to change that. So when the tieback narrows\n * the span, the local fullness rises — same cloth, less width — and the folds deepen on their own. That\n * is why there is no fold-depth option: it is an output.\n *\n * The leading edge runs through THREE anchors — at the rod, at the tieback, at the hem — which is what\n * lets a panel drop vertically from its tie or flare back out into an hourglass without either being a\n * special case. See {@link CurtainPanelGeometryOptions.hemPull}.\n *\n * By default only that edge moves; the outer one is the RETURN and stays pinned against the wall, so a\n * single panel is an L and the hourglass belongs to the pair. {@link CurtainPanelGeometryOptions.outerPull}\n * releases the return, for the panels that genuinely have none.\n *\n * **Origin is the rod**, at `y = 0`, with the cloth hanging to negative Y and the panel's outer edge at\n * `x = 0` — so a pair is this geometry and a second built with `mirror: true`. The same convention as\n * {@link SwagGeometry} and {@link CascadeGeometry}.\n *\n * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`.\n *\n * @example\n * ```ts\n * // A thin curtain: straight down from the tieback rather than flaring back out.\n * const panel = new Mesh(\n *   new CurtainPanelGeometry({ pull: 0.42, hemPull: 0.42 }),\n *   new MeshStandardMaterial({ color: 0xb8ac93, roughness: 0.92, side: DoubleSide, flatShading: true }),\n * );\n * ```\n */\nexport class CurtainPanelGeometry extends BufferGeometry {\n  constructor({\n    width = 1.4,\n    drop = 3.2,\n    fullness = 2.5,\n    pleats = 9,\n    pleat = \"pinch\",\n    relax = 0.55,\n    tiebackHeight = 0.62,\n    topPull = 0,\n    pull = 0.42,\n    hemPull = 0.12,\n    outerPull = 0,\n    mirror = false,\n    slack = 0.7,\n    widthSegments = 160,\n    heightSegments = 40,\n  }: CurtainPanelGeometryOptions = {}) {\n    super();\n\n    const across = samplesPerPleat(widthSegments, pleats) * pleats;\n    const down = Math.max(1, Math.floor(heightSegments));\n    const own = planShape(pleat);\n    const fabric = fullness * width;\n    // A tieback at the very rod or the very hem leaves one half of the edge with no room to run.\n    const tie = Math.min(0.98, Math.max(0.02, tiebackHeight));\n\n    const grid: Vector3[][] = [];\n\n    for (let j = 0; j <= down; j++) {\n      const v = j / down;\n\n      // THE LEADING EDGE, through its three anchors. Above the tie it runs from `topPull` to `pull`;\n      // below it, from `pull` to `hemPull`. Two independent halves, which is the whole reason this is\n      // three numbers and not one bump — a single spread has to serve both and can suit neither.\n      const drawn =\n        v <= tie\n          ? topPull + (pull - topPull) * ease(v / tie, slack)\n          : pull + (hemPull - pull) * ease((v - tie) / (1 - tie), slack);\n\n      // The outer edge takes the SAME curve, scaled. It is one factor rather than a second set of\n      // anchors because a panel that draws in at both edges does so for one reason — nothing is holding\n      // its outer edge — and that is a single fact about the panel, not an independent shape.\n      const wanted = drawn * width;\n      const wantedOuter = wanted * Math.max(0, Math.min(1, outerPull));\n\n      // Scale BOTH down together if they would close the panel, so their ratio survives and the cloth\n      // stays centred. Clamping the span instead would let one edge overrun the other.\n      const total = wanted + wantedOuter;\n      const room = width * 0.94;\n      const scale = total > room ? room / total : 1;\n\n      const lead = wanted * scale;\n      const span = width - lead - wantedOuter * scale;\n\n      // Same cloth, less width: the local fullness is whatever the leading edge leaves it, and the fold\n      // depth is solved from that rather than set.\n      const blend = relax * v;\n      const shape: PleatShape = (phase) =>\n        own(phase) * (1 - blend) + Math.sin((phase - Math.floor(phase)) * Math.PI * 2) * blend;\n      const amplitude = solveAmplitude(shape, fabric / span, span / pleats);\n\n      const row: Vector3[] = [];\n      for (let i = 0; i <= across; i++) {\n        const u = i / across;\n        const x = lead + u * span;\n        // Only `x` is reflected. The depth is left alone, so a mirrored panel still faces the room.\n        row.push(new Vector3(mirror ? -x : x, -v * drop, shape(u * pleats) * amplitude));\n      }\n\n      grid.push(row);\n    }\n\n    // Reflecting one axis reverses handedness, so the mirrored panel needs its winding turned back.\n    const geometry = surfaceGrid(grid, { flip: mirror });\n    this.setIndex(geometry.getIndex());\n    for (const name of Object.keys(geometry.attributes)) {\n      this.setAttribute(name, geometry.attributes[name]!);\n    }\n    geometry.dispose();\n  }\n}\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { surfaceGrid } from \"../../modeling/surfaces/SurfaceGrid\";\n\n/**\n * The shape each tier hangs in.\n *\n * - `catenary` — `a·cosh(x/a)`, what a uniform hanging chain actually does.\n * - `parabola` — `1 − u²`, the approximation procedural code reaches for. At the sags a swag tier uses\n *   it is genuinely close; the two differ by about 5% of the sag at a deep setting.\n */\nexport type SwagSagCurve = \"catenary\" | \"parabola\";\n\nexport interface SwagGeometryOptions {\n  /** Distance between the two pins. Defaults to `2`. */\n  span?: number;\n  /** How far the LOWEST tier falls below the pins. Defaults to `0.85`. */\n  sag?: number;\n  /**\n   * How far the HIGHEST tier falls below the pins. Defaults to `0` — flat against the board.\n   *\n   * Zero is the usual answer, because the top of a swag is stapled to a straight piece of timber. Lift\n   * it and the first visible fold hangs on its own, which is what a swag mounted on a pole or a rod does\n   * rather than on a board. The pins stay at `y = 0` either way: the cinch takes every tier to zero at\n   * `u = ±1`, so this deepens the middle of the top tier without moving where it is fixed.\n   *\n   * Clamped to {@link sag}, since a top fold hanging below the bottom one is not a swag.\n   */\n  topSag?: number;\n  /**\n   * How the tiers distribute down the sag. Defaults to `1.2`.\n   *\n   * Above 1 they bunch toward the hem instead of stacking evenly, which is what stops a swag reading as\n   * a set of concentric arcs at equal spacing. Cloth does not distribute itself linearly.\n   */\n  sagPower?: number;\n  /** Fold cycles down the tier stack. Defaults to `3.5`. Fractional values are legitimate. */\n  folds?: number;\n  /** Depth of the fold ripple. Defaults to `0.12`. */\n  foldDepth?: number;\n  /**\n   * How far the lower tiers push forward. Defaults to `0.1`.\n   *\n   * Cloth has mass and the deeper folds hang out over the ones above them, which is what turns a flat\n   * scallop into the nested crescent a real swag makes.\n   */\n  bulge?: number;\n  /** How much narrower the upper tiers are. Defaults to `0.16`, because a higher fold spans less. */\n  taper?: number;\n  /** The tier envelope. Defaults to `\"catenary\"`. See {@link SwagSagCurve}. */\n  sagCurve?: SwagSagCurve;\n  /** Samples across the span. Defaults to `90`. Tessellation only — it never moves the silhouette. */\n  widthSegments?: number;\n  /** Samples down the tiers. Defaults to `110`. Carries the fold ripple, so it wants to be generous. */\n  heightSegments?: number;\n}\n\n/** The catenary parameter `a` for a half-span and sag. No closed form; monotonic, so it bisects. */\nfunction catenaryParameter(halfSpan: number, sag: number): number {\n  let low = 1e-9;\n  let high = 1e5;\n\n  for (let i = 0; i < 200; i++) {\n    const mid = (low + high) / 2;\n    if (mid * (Math.cosh(halfSpan / mid) - 1) > sag) low = mid;\n    else high = mid;\n  }\n\n  return (low + high) / 2;\n}\n\n/**\n * The tier's hanging shape, normalized: 1 at the centre, exactly 0 at both horns.\n *\n * **This factor is the cinch, and it is the whole geometry.** It multiplies both the sag and the fold\n * amplitude, so every tier converges on the same point at the pins and the folds compress smoothly to\n * nothing as they arrive. Nothing is cut and no fold is placed; one term gathers the cloth. Without it\n * the folds run at full depth into the corners and the result reads as corrugated sheet, not cloth.\n */\nfunction envelope(kind: SwagSagCurve, u: number): number {\n  if (kind === \"parabola\") return 1 - u * u;\n\n  // Shape only; the caller scales it. `a` is solved once against a unit half-span and unit sag so the\n  // profile keeps its character as `sag` moves, instead of re-solving into a different curve.\n  const a = catenaryParameter(1, 1);\n  const top = Math.cosh(1 / a);\n  return (top - Math.cosh(u / a)) / (top - 1);\n}\n\n/**\n * A swag — cloth hung in a curve between two pins, cinched to a knot at each end.\n *\n * One continuous surface over `(u, v)`: `u` across the span, `v` down the fold tiers. Three terms —\n * the macro sag hanging each tier, the micro fold rippling down them, and the cinch `E(u)` collapsing\n * both to zero at the horns.\n *\n * ```\n *   x(u,v) = u · (span/2 − taper·(1 − v))\n *   y(u,v) = −(topSag + (sag − topSag)·v^sagPower) · E(u)\n *   z(u,v) = (bulge·v + foldDepth·v·sin(2π·folds·v)) · E(u)\n * ```\n *\n * A vertical cut through the middle is a stack of waves — the S you see edge-on in any velvet valance,\n * and the profile this surface is a loft of. It is a LOFT rather than a sweep precisely because that\n * profile's amplitude changes across the span; carried unchanged it would be a sweep, and it would stop\n * looking like cloth.\n *\n * **Origin is the pin line**, at `y = 0`, with the cloth hanging to negative Y — the same convention as\n * {@link CascadeGeometry}.\n *\n * **This is a sheet with no thickness**, so it needs a material with `side: DoubleSide`.\n *\n * @example\n * ```ts\n * const swag = new Mesh(\n *   new SwagGeometry({ span: 2, sag: 0.85, folds: 3.5 }),\n *   new MeshStandardMaterial({ color: 0x1f5b45, roughness: 0.95, side: DoubleSide, flatShading: true }),\n * );\n * ```\n */\nexport class SwagGeometry extends BufferGeometry {\n  constructor({\n    span = 2,\n    sag = 0.85,\n    topSag = 0,\n    sagPower = 1.2,\n    folds = 3.5,\n    foldDepth = 0.12,\n    bulge = 0.1,\n    taper = 0.16,\n    sagCurve = \"catenary\",\n    widthSegments = 90,\n    heightSegments = 110,\n  }: SwagGeometryOptions = {}) {\n    super();\n\n    const across = Math.max(2, Math.floor(widthSegments));\n    const tiers = Math.max(1, Math.floor(heightSegments));\n    // A top fold hanging below the bottom one is not a swag, so the shallower value wins.\n    const top = Math.max(0, Math.min(topSag, sag));\n    const grid: Vector3[][] = [];\n\n    for (let j = 0; j <= tiers; j++) {\n      const v = j / tiers;\n      // From the top tier's sag to the bottom's, distributed by `sagPower`. At the default `topSag` of 0\n      // this is exactly `sag * v^p`, so the shape is unchanged unless the option is asked for.\n      const drop = top + (sag - top) * Math.pow(v, sagPower);\n      // Both terms carry `v`: at the board the cloth is held flat, and the ripple has only reached full\n      // depth by the hem.\n      const ripple = bulge * v + foldDepth * v * Math.sin(Math.PI * 2 * folds * v);\n      // Taper moves the horns themselves, so it is the one term the cinch does not multiply.\n      const halfWidth = span / 2 - taper * (1 - v);\n\n      const row: Vector3[] = [];\n      for (let i = 0; i <= across; i++) {\n        const u = -1 + (2 * i) / across;\n        const e = envelope(sagCurve, u);\n        row.push(new Vector3(u * halfWidth, -drop * e, ripple * e));\n      }\n\n      grid.push(row);\n    }\n\n    const geometry = surfaceGrid(grid);\n    this.setIndex(geometry.getIndex());\n    for (const name of Object.keys(geometry.attributes)) {\n      this.setAttribute(name, geometry.attributes[name]!);\n    }\n    geometry.dispose();\n  }\n}\n","import { BufferGeometry, CylinderGeometry, Quaternion, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport { createRandom, type RandomSource } from \"../../utils/Random\";\n\nconst UP = /*@__PURE__*/ new Vector3(0, 1, 0);\n\nexport interface ClearingTreeGeometryOptions {\n  /** Radius at the foot of the trunk. Defaults to `0.28`. */\n  trunkRadius?: number;\n  /** Length of one trunk growth step. Defaults to `0.76`. */\n  segmentLength?: number;\n  /** Growth steps in the trunk before it forks. Defaults to `7` — this is what makes the tree tall. */\n  trunkSteps?: number;\n  /** Growth seed — a tree is an address, not an accident. Defaults to `1`. */\n  seed?: number;\n  /**\n   * Height of the straight vertical rise before the trunk leans. Defaults to `0.3`.\n   *\n   * The trunk starts a hair off vertical so no two trees stand to identical attention, which tilts its bottom\n   * face and sinks the low edge below `y = 0`. One vertical segment makes the base tangent exactly UP so the\n   * face lies flat. A correction to the PATH, not the geometry — the same fix as\n   * {@link GnarledTreeGeometry}'s `baseRise`. Set `0` to see the original tilt.\n   */\n  baseRise?: number;\n}\n\n/** A random unit axis perpendicular to `direction` — the plane a segment bends in. */\nfunction perpendicular(direction: Vector3, random: () => number): Vector3 {\n  const reference = Math.abs(direction.y) < 0.98 ? UP : new Vector3(1, 0, 0);\n  return new Vector3()\n    .crossVectors(direction, reference)\n    .normalize()\n    .applyAxisAngle(direction, random() * Math.PI * 2);\n}\n\n/** A tapered tube from `start` (radius `startRadius`) to `end` (radius `endRadius`). */\nfunction frustum(start: Vector3, end: Vector3, startRadius: number, endRadius: number): BufferGeometry {\n  const direction = end.clone().sub(start);\n  const length = Math.max(direction.length(), 0.001);\n  direction.normalize();\n  const geometry = new CylinderGeometry(endRadius, startRadius, length, 5, 1);\n  geometry.translate(0, length / 2, 0);\n  geometry.applyQuaternion(new Quaternion().setFromUnitVectors(UP, direction));\n  geometry.translate(start.x, start.y, start.z);\n  return geometry;\n}\n\n/**\n * Grow one branch and, recursively, its offshoots.\n *\n * `depth === 0` is the trunk: it bends less, leans back toward vertical each step, tapers slower, and holds\n * its first limbs until it has risen a few steps — the open bole. Deeper branches reach outward and split\n * more freely.\n */\nfunction grow(\n  parts: BufferGeometry[],\n  random: () => number,\n  origin: Vector3,\n  initialDirection: Vector3,\n  radius: number,\n  segmentLength: number,\n  depth: number,\n  steps: number,\n): void {\n  let position = origin.clone();\n  const direction = initialDirection.clone().normalize();\n  let currentRadius = radius;\n\n  for (let step = 0; step < steps; step++) {\n    const bend = depth === 0 ? 0.07 + random() * 0.13 : 0.17 + random() * 0.3;\n    direction.applyAxisAngle(perpendicular(direction, random), bend);\n    if (depth === 0) direction.lerp(UP, 0.34).normalize();\n\n    const nextRadius = currentRadius * (depth === 0 ? 0.84 : 0.76);\n    const next = position.clone().addScaledVector(direction, segmentLength * (0.86 + random() * 0.28));\n    parts.push(frustum(position, next, currentRadius, nextRadius));\n\n    // Keep the lower trunk clear: the first limbs wait until step 3. That open bole is the whole point of\n    // this tree — it is what lets an iron guard ring a clean trunk instead of fouling a thicket of low boughs.\n    if (\n      depth < 3 &&\n      (depth > 0 || step >= 3) &&\n      currentRadius > 0.022 &&\n      random() < (depth === 0 ? 0.72 : 0.42)\n    ) {\n      const childDirection = direction\n        .clone()\n        .applyAxisAngle(perpendicular(direction, random), 0.62 + random() * 0.62);\n      childDirection.y += depth === 0 ? 0.16 : -0.02 + random() * 0.14;\n      childDirection.normalize();\n      grow(\n        parts,\n        random,\n        next,\n        childDirection,\n        nextRadius * 0.58,\n        segmentLength * 0.74,\n        depth + 1,\n        Math.max(2, 4 - depth),\n      );\n    }\n\n    position = next;\n    currentRadius = nextRadius;\n  }\n\n  if (depth < 3 && currentRadius > 0.018) {\n    const forks = depth === 0 ? 3 : 2;\n    for (let fork = 0; fork < forks; fork++) {\n      const forkDirection = direction\n        .clone()\n        .applyAxisAngle(perpendicular(direction, random), 0.48 + random() * 0.72);\n      forkDirection.y += 0.05 + random() * 0.18;\n      forkDirection.normalize();\n      grow(\n        parts,\n        random,\n        position,\n        forkDirection,\n        currentRadius * 0.64,\n        segmentLength * 0.77,\n        depth + 1,\n        Math.max(2, 4 - depth),\n      );\n    }\n  }\n}\n\n/**\n * A tall, open-trunked gnarled tree — the single tree from a clearing forest.\n *\n * One recursive routine grows the whole skeleton: a branch is a short walk of tapered frustums, gnarled by a\n * small random bend at each step, splitting into thinner children when it runs out of steps. Merged to a\n * single geometry.\n *\n * It differs from {@link GnarledTreeGeometry} in the one way that matters: **the lower trunk is kept open.**\n * The trunk is continually nudged back toward vertical and its first limbs are withheld until several steps\n * up, so the bole rises clear before it branches. That is what lets something ring a clean trunk — an iron\n * guard, a bench, a lantern — rather than fouling a thicket of low boughs.\n *\n * Material groups: **none** — bare bark, one material for the whole skeleton.\n *\n * Local frame: **grows from the origin**, base flat on the `y = 0` plane, occupying `+Y`. See\n * {@link ClearingTreeGeometryOptions.baseRise}.\n *\n * @example\n * ```typescript\n * const tree = new Mesh(new ClearingTreeGeometry({ seed: 1 }), bark);\n * ```\n */\nexport class ClearingTreeGeometry extends BufferGeometry {\n  readonly trunkRadius: number;\n\n  constructor({\n    trunkRadius = 0.28,\n    segmentLength = 0.76,\n    trunkSteps = 7,\n    seed = 1,\n    baseRise = 0.3,\n  }: ClearingTreeGeometryOptions = {}) {\n    super();\n\n    this.trunkRadius = trunkRadius;\n\n    // The library's seeded source rather than the scene's own linear congruential generator, so one random\n    // implementation serves the whole library. A given seed therefore grows a different — equally valid — tree\n    // than the website's.\n    const source: RandomSource = createRandom(seed);\n    const random = () => source.next();\n    const parts: BufferGeometry[] = [];\n\n    // Rise straight up before leaning, so the bottom face lands flat in the ground plane.\n    let foot = new Vector3();\n    if (baseRise > 0) {\n      const risen = new Vector3(0, baseRise, 0);\n      parts.push(frustum(foot, risen, trunkRadius, trunkRadius));\n      foot = risen;\n    }\n\n    // A hair off vertical so no two trees stand to identical attention.\n    const lean = new Vector3((random() - 0.5) * 0.18, 1, (random() - 0.5) * 0.18);\n    grow(parts, random, foot, lean, trunkRadius, segmentLength, 0, trunkSteps);\n\n    const merged = mergeGeometries(parts);\n    if (!merged) throw new Error(\"ClearingTreeGeometry: branch parts failed to merge.\");\n    parts.forEach((part) => part.dispose());\n\n    this.copy(merged);\n    merged.dispose();\n    this.computeBoundingSphere();\n  }\n}\n","import { BufferGeometry, CatmullRomCurve3, Vector3 } from \"three\";\nimport { mergeGeometries } from \"three/addons/utils/BufferGeometryUtils.js\";\nimport type { PathPoint } from \"../../modeling/paths/PathPoint\";\nimport { circleProfile } from \"../../modeling/profiles/Profiles\";\nimport { sweep, transportFrames } from \"../../modeling/surfaces/Sweep\";\nimport { createRandom, type RandomSource } from \"../../utils/Random\";\n\nconst UP = new Vector3(0, 1, 0);\n\nexport interface GnarledTreeGeometryOptions {\n  /** Radius of the trunk at the collar. Defaults to `0.24`. */\n  trunkRadius?: number;\n  /** Length of one step of growth. Defaults to `0.5`. */\n  segmentLength?: number;\n  /** How many times a branch may fork. Defaults to `4`. */\n  maxDepth?: number;\n  /** How hard each step bends. Defaults to `1`. */\n  gnarl?: number;\n  /** How much each step narrows. Defaults to `0.86`. */\n  taper?: number;\n  /** Sides of a branch's cross-section — the low-poly knob. Defaults to `5`. */\n  sides?: number;\n  /** Stations swept per skeleton step. `1` is faceted; higher smooths the gnarl into a curve. Defaults to `4`. */\n  smoothing?: number;\n  /**\n   * A straight vertical rise before the trunk starts to gnarl — the root collar. Defaults to `0.35`.\n   *\n   * Without it the trunk bends on its very first step and leaves the ground already leaning, so its\n   * base cannot lie flat. This is a correction to the PATH, not to the geometry: a real trunk rises\n   * vertically out of the earth before it does anything interesting.\n   */\n  baseRise?: number;\n  /** How much wider the trunk is at the ground than just above it — the root flare. `1` is none. Defaults to `1.5`. */\n  rootFlare?: number;\n  /** Optional seed for a reproducible tree. Omit for unique per runtime. */\n  seed?: number;\n}\n\n/** One branch: the skeleton nodes it walked through, and how thick it was at each. */\ninterface Branch {\n  nodes: { position: Vector3; radius: number }[];\n}\n\n/** A random unit axis perpendicular to `d` — the direction a segment bends in. */\nfunction randomPerp(d: Vector3, source: RandomSource): Vector3 {\n  const reference = Math.abs(d.y) < 0.99 ? UP : new Vector3(1, 0, 0);\n\n  return new Vector3()\n    .crossVectors(d, reference)\n    .normalize()\n    .applyAxisAngle(d, source.float(0, Math.PI * 2));\n}\n\n/**\n * A gnarled, leafless oak — the kind that belongs in a graveyard.\n *\n * One recursive routine grows the skeleton. A branch is not a straight tube: it is a short *walk*, and\n * at every step the growth direction is nudged by a small random angle (the gnarl) while the radius\n * tapers. When a branch runs out of steps it forks into thinner, shorter children that walk the same\n * way, and recursion does the rest.\n *\n * Each branch is then given a body by the SWEEP — one continuous tube carried along a curve threaded\n * through its nodes. The obvious alternative, a chain of tapered frustums, needs a sphere at every\n * joint to mask the seam where consecutive tubes fail to meet; parallel transport carries one ring\n * around the bend and there is no seam to hide. Where a child meets its parent the tubes simply\n * intersect — welding branch surfaces is skinning, and at this polygon budget nobody will see it.\n *\n * Local frame: base at Y=0, growing +Y.\n *\n * @example\n * ```ts\n * const geometry = new GnarledTreeGeometry({ seed: 1337, maxDepth: 4 });\n * ```\n */\nexport class GnarledTreeGeometry extends BufferGeometry {\n  readonly trunkRadius: number;\n\n  constructor(options: GnarledTreeGeometryOptions = {}) {\n    super();\n\n    const {\n      trunkRadius = 0.24,\n      segmentLength = 0.5,\n      maxDepth = 4,\n      gnarl = 1,\n      taper = 0.86,\n      sides = 5,\n      smoothing = 4,\n      baseRise = 0.35,\n      rootFlare = 1.5,\n      seed,\n    } = options;\n\n    this.trunkRadius = trunkRadius;\n\n    const source = createRandom(seed);\n    const branches: Branch[] = [];\n\n    const grow = (\n      origin: Vector3,\n      direction: Vector3,\n      radius: number,\n      segLen: number,\n      depth: number,\n    ): void => {\n      const steps = Math.max(2, source.int(4, 6) - depth); // deeper branches are shorter\n      const upBias = Math.max(0, 0.2 - depth * 0.06); // the trunk reaches up; branches reach out\n\n      const nodes: Branch[\"nodes\"] = [{ position: origin.clone(), radius }];\n\n      let pos = origin.clone();\n      const dir = direction.clone().normalize();\n      let r = radius;\n\n      // The trunk rises straight before it gnarls. That one vertical segment is what lets the base sit\n      // flat: the tangent at t=0 is then exactly the first segment's direction, which is UP. It also\n      // gives the root flare somewhere to live.\n      if (depth === 0 && baseRise > 0) {\n        nodes[0]!.radius = radius * rootFlare;\n        pos = origin.clone().addScaledVector(dir, baseRise);\n        nodes.push({ position: pos.clone(), radius });\n      }\n\n      for (let i = 0; i < steps; i++) {\n        // gnarl: bend around a random perpendicular axis, then nudge back toward upright\n        dir.applyAxisAngle(randomPerp(dir, source), source.float(0.18, 0.5) * gnarl);\n        if (upBias > 0) dir.lerp(UP, upBias).normalize();\n\n        const r2 = r * taper;\n        const next = pos.clone().addScaledVector(dir, segLen * source.float(0.85, 1.15));\n        nodes.push({ position: next.clone(), radius: r2 });\n\n        // an offshoot partway along the branch\n        if (depth < maxDepth && i > 0 && r2 > 0.05 && source.float(0, 1) < 0.28) {\n          const off = dir.clone().applyAxisAngle(randomPerp(dir, source), source.float(0.6, 1.1));\n          grow(next.clone(), off, r2 * 0.6, segLen * 0.8, depth + 1);\n        }\n\n        pos = next;\n        r = r2;\n      }\n\n      branches.push({ nodes });\n\n      // terminal fork\n      if (depth < maxDepth && r > 0.045) {\n        const children = depth === 0 ? source.int(2, 3) : source.int(1, 3);\n\n        for (let c = 0; c < children; c++) {\n          const childDir = dir.clone().applyAxisAngle(randomPerp(dir, source), source.float(0.4, 0.9));\n          grow(pos.clone(), childDir, r * 0.7, segLen * 0.82, depth + 1);\n        }\n      }\n    };\n\n    grow(new Vector3(0, 0, 0), UP.clone(), trunkRadius, segmentLength, 0);\n\n    // A unit circle, scaled per station by the branch's own radius — the path carries its thickness.\n    const profile = circleProfile(1, sides);\n\n    const parts = branches\n      .map((branch) => this.branchPath(branch, smoothing))\n      .filter((path) => path.length >= 2)\n      .map((path) => sweep(profile, transportFrames(path)));\n\n    // Not cast — `mergeGeometries` returns null on mismatched attributes, and a cast turns that into an\n    // unreadable \"cannot read properties of null\" further down.\n    const merged = mergeGeometries(parts, false);\n    if (!merged) throw new Error(\"GnarledTreeGeometry: branch sweeps failed to merge.\");\n\n    this.copy(merged);\n    merged.dispose();\n    parts.forEach((part) => part.dispose());\n  }\n\n  /**\n   * Turn a skeleton into a path. A Catmull-Rom curve threads the nodes, which turns the gnarl from a\n   * chain of hard corners into an actual curve — and, crucially, the curve KNOWS ITS OWN TANGENT. We\n   * ask it rather than estimating from the chords.\n   *\n   * The radius is read from the nodes the generator actually walked, not fitted to its endpoints: a\n   * formula would smear the root flare all the way up the trunk.\n   */\n  private branchPath(branch: Branch, smoothing: number): PathPoint[] {\n    const nodes = branch.nodes;\n    if (nodes.length < 2) return [];\n\n    const curve = new CatmullRomCurve3(\n      nodes.map((n) => n.position),\n      false,\n      \"centripetal\", // handles the tight kinks of a gnarl without overshooting\n    );\n\n    const stations = Math.max(2, (nodes.length - 1) * smoothing);\n\n    const radiusAt = (t: number) => {\n      const x = t * (nodes.length - 1);\n      const i = Math.min(nodes.length - 2, Math.floor(x));\n      const f = x - i;\n      return nodes[i]!.radius * (1 - f) + nodes[i + 1]!.radius * f;\n    };\n\n    return Array.from({ length: stations + 1 }, (_, i) => {\n      const t = i / stations;\n\n      return {\n        position: curve.getPointAt(t),\n        tangent: curve.getTangentAt(t),\n        scale: radiusAt(t),\n      };\n    });\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\n\nexport interface BeakerGeometryOptions {\n  /** Body radius. Defaults to `0.8`. */\n  radius?: number;\n  /** Overall height. Defaults to `1.6`. */\n  height?: number;\n  /** Pour-spout reach, as a fraction of the radius — how far the lip juts out. `0` is a plain cylinder. Defaults to `0.3`. */\n  spout?: number;\n  /** Angular half-width of the spout, in radians. Defaults to `0.5`. */\n  spoutWidth?: number;\n  /** Circumference segments — also the spout's smoothness. Defaults to `48`. */\n  radialSegments?: number;\n}\n\n/**\n * Beaker — a straight-walled cylinder with a flat base and a pour spout.\n *\n * The body is a lathe of its silhouette (exposed as `.profile`, so the fill works like any vessel). The\n * SPOUT is not a lathe — it breaks rotational symmetry — so it is a post-pass: the top rings of the wall\n * are pushed radially outward over a narrow arc (centered on +Z), tapering to nothing below the lip and at\n * the arc's edges. Normals are recomputed afterward.\n *\n * Local frame: flat base on Y=0, opening up +Y, spout facing +Z.\n */\nexport class BeakerGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor({ radius = 0.8, height = 1.6, spout = 0.3, spoutWidth = 0.5, radialSegments = 48 }: BeakerGeometryOptions = {}) {\n    const chamfer = radius * 0.12;\n    const silhouette = [\n      new Vector2(0, 0),\n      new Vector2(radius - chamfer, 0),\n      new Vector2(radius, chamfer),\n      new Vector2(radius, height * 0.8), // a ring partway up, so the spout flare has vertical resolution\n      new Vector2(radius, height),\n    ];\n    super(silhouette, radialSegments);\n    this.profile = silhouette;\n    this.radius = radius;\n    this.height = height;\n\n    if (spout > 0) {\n      const pos = this.getAttribute(\"position\");\n      const reach = spout * radius;\n      const flareStart = height * 0.8;\n      for (let i = 0; i < pos.count; i++) {\n        const x = pos.getX(i);\n        const y = pos.getY(i);\n        const z = pos.getZ(i);\n        if (y < flareStart - 1e-6) continue;\n        const r = Math.hypot(x, z);\n        if (r < 1e-4) continue;\n        // Angular distance from the spout centre (+Z, phi = atan2(x, z) = 0), wrapped.\n        let d = Math.atan2(x, z);\n        while (d > Math.PI) d -= Math.PI * 2;\n        while (d < -Math.PI) d += Math.PI * 2;\n        if (Math.abs(d) > spoutWidth) continue;\n        const across = 1 - Math.abs(d) / spoutWidth; // 1 at centre → 0 at the arc edges\n        const smooth = across * across * (3 - 2 * across); // smoothstep\n        const vertical = (y - flareStart) / (height - flareStart); // 0 at the ring → 1 at the rim\n        const push = reach * smooth * vertical;\n        pos.setX(i, x + (x / r) * push);\n        pos.setZ(i, z + (z / r) * push);\n      }\n      pos.needsUpdate = true;\n      this.computeVertexNormals();\n    }\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  erlenmeyerFlaskProfile,\n  vesselShell,\n  type ErlenmeyerFlaskProfileOptions,\n  type VesselShellOptions,\n} from \"./vesselProfiles\";\n\nexport interface ErlenmeyerFlaskGeometryOptions extends ErlenmeyerFlaskProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `16`. */\n  radialSegments?: number;\n}\n\n/**\n * Erlenmeyer flask — a conical body rising to a straight neck, walled to a real glass thickness.\n *\n * A lathe of {@link vesselShell} over {@link erlenmeyerFlaskProfile}. The outer silhouette is exposed as\n * `.profile`, so the same curve drives the glass, the liquid inside it ({@link LiquidFillGeometry}), or a\n * measurement. Local frame: base on Y=0.\n */\nexport class ErlenmeyerFlaskGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly bodyRadius: number;\n  readonly height: number;\n\n  constructor(options: ErlenmeyerFlaskGeometryOptions = {}) {\n    const silhouette = erlenmeyerFlaskProfile(options);\n    super(vesselShell(silhouette, options), options.radialSegments ?? 16);\n    this.profile = silhouette;\n    this.bodyRadius = options.bodyRadius ?? 1;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport {\n  graduatedCylinderProfile,\n  vesselShell,\n  type GraduatedCylinderProfileOptions,\n  type VesselShellOptions,\n} from \"./vesselProfiles\";\n\nexport interface GraduatedCylinderGeometryOptions extends GraduatedCylinderProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `24`. */\n  radialSegments?: number;\n}\n\n/**\n * Graduated cylinder — a straight bore on a flared base foot, with a rolled rim.\n *\n * A lathe of {@link vesselShell} over {@link graduatedCylinderProfile}; the silhouette is exposed as\n * `.profile` for the fill. Local frame: base on Y=0, opening up +Y.\n */\nexport class GraduatedCylinderGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: GraduatedCylinderGeometryOptions = {}) {\n    const silhouette = graduatedCylinderProfile(options);\n    super(vesselShell(silhouette, options), options.radialSegments ?? 24);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 0.35;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { LatheGeometry, Vector2 } from \"three\";\nimport { pipetteProfile, vesselShell, type PipetteProfileOptions, type VesselShellOptions } from \"./vesselProfiles\";\n\nexport interface PipetteGeometryOptions extends PipetteProfileOptions, VesselShellOptions {\n  /** Circumference segments — the low-poly knob. Defaults to `16`. */\n  radialSegments?: number;\n}\n\n/**\n * Pipette — a thin tube tapering through a cone to a point at the base, with a rolled rim.\n *\n * A lathe of {@link vesselShell} over {@link pipetteProfile}; the silhouette is exposed as `.profile` for\n * the fill. Local frame: tip on Y=0, opening up +Y.\n */\nexport class PipetteGeometry extends LatheGeometry {\n  readonly profile: Vector2[];\n  readonly radius: number;\n  readonly height: number;\n\n  constructor(options: PipetteGeometryOptions = {}) {\n    const silhouette = pipetteProfile(options);\n    super(vesselShell(silhouette, options), options.radialSegments ?? 16);\n    this.profile = silhouette;\n    this.radius = options.radius ?? 0.1;\n    this.height = silhouette.reduce((m, p) => Math.max(m, p.y), 0);\n  }\n}\n","import { LatheGeometry, SplineCurve, Vector2 } from \"three\";\n\nexport interface VaseGeometryOptions {\n  /**\n   * The silhouette, as radii from the foot to the lip. The spline passes THROUGH these, so they behave\n   * like handles you drag rather than weights you nudge. Defaults to `[0.55, 0.95, 0.8, 0.5, 0.62]` —\n   * a swelling belly, a slight waist, and a flared lip.\n   *\n   * Any number of points is accepted; they are spaced evenly up `height`. Two gives a cone.\n   */\n  radii?: number[];\n  /** Overall height. Defaults to `2.4`. */\n  height?: number;\n  /** How finely the silhouette is sampled — the smoothness of the curve. Defaults to `40`. */\n  profileSegments?: number;\n  /** How many times the silhouette is revolved — the low-poly knob. `6` gives a faceted, hand-thrown pot. Defaults to `32`. */\n  radialSegments?: number;\n  /**\n   * Horizontal bands, as ascending fractions of `height`, where the material index steps up. Defaults to\n   * none — a single group.\n   *\n   * `[0.1, 0.9]` yields three groups: material `0` below a tenth of the height, `1` between, `2` above —\n   * a contrasting foot and lip against the body. Supply one material per band plus one; repeats are fine.\n   */\n  bands?: number[];\n}\n\n/**\n * Vase — a silhouette revolved around an axis.\n *\n * A vase is a LATHE, not a sweep: it revolves a profile rather than carrying a cross-section along a\n * path. Which is easy to say, and still misses the thing that actually makes pottery hard:\n *\n * **A pot's silhouette is not a mathematical function.**\n *\n * *Swelling at the foot, pinched at the waist, flaring at the lip* does not come out of a parabola, a\n * sine, or an easing curve — those are single-inflection shapes, and a pot has three or four. Reaching\n * for a formula is the mistake, because no formula has the shape in it.\n *\n * What a pot's profile actually is: **a handful of control points with a spline through them.** Which\n * is precisely what the Utah teapot is — a few hundred hand-placed control points and an evaluator.\n * You do not compute a pot's curve; you AUTHOR it, and then you tessellate it. Small data, plus a\n * generating function.\n *\n * So the radii ARE the design. Raise the second and the bulge sits low; raise the fourth instead and\n * it climbs to the shoulder; pinch the middle for an hourglass. One geometry covers all three, because\n * it is not committed to any curve family.\n *\n * Local frame: foot on Y=0, opening up +Y.\n *\n * @example\n * ```ts\n * const geometry = new VaseGeometry({ radii: [0.4, 1, 0.7, 0.35, 0.5], height: 2.4 });\n * ```\n */\nexport class VaseGeometry extends LatheGeometry {\n  readonly height: number;\n\n  constructor({\n    radii = [0.55, 0.95, 0.8, 0.5, 0.62],\n    height = 2.4,\n    profileSegments = 40,\n    radialSegments = 32,\n    bands = [],\n  }: VaseGeometryOptions = {}) {\n    const control = radii.map(\n      (r, i) => new Vector2(Math.max(r, 0.001), (i / Math.max(1, radii.length - 1)) * height),\n    );\n\n    const silhouette = new SplineCurve(control).getPoints(profileSegments);\n\n    // A flat foot: run in from the axis out to the base radius before the curve begins. Without it the\n    // pot is an open shell and you can see straight up inside it — which is what DoubleSide hides\n    // rather than fixes. The tiny non-zero x keeps the lathe from collapsing the ring onto the axis.\n    //\n    // Only ONE point is prepended. The spline already starts at the foot, so adding `(r0, 0)` as well\n    // would duplicate it — and a repeated profile point lathes into a ring of zero-area quads whose\n    // normals are undefined.\n    const profile = [new Vector2(0.001, 0), ...silhouette];\n    super(profile, radialSegments);\n\n    this.height = height;\n\n    if (bands.length > 0) {\n      //  **A lathe's index is SEGMENT-major, so a horizontal band is not contiguous in it.**\n      //  `LatheGeometry` walks every profile point of one radial segment before moving to the next, which\n      //  means a band around the pot is a stripe scattered through the whole buffer at a stride of the\n      //  profile length. A material group is a `start` and a `count` into that buffer, so bands cannot be\n      //  expressed as groups without reordering it first — the vertices never move, only the index does.\n      const index = this.getIndex();\n      if (index) {\n        const points = profile.length;\n        const edges = [...bands].sort((a, b) => a - b);\n        //  Which band a profile point falls in, by its own height rather than by its ordinal — the spline\n        //  samples evenly in parameter, not in `y`, so the two part company on any pot with a waist.\n        const bandOf = (j: number) => {\n          const at = profile[Math.min(j, points - 1)]!.y / Math.max(1e-9, height);\n          let band = 0;\n          while (band < edges.length && at >= edges[band]!) band++;\n          return band;\n        };\n\n        const source = Array.from(index.array);\n        const buckets: number[][] = Array.from({ length: edges.length + 1 }, () => []);\n        for (let t = 0; t < source.length; t += 3) {\n          const [a, b, c] = [source[t]!, source[t + 1]!, source[t + 2]!];\n          //  A quad spans profile points `j` and `j + 1`; the LOWER one names the band, so a boundary\n          //  falls on a ring of the lathe rather than cutting through a row of quads.\n          const j = Math.min(a % points, b % points, c % points);\n          buckets[bandOf(j)]!.push(a, b, c);\n        }\n\n        const reordered: number[] = [];\n        this.clearGroups();\n        buckets.forEach((tris, material) => {\n          if (tris.length === 0) return;\n          this.addGroup(reordered.length, tris.length, material);\n          reordered.push(...tris);\n        });\n        index.set(reordered);\n        index.needsUpdate = true;\n      }\n    }\n  }\n}\n","import {\n  BufferAttribute,\n  BufferGeometry,\n  ColorRepresentation,\n  DoubleSide,\n  Material,\n  Mesh,\n  MeshStandardMaterial,\n} from \"three\";\n\nexport interface CycloramaOptions {\n  /** Extent across, along X. Defaults to `3`. */\n  width?: number;\n  /** How far the back wall rises. Defaults to `1.8`. */\n  height?: number;\n  /** How far the floor runs toward the camera. Defaults to `1.8`. */\n  depth?: number;\n  /**\n   * The cove's radius — **the only control the bend has.** Defaults to `0.7`.\n   *\n   * A cyclorama's corner is always 90°, and a quarter arc is fully determined by its radius, so there is\n   * no span or angle to give. Clamped to `min(height, depth)`, because a curve larger than its own flats\n   * would run past the ends of the sheet; the value used is reported as {@link Cyclorama.radius}.\n   */\n  radius?: number;\n  /**\n   * How finely the cove is cut. Defaults to `12`.\n   *\n   * **This one is not a style knob.** A cyclorama exists so the bend is not visible, and faceting is the\n   * bend becoming visible — see {@link Cyclorama.sagitta} for how to choose it by measurement rather than\n   * by eye.\n   */\n  segments?: number;\n  /** Backdrop tint. Defaults to `0xd8d5d0` — a paper gray. */\n  color?: ColorRepresentation;\n  /**\n   * A material to use instead of the default.\n   *\n   * **Do not give it `flatShading: true`.** The house style everywhere else in this library is faceted,\n   * and here it defeats the object entirely — see the note on shading below.\n   */\n  material?: Material;\n}\n\n/**\n * A seamless backdrop: a wall curving into a floor with no visible join. A CYCLORAMA — an infinity cove,\n * or in a photographer's words simply a SWEEP, after the roll of paper it imitates.\n *\n * Stands with its back wall on `z = 0` rising in `+Y`, and its floor running toward `+Z`, centered on X.\n * A development and presentation aid like {@link GroundGrid}, not scene content.\n *\n * **The bend has exactly one control, and that is a property of the shape rather than a simplification.**\n * The corner is always 90°, so the arc is fully determined by its `radius`. `width`, `height` and `depth`\n * only say where the flats END; none of them touches what the curve does.\n *\n * **Why the join disappears.** The arc's center sits at `(radius, radius)` — one radius in from the wall\n * and one up from the floor — which is the only place a circle can be tangent to both planes at once. At\n * tangency the curve leaves each flat traveling in exactly that flat's own direction, so there is no\n * crease for light to catch. Move the center anywhere else and a corner appears, however smooth the\n * geometry.\n *\n * **Shading: this is the one place `flatShading` is wrong.** Every other low-poly surface in this library\n * wants to read as intentionally faceted; a cyclorama wants to read as continuous, and faceting IS seeing\n * the bend. The geometry is therefore INDEXED on purpose, so `computeVertexNormals` averages across each\n * seam along the profile and the cove shades as one surface. Supply your own material and it must be\n * smooth, or the whole thing collapses into a fan of bands.\n *\n * **Choosing `segments` by measurement.** {@link Cyclorama.sagitta} reports how deep each facet dips\n * inside the true arc — `r · (1 − cos(θ/2))`. On a `0.7` radius, 3 segments dips 24mm and the banding is\n * obvious; 12 dips 1.5mm and it is not. Compare it against how close the camera gets rather than guessing.\n *\n * @example\n * ```ts\n * const backdrop = new Cyclorama({ width: 4, radius: 0.9 });\n * scene.add(backdrop);\n * backdrop.sagitta; // how visible the faceting is, in world units\n * backdrop.dispose();\n * ```\n */\nexport class Cyclorama extends Mesh<BufferGeometry, Material> {\n  /** The cove radius actually used, after clamping to `min(height, depth)`. */\n  readonly radius: number;\n  /**\n   * How far each facet's chord dips inside the true arc, in world units — the thing an eye catches.\n   * Raise `segments` until this is small against the distance the backdrop is seen from.\n   */\n  readonly sagitta: number;\n\n  readonly #ownsMaterial: boolean;\n\n  constructor({\n    width = 3,\n    height = 1.8,\n    depth = 1.8,\n    radius = 0.7,\n    segments = 12,\n    color = 0xd8d5d0,\n    material,\n  }: CycloramaOptions = {}) {\n    // The arc has to fit inside both flats, or it would run past the ends of the sheet.\n    const fitted = Math.min(radius, height, depth);\n    const steps = Math.max(1, Math.round(segments));\n\n    // The profile, in (z out from the wall, y up).\n    const profile: [number, number][] = [[0, height]];\n    if (height - fitted > 1e-6) profile.push([0, fitted]);\n    // The quarter, walked from 180° to 270° about a center at (r, r): at 180° it meets the wall, at 270°\n    // the floor. Those are the tangent points, and putting the center there is what removes the crease.\n    for (let i = 0; i <= steps; i++) {\n      const t = Math.PI + (Math.PI / 2) * (i / steps);\n      profile.push([fitted + fitted * Math.cos(t), fitted + fitted * Math.sin(t)]);\n    }\n    if (depth - fitted > 1e-6) profile.push([depth, 0]);\n\n    // A radius that exactly fills a flat repeats a point. A zero-length step would leave a degenerate quad\n    // in the ribbon and a NaN in its normal.\n    const points = profile.filter(\n      (p, i) => i === 0 || Math.hypot(p[0] - profile[i - 1]![0], p[1] - profile[i - 1]![1]) > 1e-9,\n    );\n\n    // INDEXED, deliberately. Sharing the vertices along the profile is what lets `computeVertexNormals`\n    // average across each seam — and that averaging is the entire reason the cove reads as continuous. A\n    // non-indexed ribbon gets one normal per facet and shows every one of them.\n    const half = width / 2;\n    const positions = new Float32Array(points.length * 6);\n    const uvs = new Float32Array(points.length * 4);\n    points.forEach(([z, y], i) => {\n      const t = points.length === 1 ? 0 : i / (points.length - 1);\n      positions.set([-half, y, z], i * 6);\n      positions.set([half, y, z], i * 6 + 3);\n      uvs.set([0, t], i * 4);\n      uvs.set([1, t], i * 4 + 2);\n    });\n\n    const indices: number[] = [];\n    for (let i = 0; i < points.length - 1; i++) {\n      const a = i * 2;\n      indices.push(a, a + 1, a + 3, a, a + 3, a + 2);\n    }\n\n    const geometry = new BufferGeometry();\n    geometry.setAttribute(\"position\", new BufferAttribute(positions, 3));\n    geometry.setAttribute(\"uv\", new BufferAttribute(uvs, 2));\n    geometry.setIndex(indices);\n    geometry.computeVertexNormals();\n\n    super(\n      geometry,\n      material ??\n        // Smooth, and double-sided so a camera that strays behind it does not see through the sheet.\n        new MeshStandardMaterial({\n          color,\n          roughness: 0.95,\n          metalness: 0,\n          side: DoubleSide,\n          flatShading: false,\n        }),\n    );\n\n    this.#ownsMaterial = material === undefined;\n    this.radius = fitted;\n    this.sagitta = fitted * (1 - Math.cos(Math.PI / 4 / steps));\n    this.receiveShadow = true;\n  }\n\n  /** Releases the geometry, and the material when this backdrop made it. */\n  dispose(): void {\n    this.geometry.dispose();\n    if (this.#ownsMaterial) this.material.dispose();\n  }\n}\n","import {\n  ColorRepresentation,\n  DoubleSide,\n  GridHelper,\n  Group,\n  Material,\n  Mesh,\n  MeshStandardMaterial,\n  PlaneGeometry,\n} from \"three\";\n\nexport interface GroundGridOptions {\n  /** Square extent of the floor and grid, in world units. Defaults to `24`. */\n  size?: number;\n  /** Grid divisions across `size`. Defaults to `size` (one cell per unit). */\n  divisions?: number;\n  /** Solid floor tint. Defaults to `0x1a2430`. */\n  planeColor?: ColorRepresentation;\n  /** Grid line color. Defaults to `0x223344`. */\n  gridColor?: ColorRepresentation;\n  /** Center cross-line color. Defaults to `0x334455`. */\n  centerColor?: ColorRepresentation;\n  /** World Y of the floor. Defaults to `0`. */\n  y?: number;\n}\n\n/**\n * A reference floor — a shadow-receiving plane with a coplanar {@link GridHelper}, ready to\n * `scene.add()`. A development aid for placing and scaling objects, not scene content.\n *\n * **The grid and the plane are exactly coplanar and do not z-fight.** Put a `GridHelper` on a plane at\n * the same Y and the depth buffer cannot separate them: the two surfaces round to the same depth and\n * the lines tear and shimmer as the camera moves. The usual workaround is to lift the grid by some\n * epsilon, which trades one bug for a subtler one — the lines float, visibly so at grazing angles, and\n * the epsilon has to be retuned every time the scene changes scale.\n *\n * The real fix is to bias the DEPTH rather than the position. The plane's material sets\n * `polygonOffset`, which pushes its fill back in the depth buffer *without moving it in space* — and\n * polygon offset does not apply to lines, so the grid stays exactly where it is and simply wins the\n * depth test. Perfectly coplanar, no tearing, no geometric lift, at any scale.\n *\n * A {@link Group}, because a `Mesh` and a `GridHelper` cannot merge into one object. Shadow receipt is\n * configured on the plane, where it belongs.\n *\n * @example\n * ```ts\n * const floor = new GroundGrid({ size: 16, planeColor: 0x1c2428 });\n * scene.add(floor);\n * // toggle both together: floor.visible = false;\n * // release GPU resources:  floor.dispose();\n * ```\n */\nexport class GroundGrid extends Group {\n  readonly plane: Mesh<PlaneGeometry, MeshStandardMaterial>;\n  readonly grid: GridHelper;\n\n  constructor({\n    size = 24,\n    divisions = size,\n    planeColor = 0x1a2430,\n    gridColor = 0x223344,\n    centerColor = 0x334455,\n    y = 0,\n  }: GroundGridOptions = {}) {\n    super();\n\n    this.plane = new Mesh(\n      new PlaneGeometry(size, size),\n      new MeshStandardMaterial({\n        color: planeColor,\n        roughness: 1,\n        metalness: 0,\n        side: DoubleSide,\n        polygonOffset: true,\n        polygonOffsetFactor: 1,\n        polygonOffsetUnits: 1,\n      }),\n    );\n    this.plane.rotation.x = -Math.PI / 2;\n    this.plane.receiveShadow = true;\n    this.add(this.plane);\n\n    this.grid = new GridHelper(size, divisions, centerColor, gridColor);\n    this.add(this.grid);\n\n    this.position.y = y;\n  }\n\n  /** Dispose the plane and grid geometry/material. */\n  dispose(): void {\n    this.plane.geometry.dispose();\n    this.plane.material.dispose();\n    this.grid.geometry.dispose();\n    const gridMaterial = this.grid.material as Material | Material[];\n    (Array.isArray(gridMaterial) ? gridMaterial : [gridMaterial]).forEach((m) => m.dispose());\n  }\n}\n","import { Box3, BufferGeometry, Curve, Float32BufferAttribute, Matrix3, Matrix4, Quaternion, Vector3 } from \"three\";\nimport { transportFrames } from \"../surfaces/Sweep\";\n\n/** Deforms existing vertices in geometry-local coordinates; never subdivides the mesh. */\nexport interface CurveDeformationOptions {\n  axis?: \"X\" | \"Y\" | \"Z\";\n  anchor?: \"Start\" | \"Center\" | \"End\";\n  fit?: \"Fit guide\" | \"Keep axis length\";\n  /** Rotation around the source axis, in radians. */\n  roll?: number;\n  /** Uniformly scale the guide to this length; defaults to the sampled curve length. */\n  guideLength?: number;\n  /** Number of curve intervals (default 256). Independent of mesh tessellation. */\n  samples?: number;\n  normals?: \"Jacobian\" | \"Facet\";\n  /** Local fold detection only; does not certify global non-intersection. */\n  onInvalid?: \"throw\" | \"report\";\n}\nexport interface BendGeometryOptions extends CurveDeformationOptions {\n  /** Signed arc angle in radians. Zero produces a straight guide. */\n  angle: number;\n}\ntype ResolvedOptions = Required<Omit<BendGeometryOptions, \"guideLength\">> & { guideLength?: number };\n\nfunction resolve(options: CurveDeformationOptions, angle = 0): ResolvedOptions {\n  const o = {\n    axis: \"X\",\n    anchor: \"Center\",\n    fit: \"Fit guide\",\n    roll: 0,\n    samples: 256,\n    normals: \"Jacobian\",\n    onInvalid: \"throw\",\n    ...options,\n    angle,\n  } as ResolvedOptions;\n  if (\n    ![\"X\", \"Y\", \"Z\"].includes(o.axis) ||\n    ![\"Start\", \"Center\", \"End\"].includes(o.anchor) ||\n    ![\"Fit guide\", \"Keep axis length\"].includes(o.fit) ||\n    ![\"Jacobian\", \"Facet\"].includes(o.normals) ||\n    ![\"throw\", \"report\"].includes(o.onInvalid)\n  )\n    throw new Error(\"Invalid deformation option.\");\n  if (\n    !Number.isFinite(o.angle) ||\n    !Number.isFinite(o.roll) ||\n    (o.guideLength !== undefined && !(Number.isFinite(o.guideLength) && o.guideLength > 0)) ||\n    !Number.isInteger(o.samples) ||\n    o.samples < 2 ||\n    o.samples > 65536\n  )\n    throw new Error(\"Deformation requires finite angles, positive guide length, and 2–65536 sample intervals.\");\n  return o;\n}\nfunction validateSource(source: BufferGeometry) {\n  const p = source.getAttribute(\"position\");\n  if (!p || p.itemSize !== 3 || !p.count) throw new Error(\"A nonempty position attribute of size 3 is required.\");\n  if (Object.keys(source.morphAttributes).length || source.drawRange.start !== 0 || source.drawRange.count !== Infinity)\n    throw new Error(\"Morph attributes and partial draw ranges are unsupported.\");\n  for (const name of Object.keys(source.attributes)) {\n    const a = source.getAttribute(name);\n    if (\n      ![\"position\", \"normal\", \"uv\", \"color\"].includes(name) ||\n      a.count !== p.count ||\n      ((name === \"position\" || name === \"normal\") && a.itemSize !== 3)\n    )\n      throw new Error(`Unsupported or mismatched attribute: ${name}.`);\n    for (let i = 0; i < a.count; i++)\n      for (let j = 0; j < a.itemSize; j++)\n        if (!Number.isFinite(a.getComponent(i, j))) throw new Error(`Nonfinite ${name} attribute.`);\n  }\n  const count = source.index?.count ?? p.count;\n  if (count % 3) throw new Error(\"Geometry must contain complete triangles.\");\n  if (source.index)\n    for (let i = 0; i < count; i++) {\n      const n = source.index.getX(i);\n      if (!Number.isInteger(n) || n < 0 || n >= p.count) throw new Error(\"Invalid triangle index.\");\n    }\n  for (const g of source.groups)\n    if (\n      !Number.isInteger(g.start) ||\n      !Number.isInteger(g.count) ||\n      g.start < 0 ||\n      g.count < 0 ||\n      g.start % 3 ||\n      g.count % 3 ||\n      g.start + g.count > count\n    )\n      throw new Error(\"Invalid material group.\");\n}\n\n/** Bend around an analytic circular guide; default guide length is the source-axis extent. */\nexport function bendGeometry(source: BufferGeometry, options: BendGeometryOptions) {\n  if (!Number.isFinite(options.angle)) throw new Error(\"A finite bend angle is required.\");\n  return deform(source, resolve(options, options.angle));\n}\n/** Use an open Three.js Curve, including splines. The selected source anchor retains its position and frame. */\nexport function deformAlongCurve(source: BufferGeometry, curve: Curve<Vector3>, options: CurveDeformationOptions = {}) {\n  return deform(source, resolve(options), curve);\n}\nconst basis = (axis: \"X\" | \"Y\" | \"Z\"): [Vector3, Vector3, Vector3] =>\n  axis === \"X\"\n    ? [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1)]\n    : axis === \"Y\"\n      ? [new Vector3(0, 1, 0), new Vector3(0, 0, 1), new Vector3(1, 0, 0)]\n      : [new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, 1, 0)];\n\ntype Frame = { p: Vector3; q: Quaternion };\n/** Maps local source positions; guide evaluation and source tessellation are deliberately independent. */\nfunction createMapping(source: BufferGeometry, settings: ResolvedOptions, curve?: Curve<Vector3>) {\n  const [a, b, c] = basis(settings.axis),\n    bounds = new Box3().setFromBufferAttribute(source.getAttribute(\"position\") as Float32BufferAttribute);\n  const center = bounds.getCenter(new Vector3()),\n    size = bounds.getSize(new Vector3()),\n    length = Math.abs(size.dot(a));\n  if (!(Number.isFinite(length) && length > 0)) throw new Error(\"The source axis must have positive length.\");\n  const start = center.dot(a) - length / 2,\n    anchor = settings.anchor === \"Start\" ? 0 : settings.anchor === \"End\" ? 1 : 0.5;\n  const anchorX = start + anchor * length,\n    guideLengthTarget = settings.guideLength ?? length;\n  let guideLength = guideLengthTarget;\n  let evaluate: (s: number) => Frame;\n  if (!curve) {\n    const curvature = settings.angle / guideLength;\n    evaluate = (s) => {\n      const at = Math.max(0, Math.min(guideLength, s)),\n        theta = curvature * at;\n      const p =\n        Math.abs(curvature) < 1e-10\n          ? new Vector3(at, 0, 0)\n          : new Vector3(Math.sin(theta) / curvature, (2 * Math.sin(theta / 2) ** 2) / curvature, 0);\n      p.addScaledVector(new Vector3(Math.cos(theta), Math.sin(theta), 0), s - at);\n      return { p, q: new Quaternion().setFromAxisAngle(new Vector3(0, 0, 1), theta) };\n    };\n  } else {\n    const samples = settings.samples;\n    const positions = Array.from({ length: samples + 1 }, (_, i) => curve.getPoint(i / samples).clone());\n    if (positions.some((p) => !p.toArray().every(Number.isFinite))) throw new Error(\"Curve points must be finite.\");\n    const distances = [0];\n    for (let i = 1; i <= samples; i++) {\n      const d = positions[i].distanceTo(positions[i - 1]);\n      if (!(d > 0)) throw new Error(\"Curve samples must be distinct; use a regular open curve.\");\n      distances.push(distances[i - 1] + d);\n    }\n    if (!Number.isFinite(distances[samples]) || positions[0].distanceTo(positions[samples]) <= distances[samples] * 1e-10)\n      throw new Error(\"Curve must have finite length and open endpoints.\");\n    guideLength = settings.guideLength ?? distances[samples];\n    const path = positions.map((p, i) => {\n      const tangent = curve.getTangent(i / samples).clone();\n      if (!tangent.toArray().every(Number.isFinite) || tangent.lengthSq() < 1e-20)\n        throw new Error(\"Curve tangents must be finite and nonzero.\");\n      tangent.normalize();\n      return { position: p, tangent };\n    });\n    for (let i = 1; i <= samples; i++)\n      if (path[i].tangent.dot(path[i - 1].tangent) < -0.999)\n        throw new Error(\"Curve has an unresolved tangent reversal; increase samples or remove the cusp.\");\n    const reference = Math.abs(path[0].tangent.y) < 0.9 ? new Vector3(0, 1, 0) : new Vector3(0, 0, 1);\n    const stations = transportFrames(\n      path.map((p) => ({ ...p, position: p.position.clone().sub(positions[0]).divideScalar(distances[samples]) })),\n      reference,\n    );\n    if (stations.length !== positions.length) throw new Error(\"Curve samples are too closely spaced.\");\n    const factor = guideLength / distances[samples];\n    positions.forEach((p) => p.multiplyScalar(factor));\n    distances.forEach((d, i) => (distances[i] = d * factor));\n    const rotations = stations.map((f) =>\n      new Quaternion().setFromRotationMatrix(new Matrix4().makeBasis(f.tangent, f.normal, f.binormal)),\n    );\n    evaluate = (s) => {\n      if (s < 0 || s > guideLength) {\n        const i = s < 0 ? 0 : samples;\n        return { p: positions[i].clone().addScaledVector(stations[i].tangent, s - distances[i]), q: rotations[i].clone() };\n      }\n      let lo = 0,\n        hi = samples;\n      while (hi - lo > 1) {\n        const m = (lo + hi) >> 1;\n        if (distances[m] <= s) lo = m;\n        else hi = m;\n      }\n      const t = (s - distances[lo]) / (distances[hi] - distances[lo]);\n      return { p: positions[lo].clone().lerp(positions[hi], t), q: rotations[lo].clone().slerp(rotations[hi], t) };\n    };\n  }\n  const anchorDistance = anchor * guideLength,\n    originFrame = evaluate(anchorDistance),\n    inverse = originFrame.q.clone().invert();\n  const anchorPoint = center.clone().addScaledVector(a, anchorX - center.dot(a));\n  const toWorld = (p: Vector3) => anchorPoint.clone().addScaledVector(a, p.x).addScaledVector(b, p.y).addScaledVector(c, p.z);\n  const sFor = (x: number) =>\n    settings.fit === \"Fit guide\" ? ((x - start) / length) * guideLength : anchorDistance + x - anchorX;\n  const roll = new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), settings.roll ?? 0);\n  const unroll = roll.clone().invert();\n  const map = (point: Vector3) => {\n    const frame = evaluate(sFor(point.dot(a)));\n    const transverse = new Vector3(0, point.clone().sub(center).dot(b), point.clone().sub(center).dot(c))\n      .applyQuaternion(unroll)\n      .applyQuaternion(frame.q);\n    return toWorld(frame.p.clone().sub(originFrame.p).add(transverse).applyQuaternion(inverse).applyQuaternion(roll));\n  };\n  const s0 = sFor(start),\n    s1 = sFor(start + length);\n  const guide = Array.from({ length: 257 }, (_, i) =>\n    toWorld(\n      evaluate((i / 256) * guideLength)\n        .p.clone()\n        .sub(originFrame.p)\n        .applyQuaternion(inverse)\n        .applyQuaternion(roll),\n    ),\n  );\n  return {\n    map,\n    guide,\n    length,\n    guideLength,\n    usedLength: s1 - s0,\n    extension: Math.max(0, -s0) + Math.max(0, s1 - guideLength),\n    anchorPoint: anchorPoint.clone(),\n  };\n}\n\nfunction deform(source: BufferGeometry, settings: ResolvedOptions, curve?: Curve<Vector3>) {\n  validateSource(source);\n  const mapping = createMapping(source, settings, curve);\n  let geometry = source.clone();\n  const sourcePosition = source.getAttribute(\"position\");\n  geometry.setAttribute(\n    \"position\",\n    new Float32BufferAttribute(\n      Array.from({ length: sourcePosition.count * 3 }, (_, i) => sourcePosition.getComponent(Math.floor(i / 3), i % 3)),\n      3,\n    ),\n  );\n  if (!geometry.getAttribute(\"normal\")) geometry.computeVertexNormals();\n  const position = source.getAttribute(\"position\"),\n    output = geometry.getAttribute(\"position\"),\n    normals = geometry.getAttribute(\"normal\");\n  const transformed: number[] = [],\n    epsilon = mapping.length * 1e-5;\n  let minDet = Infinity,\n    folded = 0;\n  const axes = [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1)];\n  for (let i = 0; i < position.count; i++) {\n    const p = new Vector3().fromBufferAttribute(position, i),\n      mapped = mapping.map(p);\n    output.setXYZ(i, mapped.x, mapped.y, mapped.z);\n    const derivative = axes.map((axis) =>\n      mapping\n        .map(p.clone().addScaledVector(axis, epsilon))\n        .sub(mapping.map(p.clone().addScaledVector(axis, -epsilon)))\n        .divideScalar(2 * epsilon),\n    );\n    const matrix = new Matrix3().set(\n      derivative[0].x,\n      derivative[1].x,\n      derivative[2].x,\n      derivative[0].y,\n      derivative[1].y,\n      derivative[2].y,\n      derivative[0].z,\n      derivative[1].z,\n      derivative[2].z,\n    );\n    const determinant = matrix.determinant();\n    minDet = Math.min(minDet, determinant);\n    if (!Number.isFinite(determinant) || determinant <= 1e-6) folded++;\n    const normal = normals ? new Vector3().fromBufferAttribute(normals, i) : new Vector3();\n    if (Math.abs(determinant) > 1e-8) normal.applyMatrix3(matrix.invert().transpose()).normalize();\n    else normal.set(0, 0, 0);\n    transformed.push(...normal.toArray());\n  }\n  if (settings.normals === \"Facet\") {\n    if (geometry.index) {\n      const plain = geometry.toNonIndexed();\n      geometry.dispose();\n      geometry = plain;\n    }\n    geometry.computeVertexNormals();\n  } else geometry.setAttribute(\"normal\", new Float32BufferAttribute(transformed, 3));\n  output.needsUpdate = true;\n  geometry.computeBoundingBox();\n  geometry.computeBoundingSphere();\n  // Chord error evaluates the deformation at edge midpoints, where the mesh has no new vertex.\n  let chordError = 0;\n  const index = source.index,\n    count = index?.count ?? position.count;\n  for (let i = 0; i < count; i += 3)\n    for (let k = 0; k < 3; k++) {\n      const j = index ? index.getX(i + k) : i + k,\n        l = index ? index.getX(i + ((k + 1) % 3)) : i + ((k + 1) % 3);\n      const a = new Vector3().fromBufferAttribute(position, j),\n        b = new Vector3().fromBufferAttribute(position, l);\n      chordError = Math.max(\n        chordError,\n        mapping.map(a.clone().add(b).multiplyScalar(0.5)).distanceTo(mapping.map(a).add(mapping.map(b)).multiplyScalar(0.5)),\n      );\n    }\n  const finalPosition = geometry.getAttribute(\"position\");\n  for (let i = 0; i < finalPosition.count; i++) {\n    if (![finalPosition.getX(i), finalPosition.getY(i), finalPosition.getZ(i)].every(Number.isFinite)) {\n      geometry.dispose();\n      throw new Error(\"Deformed positions exceed finite Float32 storage.\");\n    }\n  }\n  if (folded && settings.onInvalid === \"throw\") {\n    geometry.dispose();\n    throw new Error(`Deformation has ${folded} folded or collapsed vertex samples.`);\n  }\n  return { geometry, ...mapping, diagnostics: { minDet, folded, chordError, selfIntersectionsChecked: false as const } };\n}\n","import { Box3, BufferGeometry, Float32BufferAttribute, Vector3 } from \"three\";\nimport { ConvexHull } from \"three/addons/math/ConvexHull.js\";\n\nexport interface BevelConvexGeometryOptions {\n  /** Rolling-ball radius in geometry-local units. Zero rebuilds the source triangles without bevels. */\n  radius: number;\n  /** 1 for chamfers; 2–12 for faceted rounding. Defaults to 3. */\n  segments?: number;\n}\nexport interface BevelConvexGeometryResult {\n  /** New nonindexed geometry with flat normals. Groups 0/1/2: original faces / edge bands / corners. */\n  geometry: BufferGeometry;\n  /** Inset-core corners in source coordinates, useful for inspection. */\n  core: Vector3[];\n  /** Number of source supporting planes after merging coplanar triangles. */\n  faces: number;\n  /** Triangle counts for original faces, edge bands, and corners, respectively. */\n  patches: [number, number, number];\n}\ntype Plane = { n: Vector3; d: number };\n/** Rebuild all edges of a closed convex mesh. Source attributes/materials are not transferred. */\nexport function bevelConvexGeometry(source: BufferGeometry, options: BevelConvexGeometryOptions): BevelConvexGeometryResult {\n  let radius = options.radius;\n  const segments = options.segments ?? 3;\n  if (\n    Object.keys(source.morphAttributes).length ||\n    Object.keys(source.attributes).some((k) => ![\"position\", \"normal\", \"uv\", \"color\"].includes(k))\n  )\n    throw new Error(\"bevelConvexGeometry: morphs, skinning, tangents and custom attributes are unsupported.\");\n  if (!Number.isFinite(radius) || radius < 0 || !Number.isInteger(segments) || segments < 1 || segments > 12)\n    throw new Error(\"Use a nonnegative radius and 1–12 segments.\");\n  const p = source.getAttribute(\"position\"),\n    index = source.index;\n  if (!p || p.itemSize !== 3 || (index?.count ?? p.count) % 3) throw new Error(\"Expected triangle geometry.\");\n  const vertices = Array.from({ length: p.count }, (_, i) => new Vector3().fromBufferAttribute(p, i));\n  if (!vertices.length || vertices.some((v) => !v.toArray().every(Number.isFinite))) throw new Error(\"Invalid source positions.\");\n  const bounds = new Box3().setFromPoints(vertices),\n    origin = bounds.getCenter(new Vector3());\n  const scale = bounds.getSize(new Vector3()).length();\n  if (!(Number.isFinite(scale) && scale > 0)) throw new Error(\"Invalid source extent.\");\n  vertices.forEach((v) => v.sub(origin).divideScalar(scale));\n  radius /= scale;\n  const tolerance = 1e-7;\n  if (!Number.isFinite(radius) || (radius > 0 && radius < tolerance * 16))\n    throw new Error(\"Radius is too small for the source extent; use zero or at least 1.6e-6 of the bounding diagonal.\");\n  const key = (v: Vector3) =>\n    v\n      .toArray()\n      .map((x) => Math.round(x / tolerance))\n      .join(\",\");\n  const planes: Plane[] = [],\n    edges = new Map<string, number[]>();\n  const count = index?.count ?? p.count;\n  if (index && index.itemSize !== 1) throw new Error(\"Expected scalar triangle indices.\");\n  if (source.drawRange.start !== 0 || (source.drawRange.count !== Infinity && source.drawRange.count !== count))\n    throw new Error(\"Partial draw ranges are unsupported.\");\n  for (let i = 0; i < count; i += 3) {\n    const ids = [0, 1, 2].map((k) => (index ? index.getX(i + k) : i + k));\n    if (ids.some((j) => !Number.isInteger(j) || !vertices[j])) throw new Error(\"Invalid source index.\");\n    const [a, b, c] = ids.map((j) => vertices[j]);\n    const n = b.clone().sub(a).cross(c.clone().sub(a));\n    if (n.length() <= tolerance * tolerance) throw new Error(\"Degenerate source triangle.\");\n    n.normalize();\n    const d = n.dot(a);\n    if (vertices.some((v) => n.dot(v) > d + tolerance)) throw new Error(\"Source must be convex with outward winding.\");\n    if (!planes.some((f) => f.n.distanceTo(n) < 1e-6 && Math.abs(f.d - d) < tolerance)) planes.push({ n, d });\n    for (let k = 0; k < 3; k++) {\n      const ka = key(vertices[ids[k]]),\n        kb = key(vertices[ids[(k + 1) % 3]]);\n      const edge = [ka, kb].sort().join(\"/\");\n      const uses = edges.get(edge) ?? [];\n      uses.push(ka < kb ? 1 : -1);\n      edges.set(edge, uses);\n    }\n  }\n  if ([...edges.values()].some((e) => e.length !== 2 || e[0] + e[1] !== 0))\n    throw new Error(\"Source must be a closed two-manifold mesh.\");\n  if (planes.length < 4 || planes.length > 64) throw new Error(\"Expected 4–64 distinct supporting planes.\");\n  const core: Vector3[] = [];\n  for (let i = 0; i < planes.length; i++)\n    for (let j = i + 1; j < planes.length; j++)\n      for (let k = j + 1; k < planes.length; k++) {\n        const [a, b, c] = [planes[i], planes[j], planes[k]],\n          bc = b.n.clone().cross(c.n),\n          determinant = a.n.dot(bc);\n        if (Math.abs(determinant) < 1e-9) continue;\n        const v = bc\n          .multiplyScalar(a.d - radius)\n          .addScaledVector(c.n.clone().cross(a.n), b.d - radius)\n          .addScaledVector(a.n.clone().cross(b.n), c.d - radius)\n          .divideScalar(determinant);\n        if (planes.every((f) => f.n.dot(v) <= f.d - radius + tolerance) && !core.some((q) => q.distanceTo(v) < tolerance))\n          core.push(v);\n      }\n  if (core.length < 4 || planes.some((f) => core.filter((v) => Math.abs(f.n.dot(v) - f.d + radius) < tolerance * 4).length < 3))\n    throw new Error(\"Radius consumes a face or collapses the core; reduce it.\");\n  const finish = (buffers: number[][]): BevelConvexGeometryResult => {\n    const geometry = new BufferGeometry().setAttribute(\"position\", new Float32BufferAttribute(buffers.flat(), 3));\n    let start = 0;\n    buffers.forEach((buffer, i) => {\n      geometry.addGroup(start, buffer.length / 3, i);\n      start += buffer.length / 3;\n    });\n    // Validate the actual Float32 output, including welding across independently rendered triangles.\n    const output = geometry.getAttribute(\"position\"),\n      uses = new Map<string, number[]>();\n    const inside = core.reduce((sum, v) => sum.add(v), new Vector3()).divideScalar(core.length);\n    try {\n      for (let i = 0; i < output.count; i += 3) {\n        const tri = [0, 1, 2].map((k) => new Vector3().fromBufferAttribute(output, i + k));\n        const local = tri.map((v) => v.clone().sub(origin).divideScalar(scale));\n        if (local.some((v) => !v.toArray().every(Number.isFinite) || planes.some((f) => f.n.dot(v) > f.d + tolerance * 8)))\n          throw new Error(\"Float32 output exceeds source bounds or precision.\");\n        const normal = local[1].clone().sub(local[0]).cross(local[2].clone().sub(local[0]));\n        if (normal.length() <= 1e-14 || normal.dot(local[0].clone().sub(inside)) <= 0)\n          throw new Error(\"Bevel produces a collapsed or inverted triangle; adjust radius, segments or source coordinates.\");\n        for (let j = 0; j < 3; j++) {\n          const a = tri[j].toArray().join(\",\"),\n            b = tri[(j + 1) % 3].toArray().join(\",\");\n          const edge = [a, b].sort().join(\"/\");\n          const list = uses.get(edge) ?? [];\n          list.push(a < b ? 1 : -1);\n          uses.set(edge, list);\n        }\n      }\n      if ([...uses.values()].some((e) => e.length !== 2 || e[0] + e[1] !== 0))\n        throw new Error(\"Bevel output is not a closed two-manifold mesh.\");\n    } catch (error) {\n      geometry.dispose();\n      throw error;\n    }\n    geometry.computeVertexNormals();\n    const normals = geometry.getAttribute(\"normal\");\n    for (let i = 0; i < normals.count; i++) {\n      const n = new Vector3().fromBufferAttribute(normals, i);\n      if (!n.toArray().every(Number.isFinite) || n.lengthSq() < 0.5) {\n        geometry.dispose();\n        throw new Error(\"Output normals exceed Float32 precision; rescale the source.\");\n      }\n    }\n    geometry.computeBoundingBox();\n    geometry.computeBoundingSphere();\n    return {\n      geometry,\n      core: core.map((v) => v.clone().multiplyScalar(scale).add(origin)),\n      faces: planes.length,\n      patches: buffers.map((b) => b.length / 9) as [number, number, number],\n    };\n  };\n  const world = (v: Vector3) => v.clone().multiplyScalar(scale).add(origin).toArray();\n  if (radius === 0) {\n    const positions: number[] = [];\n    for (let i = 0; i < count; i++) positions.push(...world(vertices[index ? index.getX(i) : i]));\n    return finish([positions, [], []]);\n  }\n  const samples: Vector3[] = [],\n    owners = new Map<Vector3, number>(),\n    sampleKeys = new Set<string>();\n  const add = (center: Vector3, normal: Vector3, owner: number) => {\n    const v = center.clone().addScaledVector(normal.normalize(), radius);\n    const id = key(v);\n    if (!sampleKeys.has(id)) {\n      sampleKeys.add(id);\n      samples.push(v);\n      owners.set(v, owner);\n    }\n  };\n  core.forEach((v, owner) => {\n    const normals = planes.filter((f) => Math.abs(f.n.dot(v) - f.d + radius) < tolerance * 4).map((f) => f.n);\n    const axis = normals.reduce((sum, n) => sum.add(n), new Vector3()).normalize();\n    const u = normals[0].clone().addScaledVector(axis, -normals[0].dot(axis)).normalize(),\n      w = axis.clone().cross(u);\n    normals.sort((a, b) => Math.atan2(a.dot(w), a.dot(u)) - Math.atan2(b.dot(w), b.dot(u)));\n    // A fan triangulates the normal cone, including corners with more than three incident faces.\n    // Shared boundary directions use the same subdivision on either endpoint of each core edge.\n    for (let f = 1; f < normals.length - 1; f++) {\n      const [a, b, c] = [normals[0], normals[f], normals[f + 1]];\n      for (let i = 0; i <= segments; i++)\n        for (let j = 0; j <= segments - i; j++)\n          add(\n            v,\n            a\n              .clone()\n              .multiplyScalar(segments - i - j)\n              .addScaledVector(b, i)\n              .addScaledVector(c, j),\n            owner,\n          );\n    }\n  });\n  const hull = new ConvexHull().setFromPoints(samples);\n  const buffers: number[][] = [[], [], []];\n  for (const face of hull.faces) {\n    const triangle = [0, 1, 2].map((k) => face.getEdge(k).head().point);\n    const originalFace = planes.some((f) => triangle.every((v) => Math.abs(f.n.dot(v) - f.d) < tolerance * 4));\n    const ownersHere = new Set(triangle.map((v) => owners.get(v)));\n    const category = originalFace ? 0 : ownersHere.size > 1 ? 1 : 2;\n    triangle.forEach((v) => buffers[category].push(...world(v)));\n  }\n  return finish(buffers);\n}\n","import { Box3, BufferGeometry, Vector3 } from \"three\";\n\nexport interface InspectGeometryOptions {\n  /** Weld distance relative to the bounding-box diagonal. Default 1e-6; range [1e-12, 0.001]. */\n  tolerance?: number;\n}\nexport interface GeometryInspectionComponent {\n  /** Source triangle ordinals belonging to this edge-connected component. */\n  triangles: number[];\n  /** Closed edges and manifold vertex links, with consistent winding and no duplicate faces. */\n  closed: boolean;\n  /** Algebraic signed volume of the welded mesh, or null when inconsistent or nonfinite. Not certified occupied volume. */\n  signedVolume: number | null;\n}\nexport interface GeometryInspection {\n  /** Owned representative positions in geometry-local coordinates. */\n  points: Vector3[];\n  /** Maps each source position-attribute index to a representative point ID. */\n  vertexToPoint: number[];\n  /** Welded point IDs in source triangle order, including degenerate triangles. */\n  triangles: [number, number, number][];\n  /** Component per source triangle; -1 for excluded degenerate triangles. */\n  componentOf: number[];\n  /** Edges with one nondegenerate triangle use, as point-ID pairs. */\n  boundary: [number, number][];\n  /** Edges with more than two nondegenerate triangle uses. */\n  nonManifold: [number, number][];\n  /** Two-use edges traversed in the same direction by both triangles. */\n  winding: [number, number][];\n  /** Point IDs whose vertex link is not one cycle or boundary chain. */\n  nonManifoldVertices: number[];\n  /** Source triangle ordinals with repeated welded IDs or negligible normalized area. */\n  degenerate: number[];\n  /** All source triangle ordinals in duplicate sets, independent of winding. */\n  duplicate: number[];\n  components: GeometryInspectionComponent[];\n  /** Actual weld distance in source units. */\n  tolerance: number;\n  selfIntersectionsChecked: false;\n}\n/** Read-only inspection of stored positions and triangles. Does not repair, evaluate morphs/skinning,\n * test intersections, or interpret shell nesting. Other attributes and material groups are ignored.\n */\nexport function inspectGeometry(source: BufferGeometry, options: InspectGeometryOptions = {}): GeometryInspection {\n  const relativeTolerance = options.tolerance ?? 1e-6;\n  const p = source.getAttribute(\"position\"),\n    index = source.index,\n    count = index?.count ?? p?.count ?? 0;\n  if (\n    !p ||\n    p.itemSize !== 3 ||\n    !p.count ||\n    !count ||\n    !Number.isInteger(p.count) ||\n    !Number.isInteger(count) ||\n    count % 3 ||\n    (index && index.itemSize !== 1)\n  )\n    throw new Error(\"Expected nonempty triangle geometry.\");\n  if (!(relativeTolerance >= 1e-12 && relativeTolerance <= 0.001)) throw new Error(\"Tolerance must be in [1e-12, 0.001].\");\n  if (source.drawRange.start !== 0 || (source.drawRange.count !== Infinity && source.drawRange.count !== count))\n    throw new Error(\"Partial draw ranges are unsupported.\");\n  const input = Array.from({ length: p.count }, (_, i) => new Vector3().fromBufferAttribute(p, i));\n  if (input.some((v) => !v.toArray().every(Number.isFinite))) throw new Error(\"Positions must be finite.\");\n  const box = new Box3().setFromPoints(input),\n    origin = box.getCenter(new Vector3()),\n    scale = box.getSize(new Vector3()).length();\n  if (!Number.isFinite(scale) || scale === 0) throw new Error(\"Source has no finite extent.\");\n  const points: Vector3[] = [],\n    normalized: Vector3[] = [],\n    buckets = new Map<string, number[]>();\n  const ids = input.map((v) => {\n    const q = v.clone().sub(origin).divideScalar(scale),\n      cell = q.toArray().map((x) => Math.floor(x / relativeTolerance));\n    for (let x = -1; x <= 1; x++)\n      for (let y = -1; y <= 1; y++)\n        for (let z = -1; z <= 1; z++) {\n          const candidates = buckets.get([cell[0] + x, cell[1] + y, cell[2] + z].join(\",\")) ?? [];\n          for (const id of candidates) if (normalized[id].distanceTo(q) <= relativeTolerance) return id;\n        }\n    const id = points.length,\n      key = cell.join(\",\");\n    points.push(v.clone());\n    normalized.push(q);\n    buckets.set(key, [...(buckets.get(key) ?? []), id]);\n    return id;\n  });\n  const triangles: [number, number, number][] = [],\n    degenerate: number[] = [],\n    duplicates = new Set<number>();\n  const faces = new Map<string, number>(),\n    edgeUses = new Map<string, { edge: [number, number]; uses: { face: number; direction: number }[] }>();\n  for (let i = 0; i < count; i += 3) {\n    const raw = [0, 1, 2].map((k) => (index ? index.getX(i + k) : i + k));\n    if (raw.some((j) => !Number.isInteger(j) || j < 0 || j >= ids.length)) throw new Error(\"Invalid triangle index.\");\n    const tri = raw.map((j) => ids[j]) as [number, number, number],\n      f = triangles.length;\n    triangles.push(tri);\n    const [a, b, c] = tri.map((j) => normalized[j]);\n    if (new Set(tri).size < 3 || b.clone().sub(a).cross(c.clone().sub(a)).length() <= relativeTolerance ** 2) {\n      degenerate.push(f);\n      continue;\n    }\n    const key = [...tri].sort((a, b) => a - b).join(\",\"),\n      prior = faces.get(key);\n    if (prior !== undefined) {\n      duplicates.add(prior);\n      duplicates.add(f);\n    } else faces.set(key, f);\n    for (let k = 0; k < 3; k++) {\n      const a = tri[k],\n        b = tri[(k + 1) % 3],\n        edge: [number, number] = a < b ? [a, b] : [b, a],\n        key = edge.join(\",\");\n      const entry = edgeUses.get(key) ?? { edge, uses: [] };\n      entry.uses.push({ face: f, direction: a < b ? 1 : -1 });\n      edgeUses.set(key, entry);\n    }\n  }\n  const boundary: [number, number][] = [],\n    nonManifold: [number, number][] = [],\n    winding: [number, number][] = [];\n  const neighbors = triangles.map(() => new Set<number>()),\n    badFaces = new Set<number>(duplicates);\n  for (const { edge, uses } of edgeUses.values()) {\n    if (uses.length === 1) boundary.push(edge);\n    else if (uses.length !== 2) nonManifold.push(edge);\n    else if (uses[0].direction === uses[1].direction) winding.push(edge);\n    if (uses.length !== 2 || uses[0].direction === uses[1]?.direction) uses.forEach((u) => badFaces.add(u.face));\n    for (let j = 1; j < uses.length; j++) {\n      neighbors[uses[0].face].add(uses[j].face);\n      neighbors[uses[j].face].add(uses[0].face);\n    }\n  }\n  // Vertex links must be a single cycle (interior) or a single chain (boundary).\n  const links = points.map(() => [] as [number, number][]),\n    incident = points.map(() => [] as number[]),\n    degSet = new Set(degenerate);\n  triangles.forEach((tri, f) => {\n    if (!degSet.has(f))\n      tri.forEach((v, k) => {\n        links[v].push([tri[(k + 1) % 3], tri[(k + 2) % 3]]);\n        incident[v].push(f);\n      });\n  });\n  const nonManifoldVertices: number[] = [];\n  links.forEach((edges, v) => {\n    if (!edges.length) return;\n    const adjacency = new Map<number, number[]>();\n    edges.forEach(([a, b]) => {\n      adjacency.set(a, [...(adjacency.get(a) ?? []), b]);\n      adjacency.set(b, [...(adjacency.get(b) ?? []), a]);\n    });\n    const seen = new Set<number>(),\n      stack = [edges[0][0]];\n    while (stack.length) {\n      const n = stack.pop()!;\n      if (seen.has(n)) continue;\n      seen.add(n);\n      for (const next of adjacency.get(n)!) stack.push(next);\n    }\n    const degrees = [...adjacency.values()].map((x) => x.length),\n      ends = degrees.filter((d) => d === 1).length;\n    if (seen.size !== adjacency.size || degrees.some((d) => d > 2) || (ends !== 0 && ends !== 2)) {\n      nonManifoldVertices.push(v);\n      incident[v].forEach((f) => badFaces.add(f));\n    }\n  });\n  const componentOf = triangles.map(() => -1),\n    components: GeometryInspection[\"components\"] = [];\n  for (let f = 0; f < triangles.length; f++) {\n    if (degSet.has(f) || componentOf[f] !== -1) continue;\n    const members: number[] = [],\n      stack = [f],\n      id = components.length;\n    while (stack.length) {\n      const at = stack.pop()!;\n      if (componentOf[at] !== -1) continue;\n      componentOf[at] = id;\n      members.push(at);\n      for (const next of neighbors[at]) stack.push(next);\n    }\n    const reference = normalized[triangles[f][0]],\n      closed = members.every((i) => !badFaces.has(i));\n    let volume = 0;\n    for (const i of members) {\n      const [a, b, c] = triangles[i].map((v) => normalized[v].clone().sub(reference));\n      volume += a.dot(b.cross(c)) / 6;\n    }\n    const signed = volume * scale ** 3;\n    components.push({ triangles: members, closed, signedVolume: closed && Number.isFinite(signed) ? signed : null });\n  }\n  return {\n    points,\n    vertexToPoint: ids,\n    triangles,\n    componentOf,\n    boundary,\n    nonManifold,\n    winding,\n    nonManifoldVertices,\n    degenerate,\n    duplicate: [...duplicates],\n    components,\n    tolerance: relativeTolerance * scale,\n    selfIntersectionsChecked: false,\n  };\n}\n","import { Box3, BufferGeometry, Float32BufferAttribute, Plane, ShapeUtils, Vector2, Vector3 } from \"three\";\n\nexport interface SliceCapUVOptions {\n  /** Units keeps planar distances; fit maps the combined cap bounds to 0–1. Default units. */\n  mode?: \"units\" | \"fit\";\n  /** Positive source units per repeat in units mode. Default 1. */\n  unitsPerRepeat?: number;\n  /** Rotation in the cap plane, in radians. Default 0. */\n  rotation?: number;\n  offset?: Vector2;\n}\nexport interface SliceGeometryOptions {\n  /** Only new caps; both halves share the same mapping. Existing UVs are interpolated unchanged. */\n  capUV?: SliceCapUVOptions;\n  /** Seal both cut surfaces. Defaults to true. */\n  cap?: boolean;\n  /** Relative to the source bounding-box diagonal. Default 1e-7; range (0, 0.001]. */\n  tolerance?: number;\n}\nexport interface SliceGeometryResult {\n  /** Owned geometry on the positive side; empty when no solid remains there. */\n  positive: BufferGeometry;\n  /** Owned geometry on the negative side. */\n  negative: BufferGeometry;\n  /** Cut contours in input coordinates, without repeated endpoints. */\n  loops: Vector3[][];\n  /** Number of odd-depth hole contours. */\n  holes: number;\n  /** New cap area on one half, in source units squared. Zero if caps are disabled. */\n  capArea: number;\n  /** One greater than the largest input material index (1 when the input is ungrouped). */\n  capMaterialIndex: number;\n  diagnostics: {\n    /** Source triangles below the area tolerance, including collapsed primitive poles. */\n    discardedDegenerateTriangles: number;\n    selfIntersectionsChecked: false;\n  };\n}\ntype Corner = { p: Vector3; n: Vector3; uv: Vector2 };\ntype Face = { v: Corner[]; material: number };\n\nfunction validateAttributes(source: BufferGeometry): void {\n  const position = source.getAttribute(\"position\");\n  if (!position || position.itemSize !== 3 || !position.count)\n    throw new RangeError(\"sliceGeometry: expected nonempty positions.\");\n  if (\n    Object.keys(source.morphAttributes).length ||\n    Object.keys(source.attributes).some((k) => ![\"position\", \"normal\", \"uv\"].includes(k))\n  )\n    throw new RangeError(\"sliceGeometry: only position, normal and uv attributes are supported.\");\n  for (const name of [\"position\", \"normal\", \"uv\"] as const) {\n    const attribute = source.getAttribute(name);\n    if (!attribute) continue;\n    const size = name === \"uv\" ? 2 : 3;\n    if (attribute.itemSize !== size || attribute.count !== position.count)\n      throw new RangeError(\"sliceGeometry: mismatched attribute size/count.\");\n    for (let i = 0; i < attribute.count; i++) {\n      if (![attribute.getX(i), attribute.getY(i), ...(size === 3 ? [attribute.getZ(i)] : [])].every(Number.isFinite))\n        throw new RangeError(\"sliceGeometry: nonfinite attribute.\");\n      if (name === \"normal\" && Math.hypot(attribute.getX(i), attribute.getY(i), attribute.getZ(i)) === 0)\n        throw new RangeError(\"sliceGeometry: zero source normal.\");\n    }\n  }\n  const index = source.index,\n    count = index?.count ?? position.count;\n  if (count % 3 || (index && index.itemSize !== 1)) throw new RangeError(\"sliceGeometry: expected triangle indices.\");\n  if (index)\n    for (let i = 0; i < count; i++) {\n      const n = index.getX(i);\n      if (!Number.isInteger(n) || n < 0 || n >= position.count) throw new RangeError(\"sliceGeometry: invalid index.\");\n    }\n  if (source.drawRange.start !== 0 || (source.drawRange.count !== Infinity && source.drawRange.count !== count))\n    throw new RangeError(\"sliceGeometry: partial draw ranges are unsupported.\");\n  const ranges = [...source.groups].sort((a, b) => a.start - b.start);\n  let end = 0;\n  for (const g of ranges) {\n    if (\n      !Number.isInteger(g.start) ||\n      !Number.isInteger(g.count) ||\n      g.start % 3 ||\n      g.count % 3 ||\n      g.start < end ||\n      g.count <= 0 ||\n      g.start + g.count > count ||\n      !Number.isSafeInteger(g.materialIndex ?? 0) ||\n      (g.materialIndex ?? 0) < 0\n    )\n      throw new RangeError(\"sliceGeometry: invalid or overlapping material groups.\");\n    end = g.start + g.count;\n  }\n}\n\n/** Geometric adjacency across shading seams, with directed edge cancellation. */\nfunction validateEdges(faces: Vector3[][], key: (p: Vector3) => string, openPlane: Plane | null, epsilon = 0): void {\n  const edges = new Map<string, { count: number; balance: number; a: Vector3; b: Vector3 }>();\n  for (const face of faces)\n    for (let i = 0; i < 3; i++) {\n      const a = face[i],\n        b = face[(i + 1) % 3],\n        ka = key(a),\n        kb = key(b);\n      if (ka === kb) throw new RangeError(\"sliceGeometry: feature collapsed at tolerance.\");\n      const id = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`,\n        edge = edges.get(id) ?? { count: 0, balance: 0, a, b };\n      edge.count++;\n      edge.balance += ka < kb ? 1 : -1;\n      edges.set(id, edge);\n    }\n  for (const e of edges.values()) {\n    if (e.count === 2 && e.balance === 0) continue;\n    if (\n      e.count === 1 &&\n      openPlane &&\n      Math.abs(openPlane.distanceToPoint(e.a)) <= epsilon &&\n      Math.abs(openPlane.distanceToPoint(e.b)) <= epsilon\n    )\n      continue;\n    throw new RangeError(\"sliceGeometry: open, non-manifold or inconsistently wound mesh at the selected tolerance.\");\n  }\n}\n\n/** Reject touching/crossing loops before assigning nesting depth. */\nfunction validateContours(loops: Vector2[][], epsilon: number): void {\n  const segments = loops.flatMap((loop, l) => loop.map((a, i) => ({ a, b: loop[(i + 1) % loop.length], l, i })));\n  const cross = (a: Vector2, b: Vector2, p: Vector2) => b.clone().sub(a).cross(p.clone().sub(a));\n  const on = (a: Vector2, b: Vector2, p: Vector2) =>\n    Math.abs(cross(a, b, p)) <= epsilon * a.distanceTo(b) &&\n    p.x >= Math.min(a.x, b.x) - epsilon &&\n    p.x <= Math.max(a.x, b.x) + epsilon &&\n    p.y >= Math.min(a.y, b.y) - epsilon &&\n    p.y <= Math.max(a.y, b.y) + epsilon;\n  for (let i = 0; i < segments.length; i++)\n    for (let j = i + 1; j < segments.length; j++) {\n      const a = segments[i],\n        b = segments[j];\n      if (a.l === b.l && ((a.i + 1) % loops[a.l].length === b.i || (b.i + 1) % loops[a.l].length === a.i)) continue;\n      const ab = cross(a.a, a.b, b.a),\n        ac = cross(a.a, a.b, b.b),\n        ba = cross(b.a, b.b, a.a),\n        bc = cross(b.a, b.b, a.b);\n      if ((ab * ac < 0 && ba * bc < 0) || on(a.a, a.b, b.a) || on(a.a, a.b, b.b) || on(b.a, b.b, a.a) || on(b.a, b.b, a.b))\n        throw new RangeError(\"sliceGeometry: cut contours cross or touch.\");\n    }\n}\n\n/**\n * Split a closed, consistently outward-wound triangle mesh in local coordinates.\n * Positive means plane.normal.dot(point) + plane.constant >= 0. Caps face out of each half.\n * Supports indexed/nonindexed positions, normals, UVs and material groups. Other attributes,\n * morph data and partial draw ranges are rejected rather than silently discarded.\n *\n * Input and plane are not mutated. Owned output is nonindexed; callers dispose both geometries.\n * Contours include holes and separate islands, without a repeated closing point. Coplanar exterior\n * faces stay with the solid behind their outward normal; tangent cuts do not create duplicate caps.\n *\n * Uses a relative geometric tolerance, not exact predicates. Touching/branching contours and detected\n * topology failures throw. Global self-intersections are not checked; successful output is not a\n * general solid-validity certificate. Keep small features near the origin for Float32 precision.\n */\nexport function sliceGeometry(\n  source: BufferGeometry,\n  cuttingPlane: Plane,\n  { cap = true, tolerance = 1e-7, capUV = {} }: SliceGeometryOptions = {},\n): SliceGeometryResult {\n  if (!Number.isFinite(tolerance) || tolerance <= 0 || tolerance > 1e-3)\n    throw new RangeError(\"sliceGeometry: tolerance must be in (0, 0.001].\");\n  const uvMode = capUV.mode ?? \"units\",\n    uvUnits = capUV.unitsPerRepeat ?? 1,\n    uvRotation = capUV.rotation ?? 0,\n    uvOffset = capUV.offset ?? new Vector2();\n  if (\n    ![\"units\", \"fit\"].includes(uvMode) ||\n    !(Number.isFinite(uvUnits) && uvUnits > 0) ||\n    ![uvRotation, uvOffset.x, uvOffset.y].every(Number.isFinite)\n  )\n    throw new RangeError(\"sliceGeometry: invalid cap UV options.\");\n  validateAttributes(source);\n  const position = source.getAttribute(\"position\"),\n    normals = source.getAttribute(\"normal\"),\n    uv = source.getAttribute(\"uv\");\n  if (\n    !position ||\n    !Number.isFinite(cuttingPlane.constant) ||\n    !Number.isFinite(cuttingPlane.normal.length()) ||\n    cuttingPlane.normal.length() === 0\n  )\n    throw new Error(\"Invalid geometry or plane.\");\n  const bounds = new Box3();\n  for (let i = 0; i < position.count; i++) bounds.expandByPoint(new Vector3().fromBufferAttribute(position, i));\n  const origin = bounds.getCenter(new Vector3()),\n    scale = bounds.getSize(new Vector3()).length();\n  if (!(scale > 0) || !Number.isFinite(scale)) throw new Error(\"Invalid geometry extent.\");\n  const normal = cuttingPlane.normal.clone().normalize();\n  const plane = new Plane(normal, (cuttingPlane.constant / cuttingPlane.normal.length() + normal.dot(origin)) / scale);\n  const epsilon = tolerance;\n  const key = (p: Vector3) => [p.x, p.y, p.z].map((v) => Math.round(v / epsilon)).join(\":\");\n  const canonical = new Map<string, Vector3>();\n  const onPlane = (p: Vector3) => {\n    const projected = plane.projectPoint(p, new Vector3()),\n      id = key(projected);\n    if (!canonical.has(id)) canonical.set(id, projected);\n    return canonical.get(id)!.clone();\n  };\n  const index = source.index,\n    count = index?.count ?? position.count;\n  if (count % 3) throw new Error(\"Expected triangles.\");\n  const inputFaces: Vector3[][] = [];\n  let discardedDegenerateTriangles = 0;\n  const halves: Face[][] = [[], []];\n  const area = (v: Corner[]) => v[1].p.clone().sub(v[0].p).cross(v[2].p.clone().sub(v[0].p)).length();\n  const emit = (out: Face[], v: Corner[], material: number) => {\n    for (let i = 1; i < v.length - 1; i++) {\n      const tri = [v[0], v[i], v[i + 1]];\n      if (area(tri) > epsilon * epsilon) out.push({ v: tri, material });\n    }\n  };\n  for (let at = 0; at < count; at += 3) {\n    const vertices = [0, 1, 2].map((k) => {\n      const i = index ? index.getX(at + k) : at + k;\n      const p = new Vector3().fromBufferAttribute(position, i).sub(origin).divideScalar(scale);\n      if (!p.toArray().every(Number.isFinite)) throw new Error(\"Nonfinite source coordinates.\");\n      if (Math.abs(plane.distanceToPoint(p)) <= epsilon) p.copy(onPlane(p));\n      return {\n        p,\n        n: normals ? new Vector3().fromBufferAttribute(normals, i).normalize() : new Vector3(),\n        uv: uv ? new Vector2(uv.getX(i), uv.getY(i)) : new Vector2(),\n      };\n    });\n    if (area(vertices) <= epsilon * epsilon) {\n      discardedDegenerateTriangles++;\n      continue;\n    }\n    inputFaces.push(vertices.map((v) => v.p));\n    const faceNormal = vertices[1].p.clone().sub(vertices[0].p).cross(vertices[2].p.clone().sub(vertices[0].p)).normalize();\n    if (!normals) vertices.forEach((v) => v.n.copy(faceNormal));\n    const material = source.groups.find((g) => at >= g.start && at < g.start + g.count)?.materialIndex ?? 0;\n    const distances = vertices.map((v) => plane.distanceToPoint(v.p));\n    if (distances.every((d) => Math.abs(d) <= epsilon)) {\n      emit(halves[faceNormal.dot(normal) > 0 ? 1 : 0], vertices, material);\n      continue;\n    }\n    for (let half = 0; half < 2; half++) {\n      const sign = half === 0 ? 1 : -1,\n        polygon: Corner[] = [];\n      for (let k = 0; k < 3; k++) {\n        const a = vertices[k],\n          b = vertices[(k + 1) % 3],\n          da = distances[k] * sign,\n          db = distances[(k + 1) % 3] * sign;\n        if (da >= -epsilon) polygon.push(a);\n        if ((da > epsilon && db < -epsilon) || (da < -epsilon && db > epsilon)) {\n          const t = da / (da - db);\n          polygon.push({\n            p: onPlane(a.p.clone().lerp(b.p, t)),\n            n: a.n.clone().lerp(b.n, t).normalize(),\n            uv: a.uv.clone().lerp(b.uv, t),\n          });\n        }\n      }\n      const clean = polygon.filter((v, i) => i === 0 || v.p.distanceToSquared(polygon[i - 1].p) > epsilon * epsilon);\n      if (clean.length > 1 && clean[0].p.distanceToSquared(clean[clean.length - 1].p) <= epsilon * epsilon) clean.pop();\n      emit(halves[half], clean, material);\n    }\n  }\n  validateEdges(inputFaces, key, null);\n  if (!inputFaces.length) throw new RangeError(\"sliceGeometry: no nondegenerate source faces.\");\n  const inputVolume = inputFaces.reduce((sum, [a, b, c]) => sum + a.dot(b.clone().cross(c)) / 6, 0);\n  if (!(inputVolume > 0)) throw new RangeError(\"sliceGeometry: source must have positive outward signed volume.\");\n  // Find the actual open boundary of the clipped skin. This handles cuts through existing edges\n  // and coplanar exterior faces without adding duplicate caps.\n  const boundary = (faces: Face[]) => {\n    const edges = new Map<string, { a: Vector3; b: Vector3; count: number; balance: number }>();\n    for (const face of faces)\n      for (let i = 0; i < 3; i++) {\n        const a = face.v[i].p,\n          b = face.v[(i + 1) % 3].p;\n        if (Math.abs(plane.distanceToPoint(a)) > epsilon * 2 || Math.abs(plane.distanceToPoint(b)) > epsilon * 2) continue;\n        const ka = key(a),\n          kb = key(b);\n        if (ka === kb) continue;\n        const id = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`,\n          e = edges.get(id) ?? { a, b, count: 0, balance: 0 };\n        e.count++;\n        e.balance += ka < kb ? 1 : -1;\n        edges.set(id, e);\n      }\n    if ([...edges.values()].some((e) => e.count > 2 || (e.count === 2 && e.balance !== 0)))\n      throw new Error(\"Ambiguous plane boundary: non-manifold or inconsistent winding.\");\n    return [...edges.values()].filter((e) => e.count === 1);\n  };\n  const edges = boundary(halves[0]),\n    negativeEdges = boundary(halves[1]);\n  const edgeId = (a: Vector3, b: Vector3) => [key(a), key(b)].sort().join(\"|\");\n  const negativeIds = new Set(negativeEdges.map((e) => edgeId(e.a, e.b)));\n  if (edges.length !== negativeEdges.length || edges.some((e) => !negativeIds.has(edgeId(e.a, e.b))))\n    throw new RangeError(\"sliceGeometry: halves have inconsistent cut boundaries.\");\n  const outgoing = new Map<string, (typeof edges)[number]>(),\n    incoming = new Map<string, number>();\n  for (const e of edges) {\n    if (outgoing.has(key(e.a))) throw new Error(\"Contours touch or branch at a cut vertex. Move the plane slightly.\");\n    outgoing.set(key(e.a), e);\n    incoming.set(key(e.b), (incoming.get(key(e.b)) ?? 0) + 1);\n  }\n  if (edges.some((e) => !outgoing.has(key(e.b)) || incoming.get(key(e.a)) !== 1))\n    throw new Error(\"Open cut contour; source may not be closed.\");\n  const loops: Vector3[][] = [],\n    visited = new Set<(typeof edges)[number]>();\n  for (const start of edges) {\n    if (visited.has(start)) continue;\n    const loop: Vector3[] = [];\n    let e = start;\n    do {\n      if (visited.has(e)) throw new Error(\"Ambiguous contour cycle.\");\n      visited.add(e);\n      loop.push(e.a);\n      e = outgoing.get(key(e.b))!;\n    } while (e !== start);\n    if (loop.length < 3) throw new Error(\"Collapsed cut loop.\");\n    loops.push(loop);\n  }\n  const seed = Math.abs(normal.x) < 0.8 ? new Vector3(1, 0, 0) : new Vector3(0, 1, 0);\n  const u = seed.addScaledVector(normal, -seed.dot(normal)).normalize(),\n    v = normal.clone().cross(u);\n  const contours = loops.map((loop) => loop.map((p) => new Vector2(p.dot(u), p.dot(v))));\n  validateContours(contours, epsilon);\n  const inside = (p: Vector2, loop: Vector2[]) => {\n    let hit = false;\n    for (let i = 0, j = loop.length - 1; i < loop.length; j = i++) {\n      const a = loop[i],\n        b = loop[j];\n      if (a.y > p.y !== b.y > p.y && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x) hit = !hit;\n    }\n    return hit;\n  };\n  const parents = contours.map((loop, i) => {\n    let parent = -1,\n      best = Infinity;\n    contours.forEach((other, j) => {\n      const a = Math.abs(ShapeUtils.area(other));\n      if (i !== j && a > Math.abs(ShapeUtils.area(loop)) && a < best && inside(loop[0], other)) {\n        parent = j;\n        best = a;\n      }\n    });\n    return parent;\n  });\n  const depth = (i: number): number => (parents[i] === -1 ? 0 : 1 + depth(parents[i]));\n  const capMaterialIndex = Math.max(0, ...source.groups.map((g) => g.materialIndex ?? 0)) + 1;\n  let capArea = 0;\n  if (cap)\n    contours.forEach((contour, i) => {\n      if (depth(i) % 2) return;\n      const holes = contours.filter((_, j) => parents[j] === i),\n        world = [...loops[i], ...loops.filter((_, j) => parents[j] === i).flat()];\n      // ShapeUtils can omit collinear boundary points. Subdivide each cap triangle's boundary edges\n      // at those points, then fan around an interior centroid to avoid T-junctions in the final shell.\n      const all = [...contour, ...holes.flat()];\n      let base = 0;\n      const simplified = [contour, ...holes].map((loop) => {\n        const ids = loop.map((_, k) => base + k);\n        base += loop.length;\n        let changed = true;\n        while (changed && ids.length > 3) {\n          changed = false;\n          for (let k = 0; k < ids.length; k++) {\n            const a = all[ids[(k + ids.length - 1) % ids.length]],\n              b = all[ids[k]],\n              c = all[ids[(k + 1) % ids.length]],\n              ac = c.clone().sub(a),\n              ab = b.clone().sub(a);\n            if (Math.abs(ab.cross(ac)) <= epsilon * ac.length() && ab.dot(ac) >= 0 && ab.dot(ac) <= ac.lengthSq()) {\n              ids.splice(k, 1);\n              changed = true;\n              break;\n            }\n          }\n        }\n        return ids;\n      });\n      // Restore only points removed from this actual contour segment. Searching every contour\n      // point near every triangulation edge can pull unrelated points onto an internal diagonal,\n      // producing overlapping cap fans after repeated cuts.\n      const boundaryChains = new Map<string, number[]>();\n      let loopBase = 0;\n      [contour, ...holes].forEach((loop, loopIndex) => {\n        const ids = simplified[loopIndex];\n        for (let k = 0; k < ids.length; k++) {\n          const a = ids[k],\n            b = ids[(k + 1) % ids.length],\n            chain = [a];\n          let cursor = (a - loopBase + 1) % loop.length;\n          while (cursor + loopBase !== b) {\n            chain.push(cursor + loopBase);\n            cursor = (cursor + 1) % loop.length;\n          }\n          boundaryChains.set(`${a}:${b}`, chain);\n          boundaryChains.set(`${b}:${a}`, [b, ...chain.slice(1).reverse()]);\n        }\n        loopBase += loop.length;\n      });\n      const active = simplified.flat();\n      const triangles = ShapeUtils.triangulateShape(\n        simplified[0].map((j) => all[j].clone()),\n        simplified.slice(1).map((loop) => loop.map((j) => all[j].clone())),\n      ).map((face) => face.map((j) => active[j]));\n      for (const face of triangles) {\n        const ring: number[] = [];\n        for (let k = 0; k < 3; k++) {\n          const a = face[k],\n            b = face[(k + 1) % 3];\n          ring.push(...(boundaryChains.get(`${a}:${b}`) ?? [a]));\n        }\n        const center = new Vector3().add(world[face[0]]).add(world[face[1]]).add(world[face[2]]).divideScalar(3);\n        for (let k = 0; k < ring.length; k++) {\n          const a = world[ring[k]],\n            b = world[ring[(k + 1) % ring.length]];\n          const cross = a.clone().sub(center).cross(b.clone().sub(center));\n          if (cross.length() <= epsilon * epsilon) continue;\n          capArea += cross.length() * 0.5 * scale * scale;\n          const positive = cross.dot(normal) > 0 ? [center, b, a] : [center, a, b];\n          for (let half = 0; half < 2; half++) {\n            const ordered = half === 0 ? positive : [...positive].reverse();\n            emit(\n              halves[half],\n              ordered.map((p) => ({\n                p,\n                n: normal.clone().multiplyScalar(half === 0 ? -1 : 1),\n                uv: new Vector2(p.dot(u) * scale, p.dot(v) * scale),\n              })),\n              capMaterialIndex,\n            );\n          }\n        }\n      }\n    });\n  const capCoords = halves.flatMap((faces) =>\n    faces.filter((f) => f.material === capMaterialIndex).flatMap((f) => f.v.map((c) => c.uv)),\n  );\n  const cosine = Math.cos(uvRotation),\n    sine = Math.sin(uvRotation);\n  let minU = Infinity,\n    minV = Infinity,\n    maxU = -Infinity,\n    maxV = -Infinity;\n  for (const uv of capCoords) {\n    const x = uv.x,\n      y = uv.y;\n    uv.set(x * cosine - y * sine, x * sine + y * cosine);\n    minU = Math.min(minU, uv.x);\n    minV = Math.min(minV, uv.y);\n    maxU = Math.max(maxU, uv.x);\n    maxV = Math.max(maxV, uv.y);\n  }\n  for (const uv of capCoords) {\n    uv.set(\n      uvMode === \"fit\" ? (uv.x - minU) / (maxU - minU || 1) : uv.x / uvUnits,\n      uvMode === \"fit\" ? (uv.y - minV) / (maxV - minV || 1) : uv.y / uvUnits,\n    ).add(uvOffset);\n    if (![uv.x, uv.y].every((v) => Number.isFinite(Math.fround(v))))\n      throw new RangeError(\"sliceGeometry: cap UVs exceed Float32 range.\");\n  }\n  const build = (faces: Face[]) => {\n    const geometry = new BufferGeometry(),\n      p: number[] = [],\n      n: number[] = [],\n      tex: number[] = [];\n    const materials = [...new Set(faces.map((f) => f.material))].sort((a, b) => a - b);\n    for (const material of materials) {\n      const start = p.length / 3;\n      for (const face of faces)\n        if (face.material === material)\n          for (const corner of face.v) {\n            const point = corner.p.clone().multiplyScalar(scale).add(origin);\n            p.push(...point.toArray());\n            n.push(...corner.n.toArray());\n            tex.push(...corner.uv.toArray());\n          }\n      geometry.addGroup(start, p.length / 3 - start, material);\n    }\n    if (n.some((value) => !Number.isFinite(value)) || tex.some((value) => !Number.isFinite(value)))\n      throw new RangeError(\"sliceGeometry: nonfinite interpolated attribute.\");\n    for (let i = 0; i < n.length; i += 3)\n      if (Math.hypot(n[i], n[i + 1], n[i + 2]) < 0.5) throw new RangeError(\"sliceGeometry: interpolated normal is undefined.\");\n    geometry.setAttribute(\"position\", new Float32BufferAttribute(p, 3));\n    geometry.setAttribute(\"normal\", new Float32BufferAttribute(n, 3));\n    geometry.setAttribute(\"uv\", new Float32BufferAttribute(tex, 2));\n    geometry.computeBoundingBox();\n    geometry.computeBoundingSphere();\n    return geometry;\n  };\n  const positive = build(halves[0]);\n  let negative: BufferGeometry | undefined;\n  try {\n    negative = build(halves[1]);\n    for (const [geometry, sign] of [\n      [positive, 1],\n      [negative, -1],\n    ] as const) {\n      const p = geometry.getAttribute(\"position\"),\n        rendered: Vector3[][] = [];\n      for (let i = 0; i < p.count; i += 3) {\n        const tri = [0, 1, 2].map((k) =>\n          new Vector3()\n            .fromBufferAttribute(p, i + k)\n            .sub(origin)\n            .divideScalar(scale),\n        );\n        const n = tri[1].clone().sub(tri[0]).cross(tri[2].clone().sub(tri[0]));\n        if (!tri.every((v) => v.toArray().every(Number.isFinite)) || n.length() <= epsilon * epsilon)\n          throw new RangeError(\"sliceGeometry: Float32 output collapsed; rescale or recenter the source.\");\n        const group = geometry.groups.find((g) => i >= g.start && i < g.start + g.count);\n        if (group?.materialIndex === capMaterialIndex && n.dot(normal) * sign >= 0)\n          throw new RangeError(\"sliceGeometry: inverted cap triangle.\");\n        rendered.push(tri);\n      }\n      validateEdges(rendered, key, cap ? null : plane, epsilon * 4);\n    }\n  } catch (error) {\n    positive.dispose();\n    negative?.dispose();\n    throw error;\n  }\n  return {\n    positive,\n    negative,\n    diagnostics: { discardedDegenerateTriangles, selfIntersectionsChecked: false },\n    loops: loops.map((loop) => loop.map((p) => p.clone().multiplyScalar(scale).add(origin))),\n    holes: contours.filter((_, i) => depth(i) % 2 === 1).length,\n    capArea,\n    capMaterialIndex,\n  };\n}\n","import { BufferGeometry, Plane, Vector3 } from \"three\";\nimport { inspectGeometry, type GeometryInspection } from \"./InspectGeometry\";\nimport { sliceGeometry, type SliceCapUVOptions } from \"./SliceGeometry\";\n\nexport interface ConvexEdge {\n  /** Endpoints in ConvexEdgeTopology.points. Edges are original mesh segments, not merged polylines. */\n  a: number;\n  b: number;\n  normals: [Vector3, Vector3];\n  /** Angle between outward incident face normals in radians. */\n  angle: number;\n}\nexport interface ConvexEdgeTopology {\n  points: Vector3[];\n  /** Array index is the edge ID, valid only for this source's unchanged position/index buffers. */\n  edges: ConvexEdge[];\n}\nexport interface ChamferConvexGeometryOptions {\n  /** Nonnegative face setback in geometry-local units, not rolling-ball radius. */\n  width: number;\n  /** Explicit original edge IDs returned by convexEdges. Empty selects nothing; duplicates are removed. */\n  edgeIds: readonly number[];\n  /** Projection policy for new chamfer faces; forwarded to slicing. */\n  capUV?: SliceCapUVOptions;\n}\nexport interface ChamferConvexGeometryResult extends ConvexEdgeTopology {\n  geometry: BufferGeometry;\n  report: GeometryInspection;\n  selected: number[];\n  /** Shared material index for new chamfers; existing source group indices are retained. */\n  chamferMaterialIndex: number;\n}\n/** Geometric edge topology of one outward closed convex triangle mesh. Coplanar diagonals are excluded. */\nexport function convexEdges(source: BufferGeometry): ConvexEdgeTopology {\n  const r = inspectGeometry(source);\n  if (\n    r.degenerate.length ||\n    r.duplicate.length ||\n    r.components.length !== 1 ||\n    !r.components[0].closed ||\n    !(r.components[0].signedVolume! > 0)\n  )\n    throw new Error(\"Expected one outward closed solid.\");\n  const map = new Map<string, { a: number; b: number; normals: Vector3[] }>();\n  for (const tri of r.triangles) {\n    const [a, b, c] = tri.map((i) => r.points[i]),\n      n = b.clone().sub(a).divideScalar(r.tolerance).cross(c.clone().sub(a).divideScalar(r.tolerance)).normalize();\n    if (r.points.some((p) => p.clone().sub(a).dot(n) > r.tolerance * 4)) throw new Error(\"convexEdges requires a convex source.\");\n    for (let k = 0; k < 3; k++) {\n      const a = Math.min(tri[k], tri[(k + 1) % 3]),\n        b = Math.max(tri[k], tri[(k + 1) % 3]),\n        key = `${a},${b}`;\n      const e = map.get(key) ?? { a, b, normals: [] };\n      e.normals.push(n);\n      map.set(key, e);\n    }\n  }\n  return {\n    points: r.points,\n    edges: [...map.values()]\n      .filter((e) => e.normals.length === 2 && e.normals[0].dot(e.normals[1]) < 1 - 1e-6)\n      .map((e) => ({\n        ...e,\n        normals: e.normals as [Vector3, Vector3],\n        angle: Math.acos(Math.max(-1, Math.min(1, e.normals[0].dot(e.normals[1])))),\n      })),\n  };\n}\n\n/** Sequential plane chamfers on selected original edges of a convex solid. Not rounded beveling.\n * Retains the intersection of the selected halfspaces; large widths can remove unrelated features.\n */\nexport function chamferConvexGeometry(\n  source: BufferGeometry,\n  options: ChamferConvexGeometryOptions,\n): ChamferConvexGeometryResult {\n  const { width, edgeIds, capUV } = options;\n  if (!Number.isFinite(width) || width < 0) throw new RangeError(\"Width must be finite and nonnegative.\");\n  if (!Array.isArray(edgeIds)) throw new RangeError(\"Explicit edgeIds are required.\");\n  if (\n    Object.keys(source.morphAttributes).length ||\n    Object.keys(source.attributes).some((k) => ![\"position\", \"normal\", \"uv\"].includes(k))\n  )\n    throw new RangeError(\"Only position, normal and uv attributes are supported; no morphs.\");\n  const topology = convexEdges(source),\n    { edges, points } = topology;\n  if (edgeIds.some((id) => !Number.isInteger(id) || id < 0 || id >= edges.length))\n    throw new RangeError(\"Invalid original edge ID.\");\n  const selected = [...new Set(edgeIds)].sort((a, b) => a - b);\n  // Run slicing's attribute/group/option validation even for a no-op, using a tangent supporting plane.\n  let maximumX = -Infinity;\n  points.forEach((p) => {\n    maximumX = Math.max(maximumX, p.x);\n  });\n  const probe = sliceGeometry(source, new Plane(new Vector3(1, 0, 0), -maximumX), { capUV });\n  probe.positive.dispose();\n  probe.negative.dispose();\n  let maximumMaterial = 0;\n  source.groups.forEach((g) => {\n    maximumMaterial = Math.max(maximumMaterial, g.materialIndex ?? 0);\n  });\n  const chamferMaterialIndex = maximumMaterial + 1;\n  if (!Number.isSafeInteger(chamferMaterialIndex)) throw new RangeError(\"No safe material index remains for chamfers.\");\n  let geometry = source.clone();\n  if (!geometry.groups.length) geometry.addGroup(0, geometry.index?.count ?? geometry.getAttribute(\"position\").count, 0);\n  try {\n    if (width > 0)\n      for (const id of selected) {\n        const e = edges[id],\n          normal = e.normals[0].clone().add(e.normals[1]).normalize();\n        const setback = width * Math.sin(e.angle / 2);\n        const cut = sliceGeometry(geometry, new Plane(normal, -normal.dot(points[e.a]) + setback), { capUV });\n        cut.positive.dispose();\n        geometry.dispose();\n        geometry = cut.negative;\n        geometry.groups.forEach((g) => {\n          if (g.materialIndex === cut.capMaterialIndex) g.materialIndex = chamferMaterialIndex;\n        });\n        if (!geometry.getAttribute(\"position\").count) throw new Error(\"Width consumes the solid; reduce it.\");\n      }\n    const report = inspectGeometry(geometry);\n    if (\n      report.degenerate.length ||\n      report.components.length !== 1 ||\n      !report.components[0].closed ||\n      !(report.components[0].signedVolume! > 0)\n    )\n      throw new Error(\"Result failed closed-solid inspection.\");\n    geometry.computeBoundingBox();\n    geometry.computeBoundingSphere();\n    return { geometry, report, selected, chamferMaterialIndex, ...topology };\n  } catch (error) {\n    geometry.dispose();\n    throw error;\n  }\n}\n","import { Vector2 } from \"three\";\nimport { type GeometryBuffers, pushQuad, type Vec3 } from \"./GeometryBuffers\";\n\n/** End extension and cut normal for a rectangular XZ member. */\nexport interface PrismEnd {\n  /** Outward extension from this endpoint, along the member run. */\n  reach?: number;\n  /** Cut-plane normal in XZ plan; omitted normal gives a square end. */\n  wall?: Vector2;\n}\n\n/**\n * Append a rectangular beam spanning two XZ points, between heights y0 and y1.\n * End normals shear the corners along the run to meet an angled surface.\n * Parallel cuts fall back to square ends; other shears are limited to twice the width.\n * Appends six quads; degenerate spans, nonpositive widths, and inverted heights add nothing.\n */\nexport function pushMiteredPrism(\n  buffers: GeometryBuffers,\n  from: Vector2,\n  to: Vector2,\n  width: number,\n  y0: number,\n  y1: number,\n  near: PrismEnd = {},\n  far: PrismEnd = {},\n): void {\n  const run = to.clone().sub(from);\n  if (run.lengthSq() < 1e-12 || width <= 0 || y1 <= y0) return;\n  const dir = run.clone().normalize();\n  //  Across the member, in plan. The world equivalent of `dir × up`.\n  const side = new Vector2(-dir.y, dir.x);\n  const half = width / 2;\n\n  const shear = (wall: Vector2 | undefined): number => {\n    if (!wall) return 0;\n    const grazing = dir.dot(wall);\n    if (Math.abs(grazing) < 1e-6) return 0;\n    return Math.min(Math.max((-half * side.dot(wall)) / grazing, -width * 2), width * 2);\n  };\n\n  const heel = from.clone().addScaledVector(dir, -(near.reach ?? 0));\n  const seat = to.clone().addScaledVector(dir, far.reach ?? 0);\n  const nearSkew = shear(near.wall);\n  const farSkew = shear(far.wall);\n\n  const a0 = heel.clone().addScaledVector(side, half).addScaledVector(dir, nearSkew);\n  const a1 = heel.clone().addScaledVector(side, -half).addScaledVector(dir, -nearSkew);\n  const b0 = seat.clone().addScaledVector(side, half).addScaledVector(dir, farSkew);\n  const b1 = seat.clone().addScaledVector(side, -half).addScaledVector(dir, -farSkew);\n\n  const lo = (p: Vector2): Vec3 => [p.x, y0, p.y];\n  const hi = (p: Vector2): Vec3 => [p.x, y1, p.y];\n  pushQuad(buffers, [lo(a0), lo(b0), hi(b0), hi(a0)], undefined);\n  pushQuad(buffers, [lo(a1), hi(a1), hi(b1), lo(b1)], undefined);\n  pushQuad(buffers, [lo(a0), lo(a1), lo(b1), lo(b0)], undefined);\n  pushQuad(buffers, [hi(a0), hi(b0), hi(b1), hi(a1)], undefined);\n  pushQuad(buffers, [lo(a0), hi(a0), hi(a1), lo(a1)], undefined);\n  pushQuad(buffers, [lo(b0), lo(b1), hi(b1), hi(b0)], undefined);\n}\n\n/** A surface's normal in plan, from the direction it runs. */\nexport function wallNormal(tangent: Vector2): Vector2 {\n  return new Vector2(tangent.y, -tangent.x);\n}\n","import { BufferGeometry, Plane, Vector3 } from \"three\";\nimport { sliceGeometry, type SliceGeometryOptions } from \"./SliceGeometry\";\n\n/** Closed cuts always produce caps. UV options affect only newly created faces. */\nexport type ClipGeometryByPlanesOptions = Omit<SliceGeometryOptions, \"cap\">;\nexport interface GeometrySection {\n  /** Normalized copy in geometry-local coordinates. */\n  plane: Plane;\n  /** Closed contours without repeated endpoints; includes hole contours. */\n  loops: Vector3[][];\n  holes: number;\n  /** Enclosed section area, subtracting holes, in source units squared. */\n  area: number;\n}\nexport interface PlaneGeometryCut extends GeometrySection {\n  planeIndex: number;\n  /** Reserved by input plane order, even if an earlier cap disappears. */\n  capMaterialIndex: number;\n}\nexport interface ClipGeometryByPlanesResult {\n  /** Caller-owned retained negative halfspace intersection. May be empty. */\n  geometry: BufferGeometry;\n  /** Caller-owned positive halves, one per executed cut, including empty halves. */\n  offcuts: BufferGeometry[];\n  /** Historical sections at each step; later cuts do not update these contours. */\n  cuts: PlaneGeometryCut[];\n}\nfunction normalizedPlane(plane: Plane): Plane {\n  const length = plane.normal.length();\n  if (!Number.isFinite(length) || length === 0 || !Number.isFinite(plane.constant))\n    throw new RangeError(\"Plane workflows: expected a finite plane with a nonzero normal.\");\n  return plane.clone().normalize();\n}\n/**\n * Extract an independent cross-section of a closed triangle mesh. Source and plane are unchanged.\n * Uses capped slicing internally and disposes its temporary solids. Tangent/coplanar contact follows\n * sliceGeometry's rules; this is not an arbitrary surface-intersection operation.\n */\nexport function sectionGeometry(\n  source: BufferGeometry,\n  plane: Plane,\n  options: Pick<SliceGeometryOptions, \"tolerance\"> = {},\n): GeometrySection {\n  const normalized = normalizedPlane(plane);\n  const result = sliceGeometry(source, normalized, { tolerance: options.tolerance, cap: true });\n  try {\n    return { plane: normalized, loops: result.loops, holes: result.holes, area: result.capArea };\n  } finally {\n    result.positive.dispose();\n    result.negative.dispose();\n  }\n}\n/**\n * Sequentially retain distanceToPoint <= 0 for each plane. Outward planes describe a convex cutting\n * region; the source itself need not be convex. Stops when the retained geometry becomes empty.\n * Preserves source materials, normals and UVs under sliceGeometry's input constraints. New cap\n * indices start above all source materials. All returned geometries belong to the caller.\n * An empty plane list returns an unchanged clone after validating the source and options.\n */\nexport function clipGeometryByPlanes(\n  source: BufferGeometry,\n  planes: readonly Plane[],\n  options: ClipGeometryByPlanesOptions = {},\n): ClipGeometryByPlanesResult {\n  const normalized = planes.map(normalizedPlane);\n  const firstCap = Math.max(0, ...source.groups.map((g) => g.materialIndex ?? 0)) + 1;\n  if (!Number.isSafeInteger(firstCap + Math.max(0, planes.length - 1)))\n    throw new RangeError(\"clipGeometryByPlanes: cap material indices exceed safe integers.\");\n  if (!normalized.length) {\n    // A separating plane exercises the same source/options validation without requiring a cut.\n    const positions = source.getAttribute(\"position\");\n    let maximum = -Infinity;\n    if (positions) for (let i = 0; i < positions.count; i++) maximum = Math.max(maximum, positions.getX(i));\n    const probe = sliceGeometry(source, new Plane(new Vector3(1, 0, 0), -maximum), { ...options, cap: true });\n    probe.positive.dispose();\n    probe.negative.dispose();\n  }\n  let geometry = source.clone();\n  const offcuts: BufferGeometry[] = [],\n    cuts: PlaneGeometryCut[] = [];\n  try {\n    for (const [planeIndex, plane] of normalized.entries()) {\n      if (planeIndex > 0 && !geometry.getAttribute(\"position\").count) break;\n      const cut = sliceGeometry(geometry, plane, { ...options, cap: true });\n      geometry.dispose();\n      geometry = cut.negative;\n      offcuts.push(cut.positive);\n      const capMaterialIndex = firstCap + planeIndex;\n      for (const part of [geometry, cut.positive])\n        for (const group of part.groups) if (group.materialIndex === cut.capMaterialIndex) group.materialIndex = capMaterialIndex;\n      cuts.push({ plane, planeIndex, capMaterialIndex, loops: cut.loops, holes: cut.holes, area: cut.capArea });\n    }\n    return { geometry, offcuts, cuts };\n  } catch (error) {\n    geometry.dispose();\n    offcuts.forEach((part) => part.dispose());\n    throw new Error(`clipGeometryByPlanes: plane ${cuts.length + 1}: ${(error as Error).message}`);\n  }\n}\n","import { Box3, BufferGeometry, Float32BufferAttribute, Vector2, Vector3 } from \"three\";\nimport { inspectGeometry, type GeometryInspection } from \"./InspectGeometry\";\n\n/** Bounded BSP Boolean operations on closed triangle meshes. */\nexport type BooleanOperation = \"Union\" | \"Intersection\" | \"Subtract\";\nexport interface BooleanGeometryOptions {\n  /** Default throw disposes output that fails topology checks; report returns it for inspection. */\n  onInvalid?: \"throw\" | \"report\";\n  /** Optional operand colors. Otherwise A indices are preserved and B indices are offset above A. */\n  materialIndices?: { a: number; b: number };\n}\nexport interface BooleanGeometryResult {\n  /** Caller-owned geometry, possibly empty. Inputs remain unchanged. */\n  geometry: BufferGeometry;\n  /** Add this to B's source material indices. Null when materialIndices overrides both operands. */\n  materialOffsetB: number | null;\n  diagnostics: {\n    /** Closure, winding, degeneracy and volume checks only; not a global solid certificate. */\n    topologyValid: boolean;\n    /** Null for an empty result. */\n    inspection: GeometryInspection | null;\n    selfIntersectionsChecked: false;\n  };\n}\nconst EPS = 1e-6;\ntype Vertex = { p: Vector3; n: Vector3; uv: Vector2 };\ntype Polygon = { vertices: Vertex[]; normal: Vector3; w: number; material: number };\nconst copyVertex = (v: Vertex): Vertex => ({ p: v.p.clone(), n: v.n.clone(), uv: v.uv.clone() });\nfunction polygon(vertices: Vertex[], material: number): Polygon | null {\n  const clean = vertices.filter(\n    (v, i) => v.p.distanceToSquared(vertices[(i + vertices.length - 1) % vertices.length].p) > EPS * EPS,\n  );\n  if (clean.length < 3) return null;\n  const normal = new Vector3();\n  for (let i = 1; i < clean.length - 1; i++) {\n    normal.crossVectors(clean[i].p.clone().sub(clean[0].p), clean[i + 1].p.clone().sub(clean[0].p));\n    if (normal.lengthSq() > EPS ** 4) break;\n  }\n  if (normal.lengthSq() <= EPS ** 4) return null;\n  normal.normalize();\n  return { vertices: clean, normal, w: normal.dot(clean[0].p), material };\n}\nclass Tree {\n  normal?: Vector3;\n  w = 0;\n  faces: Polygon[] = [];\n  front?: Tree;\n  back?: Tree;\n  constructor(\n    private budget: { splits: number },\n    polygons: Polygon[] = [],\n    depth = 0,\n  ) {\n    this.build(polygons, depth);\n  }\n  split(face: Polygon, coplanarFront: Polygon[], coplanarBack: Polygon[], front: Polygon[], back: Polygon[]) {\n    if (++this.budget.splits > 2000000) throw new Error(\"BSP split budget exceeded; use simpler operands.\");\n    const distances = face.vertices.map((v) => this.normal!.dot(v.p) - this.w);\n    const types = distances.map((d) => (d > EPS ? 1 : d < -EPS ? 2 : 0));\n    const type = types.reduce<number>((a, b) => a | b, 0);\n    if (type === 0) {\n      (this.normal!.dot(face.normal) >= 0 ? coplanarFront : coplanarBack).push(face);\n      return;\n    }\n    if (type === 1) {\n      front.push(face);\n      return;\n    }\n    if (type === 2) {\n      back.push(face);\n      return;\n    }\n    const f: Vertex[] = [],\n      b: Vertex[] = [];\n    for (let i = 0; i < face.vertices.length; i++) {\n      const j = (i + 1) % face.vertices.length,\n        a = face.vertices[i],\n        next = face.vertices[j];\n      if (types[i] !== 2) f.push(copyVertex(a));\n      if (types[i] !== 1) b.push(copyVertex(a));\n      if ((types[i] | types[j]) === 3) {\n        const t = distances[i] / (distances[i] - distances[j]);\n        const v = {\n          p: a.p.clone().lerp(next.p, t),\n          n: a.n.clone().lerp(next.n, t).normalize(),\n          uv: a.uv.clone().lerp(next.uv, t),\n        };\n        f.push(v);\n        b.push(copyVertex(v));\n      }\n    }\n    const fp = polygon(f, face.material),\n      bp = polygon(b, face.material);\n    if (fp) front.push(fp);\n    if (bp) back.push(bp);\n  }\n  build(polygons: Polygon[], depth = 0) {\n    if (!polygons.length) return;\n    if (depth > 180) throw new Error(\"BSP depth limit exceeded; use simpler operands.\");\n    if (!this.normal) {\n      this.normal = polygons[0].normal.clone();\n      this.w = polygons[0].w;\n    }\n    const front: Polygon[] = [],\n      back: Polygon[] = [];\n    for (const p of polygons) this.split(p, this.faces, this.faces, front, back);\n    if (front.length) {\n      this.front ??= new Tree(this.budget);\n      this.front.build(front, depth + 1);\n    }\n    if (back.length) {\n      this.back ??= new Tree(this.budget);\n      this.back.build(back, depth + 1);\n    }\n  }\n  clip(polygons: Polygon[]): Polygon[] {\n    if (!this.normal) return polygons;\n    let front: Polygon[] = [],\n      back: Polygon[] = [];\n    for (const p of polygons) this.split(p, front, back, front, back);\n    if (this.front) front = this.front.clip(front);\n    back = this.back ? this.back.clip(back) : [];\n    return front.concat(back);\n  }\n  clipTo(other: Tree) {\n    this.faces = other.clip(this.faces);\n    this.front?.clipTo(other);\n    this.back?.clipTo(other);\n  }\n  invert() {\n    for (const p of this.faces) {\n      p.vertices.reverse();\n      p.vertices.forEach((v) => v.n.negate());\n      p.normal.negate();\n      p.w = -p.w;\n    }\n    this.normal?.negate();\n    this.w = -this.w;\n    this.front?.invert();\n    this.back?.invert();\n    [this.front, this.back] = [this.back, this.front];\n  }\n  all(): Polygon[] {\n    return this.faces.concat(this.front?.all() ?? [], this.back?.all() ?? []);\n  }\n}\nfunction validateInput(g: BufferGeometry): void {\n  const positions = g.getAttribute(\"position\");\n  const count = g.index?.count ?? positions?.count ?? 0;\n  if (!positions || positions.itemSize !== 3 || !positions.count || count % 3 || count > 4500)\n    throw new RangeError(\"booleanGeometry: expected 1–1500 complete input triangles per operand.\");\n  if (\n    Object.keys(g.attributes).some((k) => ![\"position\", \"normal\", \"uv\"].includes(k)) ||\n    Object.keys(g.morphAttributes).length ||\n    g.drawRange.start !== 0 ||\n    (g.drawRange.count !== Infinity && g.drawRange.count !== count)\n  )\n    throw new RangeError(\"booleanGeometry: only complete position/normal/UV meshes are supported.\");\n  for (const name of [\"position\", \"normal\", \"uv\"] as const) {\n    const attribute = g.getAttribute(name);\n    if (!attribute) continue;\n    const size = name === \"uv\" ? 2 : 3;\n    if (attribute.itemSize !== size || attribute.count !== positions.count)\n      throw new RangeError(\"booleanGeometry: mismatched attribute size/count.\");\n    for (let i = 0; i < attribute.count; i++) {\n      for (let j = 0; j < size; j++)\n        if (!Number.isFinite(attribute.getComponent(i, j))) throw new RangeError(\"booleanGeometry: nonfinite attribute.\");\n      if (name === \"normal\" && Math.hypot(attribute.getX(i), attribute.getY(i), attribute.getZ(i)) === 0)\n        throw new RangeError(\"booleanGeometry: zero source normal.\");\n    }\n  }\n  let end = 0;\n  for (const group of [...g.groups].sort((a, b) => a.start - b.start)) {\n    if (\n      !Number.isInteger(group.start) ||\n      !Number.isInteger(group.count) ||\n      group.start < end ||\n      group.count <= 0 ||\n      group.start % 3 ||\n      group.count % 3 ||\n      group.start + group.count > count ||\n      !Number.isSafeInteger(group.materialIndex ?? 0) ||\n      (group.materialIndex ?? 0) < 0\n    )\n      throw new RangeError(\"booleanGeometry: invalid or overlapping material groups.\");\n    end = group.start + group.count;\n  }\n  const report = inspectGeometry(g);\n  if (\n    !report.components.length ||\n    !cleanTopology(report) ||\n    report.components.some((c) => c.signedVolume === null || c.signedVolume <= 0)\n  )\n    throw new Error(\n      \"booleanGeometry: operands must be closed, outward-wound, nondegenerate shells; inward cavity shells are unsupported as inputs.\",\n    );\n}\nfunction cleanTopology(report: GeometryInspection): boolean {\n  return (\n    !report.boundary.length &&\n    !report.winding.length &&\n    !report.nonManifold.length &&\n    !report.nonManifoldVertices.length &&\n    !report.degenerate.length &&\n    !report.duplicate.length &&\n    report.components.every((c) => c.closed && c.signedVolume !== null)\n  );\n}\nfunction inputPolygons(g: BufferGeometry, materialAt: (triangleOffset: number) => number, center: Vector3, scale: number) {\n  const positions = g.getAttribute(\"position\"),\n    normals = g.getAttribute(\"normal\"),\n    uv = g.getAttribute(\"uv\");\n  const result: Polygon[] = [];\n  for (let i = 0; i < (g.index?.count ?? positions.count); i += 3) {\n    const vertices = [0, 1, 2].map((k) => {\n      const j = g.index ? g.index.getX(i + k) : i + k;\n      return {\n        p: new Vector3().fromBufferAttribute(positions, j).sub(center).divideScalar(scale),\n        n: normals ? new Vector3().fromBufferAttribute(normals, j).normalize() : new Vector3(),\n        uv: uv ? new Vector2(uv.getX(j), uv.getY(j)) : new Vector2(),\n      };\n    });\n    const p = polygon(vertices, materialAt(i));\n    if (p) {\n      if (!normals) p.vertices.forEach((v) => v.n.copy(p.normal));\n      result.push(p);\n    }\n  }\n  return result;\n}\nfunction render(polygons: Polygon[], center: Vector3, scale: number) {\n  if (polygons.length > 12000) throw new Error(\"booleanGeometry: polygon limit exceeded.\");\n  // Splits on one face can terminate on a neighboring unsplit edge. Insert those shared\n  // boundary vertices before triangulating, avoiding render-only T-junctions.\n  const points = new Map<string, Vector3>();\n  const key = (p: Vector3) =>\n    p\n      .toArray()\n      .map((v) => Math.round(v / EPS))\n      .join(\":\");\n  polygons.forEach((p) =>\n    p.vertices.forEach((v) => {\n      if (!points.has(key(v.p))) points.set(key(v.p), v.p);\n    }),\n  );\n  const candidates = [...points.values()];\n  if (candidates.length * polygons.reduce((s, p) => s + p.vertices.length, 0) > 12000000)\n    throw new Error(\"booleanGeometry: boundary reconciliation budget exceeded.\");\n  const positions: number[] = [],\n    normals: number[] = [],\n    uvs: number[] = [];\n  const geometry = new BufferGeometry();\n  for (const face of polygons.sort((a, b) => a.material - b.material)) {\n    const ring: Vertex[] = [];\n    for (let i = 0; i < face.vertices.length; i++) {\n      const a = face.vertices[i],\n        b = face.vertices[(i + 1) % face.vertices.length],\n        edge = b.p.clone().sub(a.p),\n        length = edge.lengthSq();\n      const hits = [{ t: 0, p: a.p }];\n      for (const p of candidates) {\n        const t = p.clone().sub(a.p).dot(edge) / length;\n        if (t > EPS && t < 1 - EPS && a.p.clone().addScaledVector(edge, t).distanceToSquared(p) < EPS * EPS) hits.push({ t, p });\n      }\n      hits.sort((x, y) => x.t - y.t);\n      for (const hit of hits)\n        if (!ring.length || ring[ring.length - 1].p.distanceToSquared(hit.p) > EPS * EPS)\n          ring.push({ p: hit.p.clone(), n: a.n.clone().lerp(b.n, hit.t).normalize(), uv: a.uv.clone().lerp(b.uv, hit.t) });\n    }\n    if (ring.length > 1 && ring[0].p.distanceToSquared(ring[ring.length - 1].p) <= EPS * EPS) ring.pop();\n    if (ring.length < 3) continue;\n    const middle: Vertex = { p: new Vector3(), n: new Vector3(), uv: new Vector2() };\n    ring.forEach((v) => {\n      middle.p.add(v.p);\n      middle.n.add(v.n);\n      middle.uv.add(v.uv);\n    });\n    middle.p.divideScalar(ring.length);\n    middle.n.normalize();\n    middle.uv.divideScalar(ring.length);\n    const start = positions.length / 3;\n    for (let i = 0; i < ring.length; i++)\n      for (const v of [middle, ring[i], ring[(i + 1) % ring.length]]) {\n        positions.push(...v.p.clone().multiplyScalar(scale).add(center).toArray());\n        normals.push(...(v.n.lengthSq() ? v.n : face.normal).toArray());\n        uvs.push(v.uv.x, v.uv.y);\n      }\n    const count = positions.length / 3 - start;\n    const previous = geometry.groups[geometry.groups.length - 1];\n    if (previous?.materialIndex === face.material) previous.count += count;\n    else geometry.addGroup(start, count, face.material);\n  }\n  geometry.setAttribute(\"position\", new Float32BufferAttribute(positions, 3));\n  geometry.setAttribute(\"normal\", new Float32BufferAttribute(normals, 3));\n  geometry.setAttribute(\"uv\", new Float32BufferAttribute(uvs, 2));\n  return geometry;\n}\n/**\n * Union, intersection or A-minus-B in a shared geometry-local frame. No external CSG dependency.\n * A bounded, tolerance-based BSP solver for small meshes (1500 triangles per input), not exact CSG.\n * Empty results are supported; empty inputs and inward cavity shells as inputs are rejected.\n * Output normals/UVs interpolate source attributes; new cavity faces inherit B and reverse winding.\n * Caller disposes result.geometry. Default failure mode never returns known-invalid topology.\n */\nexport function booleanGeometry(\n  a: BufferGeometry,\n  b: BufferGeometry,\n  operation: BooleanOperation,\n  { onInvalid = \"throw\", materialIndices }: BooleanGeometryOptions = {},\n): BooleanGeometryResult {\n  if (![\"Union\", \"Intersection\", \"Subtract\"].includes(operation)) throw new RangeError(\"booleanGeometry: unknown operation.\");\n  if (![\"throw\", \"report\"].includes(onInvalid)) throw new RangeError(\"booleanGeometry: invalid onInvalid mode.\");\n  if (materialIndices && ![materialIndices.a, materialIndices.b].every((i) => Number.isSafeInteger(i) && i >= 0))\n    throw new RangeError(\"booleanGeometry: invalid operand material indices.\");\n  validateInput(a);\n  validateInput(b);\n  const materialOffsetB = materialIndices ? null : Math.max(0, ...a.groups.map((g) => g.materialIndex ?? 0)) + 1;\n  if (\n    materialOffsetB !== null &&\n    !Number.isSafeInteger(materialOffsetB + Math.max(0, ...b.groups.map((g) => g.materialIndex ?? 0)))\n  )\n    throw new RangeError(\"booleanGeometry: material indices exceed safe integers.\");\n  const materialAt = (source: BufferGeometry, offset: number, override?: number) => (i: number) =>\n    override ?? (source.groups.find((g) => i >= g.start && i < g.start + g.count)?.materialIndex ?? 0) + offset;\n  const bounds = new Box3();\n  for (const g of [a, b]) {\n    const p = g.getAttribute(\"position\");\n    if (!p) throw new Error(\"Missing positions.\");\n    for (let i = 0; i < p.count; i++) bounds.expandByPoint(new Vector3().fromBufferAttribute(p, i));\n  }\n  const center = bounds.getCenter(new Vector3()),\n    scale = bounds.getSize(new Vector3()).length();\n  if (!Number.isFinite(scale) || scale <= 0) throw new Error(\"Invalid operand extent.\");\n  const budget = { splits: 0 },\n    left = new Tree(budget, inputPolygons(a, materialAt(a, 0, materialIndices?.a), center, scale)),\n    right = new Tree(budget, inputPolygons(b, materialAt(b, materialOffsetB ?? 0, materialIndices?.b), center, scale));\n  if (operation === \"Union\") {\n    left.clipTo(right);\n    right.clipTo(left);\n    right.invert();\n    right.clipTo(left);\n    right.invert();\n    left.build(right.all());\n  } else if (operation === \"Subtract\") {\n    left.invert();\n    left.clipTo(right);\n    right.clipTo(left);\n    right.invert();\n    right.clipTo(left);\n    right.invert();\n    left.build(right.all());\n    left.invert();\n  } else {\n    left.invert();\n    right.clipTo(left);\n    right.invert();\n    left.clipTo(right);\n    right.clipTo(left);\n    left.build(right.all());\n    left.invert();\n  }\n  const geometry = render(left.all(), center, scale);\n  try {\n    for (const name of [\"position\", \"normal\", \"uv\"] as const) {\n      const attribute = geometry.getAttribute(name);\n      for (let i = 0; i < attribute.count; i++)\n        for (let j = 0; j < attribute.itemSize; j++)\n          if (!Number.isFinite(attribute.getComponent(i, j)))\n            throw new Error(\"booleanGeometry: output exceeds finite Float32 storage.\");\n    }\n    const inspection = geometry.getAttribute(\"position\").count ? inspectGeometry(geometry) : null;\n    const topologyValid =\n      inspection === null ||\n      (cleanTopology(inspection) && inspection.components.reduce((sum, c) => sum + (c.signedVolume ?? 0), 0) > 0);\n    if (!topologyValid && onInvalid === \"throw\")\n      throw new Error(\"booleanGeometry: result failed topology validation; use onInvalid: report to inspect it.\");\n    return { geometry, materialOffsetB, diagnostics: { topologyValid, inspection, selfIntersectionsChecked: false } };\n  } catch (error) {\n    geometry.dispose();\n    throw error;\n  }\n}\n","/**\n * Project by dropping the named coordinate; output remains in source units.\n *\n * ```\n * const vertices = [\n *   [-1, -1, 1],\n *   [1, -1, 1],\n *   [-1, 1, 1],\n *   [1, 1, 1],\n * ];\n *\n * const uvs = planarUVMapping(vertices, 'z'); // Project onto the Z-axis\n * ```\n */\nexport function planarUVMapping(vertices: [number, number, number][], axis: 'x' | 'y' | 'z'): [number, number][] {\n  return vertices.map(([x, y, z]) => {\n    switch (axis) {\n      case 'x': return [y, z]; // Use Y and Z for X-axis projection\n      case 'y': return [x, z]; // Use X and Z for Y-axis projection\n      case 'z': return [x, y]; // Use X and Y for Z-axis projection\n      default: throw new Error(`Invalid axis: ${axis}`);\n    }\n  });\n}\n\n/**\n * Choose a coordinate projection from the largest absolute position component; this does not use face normals.\n *\n * ```\n * const cubeVertices = [\n *   [-1, -1, 1],\n *   [1, -1, 1],\n *   [-1, 1, 1],\n *   [1, 1, 1],\n * ];\n *\n * const cubeUVs = cubicUVMappingBatch(cubeVertices);\n * ```\n */\nexport function cubicUVMapping(vertex: [number, number, number]): [number, number] {\n  const [x, y, z] = vertex;\n  const absX = Math.abs(x), absY = Math.abs(y), absZ = Math.abs(z);\n\n  if (absX >= absY && absX >= absZ) {        // X face\n    return [z > 0 ? z : -z, y];\n  } else if (absY >= absX && absY >= absZ) { // Y face\n    return [x, z > 0 ? z : -z];\n  } else {                                   // Z face\n    return [x, y];\n  }\n}\n\nexport function cubicUVMappingBatch(vertices: [number, number, number][]): [number, number][] {\n  return vertices.map((vertex) => cubicUVMapping(vertex));\n}\n\n/**\n * Map directions about the origin to spherical UVs around Y; zero-length positions produce undefined coordinates.\n *\n * ```\n * const sphereVertices = [\n *   [1, 0, 0],\n *   [0, 1, 0],\n *   [0, 0, 1],\n * ];\n *\n * const sphereUVs = sphericalUVMapping(sphereVertices);\n * ```\n */\nexport function sphericalUVMapping(vertices: [number, number, number][]): [number, number][] {\n  return vertices.map(([x, y, z]) => {\n    const theta = Math.atan2(z, x);                                  // Angle around the Y-axis\n    const phi = Math.acos(y / Math.sqrt(x ** 2 + y ** 2 + z ** 2));  // Angle from the Y-axis\n    const u = (theta / (2 * Math.PI)) + 0.5;                         // Map theta to [0, 1]\n    const v = 1 - (phi / Math.PI);                                   // Map phi to [0, 1]\n    return [u, v];\n  });\n}\n\n/**\n * Map angle around Y to u and raw height y to v; no height normalization or seam splitting.\n *\n * ```\n * const cylinderVertices = [\n *   [1, 0, 0],   // Vertex on the \"equator\"\n *   [0, 1, 0],   // Vertex on the top\n *   [0, -1, 1],  // Vertex on the bottom\n *   [-1, 0, 0],  // Opposite side of the equator\n * ];\n *\n * const cylinderUVs = cylindricalUVMapping(cylinderVertices);\n * ```\n */\nexport function cylindricalUVMapping(vertices: [number, number, number][]): [number, number][] {\n  return vertices.map(([x, y, z]) => {\n    const theta = Math.atan2(z, x);           // Angle around the Y-axis (circumference)\n    const u = (theta / (2 * Math.PI)) + 0.5;  // Map theta to [0, 1]\n    const v = y;                              // Use Y-axis as the vertical component (height)\n    return [u, v];\n  });\n}\n\n/**\n * Map angle around Y to u and XZ radius to v, in source units.\n *\n * const discVertices = [\n *   [1, 0, 0],   // Vertex at (1, 0, 0)\n *   [0, 0, 1],   // Vertex at (0, 0, 1)\n *   [-1, 0, 0],  // Vertex at (-1, 0, 0)\n *   [0, 0, -1],  // Vertex at (0, 0, -1)\n * ];\n *\n * const polarUVs = polarUVMapping(discVertices);\n */\nexport function polarUVMapping(vertices: [number, number, number][]): [number, number][] {\n  return vertices.map(([x, y, z]) => {\n    const radius = Math.sqrt(x ** 2 + z ** 2); // Distance from the Y-axis (polar radius)\n    const theta = Math.atan2(z, x);            // Angle around the Y-axis (polar angle)\n\n    const u = (theta / (2 * Math.PI)) + 0.5;   // Map angle to [0, 1]\n    const v = radius;                          // Use radial distance for v\n    return [u, v];\n  });\n}\n\n/**\n * Normalize UVs using supplied bounds; each axis must have nonzero extent.\n *\n * ```\n * const planarMapping = (vertex: [number, number, number]) => [vertex[0], vertex[1]];\n * const uvs = vertices.map(mappingFunction);\n * const { minBounds, maxBounds } = calculateUVBounds(uvs);\n * const normalizedUVs = normalizeUVBatch(uvs, minBounds, maxBounds);\n * ```\n */\nexport function normalizeUV(\n  uv: [number, number],\n  minU: number,\n  maxU: number,\n  minV: number,\n  maxV: number\n): [number, number] {\n  return [\n    (uv[0] - minU) / (maxU - minU),\n    (uv[1] - minV) / (maxV - minV),\n  ];\n}\n\n/** Normalize each UV using common nonzero-extent bounds. */\nexport function normalizeUVBatch(\n  uvs: [number, number][],\n  minBounds: [number, number],\n  maxBounds: [number, number]\n): [number, number][] {\n  const [minU, minV] = minBounds;\n  const [maxU, maxV] = maxBounds;\n\n  return uvs.map((uv) => normalizeUV(uv, minU, maxU, minV, maxV));\n}\n\n/** Componentwise UV bounds; an empty input returns infinite bounds. */\nexport function calculateUVBounds(uvs: [number, number][]): { minBounds: [number, number]; maxBounds: [number, number] } {\n  const minU = Math.min(...uvs.map((uv) => uv[0]));\n  const maxU = Math.max(...uvs.map((uv) => uv[0]));\n  const minV = Math.min(...uvs.map((uv) => uv[1]));\n  const maxV = Math.max(...uvs.map((uv) => uv[1]));\n\n  return {\n    minBounds: [minU, minV],\n    maxBounds: [maxU, maxV],\n  };\n}\n","import { Vector2 } from \"three\";\n\n/** Clamp (t - min) / (max - min) to [0, 1]; min and max must differ. */\nfunction normalizeT(t: number, min: number, max: number): number {\n  return Math.max(0, Math.min(1, (t - min) / (max - min)));\n}\n\n/**\n * Sample Vector2(radius, height): radius follows curveFunction over the clamped min/max interval;\n * height interpolates linearly. segments must be positive.\n */\nexport function interpolateCurve(\n  curveFunction: (t: number) => number,\n  startRadius: number,\n  endRadius: number,\n  startHeight: number,\n  endHeight: number,\n  segments: number = 20,\n  min: number = 0,\n  max: number = 1\n): Vector2[] {\n  const points: Vector2[] = [];\n\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const easedT = curveFunction(normalizeT(t, min, max));\n    const x = startRadius + easedT * (endRadius - startRadius);\n    const y = startHeight + t * (endHeight - startHeight);\n    points.push(new Vector2(x, y));\n  }\n\n  return points;\n}\n","import { ParametricCurve } from \"../../constants/ParametricCurve\";\nimport { Vector2 } from \"three\";\n\n/**\n * Sample a cubic Bézier in XY, including both endpoints; segments must be positive.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createCubicCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the cubic curve\n *     new THREE.Vector2(1.5, 3),    // First control point (outward curve)\n *     new THREE.Vector2(1.5, 4),    // Second control point (outward curve)\n *     new THREE.Vector2(0.5, 5),    // End of the curve\n *     24,                           // Resolution of the quadratic curve\n * ),\n * ]\n * ```\n */\nexport const createCubicCurvePoints = (start: Vector2, control1: Vector2, control2: Vector2, end: Vector2, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.cubic(t, start.x, control1.x, control2.x, end.x);\n    const y = ParametricCurve.cubic(t, start.y, control1.y, control2.y, end.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample damped radius x and linear height y; the scalar damping function determines endpoint x values.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createDampedCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the damped curve\n *     new THREE.Vector2(1.5, 5),    // End of the damped curve\n *     5,                            // Damping factor\n *     24,                           // Resolution of the damped curve\n *   ),\n * ]\n * ```\n */\nexport const createDampedCurvePoints = (start: Vector2, end: Vector2, damping: number, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.damped(t, damping) * (end.x - start.x) + start.x;\n    const y = start.y + t * (end.y - start.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample exponential x and linear y; x is scaled by end.x - start.x without endpoint normalization.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createExponentialCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the exponential curve\n *     new THREE.Vector2(1.5, 5),    // End of the exponential curve\n *     0.5,                          // Base of the exponential function\n *     2,                            // Exponential factor\n *     24                            // Resolution of the exponential curve\n *   ),\n * ]\n * ```\n */\nexport const createExponentialCurvePoints = (start: Vector2, end: Vector2, base: number, factor: number, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.exponential(t, base, factor) * (end.x - start.x) + start.x;\n    const y = start.y + t * (end.y - start.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample logarithmic x and linear y; scalar parameters must produce finite values over t ∈ [0, 1].\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createLogarithmicCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the logarithmic curve\n *     new THREE.Vector2(1.5, 5),    // End of the logarithmic curve\n *     0.5,                          // Base of the logarithmic function\n *     10,                           // Logarithmic factor\n *     24,                           // Resolution of the logarithmic curve\n *   ),\n * ]\n * ```\n */\nexport const createLogarithmicCurvePoints = (start: Vector2, end: Vector2, base: number, factor: number, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.logarithmic(t, base, factor) * (end.x - start.x) + start.x;\n    const y = start.y + t * (end.y - start.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample x = start.x + a·t² + b·t + c and linear y; end.x is unused.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createParabolicCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start point\n *     new THREE.Vector2(0.5, 5),    // End point\n *     1,                            // Coefficient for t^2 (controls curvature)\n *     0,                            // Coefficient for t (linear component)\n *     0,                            // Constant term (vertical offset)\n *     24,                           // Resolution\n *   ),\n * ]\n * ```\n */\nexport const createParabolicCurvePoints = (start: Vector2, end: Vector2, a: number, b: number, c: number, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = a * t * t + b * t + c + start.x;\n    const y = start.y + t * (end.y - start.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample a quadratic Bézier in XY, including both endpoints; segments must be positive.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createQuadraticCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the quadratic curve\n *     new THREE.Vector2(1.5, 5),    // Control point (outward curve)\n *     new THREE.Vector2(0.5, 5),    // End of the curve\n *     24,                           // Resolution of the quadratic curve\n *   ),\n * ]\n * ```\n */\nexport const createQuadraticCurvePoints = (start: Vector2, control: Vector2, end: Vector2, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.quadratic(t, start.x, control.x, end.x);\n    const y = ParametricCurve.quadratic(t, start.y, control.y, end.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\n/**\n * Sample sigmoid x and linear y; sigmoid endpoint values need not equal 0 and 1.\n *\n * ```\n * const points = [\n *   ...ParametricCurveUtils.createSigmoidCurvePoints(\n *     new THREE.Vector2(0.5, 2),    // Start of the sigmoid curve\n *     new THREE.Vector2(1.5, 5),    // End of the sigmoid curve\n *     20,                           // Sigmoid steepness factor\n *     24,                           // Resolution of the sigmoid curve\n *   ),\n * ]\n * ```\n */\nexport const createSigmoidCurvePoints = (start: Vector2, end: Vector2, a: number, segments = 24) => {\n  let curvePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const t = i / segments;\n    const x = ParametricCurve.sigmoid(t, a) * (end.x - start.x) + start.x;\n    const y = start.y + t * (end.y - start.y);\n    curvePoints.push(new Vector2(x, y));\n  }\n  return curvePoints;\n};\n\nexport const ParametricCurveUtils = {\n  createCubicCurvePoints,\n  createDampedCurvePoints,\n  createExponentialCurvePoints,\n  createLogarithmicCurvePoints,\n  createParabolicCurvePoints,\n  createQuadraticCurvePoints,\n  createSigmoidCurvePoints,\n};\n","/**\n * Calculate the radius to achieve a spherical cap height.\n *   R = r / (1 - cos(thetaLength))\n */\nexport const radiusFromCapHeight = (height: number, thetaLength: number) => height / (1 - Math.cos(thetaLength));\n\n/**\n * Calculate the radius to achieve a spherical cap width.\n *   R = w / (2 * sin(thetaLength))\n */\nexport const radiusFromCapWidth = (width: number, thetaLength: number) => width / (2 * Math.sin(thetaLength));\n\n/**\n * Calculate the height of a spherical cap.\n *   h = R * (1 - cos(thetaLength))\n */\nexport const capHeightFromRadius = (radius: number, thetaLength: number) => radius * (1 - Math.cos(thetaLength));\n\n/**\n * Calculate the width of a spherical cap.\n *   w = 2 * R * sin(thetaLength)\n */\nexport const capWidthFromRadius = (radius: number, thetaLength: number) => 2 * radius * Math.sin(thetaLength);\n\n/**\n * Calculate the thetaLength to achieve a specific hole radius in a sphere.\n *   thetaLength = asin(w / (2 * R))\n *\n * Returns the thetaLength in radians.\n *\n * Example usage:\n * ```\n * const sphereRadius = 5; // Radius of the sphere\n * const holeRadius = 1;   // Desired radius of the hole at the top\n * const thetaLength = thetaLengthForRadius(sphereRadius, holeRadius);\n * ```\n */\nexport const thetaLengthForRadius = (sphereRadius: number, holeRadius: number) => {\n  const holeDiameter = 2 * holeRadius; // Hole width\n  return Math.asin(holeDiameter / (2 * sphereRadius));\n};\n\n/**\n * Convert spherical coordinates to Cartesian coordinates.\n * @param {number} radius - The radius of the sphere.\n * @param {number} theta - The azimuthal angle in radians (from the x-axis in the x-y plane).\n * @param {number} phi - The polar angle in radians (from the positive z-axis).\n * @returns {{x: number, y: number, z: number}} The Cartesian coordinates.\n */\nexport const sphericalToCartesian = (radius: number, theta: number, phi: number) => {\n  return {\n    x: radius * Math.sin(phi) * Math.cos(theta),\n    y: radius * Math.sin(phi) * Math.sin(theta),\n    z: radius * Math.cos(phi),\n  };\n};\n\n/**\n * Convert Cartesian coordinates to spherical coordinates.\n * @param {number} x - The x-coordinate.\n * @param {number} y - The y-coordinate.\n * @param {number} z - The z-coordinate.\n * @returns {{radius: number, theta: number, phi: number}} The spherical coordinates.\n */\nexport const cartesianToSpherical = (x: number, y: number, z: number) => {\n  const radius = Math.sqrt(x * x + y * y + z * z);\n  const theta = Math.atan2(y, x); // Azimuthal angle\n  const phi = Math.acos(z / radius); // Polar angle\n  return { radius, theta, phi };\n};\n","import { Vector2 } from \"three\";\nimport { thetaLengthForRadius } from \"../../utils/SphericalGeometryUtils\";\n\n/**\n * Sample a bottom-to-top ellipsoidal lathe profile centered at sphereStartY.\n * Hole radii truncate the ends; radii must fit within sphereRadiusX.\n *\n * ```\n *    const points: Vector2[] = [\n *       new Vector2(1, 0),\n *       ...appendSphericalCurve(\n *         2,  // Radius x\n *         2,  // Radius y\n *         5,  // Start y\n *         0,  // Hole top radius\n *         1,  // Hole bottom radius\n *         32, // Segments\n *       ),\n *     ];\n *\n *     const latheGeometry = new LatheGeometry(points, 32);\n * ```\n *\n * ```\n *    const points: Vector2[] = [\n *       ...appendSphericalCurve(\n *         2,  // Radius x\n *         2,  // Radius y\n *         1,  // Start y\n *         1,  // Hole top radius\n *         0,  // Hole bottom radius\n *         32, // Segments\n *       ),\n *       new Vector2(1, 5),\n *     ];\n *\n *     const latheGeometry = new LatheGeometry(points, 32);\n * ```\n */\nexport function appendSphericalCurve(\n  sphereRadiusX: number,\n  sphereRadiusY: number,\n  sphereStartY: number,\n  holeTopRadius: number = 0,\n  holeBottomRadius: number = 0,\n  segments: number = 32,\n) {\n  const thetaTop = holeTopRadius ? thetaLengthForRadius(sphereRadiusX, holeTopRadius) : 0;\n  const thetaBottom = holeBottomRadius ? Math.PI - thetaLengthForRadius(sphereRadiusX, holeBottomRadius) : Math.PI;\n\n  const spherePoints = [];\n  for (let i = 0; i <= segments; i++) {\n    const theta = thetaBottom - ((thetaBottom - thetaTop) / segments) * i;\n    const x = sphereRadiusX * Math.sin(theta);\n    const y = sphereRadiusY * Math.cos(theta) + sphereStartY;\n    spherePoints.push(new Vector2(x, y));\n  }\n\n  return [...spherePoints];\n}\n","import type { Vec2 } from \"../mesh/GeometryBuffers\";\n\n/**\n * Single-surface profiles: fillet band, bead half-round, astragal stepped bead, reed repeated beads,\n * ovolo quarter, ogee S-curve, and lip overhang with an undercut throat.\n */\nexport type SurfaceStyle = \"fillet\" | \"bead\" | \"astragal\" | \"reed\" | \"ovolo\" | \"ogee\" | \"lip\";\n\nexport interface SurfaceProfileOptions {\n  /** Exposed contour projecting from the flat back. */\n  style?: SurfaceStyle;\n  /** Extent along the supporting surface. */\n  height?: number;\n  /** Projection from the supporting surface. */\n  projection?: number;\n  /** Curve subdivision count. */\n  segments?: number;\n  /** Bead count for reed; ignored by other styles. */\n  reeds?: number;\n}\n\n/**\n * Closed CCW profile with one flat back from (0, 0) to (height, 0); projection is the outward y extent.\n *\n * ```\n *   CORNER (moldingProfile)              SURFACE (this)\n *        ceiling                                 ╭──╮\n *    ────┬────────►  projection             ────┴──┴────►  projection\n *        │╲                                 ▲\n *   wall │ ╲___                             │  one back, flat on the wall\n *        ▼                                  │\n *       drop                              height\n * ```\n *\n * ```ts\n * // A chair rail: an astragal, run along a wall at chair height.\n * const rail = new MoldingGeometry({\n *   points: wallLine(0.9),\n *   profile: surfaceProfile({ style: \"astragal\", height: 0.07, projection: 0.028 }),\n *   run: \"base\",\n *   facing: \"outward\",\n * });\n * ```\n */\nexport function surfaceProfile({\n  style = \"bead\",\n  height = 0.07,\n  projection = 0.028,\n  segments = 6,\n  reeds = 4,\n}: SurfaceProfileOptions = {}): Vec2[] {\n  const steps = Math.max(1, Math.round(segments));\n  // Begin with the flat back.\n  const points: Vec2[] = [\n    [0, 0],\n    [height, 0],\n  ];\n\n  /** Half an ellipse bulging out of the surface, walked from `x1` down to `x0`. */\n  const bulge = (x0: number, x1: number, base: number, out: number, count: number) => {\n    const mid = (x0 + x1) / 2;\n    const half = (x1 - x0) / 2;\n    for (let i = 0; i <= count; i++) {\n      const t = (i / count) * Math.PI;\n      points.push([mid + half * Math.cos(t), base + out * Math.sin(t)]);\n    }\n  };\n\n  switch (style) {\n    case \"fillet\":\n      points.push([height, projection], [0, projection]);\n      break;\n\n    case \"bead\":\n      bulge(0, height, 0, projection, steps * 2);\n      break;\n\n    case \"astragal\": {\n      // Fillets border both sides of the bead.\n      const fillet = height * 0.18;\n      const step = projection * 0.3;\n      points.push([height, step], [height - fillet, step]);\n      bulge(fillet, height - fillet, step, projection - step, steps * 2);\n      points.push([0, step]);\n      break;\n    }\n\n    case \"reed\": {\n      // Walked top-down, so each bead continues the winding the back started.\n      const count = Math.max(1, Math.round(reeds));\n      const pitch = height / count;\n      for (let i = 0; i < count; i++) {\n        bulge(height - (i + 1) * pitch, height - i * pitch, 0, projection, steps);\n      }\n      break;\n    }\n\n    case \"ovolo\":\n      points.push([height, projection]);\n      for (let i = 0; i <= steps; i++) {\n        const t = (i / steps) * (Math.PI / 2);\n        points.push([height * (1 - Math.sin(t)), projection * Math.cos(t)]);\n      }\n      break;\n\n    case \"ogee\": {\n      // Two quarters of half size meeting at the middle: HOLLOW above, BULGING below, so it returns to\n      // the wall the way a cyma does.\n      //\n      //   upper arc, center (h/2, projection) -> falls away from the chord, reading as hollow\n      //   lower arc, center (h/2, 0)          -> stands proud of it, reading as a bulge\n      const half = Math.max(1, Math.round(steps / 2));\n      const hx = height / 2;\n      const hy = projection / 2;\n      for (let i = 0; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([hx + hx * Math.cos(t), projection - hy * Math.sin(t)]);\n      }\n      for (let i = 1; i <= half; i++) {\n        const t = (i / half) * (Math.PI / 2);\n        points.push([hx - hx * Math.sin(t), hy * Math.cos(t)]);\n      }\n      break;\n    }\n\n    case \"lip\": {\n      // The crest overhangs an undercut throat.\n      //\n      //         ╭──╮   ← crest, at full projection\n      //        ╱   │\n      //       ╱    ╯   ← the UNDERCUT cuts back in\n      //      │  ╲\n      //      │   ╲     ← throat: where the hook grips\n      //      ╰────╲\n      //\n      const crestX = height * 0.72;\n      const throat: Vec2 = [height * 0.5, projection * 0.28];\n\n      // Over the top: a quarter running from the flat top face out to the crest.\n      points.push([height, projection * 0.45]);\n      for (let i = 1; i <= steps; i++) {\n        const t = (i / steps) * (Math.PI / 2);\n        points.push([\n          crestX + (height - crestX) * Math.cos(t),\n          projection * 0.45 + projection * 0.55 * Math.sin(t),\n        ]);\n      }\n      // A straight segment preserves the sharp undercut.\n      points.push(throat);\n      // Below the throat, a plain ovolo dying into the wall.\n      for (let i = 1; i <= steps; i++) {\n        const t = (i / steps) * (Math.PI / 2);\n        points.push([throat[0] * (1 - Math.sin(t)), throat[1] * Math.cos(t)]);\n      }\n      break;\n    }\n  }\n\n  // Remove repeated adjacent/closing points to avoid zero-length sweep edges.\n  const distinct = points.filter(\n    (p, i) => i === 0 || Math.hypot(p[0] - points[i - 1]![0], p[1] - points[i - 1]![1]) > 1e-12,\n  );\n  const first = distinct[0]!;\n  const last = distinct[distinct.length - 1]!;\n  if (distinct.length > 1 && Math.hypot(last[0] - first[0], last[1] - first[1]) < 1e-12) distinct.pop();\n\n  return distinct;\n}\n","import { Vector3 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\nexport interface ArcPathOptions {\n  /** Radius in the XY plane. */\n  radius?: number;\n  /** Start angle in radians; 0 is +X. */\n  startAngle?: number;\n  /** End angle in radians. */\n  endAngle?: number;\n  /** Circle center; its Z coordinate sets the arc plane. */\n  center?: Vector3;\n  /** Number of arc intervals. */\n  segments?: number;\n}\n\n/**\n * Circular arc in XY. A full 2π turn omits the repeated endpoint; use a closed sweep to join its seam.\n *\n * ```ts\n * const semicircle = arcPath({ radius: 2, startAngle: Math.PI, endAngle: 0 });\n * const ring       = arcPath({ radius: 0.3, startAngle: 0, endAngle: Math.PI * 2 });\n * ```\n */\nexport function arcPath({\n  radius = 1,\n  startAngle = 0,\n  endAngle = Math.PI,\n  center = new Vector3(),\n  segments = 24,\n}: ArcPathOptions = {}): PathPoint[] {\n  const full = Math.abs(Math.abs(endAngle - startAngle) - Math.PI * 2) < 1e-9;\n  const count = full ? segments : segments + 1;\n  const direction = Math.sign(endAngle - startAngle) || 1;\n\n  return Array.from({ length: count }, (_, i) => {\n    const theta = startAngle + (endAngle - startAngle) * (i / segments);\n\n    return {\n      position: new Vector3(\n        center.x + radius * Math.cos(theta),\n        center.y + radius * Math.sin(theta),\n        center.z,\n      ),\n      // d/dθ of (cos θ, sin θ), signed by which way we are going around.\n      tangent: new Vector3(-Math.sin(theta), Math.cos(theta), 0).multiplyScalar(direction),\n    };\n  });\n}\n","import { Curve, Vector3 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\n/**\n * Sample a Three Curve by arc length using getPointAt/getTangentAt, including both endpoints.\n * Tangent accuracy follows the supplied Curve implementation.\n *\n * ```ts\n * const curve = new CatmullRomCurve3(points, false, \"centripetal\");\n * const path = curvePath(curve, 64);\n * ```\n */\nexport function curvePath(curve: Curve<Vector3>, segments = 64): PathPoint[] {\n  return Array.from({ length: segments + 1 }, (_, i) => {\n    const t = i / segments;\n\n    return {\n      position: curve.getPointAt(t),\n      tangent: curve.getTangentAt(t),\n    };\n  });\n}\n","import { Vector3 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\nexport interface HelixPathOptions {\n  /** Radial distance from the Y axis. */\n  radius?: number;\n  /** Total displacement along +Y. */\n  height?: number;\n  /** Number of turns. */\n  turns?: number;\n  /** Start angle in radians. */\n  startAngle?: number;\n  /** Number of helix intervals. */\n  segments?: number;\n}\n\n/** Helix about +Y with circular sections in XZ; samples include both endpoints and carry derivatives. */\nexport function helixPath({\n  radius = 1,\n  height = 2,\n  turns = 3,\n  startAngle = 0,\n  segments = 96,\n}: HelixPathOptions = {}): PathPoint[] {\n  const dTheta = turns * Math.PI * 2; // θ'(t)\n\n  return Array.from({ length: segments + 1 }, (_, i) => {\n    const t = i / segments;\n    const theta = startAngle + dTheta * t;\n\n    return {\n      position: new Vector3(radius * Math.cos(theta), height * t, radius * Math.sin(theta)),\n      // dx/dt = −r·sinθ·θ' ; dy/dt = height ; dz/dt = r·cosθ·θ'\n      tangent: new Vector3(\n        -radius * Math.sin(theta) * dTheta,\n        height,\n        radius * Math.cos(theta) * dTheta,\n      ),\n    };\n  });\n}\n","import { Vector3 } from \"three\";\n\n/** Cached polyline distances; points remain caller-owned and must stay fixed while the measure is used. */\nexport interface PathMeasure {\n  /** The vertices, in order. Not copied: the caller still owns them. */\n  points: Vector3[];\n  /** Whether the last vertex joins back to the first. */\n  closed: boolean;\n  /** Cumulative vertex distances plus a final total: distances[i + 1] - distances[i] is segment i length. */\n  distances: number[];\n  /** Total arc length. The perimeter, for a closed run. */\n  length: number;\n}\n\nexport interface MeasurePathOptions {\n  /** Include the closing segment from last point to first. */\n  closed?: boolean;\n}\n\n/**\n * Cache cumulative chord distances for at least two points. Closed-path sampling requires positive total length.\n *\n * ```ts\n * const plan = measurePath(footprint, { closed: true });\n * plan.length; // the perimeter\n * ```\n */\nexport function measurePath(points: Vector3[], { closed = false }: MeasurePathOptions = {}): PathMeasure {\n  if (points.length < 2) throw new Error(\"measurePath: a path needs at least two points.\");\n\n  const distances = [0];\n  const segments = closed ? points.length : points.length - 1;\n  for (let i = 0; i < segments; i++) {\n    distances.push(distances[i]! + points[i]!.distanceTo(points[(i + 1) % points.length]!));\n  }\n  return { points, closed, distances, length: distances[distances.length - 1]! };\n}\n\n/** Return a point at distance; closed paths wrap (-0.1 equals length - 0.1), open paths clamp. */\nexport function pointAtDistance({ points, closed, distances, length }: PathMeasure, distance: number): Vector3 {\n  const target = closed\n    ? ((distance % length) + length) % length\n    : Math.min(Math.max(distance, 0), length);\n\n  let i = 0;\n  while (i < distances.length - 2 && distances[i + 1]! <= target) i++;\n\n  const a = points[i]!;\n  const b = points[(i + 1) % points.length]!;\n  const span = distances[i + 1]! - distances[i]!;\n  return a.clone().lerp(b, span > 1e-12 ? (target - distances[i]!) / span : 0);\n}\n\n/**\n * Return endpoints and crossed vertices in distance order; to must exceed from.\n * Closed ranges can cross the seam; each source vertex is included at most once, even over multiple laps.\n *\n * ```ts\n * // One merlon, wherever it happens to land.\n * const merlon = sweep(section, miterFrames(\n *   slicePath(plan, center - width / 2, center + width / 2).map((p) => ({ position: p, tangent: new Vector3() })),\n *   { reference: new Vector3(0, 1, 0) },\n * ));\n * ```\n */\nexport function slicePath(measure: PathMeasure, from: number, to: number): Vector3[] {\n  if (!(to > from)) throw new Error(\"slicePath: `to` must be greater than `from`.\");\n\n  const { points, closed, distances, length } = measure;\n  const span = to - from;\n  const out = [pointAtDistance(measure, from)];\n\n  // Include only vertices strictly inside the interval; endpoint samples already exist.\n  const crossed: { at: number; point: Vector3 }[] = [];\n  for (let i = 0; i < points.length; i++) {\n    let at = distances[i]! - from;\n    if (closed) at = ((at % length) + length) % length;\n    if (at > 1e-9 && at < span - 1e-9) crossed.push({ at, point: points[i]!.clone() });\n  }\n  crossed.sort((a, b) => a.at - b.at);\n\n  out.push(...crossed.map((c) => c.point), pointAtDistance(measure, to));\n  return out;\n}\n","import { Matrix4 } from \"three\";\nimport type { PathPoint } from \"./PathPoint\";\n\n/** Concatenate paths without cloning entries or removing coincident joints. */\nexport function joinPaths(...paths: PathPoint[][]): PathPoint[] {\n  return paths.flat();\n}\n\n/** Reverse entry order and clone/negate tangents; position vectors remain shared. */\nexport function reversePath(path: PathPoint[]): PathPoint[] {\n  return path\n    .slice()\n    .reverse()\n    .map((p) => ({ ...p, tangent: p.tangent.clone().negate() }));\n}\n\n/** Return transformed position/tangent copies. Tangents use transformDirection; scale metadata is unchanged. */\nexport function transformPath(path: PathPoint[], matrix: Matrix4): PathPoint[] {\n  return path.map((p) => ({\n    ...p,\n    position: p.position.clone().applyMatrix4(matrix),\n    tangent: p.tangent.clone().transformDirection(matrix),\n  }));\n}\n","import type { PathMeasure } from \"./PathMeasure\";\n\n/** corners adjusts spacing per segment to place an item at each vertex; pitch keeps fixed spacing from the start. */\nexport type RepeatAnchor = \"corners\" | \"pitch\";\n\nexport interface RepeatAlongPathOptions {\n  /** Requested center-to-center distance, greater than zero. */\n  pitch: number;\n  /** How spacing handles a remainder along the measured path. */\n  anchor?: RepeatAnchor;\n}\n\nexport interface PathRepeat {\n  /** Item centers, as distances along the path. Feed each to `slicePath` or `pointAtDistance`. */\n  centers: number[];\n  /** Actual pitch; corners reports total spanned length / step count, an average across segments. */\n  pitch: number;\n  /** Unallocated distance under pitch anchoring; zero under corners anchoring. */\n  slack: number;\n  /** Whether every vertex ended up with an item on it. */\n  anchored: boolean;\n}\n\n/**\n * Return item-center distances and spacing metadata. Use pointAtDistance for placement or\n * slicePath(center ± width / 2) for an item spanning multiple segments.\n *\n * ```ts\n * const plan = measurePath(footprint, { closed: true });\n * const { centers } = repeatAlongPath(plan, { pitch: 1.2 });\n *\n * // A battlement: the interval, swept. Corner merlons need no special case — their slice comes back\n * // with the corner in it, and `miterFrames` cuts it.\n * const merlons = centers.map((c) =>\n *   sweep(\n *     section,\n *     miterFrames(\n *       slicePath(plan, c - 0.4, c + 0.4).map((position) => ({ position, tangent: new Vector3() })),\n *       { reference: new Vector3(0, 1, 0) },\n *     ),\n *   ),\n * );\n *\n * // A balustrade: the center, populated.\n * for (const c of centers) baluster.position.copy(pointAtDistance(plan, c));\n * ```\n */\nexport function repeatAlongPath(\n  { points, closed, distances, length }: PathMeasure,\n  { pitch, anchor = \"corners\" }: RepeatAlongPathOptions,\n): PathRepeat {\n  if (!(pitch > 0)) throw new Error(\"repeatAlongPath: pitch must be greater than zero.\");\n\n  if (anchor === \"pitch\") {\n    const count = Math.max(1, Math.floor(length / pitch));\n    return {\n      centers: Array.from({ length: count }, (_, i) => i * pitch),\n      pitch,\n      slack: length - count * pitch,\n      anchored: false,\n    };\n  }\n\n  const centers: number[] = [];\n  let spanned = 0;\n  let steps = 0;\n  const segments = closed ? points.length : points.length - 1;\n\n  for (let i = 0; i < segments; i++) {\n    const segment = distances[i + 1]! - distances[i]!;\n    // At least one step, so a segment shorter than the pitch still gets its corners rather than vanishing.\n    const count = Math.max(1, Math.round(segment / pitch));\n    const actual = segment / count;\n    spanned += segment;\n    steps += count;\n\n    // Emit shared corners once; only the last open segment includes its far endpoint.\n    const emit = closed || i < segments - 1 ? count : count + 1;\n    for (let k = 0; k < emit; k++) centers.push(distances[i]! + k * actual);\n  }\n\n  return { centers, pitch: spanned / steps, slack: 0, anchored: true };\n}\n","import { Vector2, Vector3 } from \"three\";\n\n/**\n * arclength samples perimeter distance; index selects loop[floor(i * n / count)] and can duplicate vertices.\n * angular takes nearest centroid-ray hits, falling back to the first point when a ray misses.\n */\nexport type ResampleMethod = \"arclength\" | \"index\" | \"angular\";\n\nexport interface CorrespondOptions {\n  /** Requested output point count; resampling can omit authored corners. */\n  count?: number;\n  /** Resampling method for each input loop. */\n  method?: ResampleMethod;\n}\n\n/** Cumulative edge lengths around a closed loop, plus its perimeter. */\nfunction perimeter(loop: Vector2[]): { runs: number[]; total: number } {\n  const runs = [0];\n  let total = 0;\n\n  for (let i = 0; i < loop.length; i++) {\n    total += loop[i]!.distanceTo(loop[(i + 1) % loop.length]!);\n    runs.push(total);\n  }\n\n  return { runs, total };\n}\n\n/**\n * Return count samples of a closed outline without a repeated endpoint. Empty input or count ≤ 0 returns [].\n * Angular sampling assumes a star-shaped outline about its centroid; index upsampling repeats vertices.\n *\n * ```ts\n * const circle = resampleLoop(squareOutline, 32);        // arc length: 32 evenly spaced points\n * const corners = resampleLoop(squareOutline, 32, \"index\"); // 4 distinct points, 28 collapsed edges\n * ```\n */\nexport function resampleLoop(loop: Vector2[], count: number, method: ResampleMethod = \"arclength\"): Vector2[] {\n  const n = loop.length;\n  if (n === 0 || count <= 0) return [];\n  if (n === count && method === \"index\") return loop.map((p) => p.clone());\n\n  if (method === \"index\") {\n    return Array.from({ length: count }, (_, i) => loop[Math.floor((i * n) / count)]!.clone());\n  }\n\n  if (method === \"angular\") {\n    const centroid = loop.reduce((sum, p) => sum.add(p), new Vector2()).divideScalar(n);\n    // Use the first vertex’s centroid angle as the seam reference.\n    const start = Math.atan2(loop[0]!.y - centroid.y, loop[0]!.x - centroid.x);\n\n    return Array.from({ length: count }, (_, i) => {\n      const theta = start + (i / count) * Math.PI * 2;\n      const rx = Math.cos(theta);\n      const ry = Math.sin(theta);\n\n      // Keep the nearest forward edge hit; concave outlines can have multiple crossings.\n      let best = Infinity;\n      for (let e = 0; e < n; e++) {\n        const a = loop[e]!;\n        const b = loop[(e + 1) % n]!;\n        const ex = b.x - a.x;\n        const ey = b.y - a.y;\n        const denominator = rx * ey - ry * ex;\n        if (Math.abs(denominator) < 1e-12) continue;\n\n        const dx = a.x - centroid.x;\n        const dy = a.y - centroid.y;\n        const t = (dx * ey - dy * ex) / denominator;\n        const u = (dx * ry - dy * rx) / denominator;\n        if (t > 1e-9 && u >= -1e-9 && u <= 1 + 1e-9 && t < best) best = t;\n      }\n\n      return Number.isFinite(best)\n        ? new Vector2(centroid.x + rx * best, centroid.y + ry * best)\n        : loop[0]!.clone();\n    });\n  }\n\n  const { runs, total } = perimeter(loop);\n  if (total < 1e-12) return Array.from({ length: count }, () => loop[0]!.clone());\n\n  return Array.from({ length: count }, (_, i) => {\n    const target = (i / count) * total;\n    let e = 0;\n    while (e < n - 1 && runs[e + 1]! < target) e++;\n    const span = runs[e + 1]! - runs[e]!;\n    const t = span < 1e-12 ? 0 : (target - runs[e]!) / span;\n    return loop[e]!.clone().lerp(loop[(e + 1) % n]!, t);\n  });\n}\n\n/** Resample outlines to a common point count; seam alignment and winding remain unchanged. */\nexport function correspondLoops(loops: Vector2[][], { count, method = \"arclength\" }: CorrespondOptions = {}): Vector2[][] {\n  if (loops.length === 0) return [];\n  const target = count ?? Math.max(...loops.map((loop) => loop.length));\n  return loops.map((loop) => resampleLoop(loop, target, method));\n}\n\n/** Return cloned ring points with the start index shifted cyclically; offset must be an integer. */\nexport function rotateRing(ring: Vector3[], offset: number): Vector3[] {\n  const n = ring.length;\n  if (n === 0) return [];\n  const k = ((offset % n) + n) % n;\n  return Array.from({ length: n }, (_, i) => ring[(i + k) % n]!.clone());\n}\n\n/**\n * Choose the cyclic offset minimizing summed a[i]-to-b[(i+k) % n] distance in O(n²) for equal counts.\n * Ties retain the first offset; winding is never reversed.\n */\nexport function bestRingOffset(a: Vector3[], b: Vector3[]): number {\n  if (a.length === 0 || b.length === 0) return 0;\n\n  let best = 0;\n  let shortest = Infinity;\n\n  for (let k = 0; k < b.length; k++) {\n    let sum = 0;\n    for (let i = 0; i < a.length; i++) sum += a[i]!.distanceTo(b[(i + k) % b.length]!);\n    if (sum < shortest) {\n      shortest = sum;\n      best = k;\n    }\n  }\n\n  return best;\n}\n\n/**\n * Clone rings and cyclically align each to its aligned predecessor; the first seam stays fixed.\n * Unequal neighboring counts keep their order; winding is never reversed.\n */\nexport function alignRings(rings: Vector3[][]): Vector3[][] {\n  if (rings.length === 0) return [];\n\n  const aligned: Vector3[][] = [rings[0]!.map((p) => p.clone())];\n\n  for (let s = 1; s < rings.length; s++) {\n    const previous = aligned[s - 1]!;\n    const offset = previous.length === rings[s]!.length ? bestRingOffset(previous, rings[s]!) : 0;\n    aligned.push(rotateRing(rings[s]!, offset));\n  }\n\n  return aligned;\n}\n","import { BufferAttribute, BufferGeometry, Vector3 } from \"three\";\n\n/**\n * Member-end plane whose normal points into the allowed region. For unit outward member axes,\n * a shared miter normal is normalize(a_i - a_j); swapping axes reverses it.\n */\nexport interface CutPlane {\n  point: Vector3;\n  normal: Vector3;\n}\n\n/** Original ring point, projected endpoint, and selected bounding-plane index. */\nexport interface CutPoint {\n  /** Source ring position. */\n  start: Vector3;\n  /** Projected endpoint. */\n  end: Vector3;\n  /** `0` or `1` for the plane it met, or `-1` for a point sitting exactly on the crease between them. */\n  owner: number;\n}\n\nexport interface CutEndOptions {\n  /**\n * first selects the smaller axis parameter (intersection of halfspaces); last selects the larger (union).\n * Use finite forward hits from ring points inside both bounds.\n */\n  stopAt?: \"first\" | \"last\";\n}\n\n/**\n * How far along `axis` from `p` until `plane` is met. `Infinity` when the axis runs parallel to it.\n */\nconst hitDistance = (p: Vector3, axis: Vector3, plane: CutPlane): number => {\n  const denominator = axis.dot(plane.normal);\n  if (Math.abs(denominator) < 1e-9) return Infinity;\n  return plane.point.clone().sub(p).dot(plane.normal) / denominator;\n};\n\n/**\n * Project an ordered ring along axis to two bounding planes, inserting points where plane ownership changes.\n * Hit-distance differences are linear along an edge, so crease fraction is f0 / (f0 - f1); avoid parallel axes.\n *\n * ```ts\n * // Two hips meeting at a roof apex: each cap is cut against its two neighbors.\n * const bound = (mine: Vector3, theirs: Vector3): CutPlane => ({\n *   point: apex,\n *   normal: mine.clone().sub(theirs).normalize(),\n * });\n * const points = cutEnd(ring, direction, [bound(mine, previous), bound(mine, next)]);\n * const geometry = cutEndGeometry(points, direction);\n * ```\n */\nexport function cutEnd(\n  ring: Vector3[],\n  axis: Vector3,\n  planes: [CutPlane, CutPlane],\n  { stopAt = \"first\" }: CutEndOptions = {},\n): CutPoint[] {\n  const distances = ring.map((p) => [hitDistance(p, axis, planes[0]), hitDistance(p, axis, planes[1])]);\n  const pick = (t: number[]) => (stopAt === \"first\" ? (t[0]! <= t[1]! ? 0 : 1) : t[0]! >= t[1]! ? 0 : 1);\n\n  const out: CutPoint[] = [];\n  for (let i = 0; i < ring.length; i++) {\n    const j = (i + 1) % ring.length;\n    const here = pick(distances[i]!);\n\n    out.push({\n      start: ring[i]!.clone(),\n      end: ring[i]!.clone().addScaledVector(axis, distances[i]![here]!),\n      owner: here,\n    });\n    if (here === pick(distances[j]!)) continue;\n\n    const f0 = distances[i]![0]! - distances[i]![1]!;\n    const f1 = distances[j]![0]! - distances[j]![1]!;\n    const s = f0 / (f0 - f1);\n    if (!Number.isFinite(s) || s <= 0 || s >= 1) continue;\n\n    const crease = ring[i]!.clone().lerp(ring[j]!, s);\n    out.push({\n      start: crease,\n      end: crease.clone().addScaledVector(axis, hitDistance(crease, axis, planes[0])),\n      owner: -1,\n    });\n  }\n  return out;\n}\n\n/**\n * Build flat-shaded, nonindexed sides and caps from CutPoints; requires a convex source section.\n * Each planar end facet is fanned separately; axis is accepted but unused.\n */\nexport function cutEndGeometry(points: CutPoint[], axis: Vector3): BufferGeometry {\n  const triangles: Vector3[][] = [];\n  const count = points.length;\n\n  // The sides. Each band is planar by construction: both of its ends travel along the SAME axis.\n  for (let i = 0; i < count; i++) {\n    const j = (i + 1) % count;\n    triangles.push(\n      [points[j]!.start, points[i]!.start, points[i]!.end],\n      [points[j]!.start, points[i]!.end, points[j]!.end],\n    );\n  }\n\n  // The start, square to the axis.\n  for (let i = 1; i < count - 1; i++) {\n    triangles.push([points[0]!.start, points[i]!.start, points[i + 1]!.start]);\n  }\n\n  const creases = points.map((p, i) => (p.owner === -1 ? i : -1)).filter((i) => i >= 0);\n  if (creases.length === 2) {\n    for (const [from, to] of [\n      [creases[0]!, creases[1]!],\n      [creases[1]!, creases[0]!],\n    ]) {\n      const arc: Vector3[] = [];\n      for (let i = from; ; i = (i + 1) % count) {\n        arc.push(points[i]!.end);\n        if (i === to) break;\n      }\n      for (let i = 1; i < arc.length - 1; i++) {\n        triangles.push([arc[0]!, arc[i + 1]!, arc[i]!]);\n      }\n    }\n  } else {\n    // No crossing: every point met the same plane, so this is an ordinary seat cut.\n    for (let i = 1; i < count - 1; i++) {\n      triangles.push([points[0]!.end, points[i + 1]!.end, points[i]!.end]);\n    }\n  }\n\n  // Discard collapsed triangles before computing face normals.\n  const solid = triangles.filter(\n    ([a, b, c]) => new Vector3().subVectors(b!, a!).cross(new Vector3().subVectors(c!, a!)).length() > 1e-12,\n  );\n\n  const positions = new Float32Array(solid.length * 9);\n  solid.forEach((triangle, i) => triangle.forEach((p, v) => positions.set([p.x, p.y, p.z], i * 9 + v * 3)));\n\n  const geometry = new BufferGeometry();\n  geometry.setAttribute(\"position\", new BufferAttribute(positions, 3));\n  geometry.computeVertexNormals();\n  return geometry;\n}\n\n/**\n * For unit axes pointing away from joint, normal = normalize(a - b), with (a - b) · a = 1 - a · b > 0.\n * A closing joint requires mirrored section and roll as well as axes; distinct axes are required.\n */\nexport function miterPlane(joint: Vector3, a: Vector3, b: Vector3): CutPlane {\n  return { point: joint.clone(), normal: a.clone().sub(b).normalize() };\n}\n\n/** Which planes bound each end of a segment cut at both ends. */\nexport interface SegmentBounds {\n  start: [CutPlane, CutPlane];\n  end: [CutPlane, CutPlane];\n}\n\n/**\n * Project a convex ring to two bounds at each end, along ±axis, splitting at both ends’ crease crossings.\n * Returns a flat-shaded nonindexed solid; selected hits must be finite and lie in the intended direction.\n */\nexport function cutSegment(\n  ring: Vector3[],\n  axis: Vector3,\n  { start, end }: SegmentBounds,\n  { stopAt = \"first\" }: CutEndOptions = {},\n): BufferGeometry {\n  const backward = axis.clone().negate();\n  const pick = (t: number[]) => (stopAt === \"first\" ? (t[0]! <= t[1]! ? 0 : 1) : t[0]! >= t[1]! ? 0 : 1);\n  const distances = (p: Vector3, along: Vector3, planes: [CutPlane, CutPlane]) => [\n    hitDistance(p, along, planes[0]),\n    hitDistance(p, along, planes[1]),\n  ];\n\n  // Every crossing from both ends, as (edge index, fraction along it).\n  const stations: [number, number][] = [];\n  for (let i = 0; i < ring.length; i++) {\n    const j = (i + 1) % ring.length;\n    stations.push([i, 0]);\n    for (const [along, planes] of [\n      [axis, end],\n      [backward, start],\n    ] as const) {\n      const a = distances(ring[i]!, along, planes);\n      const b = distances(ring[j]!, along, planes);\n      if (pick(a) === pick(b)) continue;\n      const f0 = a[0]! - a[1]!;\n      const f1 = b[0]! - b[1]!;\n      const s = f0 / (f0 - f1);\n      if (Number.isFinite(s) && s > 1e-9 && s < 1 - 1e-9) stations.push([i, s]);\n    }\n  }\n  stations.sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n\n  const points = stations.map(([i, s]) => ring[i]!.clone().lerp(ring[(i + 1) % ring.length]!, s));\n  const land = (p: Vector3, along: Vector3, planes: [CutPlane, CutPlane]) => {\n    const t = distances(p, along, planes);\n    const owner = pick(t);\n    return { point: p.clone().addScaledVector(along, t[owner]!), owner };\n  };\n  const heads = points.map((p) => land(p, axis, end));\n  const tails = points.map((p) => land(p, backward, start));\n\n  const triangles: Vector3[][] = [];\n  const count = points.length;\n  for (let i = 0; i < count; i++) {\n    const j = (i + 1) % count;\n    triangles.push(\n      [tails[j]!.point, tails[i]!.point, heads[i]!.point],\n      [tails[j]!.point, heads[i]!.point, heads[j]!.point],\n    );\n  }\n\n  // Merge owner runs across the seam; the previous run’s crease closes each planar facet.\n  const fan = (landings: { point: Vector3; owner: number }[], flip: boolean) => {\n    const runs: number[][] = [];\n    for (let i = 0; i < count; i++) {\n      const previous = landings[(i + count - 1) % count]!.owner;\n      if (runs.length > 0 && landings[i]!.owner === previous) runs[runs.length - 1]!.push(i);\n      else runs.push([i]);\n    }\n    if (runs.length > 1 && landings[runs[0]![0]!]!.owner === landings[runs[runs.length - 1]![0]!]!.owner) {\n      runs[0] = [...runs.pop()!, ...runs[0]!];\n    }\n    const emit = (arc: Vector3[]) => {\n      for (let i = 1; i < arc.length - 1; i++) {\n        const tri = [arc[0]!, arc[i]!, arc[i + 1]!];\n        triangles.push(flip ? [tri[0]!, tri[2]!, tri[1]!] : tri);\n      }\n    };\n    if (runs.length < 2) {\n      emit(landings.map((p) => p.point));\n      return;\n    }\n    runs.forEach((run, r) => {\n      const previous = runs[(r + runs.length - 1) % runs.length]!;\n      emit([landings[previous[previous.length - 1]!]!.point, ...run.map((i) => landings[i]!.point)]);\n    });\n  };\n  fan(heads, false);\n  fan(tails, true);\n\n  const solid = triangles.filter(\n    ([a, b, c]) => new Vector3().subVectors(b!, a!).cross(new Vector3().subVectors(c!, a!)).length() > 1e-12,\n  );\n  const positions = new Float32Array(solid.length * 9);\n  solid.forEach((triangle, i) => triangle.forEach((p, v) => positions.set([p.x, p.y, p.z], i * 9 + v * 3)));\n\n  const geometry = new BufferGeometry();\n  geometry.setAttribute(\"position\", new BufferAttribute(positions, 3));\n  geometry.computeVertexNormals();\n  return geometry;\n}\n","import { BufferGeometry, ShapeUtils, Vector2, Vector3 } from \"three\";\nimport {\n  createGeometryBuffers,\n  pushQuad,\n  pushTriangle,\n  toBufferGeometry,\n  type GeometryBuffers,\n  type Vec3,\n} from \"../mesh/GeometryBuffers\";\n\nexport interface LoftOptions {\n  /** Triangulate each end after projection onto its Newell-normal plane. */\n  cap?: boolean;\n  /** Stitch the final ring to the first and omit caps; do not repeat the first ring. */\n  closed?: boolean;\n}\n\n/** Newell’s area-weighted normal accumulated from every ring edge; degenerate rings return zero. */\nfunction ringNormal(ring: Vector3[]): Vector3 {\n  const normal = new Vector3();\n\n  for (let i = 0; i < ring.length; i++) {\n    const a = ring[i]!;\n    const b = ring[(i + 1) % ring.length]!;\n    normal.x += (a.y - b.y) * (a.z + b.z);\n    normal.y += (a.z - b.z) * (a.x + b.x);\n    normal.z += (a.x - b.x) * (a.y + b.y);\n  }\n\n  return normal.normalize();\n}\n\n/** Project a ring onto its Newell-normal plane, triangulate it, and orient the cap toward outward. */\nfunction capRing(buffers: GeometryBuffers, ring: Vector3[], outward: Vector3): void {\n  if (ring.length < 3) return;\n\n  const normal = ringNormal(ring);\n  if (normal.lengthSq() < 0.5) return; // Degenerate ring — no plane, nothing to cap.\n\n  // Any axis not parallel to the normal will seed the basis; the smallest component is the safest pick.\n  const seed =\n    Math.abs(normal.x) <= Math.abs(normal.y) && Math.abs(normal.x) <= Math.abs(normal.z)\n      ? new Vector3(1, 0, 0)\n      : Math.abs(normal.y) <= Math.abs(normal.z)\n        ? new Vector3(0, 1, 0)\n        : new Vector3(0, 0, 1);\n\n  const u = new Vector3().crossVectors(normal, seed).normalize();\n  const v = new Vector3().crossVectors(normal, u);\n  const origin = ring[0]!;\n\n  const contour = ring.map((p) => {\n    const d = new Vector3().subVectors(p, origin);\n    return new Vector2(d.dot(u), d.dot(v));\n  });\n\n  const faces = ShapeUtils.triangulateShape(contour, []);\n\n  // `triangulateShape` hands back counter-clockwise triangles in the (u, v) frame, whose normal is `+n`.\n  // Flip them wholesale when that faces the wrong way, so the cap agrees with the skin around it.\n  const flip = normal.dot(outward) < 0;\n  const at = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n\n  if (faces.length > 0) {\n    for (const [a, b, c] of faces) {\n      const [i0, i1, i2] = flip ? [a!, c!, b!] : [a!, b!, c!];\n      pushTriangle(buffers, [at(ring[i0]!), at(ring[i1]!), at(ring[i2]!)], undefined);\n    }\n    return;\n  }\n\n  // Fan fallback can cross a concave outline; callers must validate the resulting cap.\n  for (let i = 1; i < ring.length - 1; i++) {\n    const [i1, i2] = flip ? [i + 1, i] : [i, i + 1];\n    pushTriangle(buffers, [at(ring[0]!), at(ring[i1]!), at(ring[i2]!)], undefined);\n  }\n}\n\n/**\n * Skin closed rings with equal point counts and corresponding indices; inputs remain unchanged.\n * Fewer than two rings returns empty geometry; unequal point counts throw.\n *\n * ```ts\n * // Transition from a square section to a circle.\n * const loops = correspondLoops([squareOutline, circleOutline]);\n * const rings = loops.map((loop, i) => loop.map((p) => new Vector3(p.x, i * 2, p.y)));\n * const geometry = loft(alignRings(rings));\n * ```\n */\nexport function loft(rings: Vector3[][], { cap = true, closed = false }: LoftOptions = {}): BufferGeometry {\n  const buffers = createGeometryBuffers();\n  if (rings.length < 2) return toBufferGeometry(buffers);\n\n  const width = rings[0]!.length;\n  for (let s = 1; s < rings.length; s++) {\n    if (rings[s]!.length !== width) {\n      throw new Error(\n        `loft() requires corresponding rings: ring 0 has ${width} points, ring ${s} has ${rings[s]!.length}. ` +\n          `Reconcile them with correspondLoops() before lofting.`,\n      );\n    }\n  }\n\n  const at = (p: Vector3): Vec3 => [p.x, p.y, p.z];\n  const bands = closed ? rings.length : rings.length - 1;\n\n  for (let s = 0; s < bands; s++) {\n    const lower = rings[s]!;\n    const upper = rings[(s + 1) % rings.length]!;\n\n    for (let i = 0; i < width; i++) {\n      const j = (i + 1) % width;\n      // Quad normals come from the first three corners; warped bands remain faceted.\n      pushQuad(buffers, [at(lower[j]!), at(lower[i]!), at(upper[i]!), at(upper[j]!)], undefined);\n    }\n  }\n\n  // Neighboring ring centroids determine outward cap orientation.\n  if (cap && !closed) {\n    const first = rings[0]!;\n    const second = rings[1]!;\n    const last = rings[rings.length - 1]!;\n    const penultimate = rings[rings.length - 2]!;\n\n    const centroid = (ring: Vector3[]) =>\n      ring.reduce((sum, p) => sum.add(p), new Vector3()).divideScalar(ring.length);\n\n    capRing(buffers, first, new Vector3().subVectors(centroid(first), centroid(second)));\n    capRing(buffers, last, new Vector3().subVectors(centroid(last), centroid(penultimate)));\n  }\n\n  return toBufferGeometry(buffers);\n}\n","import { BufferGeometry, Float32BufferAttribute, Vector2, Vector3 } from \"three\";\n\n/** An oriented triangle sheet. Shared point indices define connectivity, independently of shading. */\nexport interface IndexedSurface {\n  readonly points: readonly Vector3[];\n  readonly triangles: readonly (readonly [number, number, number])[];\n  /** Optional per-point UVs. Without them, each skin triangle receives a unit triangle mapping. */\n  readonly uv?: readonly Vector2[];\n}\n\nexport interface RimUVOptions {\n  /** Omit for the existing 0–1 loop fit; positive value uses source boundary distance per repeat. */\n  unitsPerRepeat?: number;\n  /** Texture-coordinate offset, applied independently to each boundary loop. */\n  offset?: Vector2;\n}\nexport interface ThickenSurfaceOptions {\n  /** Mapping of new rim walls only. Front/back retain caller UVs. */\n  rimUV?: RimUVOptions;\n  /** Positive distance in the input coordinate system. */\n  thickness: number;\n  /** Defaults to centered. Front follows input winding; back faces the opposite way. */\n  placement?: \"front\" | \"centered\" | \"back\";\n  /**\n   * Normal uses angle-weighted unit vertex normals. Crease compensation fits incident face-plane\n   * distances along that normal, and is approximate on general meshes. A vector is normalized and\n   * used as a fixed extrusion direction; it must point into every source face's front hemisphere.\n   */\n  offset?: \"normal\" | \"crease-compensated\" | Vector3;\n  /**\n   * Throw on detected output inversions, collapsed triangles or nonpositive component volumes\n   * (default). Report returns that geometry for inspection. Invalid input always throws.\n   * Neither mode detects global self-intersections.\n   */\n  onInvalid?: \"throw\" | \"report\";\n}\n\nexport interface ThicknessDiagnostics {\n  /** Source boundary edges, including hole boundaries. */\n  boundaryEdges: number;\n  /** Front/back output triangles facing against their corresponding source orientation. */\n  invertedFaces: number;\n  /** Collapsed or numerically degenerate output triangles, including walls. */\n  degenerateFaces: number;\n  /** Closed output components with zero or negative signed volume. */\n  nonPositiveVolumeComponents: number;\n  /** Sum of component signed volumes, in input units cubed. */\n  signedVolume: number;\n  /** Maximum corresponding-vertex displacement error projected on an incident source normal. */\n  maxFaceThicknessError: number;\n  /** Explicitly not a solid-validity certificate. */\n  selfIntersectionsChecked: false;\n}\n\nexport interface ThickenSurfaceResult {\n  /** Owned nonindexed geometry. Groups 0, 1, 2 are front, back, and rim; caller disposes it. */\n  geometry: BufferGeometry;\n  diagnostics: ThicknessDiagnostics;\n}\n\ntype Face = readonly [number, number, number];\ntype Edge = { a: number; b: number; faces: number[]; balance: number };\nconst EPS = 1e-12;\nconst fail = (message: string): never => {\n  throw new RangeError(`thickenSurface: ${message}`);\n};\nconst finite = (p: Vector3) => p && [p.x, p.y, p.z].every(Number.isFinite);\nconst normalOf = (points: readonly Vector3[], [a, b, c]: Face) =>\n  points[b].clone().sub(points[a]).cross(points[c].clone().sub(points[a]));\n\n/**\n * Convert an open rectangular grid to an owned indexed sheet, grid[v][u]. No seam welding,\n * periodic wrapping, or collapsed rows are inferred. Degenerate faces are rejected by thickenSurface.\n */\nexport function surfaceFromGrid(\n  grid: readonly (readonly Vector3[])[],\n  { flip = false }: { flip?: boolean } = {},\n): IndexedSurface {\n  const rows = grid.length,\n    columns = grid[0]?.length ?? 0;\n  if (rows < 2 || columns < 2 || grid.some((row) => row.length !== columns)) {\n    fail(\"grid must be rectangular with at least two rows and columns.\");\n  }\n  const points: Vector3[] = [],\n    uv: Vector2[] = [],\n    triangles: Face[] = [];\n  for (let j = 0; j < rows; j++)\n    for (let i = 0; i < columns; i++) {\n      if (!finite(grid[j][i])) fail(\"grid coordinates must be finite.\");\n      points.push(grid[j][i].clone());\n      uv.push(new Vector2(i / (columns - 1), j / (rows - 1)));\n    }\n  for (let j = 0; j < rows - 1; j++)\n    for (let i = 0; i < columns - 1; i++) {\n      const a = j * columns + i,\n        b = a + 1,\n        d = a + columns,\n        c = d + 1;\n      triangles.push(\n        ...((flip\n          ? [\n              [a, c, b],\n              [a, d, c],\n            ]\n          : [\n              [a, b, c],\n              [a, c, d],\n            ]) as Face[]),\n      );\n    }\n  return { points, triangles, uv };\n}\n\n/**\n * Add thickness to an explicitly connected, consistently oriented, manifold open sheet.\n * Disconnected open components and holes are supported. Closed components, bow-tie vertices,\n * unused points, degenerate/duplicate faces and inconsistent winding are rejected.\n *\n * Connectivity comes from point indices, never position welding. Offsets are computed on that\n * topology, then rendering corners are duplicated for flat normals and UV seams. Front/back UVs\n * retain the source map; rim UVs run 0–1 around each boundary loop and from back (0) to front (1).\n *\n * Normal and crease offsets may self-intersect or consume narrow features. Output diagnostics\n * detect local inversions and degeneracy after Float32 conversion, not global intersections.\n * Keep coordinates near the origin when small features would otherwise lose Float32 precision.\n *\n * @example\n * const { geometry, diagnostics } = thickenSurface(surfaceFromGrid(grid), {\n *   thickness: 0.08, placement: \"centered\", offset: \"normal\",\n * });\n * const mesh = new Mesh(geometry, [frontMaterial, backMaterial, rimMaterial]);\n */\nexport function thickenSurface(\n  surface: IndexedSurface,\n  { thickness, placement = \"centered\", offset = \"normal\", onInvalid = \"throw\", rimUV = {} }: ThickenSurfaceOptions,\n): ThickenSurfaceResult {\n  if (!Number.isFinite(thickness) || thickness <= 0) fail(\"thickness must be positive and finite.\");\n  if (![\"front\", \"centered\", \"back\"].includes(placement)) fail(\"unknown placement.\");\n  if (![\"throw\", \"report\"].includes(onInvalid)) fail(\"unknown onInvalid policy.\");\n  const rimUnits = rimUV.unitsPerRepeat,\n    rimOffset = rimUV.offset ?? new Vector2();\n  if (\n    (rimUnits !== undefined && !(Number.isFinite(rimUnits) && rimUnits > 0)) ||\n    ![rimOffset.x, rimOffset.y].every(Number.isFinite)\n  )\n    fail(\"invalid rim UV options.\");\n  const fixed = typeof offset !== \"string\";\n  if (fixed ? !finite(offset) || offset.length() === 0 : ![\"normal\", \"crease-compensated\"].includes(offset))\n    fail(\"invalid offset.\");\n  if (surface.points.length < 3 || !surface.triangles.length || surface.points.some((p) => !finite(p)))\n    fail(\"expected finite points and triangles.\");\n  if (\n    surface.uv &&\n    (surface.uv.length !== surface.points.length || surface.uv.some((p) => !p || !Number.isFinite(p.x) || !Number.isFinite(p.y)))\n  )\n    fail(\"UVs must be finite and match the points.\");\n\n  // Work in a local, scale-normalized frame for direction and degeneracy calculations.\n  const origin = surface.points[0].clone();\n  let scale = 0;\n  for (const p of surface.points) scale = Math.max(scale, p.distanceTo(origin));\n  if (!(scale > 0) || !Number.isFinite(scale) || !Number.isFinite(thickness / scale)) fail(\"unsupported coordinate extent.\");\n  const points = surface.points.map((p) => p.clone().sub(origin).divideScalar(scale));\n  const faces = surface.triangles;\n  const incident = points.map(() => [] as number[]);\n  const edges = new Map<string, Edge>();\n  const unique = new Set<string>();\n  const normals: Vector3[] = [];\n  faces.forEach((face, f) => {\n    if (face.length !== 3 || face.some((i) => !Number.isInteger(i) || i < 0 || i >= points.length) || new Set(face).size !== 3)\n      fail(\"invalid triangle indices.\");\n    const key = [...face].sort((a, b) => a - b).join(\":\");\n    if (unique.has(key)) fail(\"duplicate face.\");\n    unique.add(key);\n    const n = normalOf(points, face);\n    const longest = Math.max(...face.map((a, i) => points[a].distanceToSquared(points[face[(i + 1) % 3]])));\n    if (n.length() <= EPS * longest) fail(\"degenerate source triangle.\");\n    normals.push(n.normalize());\n    face.forEach((a, i) => {\n      incident[a].push(f);\n      const b = face[(i + 1) % 3],\n        key = `${Math.min(a, b)}:${Math.max(a, b)}`;\n      const e = edges.get(key) ?? { a, b, faces: [], balance: 0 };\n      e.faces.push(f);\n      e.balance += a < b ? 1 : -1;\n      edges.set(key, e);\n    });\n  });\n  if (incident.some((list) => !list.length)) fail(\"unused point.\");\n  const boundary: Edge[] = [],\n    neighbors = faces.map(() => [] as number[]);\n  const vertexEdges = points.map(() => [] as Edge[]);\n  for (const e of edges.values()) {\n    if (e.faces.length > 2 || (e.faces.length === 2 && e.balance !== 0)) fail(\"expected consistently oriented manifold edges.\");\n    vertexEdges[e.a].push(e);\n    vertexEdges[e.b].push(e);\n    if (e.faces.length === 1) boundary.push(e);\n    else {\n      neighbors[e.faces[0]].push(e.faces[1]);\n      neighbors[e.faces[1]].push(e.faces[0]);\n    }\n  }\n  // Edge manifoldness alone misses two fans touching at a single vertex.\n  incident.forEach((list, v) => {\n    const edgeList = vertexEdges[v],\n      ends = edgeList.filter((e) => e.faces.length === 1).length;\n    if (ends !== 0 && ends !== 2) fail(\"non-manifold boundary vertex.\");\n    const graph = new Map(list.map((f) => [f, [] as number[]]));\n    for (const e of edgeList)\n      if (e.faces.length === 2) {\n        graph.get(e.faces[0])!.push(e.faces[1]);\n        graph.get(e.faces[1])!.push(e.faces[0]);\n      }\n    const seen = new Set<number>(),\n      stack = [list[0]];\n    while (stack.length) {\n      const f = stack.pop()!;\n      if (!seen.has(f)) {\n        seen.add(f);\n        stack.push(...graph.get(f)!);\n      }\n    }\n    if (seen.size !== list.length) fail(\"disconnected face fans at a vertex.\");\n  });\n  const component = faces.map(() => -1),\n    componentOrigins: Vector3[] = [];\n  faces.forEach((face, start) => {\n    if (component[start] !== -1) return;\n    const id = componentOrigins.length;\n    componentOrigins.push(surface.points[face[0]].clone());\n    const stack = [start];\n    while (stack.length) {\n      const f = stack.pop()!;\n      if (component[f] === -1) {\n        component[f] = id;\n        stack.push(...neighbors[f]);\n      }\n    }\n  });\n  const openComponents = new Set(boundary.map((e) => component[e.faces[0]]));\n  if (openComponents.size !== componentOrigins.length) fail(\"each component must be an open sheet.\");\n\n  const fixedDirection = fixed ? (offset as Vector3).clone().normalize() : null;\n  if (fixedDirection && normals.some((n) => n.dot(fixedDirection) <= EPS))\n    fail(\"extrusion direction must point into every source face's front hemisphere.\");\n  let maxFaceThicknessError = 0;\n  const offsets = incident.map((list, v) => {\n    const weights = list.map((f) => {\n      const face = faces[f],\n        k = face.indexOf(v);\n      const a = points[face[(k + 1) % 3]].clone().sub(points[v]).normalize();\n      const b = points[face[(k + 2) % 3]].clone().sub(points[v]).normalize();\n      return Math.atan2(a.clone().cross(b).length(), a.dot(b));\n    });\n    const d = fixedDirection?.clone() ?? new Vector3();\n    if (!fixedDirection) {\n      list.forEach((f, k) => d.addScaledVector(normals[f], weights[k]));\n      if (d.length() <= EPS) fail(\"undefined offset normal.\");\n      d.normalize();\n      if (list.some((f) => normals[f].dot(d) <= EPS)) fail(\"offset direction folds behind an incident face.\");\n      if (offset === \"crease-compensated\") {\n        let numerator = 0,\n          denominator = 0;\n        list.forEach((f, k) => {\n          const dot = normals[f].dot(d);\n          numerator += weights[k] * dot;\n          denominator += weights[k] * dot * dot;\n        });\n        d.multiplyScalar(numerator / denominator);\n      }\n    }\n    list.forEach((f) => {\n      maxFaceThicknessError = Math.max(maxFaceThicknessError, Math.abs(d.dot(normals[f]) - 1) * thickness);\n    });\n    return d;\n  });\n  const location = placement === \"front\" ? -1 : placement === \"back\" ? 1 : 0;\n  const front = surface.points.map((p, i) => p.clone().addScaledVector(offsets[i], (thickness * (location + 1)) / 2));\n  const back = surface.points.map((p, i) => p.clone().addScaledVector(offsets[i], (thickness * (location - 1)) / 2));\n  const positions: number[] = [],\n    uvs: number[] = [],\n    faceComponents: number[] = [];\n  const emit = (corners: Vector3[], coords: readonly Vector2[], id: number) => {\n    corners.forEach((p, i) => {\n      positions.push(p.x, p.y, p.z);\n      uvs.push(coords[i].x, coords[i].y);\n    });\n    faceComponents.push(id);\n  };\n  const defaultUV = [new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1)];\n  faces.forEach((face, f) =>\n    emit(\n      face.map((i) => front[i]),\n      surface.uv ? face.map((i) => surface.uv![i]) : defaultUV,\n      component[f],\n    ),\n  );\n  faces.forEach((face, f) =>\n    emit(\n      [...face].reverse().map((i) => back[i]),\n      [...(surface.uv ? face.map((i) => surface.uv![i]) : defaultUV)].reverse(),\n      component[f],\n    ),\n  );\n  // Each oriented manifold boundary has exactly one outgoing edge per boundary vertex.\n  const outgoing = new Map(boundary.map((e) => [e.a, e]));\n  const visited = new Set<Edge>();\n  for (const first of boundary) {\n    if (visited.has(first)) continue;\n    const loop: Edge[] = [];\n    let e = first;\n    do {\n      loop.push(e);\n      visited.add(e);\n      e = outgoing.get(e.b)!;\n    } while (e !== first);\n    const lengths = loop.map((e) => points[e.a].distanceTo(points[e.b]));\n    const perimeter = lengths.reduce((a, b) => a + b, 0);\n    let distance = 0;\n    loop.forEach((e, i) => {\n      const u0 = rimUnits === undefined ? distance / perimeter : (distance * scale) / rimUnits;\n      distance += lengths[i];\n      const u1 = rimUnits === undefined ? distance / perimeter : (distance * scale) / rimUnits;\n      const a = new Vector2(u0, rimUnits === undefined ? 1 : front[e.a].distanceTo(back[e.a]) / rimUnits).add(rimOffset),\n        b = new Vector2(u1, rimUnits === undefined ? 1 : front[e.b].distanceTo(back[e.b]) / rimUnits).add(rimOffset),\n        c = new Vector2(u0, 0).add(rimOffset),\n        d = new Vector2(u1, 0).add(rimOffset);\n      emit([front[e.b], front[e.a], back[e.a]], [b, a, c], component[e.faces[0]]);\n      emit([front[e.b], back[e.a], back[e.b]], [b, c, d], component[e.faces[0]]);\n    });\n  }\n  if (uvs.some((v) => !Number.isFinite(Math.fround(v)))) fail(\"UVs exceed Float32 range.\");\n  const geometry = new BufferGeometry();\n  const buffer = new Float32BufferAttribute(positions, 3);\n  if (Array.from(buffer.array).some((n) => !Number.isFinite(n))) fail(\"output exceeds Float32 coordinate range.\");\n  geometry.setAttribute(\"position\", buffer);\n  geometry.setAttribute(\"uv\", new Float32BufferAttribute(uvs, 2));\n  geometry.addGroup(0, faces.length * 3, 0);\n  geometry.addGroup(faces.length * 3, faces.length * 3, 1);\n  geometry.addGroup(faces.length * 6, boundary.length * 6, 2);\n  geometry.computeVertexNormals();\n  geometry.computeBoundingBox();\n  geometry.computeBoundingSphere();\n  let invertedFaces = 0,\n    degenerateFaces = 0;\n  const volumes = componentOrigins.map(() => 0);\n  for (let f = 0; f < faceComponents.length; f++) {\n    const id = faceComponents[f],\n      base = componentOrigins[id];\n    const p = [0, 1, 2].map((k) =>\n      new Vector3()\n        .fromBufferAttribute(buffer, f * 3 + k)\n        .sub(base)\n        .divideScalar(scale),\n    );\n    const n = normalOf(p, [0, 1, 2]);\n    const longest = Math.max(p[0].distanceToSquared(p[1]), p[1].distanceToSquared(p[2]), p[2].distanceToSquared(p[0]));\n    if (n.length() <= EPS * longest) degenerateFaces++;\n    if (f < faces.length * 2 && n.dot(normals[f % faces.length]) * (f < faces.length ? 1 : -1) <= 0) invertedFaces++;\n    volumes[id] += p[0].dot(n) / 6;\n  }\n  const diagnostics: ThicknessDiagnostics = {\n    boundaryEdges: boundary.length,\n    invertedFaces,\n    degenerateFaces,\n    nonPositiveVolumeComponents: volumes.filter((v) => v <= 0).length,\n    signedVolume: volumes.reduce((a, b) => a + b, 0) * scale ** 3,\n    maxFaceThicknessError,\n    selfIntersectionsChecked: false,\n  };\n  if (onInvalid === \"throw\" && (invertedFaces || degenerateFaces || diagnostics.nonPositiveVolumeComponents)) {\n    geometry.dispose();\n    fail(\n      `invalid offset (${invertedFaces} inverted, ${degenerateFaces} degenerate faces, ${diagnostics.nonPositiveVolumeComponents} nonpositive components); reduce thickness or use onInvalid: \"report\" to inspect.`,\n    );\n  }\n  return { geometry, diagnostics };\n}\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { Direction } from \"../../constants/Direction\";\nimport { Falloff } from \"../../constants/Falloff\";\n\n/** Displace positions in place within radius; direction magnitude scales strength. Normals and bounds remain stale. */\nexport const displacementBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  strength: number,\n  direction: Vector3 = Direction.UP,\n  falloffFn: (distance: number, radius: number) => number = Falloff.linear,\n): void => {\n  const positions = geometry.attributes.position;\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n\n      const falloff = falloffFn(distance, radius);\n      const influence = falloff * strength;\n\n      vertex.add(direction.clone().multiplyScalar(influence));\n\n      positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { Direction } from \"../../constants/Direction\";\nimport { Falloff } from \"../../constants/Falloff\";\n\n/** Move positions toward dot(vertex, direction) = targetHeight; normalizes the supplied direction in place.\n * Normals and bounds remain stale. */\nexport const flattenBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  targetHeight: number,\n  strength: number,\n  direction: Vector3 = Direction.UP,\n  falloffFn: (distance: number, radius: number) => number = Falloff.linear\n): void => {\n  const positions = geometry.attributes.position;\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n      const falloff = falloffFn(distance, radius);\n      const influence = falloff * strength;\n\n      const projectedHeight = vertex.dot(direction.normalize());\n      const delta = targetHeight - projectedHeight;\n\n      vertex.add(direction.clone().multiplyScalar(delta * influence));\n      positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { BufferGeometry, MathUtils, Vector3 } from \"three\";\nimport { Direction } from \"../../constants/Direction\";\nimport { Falloff } from \"../../constants/Falloff\";\n\n/** Perturb positions in place with independent random components weighted by normalized direction.\n * Normals and bounds remain stale. */\nexport const noiseBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  strength: number,\n  direction: Vector3 = Direction.UP,\n  falloffFn: (distance: number, radius: number) => number = Falloff.linear\n): void => {\n  const positions = geometry.attributes.position;\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n      const falloff = falloffFn(distance, radius);\n      const noiseStrength = strength * falloff;\n\n      const noise = direction.clone().normalize();\n      vertex.x += MathUtils.randFloatSpread(noiseStrength) * noise.x;\n      vertex.y += MathUtils.randFloatSpread(noiseStrength) * noise.y;\n      vertex.z += MathUtils.randFloatSpread(noiseStrength) * noise.z;\n\n      positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { BufferGeometry, Vector3 } from \"three\";\n\n/** Average positions in place within radius; sequential updates make results vertex-order dependent.\n * Normals and bounds remain stale; neighbor search is O(n²). */\nexport const smoothBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  strength: number\n): void => {\n  const positions = geometry.attributes.position;\n  const tempPosition = new Vector3();\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n      let averagePosition = new Vector3();\n      let count = 0;\n\n      for (let j = 0; j < positions.count; j++) {\n        tempPosition.fromBufferAttribute(positions, j);\n        if (tempPosition.distanceTo(vertex) < radius) {\n          averagePosition.add(tempPosition);\n          count++;\n        }\n      }\n\n      if (count > 0) {\n        averagePosition.divideScalar(count);\n        vertex.lerp(averagePosition, strength);\n        positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n      }\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { BufferGeometry, Vector3 } from \"three\";\nimport { Falloff } from \"../../constants/Falloff\";\n\n/** Move positions radially from the target; inward reverses the displacement. Normals and bounds remain stale. */\nexport const spikeBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  strength: number,\n  inward: boolean = false,\n  falloffFn: (distance: number, radius: number) => number = Falloff.linear\n): void => {\n  const positions = geometry.attributes.position;\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n\n      const falloff = falloffFn(distance, radius);\n      const influence = falloff * strength * (inward ? -1 : 1);\n\n      const direction = vertex.clone().sub(position).normalize();\n      vertex.add(direction.multiplyScalar(influence));\n\n      positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { BufferGeometry, Quaternion, Vector3 } from \"three\";\nimport { Direction } from \"../../constants/Direction\";\nimport { Falloff } from \"../../constants/Falloff\";\n\n/** Rotate positions in place about a unit direction through the target; strength is radians before falloff.\n * Normals and bounds remain stale. */\nexport const twistBrush = <T extends BufferGeometry>(\n  geometry: T,\n  position: Vector3,\n  radius: number,\n  strength: number,\n  direction: Vector3 = Direction.UP,\n  falloffFn: (distance: number, radius: number) => number = Falloff.linear\n): void => {\n  const positions = geometry.attributes.position;\n  const quaternion = new Quaternion();\n\n  for (let i = 0; i < positions.count; i++) {\n    const vertex = new Vector3();\n    vertex.fromBufferAttribute(positions, i);\n    const distance = vertex.distanceTo(position);\n\n    if (distance < radius) {\n\n      const falloff = falloffFn(distance, radius);\n      const angle = falloff * strength;\n\n      quaternion.setFromAxisAngle(direction, angle);\n\n      vertex.sub(position).applyQuaternion(quaternion).add(position);\n      positions.setXYZ(i, vertex.x, vertex.y, vertex.z);\n    }\n  }\n  positions.needsUpdate = true;\n};\n","import { Object3D, Vector3 } from \"three\";\n\nconst viewerPosition = new Vector3();\n\n/**\n * Pin a sky layer to the viewer, so it holds a **direction but never a location** — it can never\n * be approached, dollied toward, or placed behind anything. Call once at construction; the layer\n * then needs nothing per frame, so consumers only ever `scene.add(layer)`.\n *\n * This follows camera translation only; it does not rotate the layer to face the camera.\n *\n * Each renderable's `onBeforeRender` re-snaps the layer to the active camera. The renderer invokes\n * that hook before it derives the object's model-view matrix, so the move lands in the same frame.\n * Working on the container rather than in a shader is what makes this safe for `InstancedMesh`:\n * per-instance matrices are untouched, so instanced layers need no special handling.\n *\n * Notes:\n * - **This claims `onBeforeRender` on every renderable passed in.** Assigning your own handler to one\n *   of them silently breaks the lock — the layer stops tracking the camera, with no error. Wrap or\n *   chain the existing handler rather than replacing it. Nothing needs unsubscribing, though: the\n *   renderer only invokes the hook while the object is being drawn, so removing the layer from the\n *   scene stops it, and dropping the reference collects it. `dispose()` has nothing to undo.\n * - `frustumCulled` is disabled on every renderable. Culling runs *before* `onBeforeRender`, so a\n *   layer judged against a stale position could be culled and then never get the chance to correct\n *   itself.\n * - The camera's *world* position is used and converted back into the layer's parent space, so a\n *   transformed parent or a parented camera still resolves correctly.\n * - The layer sits at the origin until the first render, since nothing has supplied a camera yet.\n *   Read world positions off a sky layer only after a frame has been drawn.\n *\n * @example\n * ```typescript\n * class Sun extends Object3D {\n *   constructor() {\n *     super();\n *     const disc = new Mesh(geometry, material);\n *     this.add(disc);\n *     lockToViewer(this, [disc]);\n *   }\n * }\n * ```\n */\nexport function lockToViewer(layer: Object3D, renderables: Object3D[]): void {\n  const snap: Object3D[\"onBeforeRender\"] = (_renderer, _scene, camera) => {\n    camera.getWorldPosition(viewerPosition);\n    layer.position.copy(viewerPosition);\n    layer.parent?.worldToLocal(layer.position);\n    layer.updateMatrixWorld(true);\n  };\n\n  for (const renderable of renderables) {\n    renderable.frustumCulled = false;\n    renderable.onBeforeRender = snap;\n  }\n}\n","import {\n  AdditiveBlending,\n  CircleGeometry,\n  ColorRepresentation,\n  DataTexture,\n  Mesh,\n  MeshBasicMaterial,\n  Object3D,\n  PlaneGeometry,\n  Vector3,\n} from \"three\";\nimport { createRadialGradientTexture, type RadialGradientStop } from \"../textures/radialGradient\";\nimport { lockToViewer } from \"./LockToViewer\";\n\n/** Cool blue-white haze: hot core, quick falloff, long faint tail. */\nconst DEFAULT_HALO_STOPS: RadialGradientStop[] = [\n  { offset: 0, color: 0xd6e2ff, alpha: 0.48 },\n  { offset: 0.24, color: 0x8baae6, alpha: 0.16 },\n  { offset: 1, color: 0x5878be, alpha: 0 },\n];\n\nexport interface FullMoonHaloOptions {\n  /**\n   * Halo extent as a multiple of the moon radius, measured at the moon's distance.\n   * Defaults to `6.2`.\n   */\n  scale?: number;\n  /** Overall halo opacity, scaling the stop alphas. Defaults to `0.72`. */\n  opacity?: number;\n  /** Radial falloff, core to rim. Any number of stops; defaults to a cool blue-white haze. */\n  stops?: RadialGradientStop[];\n}\n\nexport interface FullMoonOptions {\n  /** Moon disc radius in world units. Defaults to `14`. */\n  radius?: number;\n  /**\n   * Compass bearing in degrees, following the astronomical horizontal (alt-az) convention:\n   * `0` is north (`-Z`), `90` east (`+X`), `180` south, `270` west — clockwise seen from\n   * above. Defaults to `18`.\n   */\n  azimuth?: number;\n  /**\n   * Degrees above the horizon, `-90` to `90`. Defaults to `1.15` — a low moon just clear of\n   * the horizon. Negative values sit below it.\n   */\n  elevation?: number;\n  /**\n   * Distance along the resolved direction. Defaults to `300`, and should sit inside the\n   * camera's far plane.\n   */\n  distance?: number;\n  /** Disc color. Defaults to `0xd8e3ff`. */\n  color?: ColorRepresentation;\n  /**\n   * Disc edge count. Defaults to `64` — smooth, because a moon is the canonical round thing\n   * and a chunky one reads as broken rather than stylized. Drop it for a deliberately faceted\n   * moon; the disc is flat, so even a high count costs almost nothing.\n   */\n  segments?: number;\n  /** Halo settings, or `false` for a bare disc. */\n  halo?: FullMoonHaloOptions | false;\n  /**\n   * Whether `scene.fog` tints the moon. Defaults to `false`, so the disc stays crisp and reads\n   * as a light source rather than a distant lit sphere.\n   *\n   * Because the moon rides at a fixed distance from the camera, enabling this yields a\n   * *constant* haze wash rather than fog that varies as the viewer moves.\n   */\n  fog?: boolean;\n}\n\n/**\n * Horizontal (alt-az) angles to a unit direction. North is `-Z` and up is `+Y`, so east\n * resolves to `+X` (`east = north × up`) and bearings run clockwise viewed from above,\n * matching how sun and moon positions are actually specified.\n */\nfunction directionFromAngles(azimuthDeg: number, elevationDeg: number): Vector3 {\n  const azimuth = (azimuthDeg * Math.PI) / 180;\n  const elevation = (elevationDeg * Math.PI) / 180;\n  const horizontal = Math.cos(elevation);\n  return new Vector3(horizontal * Math.sin(azimuth), Math.sin(elevation), -horizontal * Math.cos(azimuth));\n}\n\n/**\n * A **full moon** — a bright unlit disc wrapped in a soft additive haze, for the hazy ring you get\n * on a humid night.\n *\n * Deliberately the full-moon case, not a general moon. The haze is a *filled* additive gradient\n * sitting in front of the disc, so the moon can only ever read brighter than its surroundings; a\n * crescent, a new moon, or an eclipse would need an occluding terminator and a halo that follows the\n * lit limb, which is different machinery rather than another option.\n *\n * This is a **sky layer, not a skybox**: it owns the moon and nothing else, so it composes freely\n * with {@link StarField}, a scene background, or a dome of your own. Nothing here paints\n * the rest of the sky.\n *\n * **Placement** — `azimuth` and `elevation` are horizontal (alt-az) angles in degrees, the way sun\n * and moon positions are normally given: `0°` azimuth is north, `90°` east, and elevation climbs\n * from the horizon. The resolved unit vector is exposed as {@link direction} for aiming a\n * `DirectionalLight` along the same bearing.\n *\n * The moon is **viewer-relative — direction without location.** It pins itself to the active camera\n * every frame (see {@link lockToViewer}), so `scene.add(moon)` is the whole contract: there is no\n * per-frame call, and no amount of dollying brings the moon closer. `distance` is a render depth,\n * not a place. If you want a moon that can actually be reached, build geometry instead.\n *\n * **Both parts are flat and neither billboards.** The disc is unlit and uniformly colored, so a\n * sphere would be pixel-identical to a circle while costing an order of magnitude more triangles —\n * only the silhouette does any work. Both the disc and the haze are oriented once, perpendicular\n * to {@link direction}: because the moon rides the camera, the world-space line of sight to it is\n * always `direction`, so a fixed orientation is square-on at every orbit angle, exactly and with\n * no per-frame call.\n *\n * A `Sprite` is the trap here. Sprites align to the camera's view *plane*, not toward the camera's\n * *position*, so off screen-center the card tilts off the moon axis and dips behind the disc, which\n * then depth-occludes its own glow. Sprite cut-through scales with the sprite's own size versus its\n * clearance, not with distance from the camera.\n *\n * **Depth** — the disc is a normal depth-tested opaque mesh, so terrain and trees silhouette\n * against it. The halo is additive and writes no depth, so it never occludes what's in front.\n *\n * Uses only standard materials and a {@link createRadialGradientTexture}, so it renders under either\n * `WebGPURenderer` or `WebGLRenderer`, and constructs with no DOM.\n *\n * @example\n * ```typescript\n * const moon = new FullMoon({ radius: 14, azimuth: 18, elevation: 1.15 });\n * const stars = new StarField({ radius: 480, twinkle: true });\n * scene.add(moon, stars); // both pin themselves to the viewer — nothing per frame\n *\n * // Rake moonlight in from wherever the moon actually is.\n * const moonlight = new DirectionalLight(0xc8d8ff, 1.8);\n * moonlight.position.copy(moon.direction).multiplyScalar(40);\n * scene.add(moonlight);\n * ```\n *\n * Call {@link dispose} when removing the effect to free geometry, materials, and the halo texture.\n */\nexport class FullMoon extends Object3D {\n  /** The moon body. Depth-tested and opaque, so scene geometry silhouettes against it. */\n  readonly disc: Mesh<CircleGeometry, MeshBasicMaterial>;\n  /** The additive haze card, or `undefined` when `halo` is `false`. */\n  readonly halo?: Mesh<PlaneGeometry, MeshBasicMaterial>;\n  /**\n   * Unit direction resolved from `azimuth` / `elevation`, pointing from the viewer toward the\n   * moon. Read-only output, not an input — copy it onto a `DirectionalLight` to rake moonlight\n   * in from wherever the moon actually is.\n   */\n  readonly direction: Vector3;\n  /** Distance from this object's origin to the disc center. */\n  readonly distance: number;\n\n  private readonly haloTexture?: DataTexture;\n\n  constructor({\n    radius = 14,\n    azimuth = 18,\n    elevation = 1.15,\n    distance = 300,\n    color = 0xd8e3ff,\n    segments = 64,\n    halo = {},\n    fog = false,\n  }: FullMoonOptions = {}) {\n    super();\n\n    const unit = directionFromAngles(azimuth, elevation);\n    this.direction = unit;\n    this.distance = distance;\n\n    this.disc = new Mesh(\n      new CircleGeometry(radius, segments),\n      // Unlit and untone-mapped so the disc holds its brightness as a light source rather than\n      // being graded down with the rest of the scene.\n      new MeshBasicMaterial({ color, toneMapped: false, fog }),\n    );\n    this.disc.position.copy(unit).multiplyScalar(distance);\n    this.disc.lookAt(0, 0, 0);\n    this.add(this.disc);\n\n    if (halo !== false) {\n      const { scale = 6.2, opacity = 0.72, stops = DEFAULT_HALO_STOPS } = halo;\n\n      // Nudge the card in front of the disc so it wins the depth test where the two overlap.\n      // The offset is a fraction of radius rather than a fixed value so it holds at any size,\n      // and the card is scaled by the same ratio to keep `scale` an honest angular measure.\n      const haloDistance = Math.max(distance - radius * 0.05, Number.EPSILON);\n      const size = radius * scale * (haloDistance / distance);\n\n      this.haloTexture = createRadialGradientTexture({ stops });\n      this.halo = new Mesh(\n        new PlaneGeometry(size, size),\n        new MeshBasicMaterial({\n          map: this.haloTexture,\n          blending: AdditiveBlending,\n          transparent: true,\n          opacity,\n          depthWrite: false,\n          toneMapped: false,\n          fog,\n        }),\n      );\n      this.halo.position.copy(unit).multiplyScalar(haloDistance);\n      this.halo.lookAt(0, 0, 0);\n      this.add(this.halo);\n    }\n\n    lockToViewer(this, this.halo ? [this.disc, this.halo] : [this.disc]);\n  }\n\n  /** Release GPU resources held by the moon. */\n  dispose(): void {\n    this.disc.geometry.dispose();\n    this.disc.material.dispose();\n    this.halo?.geometry.dispose();\n    this.halo?.material.dispose();\n    this.haloTexture?.dispose();\n  }\n}\n","import {\n  BufferGeometry,\n  Color,\n  ColorRepresentation,\n  DoubleSide,\n  DynamicDrawUsage,\n  InstancedBufferAttribute,\n  InstancedMesh,\n  Material,\n  Matrix4,\n  MeshBasicMaterial,\n  Object3D,\n  Quaternion,\n  Vector3,\n} from \"three\";\nimport { instancedBufferAttribute, instancedDynamicBufferAttribute } from \"three/tsl\";\nimport { PointsNodeMaterial } from \"three/webgpu\";\nimport { BurstGeometry, type BurstGeometryOptions } from \"../geometry/shapes/BurstGeometry\";\nimport { createRandom, deriveSubSeed, type RandomSource } from \"../utils/Random\";\nimport { RandomColor, type ColorSampler } from \"../utils/RandomColor\";\nimport { lockToViewer } from \"./LockToViewer\";\n\n/** How each star is turned to face the viewer. */\nexport type StarFieldOrientation = \"points\" | \"radial\";\n\nexport interface StarBurstShapeOptions extends BurstGeometryOptions {\n  /** Number of burst points. Defaults to `4` — a diffraction-spike star. */\n  points?: number;\n  /** Extrusion depth (`orientation: \"radial\"` only — screen-aligned stars are flat). Defaults to `0.05`. */\n  depth?: number;\n}\n\nexport interface StarFieldOptions {\n  /** Optional seed for placement, twinkle phases, and independent color sampling. */\n  seed?: number;\n  /** Per-star working-space color overriding color. Index follows star creation order. */\n  colors?: ColorSampler;\n  /**\n   * How each star faces the viewer. This also decides which size options apply.\n   *\n   * - `points` (default) — screen-aligned, so the field holds its orientation as the camera\n   *   orbits. Only the geometry's **XY profile** is drawn; any Z extent is ignored. Sized with\n   *   `pixelSizeMin` / `pixelSizeMax`. **Requires `WebGPURenderer`** (node material).\n   * - `radial` — full 3D geometry rotated to face the shell center. Depth is real here, and stars\n   *   shear as the camera moves, the way any world-space mesh does. Sized with `sizeMin` /\n   *   `sizeMax` as angular extents. Uses only standard materials, so it runs on either renderer.\n   */\n  orientation?: StarFieldOrientation;\n  /** Star shape used to build the default {@link BurstGeometry}. */\n  burst?: StarBurstShapeOptions;\n  /** Replace the star geometry entirely. Billboards use its XY profile; radial uses all of it. */\n  geometry?: BufferGeometry;\n  /**\n   * Override the default field material. In `points` mode this must be a `PointsNodeMaterial` —\n   * per-star position, size, and rotation are assigned onto it as node inputs.\n   */\n  material?: Material;\n  /** Number of stars. Defaults to `1500`. */\n  count?: number;\n  /** Shell radius when `minRadius` / `maxRadius` are omitted. Defaults to `500`. */\n  radius?: number;\n  /** Inner shell radius. Defaults to `radius`. */\n  minRadius?: number;\n  /** Outer shell radius. Defaults to `radius`. */\n  maxRadius?: number;\n  /**\n   * Minimum angular size (radians at 1 unit distance). Scaled by each star's shell distance\n   * so apparent size stays consistent. Defaults to `0.008`.\n   */\n  sizeMin?: number;\n  /** Maximum angular size. Defaults to `0.025`. */\n  sizeMax?: number;\n  /**\n   * Star radius in logical (CSS) pixels — **`points` only**. Defaults to `4` / `14`.\n   *\n   * Screen-aligned stars are naturally sized in screen space, so there is no distance term at all:\n   * a star is the same size wherever it sits in the shell, and nothing depends on the viewer being\n   * at the shell's center. The trade against angular sizing is that pixels are absolute, so stars\n   * occupy a smaller fraction of a larger display.\n   */\n  pixelSizeMin?: number;\n  /** Star radius in logical pixels, maximum — **`points` only**. Defaults to `14`. */\n  pixelSizeMax?: number;\n  /** Single color or palette; multiple entries pick a random color per star. */\n  color?: ColorRepresentation | ColorRepresentation[];\n  /**\n   * Whether `scene.fog` tints the stars. Defaults to `false` — the shell sits far enough out\n   * that any usable fog density saturates and flattens the whole field to fog color.\n   *\n   * Ignored when you supply your own `material`; set the flag on that material instead.\n   */\n  fog?: boolean;\n  /** Enable pulsing brightness; call {@link StarField.update} each frame when `true`. */\n  twinkle?: boolean;\n  /**\n   * Base star rotation, in radians. Defaults to `0`.\n   *\n   * Measured in screen space for `points` and world space for `radial` — the same knob means\n   * different things, because a screen-aligned star re-aligns every frame and a radial star\n   * does not.\n   */\n  rotation?: number;\n  /**\n   * Random rotation spread added per star, in radians. Defaults to `Math.PI * 2`.\n   *\n   * `0` aligns every star — with `points` that yields a coherent diffraction-spike field that\n   * stays locked as the camera orbits. `2π` is fully random.\n   */\n  rotationJitter?: number;\n}\n\nconst SHELL_CENTER = new Vector3(0, 0, 0);\n\nfunction randomUnitVector(target: Vector3, random: () => number): Vector3 {\n  const u = random();\n  const v = random();\n  const theta = Math.PI * 2 * u;\n  const phi = Math.acos(2 * v - 1);\n  const sinPhi = Math.sin(phi);\n  return target.set(sinPhi * Math.cos(theta), Math.cos(phi), sinPhi * Math.sin(theta));\n}\n\nfunction resolvePalette(color: ColorRepresentation | ColorRepresentation[]): Color[] {\n  return (Array.isArray(color) ? color : [color]).map((entry) => new Color(entry));\n}\n\n/**\n * Largest radius in the XY plane. A screen-aligned star only ever draws the XY profile, so measuring\n * the full bounding sphere would let an extruded geometry's depth shrink the visible star.\n */\nfunction profileRadiusXY(geometry: BufferGeometry): number {\n  const position = geometry.getAttribute(\"position\");\n  let maxSquared = 0;\n  for (let i = 0; i < position.count; i++) {\n    const x = position.getX(i);\n    const y = position.getY(i);\n    const squared = x * x + y * y;\n    if (squared > maxSquared) maxSquared = squared;\n  }\n  return Math.sqrt(maxSquared) || 1;\n}\n\n/**\n * Procedural star field distributed on a spherical shell — intended as an infinite sky dome.\n *\n * The shell pins itself to the active camera every frame (see {@link lockToViewer}), so\n * `scene.add(stars)` is the whole contract — the field is unreachable no matter how far the\n * viewer travels, and there is no per-frame placement call. {@link update} remains necessary\n * only for `twinkle`.\n *\n * **Orientation** decides how stars are drawn *and* how they are sized — the two travel together,\n * because screen-aligned stars are naturally measured in screen space and real geometry in world\n * space:\n *\n * - `points` — screen-aligned via `PointsNodeMaterial`, with per-star position, size, and rotation\n *   supplied as instanced attributes. The field stays visually fixed as the camera orbits. Flat by\n *   construction: only the geometry's XY profile is used. Sized by `pixelSizeMin` / `pixelSizeMax`\n *   in logical pixels, with **no distance term at all**. Requires `WebGPURenderer`.\n * - `radial` — instanced 3D meshes rotated to face the shell center, drawn `DoubleSide` so stars\n *   stay visible from inside the shell. Sized by `sizeMin` / `sizeMax` as angular extents (radians\n *   at unit distance), scaled by each star's distance from the origin so stars look similar\n *   regardless of shell depth. That conversion assumes the viewer sits at the shell's center, which\n *   {@link lockToViewer} guarantees. Uses only standard materials, so it runs on either renderer.\n *\n * Both render as a single instanced draw call, so the geometry you pass is a matter of looks rather\n * than cost.\n *\n * @example\n * ```typescript\n * const stars = new StarField({\n *   count: 2500,\n *   radius: 480,\n *   rotationJitter: 0, // every burst locked vertical on screen\n *   twinkle: true,\n * });\n *\n * scene.add(stars); // pins itself to the viewer — no placement call needed\n *\n * function animate() {\n *   stars.update(); // only for twinkle; a no-op when twinkle is false\n *   renderer.render(scene, camera);\n * }\n * ```\n *\n * Call {@link dispose} when removing the effect to free geometry and materials.\n */\n// TODO: split the two strategies. `points` and `radial` share only *data* — the shell distribution\n// (offsets, rotations, colors, twinkle phases) — while diverging on material, sizing units, twinkle\n// write-back, and renderer requirement. Extract a `starShellDistribution()` returning plain arrays and\n// give each strategy its own thin class. Payoff: the radial variant becomes importable WITHOUT\n// `three/webgpu`, since only `points` needs a node material. Do not name the classes after their\n// implementations (`StarFieldInstancedMesh`) — name them for what they are to a consumer.\nexport class StarField extends Object3D {\n  private readonly source: RandomSource;\n  private readonly colorSource: RandomSource;\n\n  readonly orientation: StarFieldOrientation;\n\n  private readonly field: InstancedMesh;\n  private readonly twinkle: boolean;\n  private readonly baseScales?: Float32Array;\n  private readonly twinklePhases?: Float32Array;\n  /** Billboard scale attribute, rewritten each frame while twinkling. */\n  private scaleAttribute?: InstancedBufferAttribute;\n  private readonly dummy = new Object3D();\n\n  constructor(options: StarFieldOptions = {}) {\n    super();\n    this.source = createRandom(options.seed);\n    this.colorSource = createRandom(options.seed === undefined ? undefined : deriveSubSeed(options.seed, 0x73746172));\n\n    const {\n      orientation = \"points\",\n      count = 1500,\n      radius = 500,\n      minRadius = radius,\n      maxRadius = radius,\n      sizeMin = 0.008,\n      sizeMax = 0.025,\n      pixelSizeMin = 4,\n      pixelSizeMax = 14,\n      color = [0xffffff, 0xcad7ff, 0xfff4e0],\n      fog = false,\n      twinkle = false,\n      rotation = 0,\n      rotationJitter = Math.PI * 2,\n      material,\n      burst = {},\n      geometry,\n    } = options;\n\n    this.orientation = orientation;\n    this.frustumCulled = false;\n    this.twinkle = twinkle;\n\n    if (twinkle) {\n      this.twinklePhases = new Float32Array(count);\n      this.baseScales = new Float32Array(count);\n    }\n\n    const starGeometry =\n      geometry ??\n      new BurstGeometry({\n        points: burst.points ?? 4,\n        innerRadius: burst.innerRadius ?? 0.6,\n        outerRadius: burst.outerRadius ?? 1.9,\n        depth: burst.depth ?? 0.05,\n      });\n\n    const shared = {\n      count,\n      minRadius,\n      maxRadius,\n      sizeMin,\n      sizeMax,\n      pixelSizeMin,\n      pixelSizeMax,\n      color,\n      sampler: options.colors,\n      fog,\n      material,\n      geometry: starGeometry,\n      rotation,\n      rotationJitter,\n    };\n\n    this.field = orientation === \"points\" ? this.createPointsField(shared) : this.createRadialField(shared);\n\n    this.add(this.field);\n    lockToViewer(this, [this.field]);\n  }\n\n  get mesh(): InstancedMesh {\n    return this.field;\n  }\n\n  get geometry(): BufferGeometry {\n    return this.field.geometry;\n  }\n\n  get material(): Material | Material[] {\n    return this.field.material;\n  }\n\n  /** Release GPU resources held by the field. */\n  dispose(): void {\n    this.geometry.dispose();\n    const materials = Array.isArray(this.material) ? this.material : [this.material];\n    for (const entry of materials) entry.dispose();\n  }\n\n  /**\n   * Animate twinkling. No-op when `twinkle` is `false`.\n   *\n   * Each star pulses on its own phase offset so the field twinkles out of sync. Billboards\n   * rewrite the instanced scale attribute; radial stars rebuild each instance matrix.\n   * Pass elapsed time in seconds (defaults to `performance.now()`).\n   */\n  update(elapsed = performance.now() * 0.001): void {\n    if (!this.twinkle || !this.baseScales || !this.twinklePhases) return;\n\n    if (this.orientation !== \"radial\") {\n      const attribute = this.scaleAttribute;\n      if (!attribute) return;\n      const array = attribute.array as Float32Array;\n      for (let i = 0; i < this.baseScales.length; i++) {\n        const pulse = 0.75 + 0.25 * Math.sin(elapsed * 2.5 + this.twinklePhases[i]);\n        array[i] = this.baseScales[i] * pulse;\n      }\n      attribute.needsUpdate = true;\n      return;\n    }\n\n    const matrix = new Matrix4();\n    const position = new Vector3();\n    const quaternion = new Quaternion();\n    const scale = new Vector3();\n\n    for (let i = 0; i < this.field.count; i++) {\n      this.field.getMatrixAt(i, matrix);\n      matrix.decompose(position, quaternion, scale);\n\n      const pulse = 0.75 + 0.25 * Math.sin(elapsed * 2.5 + this.twinklePhases[i]);\n      const s = this.baseScales[i] * pulse;\n      this.dummy.position.copy(position);\n      this.dummy.quaternion.copy(quaternion);\n      this.dummy.scale.setScalar(s);\n      this.dummy.updateMatrix();\n      this.field.setMatrixAt(i, this.dummy.matrix);\n    }\n\n    this.field.instanceMatrix.needsUpdate = true;\n  }\n\n  /**\n   * EXPERIMENTAL — screen-aligned stars sized in **screen pixels** instead of world units.\n   *\n   * `PointsNodeMaterial` extends `SpriteNodeMaterial`, aligning the geometry's XY to the view plane\n   * the same way, but scaling that offset by a pixel size and dividing by the viewport. A star is\n   * therefore N pixels wherever it sits in the shell — no angular-to-world conversion, and no\n   * dependence on where the viewer is.\n   *\n   * The geometry is normalized so its XY profile radius is `1`, which makes `pixelSize` mean an\n   * honest pixel radius rather than a multiple of whatever the burst happened to measure.\n   *\n   * Note the dispatch in `PointsNodeMaterial.setupVertex`: the pixel path runs for objects that are\n   * **not** `isPoints`, so an `InstancedMesh` is precisely what selects it.\n   */\n  private createPointsField({\n    count,\n    minRadius,\n    maxRadius,\n    pixelSizeMin,\n    pixelSizeMax,\n    color,\n    sampler,\n    fog,\n    material,\n    geometry,\n    rotation,\n    rotationJitter,\n  }: {\n    count: number;\n    minRadius: number;\n    maxRadius: number;\n    pixelSizeMin: number;\n    pixelSizeMax: number;\n    color: ColorRepresentation | ColorRepresentation[];\n    sampler?: ColorSampler;\n    fog: boolean;\n    material?: Material;\n    geometry: BufferGeometry;\n    rotation: number;\n    rotationJitter: number;\n  }): InstancedMesh {\n    const palette = resolvePalette(color);\n    if (palette.length === 0 && !sampler) throw new Error(\"StarField requires a non-empty color palette\");\n    const sample = sampler ?? RandomColor.pick(palette);\n    const context = { index: 0, random: sampler ? this.colorSource : this.source };\n    const tint = new Color();\n    const direction = new Vector3();\n    const shellSpan = Math.max(maxRadius - minRadius, 0);\n\n    const centered = geometry.clone();\n    centered.center();\n    const profile = profileRadiusXY(centered);\n    const inverse = 1 / profile;\n    centered.scale(inverse, inverse, inverse);\n\n    const offsets = new Float32Array(count * 3);\n    const sizes = new Float32Array(count);\n    const rotations = new Float32Array(count);\n    const perStarColor = !!sampler || palette.length > 1;\n    const colors = perStarColor ? new Float32Array(count * 3) : null;\n\n    for (let i = 0; i < count; i++) {\n      const distance = minRadius + this.source.next() * shellSpan;\n      randomUnitVector(direction, this.source.next).multiplyScalar(distance);\n      offsets[i * 3] = direction.x;\n      offsets[i * 3 + 1] = direction.y;\n      offsets[i * 3 + 2] = direction.z;\n\n      // No distance term — that is what makes shell depth irrelevant to apparent size.\n      sizes[i] = pixelSizeMin + this.source.next() * (pixelSizeMax - pixelSizeMin);\n      rotations[i] = rotation + this.source.next() * rotationJitter;\n\n      if (this.baseScales) this.baseScales[i] = sizes[i];\n      if (this.twinklePhases) this.twinklePhases[i] = this.source.next() * Math.PI * 2;\n\n      if (colors) {\n        if (sampler && palette.length > 1) this.source.next();\n        context.index = i;\n        sample(tint, context);\n        const starColor = tint;\n        colors[i * 3] = starColor.r;\n        colors[i * 3 + 1] = starColor.g;\n        colors[i * 3 + 2] = starColor.b;\n      }\n    }\n\n    const offsetAttribute = new InstancedBufferAttribute(offsets, 3);\n    const sizeAttribute = new InstancedBufferAttribute(sizes, 1);\n    const rotationAttribute = new InstancedBufferAttribute(rotations, 1);\n    // `update()` rewrites this attribute for twinkle.\n    this.scaleAttribute = sizeAttribute;\n\n    const starMaterial =\n      (material as PointsNodeMaterial | undefined) ??\n      new PointsNodeMaterial({\n        color: perStarColor ? 0xffffff : palette[0]!.getHex(),\n        side: DoubleSide,\n        depthWrite: false,\n        toneMapped: false,\n        fog,\n      });\n    // Pure pixel size — no perspective falloff, so a star never shrinks with shell depth.\n    starMaterial.sizeAttenuation = false;\n\n    starMaterial.positionNode = instancedBufferAttribute(offsetAttribute, \"vec3\");\n    starMaterial.rotationNode = instancedBufferAttribute(rotationAttribute, \"float\");\n\n    // `sizeNode` is consumed as logical pixels — the material multiplies by `screenDPR` itself.\n    if (this.twinkle) {\n      sizeAttribute.setUsage(DynamicDrawUsage);\n      starMaterial.sizeNode = instancedDynamicBufferAttribute(sizeAttribute, \"float\");\n    } else {\n      starMaterial.sizeNode = instancedBufferAttribute(sizeAttribute, \"float\");\n    }\n    if (colors) {\n      starMaterial.colorNode = instancedBufferAttribute(new InstancedBufferAttribute(colors, 3), \"vec3\");\n    }\n\n    const mesh = new InstancedMesh(centered, starMaterial, count);\n    mesh.frustumCulled = false;\n    mesh.renderOrder = 1;\n\n    const identity = new Matrix4();\n    for (let i = 0; i < count; i++) mesh.setMatrixAt(i, identity);\n    mesh.instanceMatrix.needsUpdate = true;\n\n    return mesh;\n  }\n\n  /** Full 3D stars rotated to face the shell center. */\n  private createRadialField({\n    count,\n    minRadius,\n    maxRadius,\n    sizeMin,\n    sizeMax,\n    color,\n    sampler,\n    fog,\n    material,\n    geometry,\n    rotation,\n    rotationJitter,\n  }: {\n    count: number;\n    minRadius: number;\n    maxRadius: number;\n    sizeMin: number;\n    sizeMax: number;\n    color: ColorRepresentation | ColorRepresentation[];\n    sampler?: ColorSampler;\n    fog: boolean;\n    material?: Material;\n    geometry: BufferGeometry;\n    rotation: number;\n    rotationJitter: number;\n  }): InstancedMesh {\n    const palette = resolvePalette(color);\n    if (palette.length === 0 && !sampler) throw new Error(\"StarField requires a non-empty color palette\");\n    const sample = sampler ?? RandomColor.pick(palette);\n    const context = { index: 0, random: sampler ? this.colorSource : this.source };\n    const tint = new Color();\n    const direction = new Vector3();\n    const shellSpan = Math.max(maxRadius - minRadius, 0);\n\n    const centered = geometry.clone();\n    centered.center();\n    centered.computeBoundingSphere();\n    const meshRadius = centered.boundingSphere?.radius ?? 1;\n\n    const starMaterial =\n      material ??\n      new MeshBasicMaterial({\n        color: !sampler && palette.length === 1 ? palette[0].getHex() : 0xffffff,\n        side: DoubleSide,\n        depthWrite: false,\n        toneMapped: false,\n        fog,\n      });\n\n    const mesh = new InstancedMesh(centered, starMaterial, count);\n    mesh.frustumCulled = false;\n    mesh.renderOrder = 1;\n\n    for (let i = 0; i < count; i++) {\n      const distance = minRadius + this.source.next() * shellSpan;\n      randomUnitVector(direction, this.source.next).multiplyScalar(distance);\n\n      const angular = sizeMin + this.source.next() * (sizeMax - sizeMin);\n      const scale = (distance * angular) / meshRadius;\n      if (this.baseScales) this.baseScales[i] = scale;\n      if (this.twinklePhases) this.twinklePhases[i] = this.source.next() * Math.PI * 2;\n\n      this.dummy.position.copy(direction);\n      this.dummy.lookAt(SHELL_CENTER);\n      this.dummy.rotateZ(rotation + this.source.next() * rotationJitter);\n      this.dummy.scale.setScalar(scale);\n      this.dummy.updateMatrix();\n      mesh.setMatrixAt(i, this.dummy.matrix);\n\n      if (sampler || palette.length > 1) {\n        if (sampler && palette.length > 1) this.source.next();\n        context.index = i;\n        sample(tint, context);\n        mesh.setColorAt(i, tint);\n      }\n    }\n\n    mesh.instanceMatrix.needsUpdate = true;\n    if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;\n\n    return mesh;\n  }\n}\n","import {\n  DataTexture,\n  NearestFilter,\n  RepeatWrapping,\n  RGBAFormat,\n  SRGBColorSpace,\n  UnsignedByteType,\n} from \"three\";\n\nexport interface CheckerboardTextureOptions {\n  /**\n   * Texture edge length in texels, which is also the number of alternating squares per tile.\n   * Defaults to `2` — the smallest true checker, and all you need when tiling a large plane via\n   * `texture.repeat`.\n   *\n   * **Rounded up to an even number.** The pattern alternates on `(x ^ y) & 1`, so with an odd\n   * count the parity repeats where the tile wraps and two same-colored rows meet at every seam.\n   */\n  size?: number;\n}\n\n/**\n * A hard-edged checkerboard as a {@link DataTexture} — the classic chessboard or tile floor.\n *\n * Nearest filtering keeps the squares crisp instead of blurring them, and the texture repeats, so\n * a two-texel array can cover an arbitrarily large plane:\n *\n * @example\n * ```typescript\n * const texture = createCheckerboardTexture({ size: 2 });\n * texture.repeat.set(8, 8); // 8×8 squares across the plane\n *\n * const floor = new Mesh(new PlaneGeometry(10, 10), new MeshStandardMaterial({ map: texture }));\n * floor.rotation.x = -Math.PI / 2;\n * ```\n */\nexport const createCheckerboardTexture = ({ size = 2 }: CheckerboardTextureOptions = {}): DataTexture => {\n  // Even counts only, so the pattern stays continuous across the repeat seam.\n  const texels = Math.max(2, Math.ceil(size / 2) * 2);\n  const data = new Uint8Array(texels * texels * 4);\n\n  for (let i = 0; i < texels * texels; i++) {\n    const stride = i * 4;\n    const shade = ((i % texels) ^ Math.floor(i / texels)) & 1 ? 255 : 0;\n    data[stride] = shade;\n    data[stride + 1] = shade;\n    data[stride + 2] = shade;\n    data[stride + 3] = 255;\n  }\n\n  const texture = new DataTexture(data, texels, texels, RGBAFormat, UnsignedByteType);\n  // Tagged as a color map. Pure black and white are fixed points of the sRGB transfer, so this\n  // changes nothing on its own — it keeps the result correct once the material tints it.\n  texture.colorSpace = SRGBColorSpace;\n  texture.wrapS = RepeatWrapping;\n  texture.wrapT = RepeatWrapping;\n  // `DataTexture` already defaults both filters to nearest; stated explicitly because hard edges\n  // are the entire point, and because `generateMipmaps` is off.\n  texture.magFilter = NearestFilter;\n  texture.minFilter = NearestFilter;\n  texture.needsUpdate = true;\n\n  return texture;\n};\n","import { Box3, Object3D, Vector3 } from \"three\";\nimport { BoxSide } from \"../constants/BoxSide\";\n\n/**\n * Aligns an array of Object3D objects (or subclasses) to a specified side\n * (left, right, top, bottom, front, or back) based on their world-space bounding boxes.\n */\nexport function alignToEdge<T extends Object3D>(objects: T[], side: BoxSide): void {\n  if (objects.length === 0) {\n    throw new Error(\"No objects provided for alignment.\");\n  }\n\n  const worldBoundingBox = new Box3();\n  const worldPosition = new Vector3();\n\n  // Precompute world-space bounding boxes and positions for all objects\n  const objectData = objects.map((object) => {\n    worldBoundingBox.setFromObject(object); // Accounts for scale and rotation\n    object.getWorldPosition(worldPosition);\n\n    return {\n      object,\n      boundingBox: worldBoundingBox.clone(), // Clone to avoid overwriting\n      worldPosition: worldPosition.clone(),\n    };\n  });\n\n  // Compute the reference alignment value based on the specified side\n  const referenceValue = objectData.reduce(\n    (acc, { boundingBox }) => {\n      switch (side) {\n        case BoxSide.LEFT:\n          return Math.min(acc, boundingBox.min.x);\n        case BoxSide.RIGHT:\n          return Math.max(acc, boundingBox.max.x);\n        case BoxSide.BOTTOM:\n          return Math.min(acc, boundingBox.min.y);\n        case BoxSide.TOP:\n          return Math.max(acc, boundingBox.max.y);\n        case BoxSide.BACK:\n          return Math.min(acc, boundingBox.min.z);\n        case BoxSide.FRONT:\n          return Math.max(acc, boundingBox.max.z);\n        default:\n          throw new Error(`Unsupported side type: ${side}`);\n      }\n    },\n    side === BoxSide.RIGHT || side === BoxSide.TOP || side === BoxSide.FRONT ? -Infinity : Infinity,\n  );\n\n  // Align each object to the computed reference value\n  objectData.forEach(({ object, boundingBox }) => {\n    switch (side) {\n      case BoxSide.LEFT:\n        object.position.x += referenceValue - boundingBox.min.x;\n        break;\n      case BoxSide.RIGHT:\n        object.position.x += referenceValue - boundingBox.max.x;\n        break;\n      case BoxSide.BOTTOM:\n        object.position.y += referenceValue - boundingBox.min.y;\n        break;\n      case BoxSide.TOP:\n        object.position.y += referenceValue - boundingBox.max.y;\n        break;\n      case BoxSide.BACK:\n        object.position.z += referenceValue - boundingBox.min.z;\n        break;\n      case BoxSide.FRONT:\n        object.position.z += referenceValue - boundingBox.max.z;\n        break;\n    }\n  });\n}\n","import { Direction } from \"../constants/Direction\";\nimport { Box3, Object3D, Vector3 } from \"three\";\n\n/**\n * Aligns an array of `Object3D` objects along a specified direction with optional spacing.\n */\nexport function alignToRow<T extends Object3D>(\n  objects: T[],\n  direction: Vector3 = Direction.RIGHT,\n  spacing: number = 0,\n): void {\n  // Ensure the direction vector is normalized\n  const alignmentDirection = direction.clone().normalize();\n\n  // Start the position tracker for alignment\n  let currentPosition = new Vector3();\n  const worldBoundingBox = new Box3();\n\n  // Align each object\n  objects.forEach((object) => {\n    // Compute the world-space bounding box\n    worldBoundingBox.setFromObject(object); // Accounts for scale and rotation\n\n    // Calculate the object's size along the alignment direction\n    const sizeVector = new Vector3(\n      worldBoundingBox.max.x - worldBoundingBox.min.x,\n      worldBoundingBox.max.y - worldBoundingBox.min.y,\n      worldBoundingBox.max.z - worldBoundingBox.min.z,\n    );\n    const size = sizeVector.dot(alignmentDirection);\n\n    // Compute the object's center in world space\n    const objectCenter = new Vector3();\n    worldBoundingBox.getCenter(objectCenter);\n\n    // Adjust the object's position so its center aligns with the current position\n    const offset = alignmentDirection.clone().multiplyScalar(size / 2);\n    object.position.copy(currentPosition.clone().add(offset).sub(objectCenter).add(object.position));\n\n    // Update the position tracker for the next object\n    currentPosition.add(alignmentDirection.clone().multiplyScalar(size + spacing));\n  });\n}\n","import { Box3, BufferGeometry, Float32BufferAttribute, InstancedMesh, Matrix4, Object3D, Quaternion, Vector3 } from \"three\";\n\n//------------------------------\n//  Object3D\n//------------------------------\n\n/**\n * Align an Object3D to a surface by adjusting its position.\n */\nexport function alignObjectToSurface(object: Object3D, targetPosition: Vector3, offset: Vector3 = new Vector3(0, 0, 0)): void {\n  const boundingBox = new Box3().setFromObject(object);\n\n  if (!boundingBox.isEmpty()) {\n    const min = boundingBox.min;\n\n    // Compute bottom center in world space\n    const bottomCenter = new Vector3((min.x + boundingBox.max.x) / 2, min.y, (min.z + boundingBox.max.z) / 2);\n    object.localToWorld(bottomCenter);\n\n    // Calculate adjustment vector in world space\n    const adjustment = targetPosition.clone().sub(bottomCenter).add(offset);\n\n    // Transform adjustment to local space and apply\n    const localAdjustment = object.worldToLocal(adjustment.clone());\n    object.position.add(localAdjustment);\n  } else {\n    console.warn(\"The object has no geometry or is not visible.\");\n  }\n}\n\n//------------------------------\n//  Buffer Geometry\n//------------------------------\n\n/**\n * Align a BufferGeometry to a surface by adjusting its vertices.\n */\nexport function alignBufferGeometryToSurface(geometry: BufferGeometry, targetPositionY: number): void {\n  const boundingBox = new Box3().setFromBufferAttribute(new Float32BufferAttribute(geometry.attributes.position.array, 3));\n\n  if (!boundingBox.isEmpty()) {\n    const minY = boundingBox.min.y;\n\n    // Translate the geometry to align the bottom to the target position\n    geometry.translate(0, targetPositionY - minY, 0);\n  } else {\n    console.warn(\"The geometry is empty or invalid.\");\n  }\n}\n\n//------------------------------\n//  Instanced Mesh\n//------------------------------\n\n/**\n * Align an InstancedMesh to a surface.\n */\nexport function alignInstancedMeshToSurface(\n  instancedMesh: InstancedMesh,\n  targetPosition: Vector3,\n  offset: Vector3 = new Vector3(0, 0, 0),\n): void {\n  // Compute the bounding box for the entire InstancedMesh\n  const boundingBox = new Box3().setFromObject(instancedMesh);\n\n  if (!boundingBox.isEmpty()) {\n    // Find the bottom Y coordinate of the bounding box\n    const minY = boundingBox.min.y;\n\n    // Compute the adjustment needed to align the bottom of the InstancedMesh to the target position\n    const adjustment = new Vector3(0, targetPosition.y - minY, 0).add(offset);\n\n    // Transform adjustment to local space and apply to position\n    const localAdjustment = instancedMesh.worldToLocal(adjustment.clone());\n    instancedMesh.position.add(localAdjustment);\n  } else {\n    console.warn(\"The InstancedMesh has no geometry or is not visible.\");\n  }\n}\n\n/**\n * Align a specific instance in an InstancedMesh to a surface.\n */\nexport function alignInstancedMeshIndexToSurface(\n  instancedMesh: InstancedMesh,\n  targetPosition: Vector3,\n  instanceIndex: number,\n  offset: Vector3 = new Vector3(0, 0, 0),\n): void {\n  const boundingBox = new Box3().setFromObject(instancedMesh);\n\n  if (!boundingBox.isEmpty()) {\n    const minY = boundingBox.min.y;\n\n    // Get the instance's current transformation matrix\n    const matrix = new Matrix4();\n    instancedMesh.getMatrixAt(instanceIndex, matrix);\n\n    // Decompose the matrix to extract position, rotation, and scale\n    const position = new Vector3();\n    const quaternion = new Quaternion();\n    const scale = new Vector3();\n    matrix.decompose(position, quaternion, scale);\n\n    // Transform targetPosition to the instanced mesh's local space\n    const localTargetPosition = instancedMesh.worldToLocal(targetPosition.clone());\n\n    // Adjust the position of the instance\n    position.y = localTargetPosition.y - minY + offset.y;\n\n    // Recompose the matrix and update the instance\n    matrix.compose(position, quaternion, scale);\n    instancedMesh.setMatrixAt(instanceIndex, matrix);\n\n    // Mark the instance matrix as needing an update\n    instancedMesh.instanceMatrix.needsUpdate = true;\n  } else {\n    console.warn(\"The instanced mesh has invalid geometry.\");\n  }\n}\n","import { Box3, Mesh, Object3D, Vector3 } from \"three\";\n\n//------------------------------\n//  Object3D\n//------------------------------\n\n/**\n * Centers an `Object3D` relative to a specified target position with an optional offset.\n *\n * This function calculates the bounding box center of the given `Object3D` and adjusts\n * its position so that it is centered at the specified target position, with an optional\n * offset applied. The centering respects the object's current transformation, including\n * its scale and rotation.\n */\nexport function centerObject<T extends Object3D>(object: T, target = new Vector3(0, 0, 0), offset = new Vector3(0, 0, 0)) {\n  const box = new Box3().setFromObject(object);\n  const center = box.getCenter(new Vector3());\n  const totalOffset = new Vector3().addVectors(target, offset);\n  const adjustment = new Vector3().subVectors(totalOffset, center);\n\n  object.position.add(adjustment);\n}\n\n/**\n * Centers the geometry of an `Object3D` relative to a target position with an optional offset.\n *\n * This function calculates the bounding box center of the given `Object3D` and adjusts its\n * geometry's position by translating it such that the geometry is centered at the specified\n * target position, with an optional offset applied. Unlike modifying the `position` property,\n * this function directly translates the geometry within the object's local space.\n */\nexport function centerObjectGeometry<T extends Object3D>(\n  object: T,\n  target: Vector3 = new Vector3(0, 0, 0),\n  offset: Vector3 = new Vector3(0, 0, 0),\n): void {\n  const box = new Box3().setFromObject(object);\n  const center = box.getCenter(new Vector3());\n  const totalTarget = new Vector3().addVectors(target, offset);\n\n  object.translateX(totalTarget.x - center.x);\n  object.translateY(totalTarget.y - center.y);\n  object.translateZ(totalTarget.z - center.z);\n  object.updateMatrixWorld(true);\n}\n\n//------------------------------\n//  Mesh\n//------------------------------\n\n/**\n * Centers the geometry of a `Mesh` relative to a target position with an optional offset.\n *\n * This function calculates the bounding box center of the `Mesh` geometry and adjusts its\n * position by translating the geometry such that it is centered at the specified target\n * position, with an optional offset applied. The function modifies the geometry directly,\n * leaving the `Mesh`'s transformation properties (`position`, `rotation`, `scale`) unchanged.\n */\nexport function centerMeshGeometry<T extends Mesh>(\n  mesh: T,\n  target: Vector3 = new Vector3(0, 0, 0),\n  offset: Vector3 = new Vector3(0, 0, 0),\n): void {\n  // Compute the bounding box for the geometry\n  mesh.geometry.computeBoundingBox();\n  const box = mesh.geometry.boundingBox;\n\n  if (box) {\n    const center = box.getCenter(new Vector3());\n    const totalTarget = new Vector3().addVectors(target, offset);\n    const translationOffset = new Vector3().subVectors(totalTarget, center);\n\n    mesh.geometry.translate(translationOffset.x, translationOffset.y, translationOffset.z);\n  }\n}\n\nexport const Center = {\n  object: centerObject,\n  objectGeometry: centerObjectGeometry,\n  meshGeometry: centerMeshGeometry,\n};\n","/**\n * Numeric color utilities: RGB channels use 0–255, HSL uses degrees / percentages.\n * These functions do not perform sRGB transfer-function conversion. normalizeRgb\n * changes scale only. Use Three Color with explicit SRGBColorSpace when rendering\n * byte RGB values. Distance helpers are numeric RGB metrics, not perceptual Delta E.\n */\n//------------------------------\n//  Hex\n//------------------------------\n\n/**\n * Convert hex color code color string to RGB array\n */\nexport function parseHexCode(hex: string): [number, number, number] {\n  if (!/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(hex)) {\n    throw new Error(\"parseHexCode expects #RGB or #RRGGBB (without alpha)\");\n  }\n  const digits = hex.slice(1);\n  const expanded = digits.length === 3 ? [...digits].map((digit) => digit + digit).join(\"\") : digits;\n  return hexToRgb(Number.parseInt(expanded, 16));\n}\n\nexport function hexToHsl(hex: number): [number, number, number] {\n  const [r, g, b] = hexToRgb(hex);\n  return rgbToHsl(r, g, b);\n}\n\n/**\n * Convert hexadecimal literal numeric color value to RGB array\n * @param hex\n */\nexport function hexToRgb(hex: number): [number, number, number] {\n  if (!Number.isInteger(hex) || hex < 0 || hex > 0xffffff) throw new Error(\"hexToRgb expects a 24-bit RGB integer\");\n  const r = (hex >> 16) & 0xff;\n  const g = (hex >> 8) & 0xff;\n  const b = hex & 0xff;\n\n  return [r, g, b];\n}\n\n//------------------------------\n//  HSL\n//------------------------------\n\n/** Convert HSL degrees / percentages to a packed 24-bit RGB number (for example, 0xff0000). */\nexport function hslToHex(h: number, s: number, l: number): number {\n  return rgbToHex(...hslToRgb(h, s, l));\n}\n\n/** Hue wraps to [0, 360); saturation/lightness are clamped to 0–100. Returns fractional RGB bytes. */\nexport function hslToRgb(h: number, s: number, l: number): [number, number, number] {\n  if (![h, s, l].every(Number.isFinite)) throw new Error(\"hslToRgb expects finite coordinates\");\n  h = ((h % 360) + 360) % 360;\n  s = Math.max(0, Math.min(100, s)) / 100;\n  l = Math.max(0, Math.min(100, l)) / 100;\n  const k = (n: number) => (n + h / 30) % 12;\n  const a = s * Math.min(l, 1 - l);\n  const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));\n\n  return [f(0) * 255, f(8) * 255, f(4) * 255];\n}\n\n//------------------------------\n//  RGB\n//------------------------------\n\n/** Divide byte channels by 255; this is normalization, not sRGB-to-linear conversion. */\nexport function normalizeRgb(r: number, g: number, b: number): [number, number, number] {\n  return [r / 255, g / 255, b / 255];\n}\n\nexport function rgbToHex(r: number, g: number, b: number): number {\n  if (![r, g, b].every(Number.isFinite)) throw new Error(\"rgbToHex expects finite channels\");\n  r = Math.round(Math.max(0, Math.min(255, r)));\n  g = Math.round(Math.max(0, Math.min(255, g)));\n  b = Math.round(Math.max(0, Math.min(255, b)));\n\n  return (r << 16) + (g << 8) + b;\n}\n\n/**\n * Converts RGB bytes to HSL degrees / percentages without rounding. Channels clamp to 0–255.\n *\n * Example usage:\n * ```\n * const rgbColor = { r: 255, g: 0, b: 0 }; // Red\n * const hslColor = rgbToHsl(rgbColor.r, rgbColor.g, rgbColor.b);\n * console.log(hslColor); // Output: [0, 100, 50]\n * ```\n */\nexport function rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n  if (![r, g, b].every(Number.isFinite)) throw new Error(\"rgbToHsl expects finite channels\");\n  r = Math.max(0, Math.min(255, r)) / 255;\n  g = Math.max(0, Math.min(255, g)) / 255;\n  b = Math.max(0, Math.min(255, b)) / 255;\n\n  const max = Math.max(r, g, b);\n  const min = Math.min(r, g, b);\n  const delta = max - min;\n\n  let h = 0;\n  let s = 0;\n  const l = (max + min) / 2;\n\n  // Calculate hue\n  if (delta !== 0) {\n    if (max === r) {\n      h = ((g - b) / delta + (g < b ? 6 : 0)) * 60;\n    } else if (max === g) {\n      h = ((b - r) / delta + 2) * 60;\n    } else if (max === b) {\n      h = ((r - g) / delta + 4) * 60;\n    }\n  }\n\n  // Calculate saturation\n  if (delta !== 0) {\n    s = delta / (1 - Math.abs(2 * l - 1));\n  }\n\n  return [h, s * 100, l * 100];\n}\n\n//------------------------------\n//  Distance functions\n//------------------------------\n\n/**\n * Calculate the Euclidean distance between two colors in RGB space\n * distance = sqrt((r1 - r2)^2 + (g1 - g2)^2 + (b1 - b2)^2)\n */\nexport function calculateDistance(color1: [number, number, number], color2: [number, number, number]): number {\n  return Math.sqrt(Math.pow(color1[0] - color2[0], 2) + Math.pow(color1[1] - color2[1], 2) + Math.pow(color1[2] - color2[2], 2));\n}\n\n/**\n * Calculate the sum of absolute differences for each channel\n * difference = |r1 - r2| + |g1 - g2| + |b1 - b2|\n */\nexport function calculateChannelDifference(color1: [number, number, number], color2: [number, number, number]): number {\n  return (\n    Math.abs(color1[0] - color2[0]) + //\n    Math.abs(color1[1] - color2[1]) +\n    Math.abs(color1[2] - color2[2])\n  );\n}\n\nexport function findClosestColor(inputColor: number, dataset: number[]): number | null {\n  const inputRgb = hexToRgb(inputColor);\n  let closestColor = null;\n  let smallestDistance = Infinity;\n\n  for (const entry of dataset) {\n    const colorRgb = hexToRgb(entry);\n    const distance = calculateDistance(inputRgb, colorRgb);\n\n    if (distance < smallestDistance) {\n      smallestDistance = distance;\n      closestColor = entry;\n    }\n  }\n\n  return closestColor;\n}\n\nexport function findClosestColorChannelWise(inputColor: number, dataset: number[]): number | null {\n  const inputRgb = hexToRgb(inputColor);\n  let closestColor = null;\n  let smallestDifference = Infinity;\n\n  for (const entry of dataset) {\n    const colorRgb = hexToRgb(entry);\n    const difference = calculateChannelDifference(inputRgb, colorRgb);\n\n    if (difference < smallestDifference) {\n      smallestDifference = difference;\n      closestColor = entry;\n    }\n  }\n\n  return closestColor;\n}\n","import { Mesh, Vector3 } from \"three\";\n\n/**\n * Select the nearest stored vertex using geometry-local distances, then return it in world space.\n *\n * `point` must be in the mesh geometry's local coordinate space. This searches the position\n * attribute only; it does not find the closest point on triangle faces or edges. Under nonuniform\n * world scaling, the selected vertex need not be the nearest vertex by world-space distance.\n * An empty position attribute returns the mesh's local origin transformed to world space.\n *\n * @example\n * ```ts\n * const localPoint = new Vector3(5, 2, 1);\n * const worldVertex = findClosestPoint(localPoint, targetMesh);\n * ```\n */\nexport function findClosestPoint(point: Vector3, mesh: Mesh) {\n  if (!mesh.geometry.isBufferGeometry) {\n    throw new Error(\"Mesh must have a BufferGeometry.\");\n  }\n\n  const geometry = mesh.geometry;\n  const positionAttribute = geometry.getAttribute(\"position\");\n  const closestPoint = new Vector3();\n  const tempPoint = new Vector3();\n  let minDistance = Infinity;\n\n  for (let i = 0; i < positionAttribute.count; i++) {\n    tempPoint.fromBufferAttribute(positionAttribute, i);\n    const distance = tempPoint.distanceTo(point);\n\n    if (distance < minDistance) {\n      minDistance = distance;\n      closestPoint.copy(tempPoint);\n    }\n  }\n\n  // Transform the closest point to world space (if the mesh is transformed)\n  mesh.localToWorld(closestPoint);\n\n  return closestPoint;\n}\n","/**\n * Calculate the x-coordinate for a given y-coordinate using the slope-intercept equation of a line.\n * x = x1 + (y - y1) / m\n *\n * Example usage\n * ```\n * const x1 = 0.8, y1 = 0, x2 = 1, y2 = 1.5, y = 1.0;\n * const x = calculateXForY(x1, y1, x2, y2, y);\n * console.log(`The x-position for y=${y} is x=${x.toFixed(4)}`);\n * ```\n */\nexport function calculateXFromSlopeIntercept(x1: number, y1: number, x2: number, y2: number, y: number): number {\n  const m = (y2 - y1) / (x2 - x1); // slope\n  return x1 + (y - y1) / m;\n}\n\n/**\n * Calculate the y-coordinate for a given x-coordinate using the slope-intercept equation of a line.\n * y = y1 + m * (x - x1)\n *\n * Example usage\n * ```\n * const x1 = 0.8, y1 = 0, x2 = 1, y2 = 1.5, x = 0.9333;\n * const y = calculateYForX(x1, y1, x2, y2, x);\n * console.log(`The y-position for x=${x} is y=${y.toFixed(4)}`);\n * ```\n */\nexport function calculateYFromSlopeIntercept(x1: number, y1: number, x2: number, y2: number, x: number): number {\n  const m = (y2 - y1) / (x2 - x1); // slope\n  return y1 + m * (x - x1);\n}\n\nexport const LineEquations = {\n  calculateXFromSlopeIntercept,\n  calculateYFromSlopeIntercept,\n};\n","/**\n * Set a random timeout that will call the callback function with a random delay between minDelay and maxDelay.\n *\n * Example usage:\n * ```\n * setRandomTimeout(() => {\n *   console.log(`Callback ${i} executed!`);\n * }, 100, 500); // Random timeout between 100ms and 500ms\n * ```\n */\nexport function setRandomTimeout(\n  callback: () => void,\n  minDelay: number,\n  maxDelay: number,\n): ReturnType<typeof setTimeout> {\n  const delay = minDelay + Math.random() * (maxDelay - minDelay);\n\n  return setTimeout(callback, delay);\n}\n\n/**\n * Set a random interval that will call the callback function with a random delay between minDelay and maxDelay.\n *\n * Example usage:\n * ```\n * const clearRandomInterval = setRandomInterval(() => {\n *   console.log('Random interval executed!');\n * }, 500, 1500); // Random delay between 500ms and 1500ms\n * ```\n */\nexport function setRandomInterval(callback: () => void, minDelay: number, maxDelay: number): () => void {\n  let timeoutId: ReturnType<typeof setTimeout>;\n\n  function scheduleNext() {\n    const delay = minDelay + Math.random() * (maxDelay - minDelay);\n    timeoutId = setTimeout(() => {\n      callback();\n      scheduleNext(); // Schedule the next execution\n    }, delay);\n  }\n\n  scheduleNext();\n\n  // Return a function to allow clearing the interval\n  return () => clearTimeout(timeoutId);\n}\n"],"mappings":"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"}