{"version":3,"file":"three-low-poly.cjs","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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"}