@selvajs/visualization 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -59
- package/dist/render.cjs +3 -3
- package/dist/render.cjs.map +1 -1
- package/dist/render.d.cts +6 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +2 -2
- package/dist/render.js.map +1 -1
- package/dist/scene.cjs +1 -1
- package/dist/scene.cjs.map +1 -1
- package/dist/scene.d.cts +12 -7
- package/dist/scene.d.ts +12 -7
- package/dist/scene.js +1 -1
- package/dist/scene.js.map +1 -1
- package/package.json +2 -2
package/dist/render.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.cjs","names":["THREE","THREE","computeCombinedBoundingBox","THREE","THREE","LineSegmentsGeometry","THREE","THREE","THREE","LineMaterial","LineSegments2","THREE","LineSegments2","THREE","CSS2DRenderer","THREE","CSS2DObject","THREE","LineGeometry","LineMaterial","Line2","THREE","ViewHelper","THREE","THREE","THREE","THREE","THREE","THREE","THREE","THREE","Pass","FullScreenQuad","EffectComposer","RenderPass","GTAOPass","SMAAPass","OutputPass","THREE","OrbitControls","HDRLoader","THREE","THREE","THREE","THREE","THREE"],"sources":["../src/shared/types.ts","../src/shared/looks.ts","../src/render/scene-ownership.ts","../src/render/up-axis.ts","../src/render/three-helpers.ts","../src/render/camera-controller.ts","../src/render/edges/line-geometry.ts","../src/render/edge-extract.ts","../src/render/edges/options.ts","../src/render/edges/extraction.ts","../src/render/edges/overlay.ts","../src/render/edges.ts","../src/render/grid.ts","../src/render/label-layer.ts","../src/render/measure.ts","../src/render/near-plane.ts","../src/render/tool-registry.ts","../src/render/view-gizmo.ts","../src/render/scene-setup/animation-loop.ts","../src/render/scene-setup/defaults.ts","../src/render/scene-setup/appearance.ts","../src/render/scene-setup/create-camera.ts","../src/render/scene-setup/create-scene.ts","../src/render/scene-setup/dispose.ts","../src/render/edge-detection-pass.ts","../src/render/render-pipeline.ts","../src/render/scene-setup/pipeline-controller.ts","../src/render/scene-setup/setup-controls.ts","../src/render/scene-setup/setup-environment.ts","../src/render/scene-setup/setup-events.ts","../src/render/scene-setup/setup-lighting.ts","../src/render/scene-setup/setup-renderer.ts","../src/render/scene-setup/init-three.ts"],"sourcesContent":["import type * as THREE from 'three';\n\n// ============================================================================\n// LOOKS\n// ============================================================================\n\n/** Source of truth for {@link Look} — lets consumers (e.g. a style picker) iterate instead of hardcoding names. */\nexport const LOOKS = ['technical', 'studio', 'showcase'] as const;\n\nexport type Look = (typeof LOOKS)[number];\n\n/**\n * The lighting/material dials a {@link Look} sets. Never carries edges or grid — those are\n * independent overlays.\n */\nexport type LookPreset = {\n\ttoneMapping: THREE.ToneMapping;\n\ttoneMappingExposure: number;\n\tenvMapIntensity: number;\n\t/** Multiplier on the HDR's IBL (`scene.environmentIntensity`). */\n\tenvironmentIntensity: number;\n\themisphereIntensity: number;\n\tambientIntensity: number;\n\tcullBackfaces: boolean;\n\tambientOcclusion: boolean;\n};\n\n/** How compute meshes read visually — the parse-time material choices baked from a {@link Look}. */\nexport interface MaterialAppearanceOptions {\n\t/** Default 1 (three.js's own material default) when omitted. */\n\tenvMapIntensity?: number;\n\t/**\n\t * `THREE.FrontSide` instead of `THREE.DoubleSide` — crisper silhouette on closed solids, but open\n\t * surfaces (which Rhino also emits) vanish when viewed from behind. Default false (DoubleSide) to\n\t * stay safe for surface geometry.\n\t */\n\tcullBackfaces?: boolean;\n}\n","import * as THREE from 'three';\n\nimport type { Look, LookPreset, MaterialAppearanceOptions } from './types.js';\n\n/** The look applied when the caller passes no `look` option. */\nexport const DEFAULT_LOOK: Look = 'technical';\n\n/**\n * Single source of truth for both `applyDefaults` (construction) and `setLook` (runtime), so the two\n * can't drift.\n *\n * `ambientOcclusion: false` on every look: GTAO is a heavy full-screen pass, so it stays opt-in\n * (`render.ambientOcclusion` or `setAmbientOcclusion(true)`) rather than costing every viewer 60fps.\n */\nexport const LOOK_PRESETS: Record<Look, LookPreset> = {\n\t// Fills shadows in (vs. showcase, which lets them fall off). IBL/fill kept modest so\n\t// env+hemisphere+ambient don't triple-stack into an ACES-washed look.\n\tstudio: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.75,\n\t\tambientIntensity: 0.4,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t},\n\t// Flat ambient kept low — a full flat ambient flattens shading toward milky grey regardless of\n\t// face orientation — with IBL near full and a little hemisphere fill for under-facing surfaces.\n\ttechnical: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 0.9,\n\t\tenvironmentIntensity: 1,\n\t\themisphereIntensity: 0.35,\n\t\tambientIntensity: 0.25,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t},\n\t// Fill pulled DOWN (low ambient/hemisphere) so light-to-shadow falloff stays strong for a\n\t// dramatic hero shot, unlike `studio` which fills shadows in.\n\tshowcase: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1.15,\n\t\tenvMapIntensity: 1.4,\n\t\tenvironmentIntensity: 1.25,\n\t\themisphereIntensity: 0.35,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t}\n};\n\n/** Baked at parse time (not toggleable at runtime). */\nexport function materialAppearanceForLook(look: Look): MaterialAppearanceOptions {\n\tconst preset = LOOK_PRESETS[look];\n\treturn {\n\t\tenvMapIntensity: preset.envMapIntensity,\n\t\tcullBackfaces: preset.cullBackfaces\n\t};\n}\n","// ============================================================================\n// Scene ownership: who put an object in the scene, and what may remove it\n// ============================================================================\n//\n// A live scene mixes content from three owners: the solve (replaced wholesale every solve),\n// viewer aids (grid/floor/labels, never replaced), and host apps drawing their own geometry\n// alongside the solve. `userData.source` records which, and `clearScene` consults it to decide\n// what a solve is allowed to destroy.\n//\n// App content carries an owner id (`app:<id>`) rather than a flat `'user'` so a host running\n// more than one app can clear its own geometry without touching another's, and so a scoped\n// solve can replace one app's results while leaving the rest standing.\n\nimport type * as THREE from 'three';\n\n/** Geometry produced by a solve. Replaced wholesale on the next one. */\nexport const SOURCE_COMPUTE = 'compute';\n\n/**\n * Host-added geometry with no owner id. Predates scoped ownership and still honoured everywhere\n * an `app:` scope is — new code should prefer {@link appSource}.\n */\nexport const SOURCE_USER = 'user';\n\nconst APP_PREFIX = 'app:';\n\n/** The `userData.source` tag for geometry owned by app `id` (`'pointcloud'` → `'app:pointcloud'`). */\nexport function appSource(id: string): string {\n\treturn `${APP_PREFIX}${id}`;\n}\n\n/** The app id from a source tag, or null if the tag isn't app-owned. */\nexport function appIdFromSource(source: unknown): string | null {\n\tif (typeof source !== 'string' || !source.startsWith(APP_PREFIX)) return null;\n\treturn source.slice(APP_PREFIX.length) || null;\n}\n\n/**\n * True for anything a host added rather than the solve — `'user'` or any `app:` scope. This is\n * the predicate `clearScene` uses, so it decides what survives a solve.\n */\nexport function isHostOwned(object: THREE.Object3D): boolean {\n\tconst source = object.userData?.source;\n\treturn source === SOURCE_USER || appIdFromSource(source) !== null;\n}\n\n/**\n * True for objects owned by `id`. Passing no id matches every host-owned object, which is what\n * `clearUserGeometry()` does.\n */\nexport function isOwnedBy(object: THREE.Object3D, id?: string): boolean {\n\tif (id === undefined) return isHostOwned(object);\n\treturn object.userData?.source === appSource(id);\n}\n","import * as THREE from 'three';\n\n/**\n * Single source of truth for \"which way is up, and what do front/right mean\" — camera framing, sun\n * position, ground offset, and view presets all derive from {@link buildUpBasis} rather than\n * hardcoding an axis, so a Y-up scene gets a correct horizon and sun instead of the below-horizon\n * result a hardcoded Z-up vector would give.\n *\n * `forward` is the camera's look direction (camera → model); a view preset's camera position is the\n * reverse (target → camera), see `camera-controller.ts`. `right` is `seed x up`, not `up x seed`, to\n * match Rhino's handedness: for Z-up this makes the Front-view camera look along +Y and the\n * Right-view camera look along -X (it sits at `right` = +X, facing back toward the origin).\n */\n\n/** Orthonormal frame derived from a scene up axis. All vectors are unit length. */\nexport interface UpBasis {\n\tup: THREE.Vector3;\n\tforward: THREE.Vector3;\n\tright: THREE.Vector3;\n}\n\n/** Ground-plane axes for a given up vector: Z-up yields forward = +Y, right = +X (Rhino's Front/Right). */\nexport function buildUpBasis(up: THREE.Vector3): UpBasis {\n\tconst u = up.clone().normalize();\n\n\t// Seed must not be (nearly) parallel to up, or the cross product is unstable.\n\tconst worldZ = new THREE.Vector3(0, 0, 1);\n\tconst worldY = new THREE.Vector3(0, 1, 0);\n\tconst seed = Math.abs(u.dot(worldZ)) > 0.9 ? worldY : worldZ;\n\n\tconst right = new THREE.Vector3().crossVectors(seed, u).normalize();\n\tconst forward = new THREE.Vector3().crossVectors(u, right).normalize();\n\n\treturn { up: u, forward, right };\n}\n\n/** Default 3/4 iso camera offset from the target (behind-left, above), scaled to `distance`. */\nexport function isoOffset(up: THREE.Vector3, distance: number): THREE.Vector3 {\n\tconst { forward, right, up: u } = buildUpBasis(up);\n\t// Normalize before scaling so `distance` is the true radius, not the diagonal of the raw sum.\n\treturn forward\n\t\t.clone()\n\t\t.multiplyScalar(-1)\n\t\t.add(right.clone().multiplyScalar(-1))\n\t\t.add(u)\n\t\t.normalize()\n\t\t.multiplyScalar(distance);\n}\n\n/** Default sun position: high above the model, offset to one side for a directional gradient. */\nexport function sunOffset(up: THREE.Vector3, sideDistance: number, height: number): THREE.Vector3 {\n\tconst { forward, right, up: u } = buildUpBasis(up);\n\treturn right\n\t\t.clone()\n\t\t.multiplyScalar(sideDistance)\n\t\t.add(forward.clone().multiplyScalar(sideDistance))\n\t\t.add(u.clone().multiplyScalar(height));\n}\n\n/**\n * Rotates an equirectangular environment map's horizon onto the scene's ground plane.\n *\n * Three's equirect mapping is hardcoded to Y-up: the HDR's horizon is assumed to lie in the XZ\n * plane with zenith along +Y. In a Z-up scene that leaves the environment on its side — horizon\n * vertical, lighting arriving from +Y instead of overhead. A neutral studio HDR hides this; any\n * sky/ground HDR makes it obvious.\n *\n * Returns the Euler rotating the map's native +Y zenith onto `up` (identity for Y-up). Apply to\n * BOTH `scene.environmentRotation` and `scene.backgroundRotation` — they're independent, and\n * setting only one desyncs background from lighting.\n */\nexport function environmentRotationFor(up: THREE.Vector3): THREE.Euler {\n\tconst u = up.clone().normalize();\n\tconst mapZenith = new THREE.Vector3(0, 1, 0);\n\n\tif (u.dot(mapZenith) > 0.9999) return new THREE.Euler();\n\n\t// Upside-down (-Y): setFromUnitVectors picks an arbitrary perpendicular axis for a 180° flip,\n\t// spinning the horizon. Roll about X instead so the horizon stays put.\n\tif (u.dot(mapZenith) < -0.9999) return new THREE.Euler(Math.PI, 0, 0);\n\n\tconst quaternion = new THREE.Quaternion().setFromUnitVectors(mapZenith, u);\n\treturn new THREE.Euler().setFromQuaternion(quaternion);\n}\n\n/** Which world axis the up vector most closely aligns with. */\nexport function upToAxis(up: THREE.Vector3): 'x' | 'y' | 'z' {\n\tconst ax = Math.abs(up.x);\n\tconst ay = Math.abs(up.y);\n\tconst az = Math.abs(up.z);\n\tif (ax >= ay && ax >= az) return 'x';\n\tif (ay >= az) return 'y';\n\treturn 'z';\n}\n","import * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport { computeCombinedBoundingBox, disposeObjectTree } from '../shared/index.js';\nimport { isHostOwned } from './scene-ownership.js';\nimport { isoOffset } from './up-axis';\n\nconst CAMERA_CONFIG = {\n\tHUGE_THRESHOLD: 10000,\n\tLARGE_THRESHOLD: 1000,\n\tSCALE_RATIO_THRESHOLD: 100,\n\tNEAR_PLANE_FACTOR: {\n\t\tTINY: 0.0001,\n\t\tSMALL: 0.001,\n\t\tNORMAL: 0.01\n\t},\n\tFAR_PLANE_FACTOR: {\n\t\tHUGE: 100,\n\t\tLARGE: 50,\n\t\tNORMAL: 20\n\t},\n\tINITIAL_DISTANCE_MULTIPLIER: 4\n};\n\n/** Replaces scene content with `meshes`, rescales the camera frustum to fit, and (first call only) positions the camera/controls. */\nexport function updateScene(\n\tscene: THREE.Scene,\n\tmeshes: THREE.Object3D[],\n\tcamera: THREE.PerspectiveCamera,\n\tcontrols: OrbitControls,\n\tinitialPositionSet: boolean\n) {\n\tclearScene(scene);\n\n\tif (meshes.length === 0) return;\n\n\tmeshes.forEach((mesh) => {\n\t\tscene.add(mesh);\n\t});\n\n\tconst unionBoundingBox = computeCombinedBoundingBox(meshes);\n\tconst center = unionBoundingBox.getCenter(new THREE.Vector3());\n\tconst size = unionBoundingBox.getSize(new THREE.Vector3());\n\tconst maxDim = Math.max(size.x, size.y, size.z);\n\n\t// Rescaled every call, not just the first, so near/far stay well-conditioned when geometry\n\t// size changes drastically between solves.\n\tconst scaleRatio = maxDim / Math.min(size.x || 1, size.y || 1, size.z || 1);\n\n\tif (scaleRatio > CAMERA_CONFIG.SCALE_RATIO_THRESHOLD || maxDim > CAMERA_CONFIG.HUGE_THRESHOLD) {\n\t\tcamera.near = maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.TINY;\n\t\tcamera.far = maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.HUGE;\n\t} else if (maxDim > CAMERA_CONFIG.LARGE_THRESHOLD) {\n\t\tcamera.near = maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.SMALL;\n\t\tcamera.far = maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.LARGE;\n\t} else {\n\t\tcamera.near = Math.max(0.01, maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.NORMAL);\n\t\tcamera.far = Math.max(2000, maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.NORMAL);\n\t}\n\n\tcamera.updateProjectionMatrix();\n\n\t// Camera/controls are repositioned on first frame only. Zoom limits (min/maxDistance) are\n\t// deliberately NOT touched here: they're owned by the host via setupControls, and overwriting\n\t// them per solve would silently discard user-supplied configuration after the first update.\n\tif (!initialPositionSet) {\n\t\tconst distance = maxDim * CAMERA_CONFIG.INITIAL_DISTANCE_MULTIPLIER;\n\n\t\t// camera.up is already the configured sceneUp (initThree sets it before this runs), so the\n\t\t// iso offset stays consistent with whatever up-axis the viewer opened at.\n\t\tcamera.position.copy(center).add(isoOffset(camera.up, distance));\n\t\tcontrols.target.copy(center);\n\n\t\tcontrols.update();\n\t}\n}\n\n// Excluded from every content-bounds query: the grid is a huge plane that re-centers on the\n// camera each frame, so including it would make fit-to-view frame the camera's position instead\n// of the geometry.\nconst VIEWER_AID_IDS = new Set(['grid', 'floor', 'label-layer', 'measure']);\n\nfunction isViewerAid(object: THREE.Object3D): boolean {\n\tlet current: THREE.Object3D | null = object;\n\twhile (current) {\n\t\tif (typeof current.userData.id === 'string' && VIEWER_AID_IDS.has(current.userData.id)) {\n\t\t\treturn true;\n\t\t}\n\t\tcurrent = current.parent;\n\t}\n\treturn false;\n}\n\n/**\n * Bounds of the scene's renderable content, excluding viewer aids (grid/floor/labels/measure).\n * Shared by fit-to-view, pick-threshold scaling, camera framing (`setView`), and shadow-frustum\n * fitting so they all measure exactly the same box.\n */\nexport function computeContentBounds(scene: THREE.Scene): THREE.Box3 {\n\t// Refresh world matrices once up front so expandByObject reads current transforms, regardless\n\t// of when the caller invokes this.\n\tscene.updateMatrixWorld(true);\n\tconst box = new THREE.Box3();\n\tscene.traverse((object) => {\n\t\tconst renderable = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\tif (object.visible && !isViewerAid(object) && renderable.geometry) {\n\t\t\tbox.expandByObject(object);\n\t\t}\n\t});\n\treturn box;\n}\n\nconst PERSISTENT_SCENE_IDS = new Set(['floor', 'grid', 'label-layer']);\n\nexport function clearScene(scene: THREE.Scene): void {\n\t// Snapshot — removeFromParent below mutates scene.children during iteration.\n\tconst topLevel = [...scene.children];\n\n\ttopLevel.forEach((object) => {\n\t\t// Removing the label-layer group here would orphan it: the CSS2D renderer only finds labels\n\t\t// by walking the live scene, so labels added afterwards would never render.\n\t\tif (PERSISTENT_SCENE_IDS.has(object.userData.id)) return;\n\n\t\t// Host-added geometry (tagged by addUserGeometry, either plain 'user' or an app: scope)\n\t\t// persists across solves so it isn't lost when compute content is replaced.\n\t\tif (isHostOwned(object)) return;\n\n\t\t// Edge overlays are children of the meshes they outline, so this traversal disposes their\n\t\t// line geometries too — each overlay owns its geometry outright.\n\t\tdisposeObjectTree(object);\n\n\t\tobject.removeFromParent();\n\t});\n}\n","import * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport { computeContentBounds } from './three-helpers';\nimport { buildUpBasis } from './up-axis';\n\n/**\n * Runtime camera control: preset views, perspective⇄orthographic toggle, rotate lock.\n *\n * Centralized because projection switching swaps the camera object that OrbitControls drives, the\n * render loop renders, resize reshapes, and the raycaster picks with — {@link getActiveCamera} is\n * the one source of truth for all four call sites.\n *\n * Orthographic mirrors perspective's position/target with a frustum derived from perspective's FOV\n * and distance, so switching projections doesn't visually jump.\n */\n\nexport type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';\n\nexport type CameraProjection = 'perspective' | 'orthographic';\n\nexport interface CameraController {\n\t/** Swaps identity on {@link setProjection}. */\n\tgetActiveCamera(): THREE.Camera;\n\tgetProjection(): CameraProjection;\n\tsetProjection(projection: CameraProjection): void;\n\ttoggleProjection(): CameraProjection;\n\tsetView(preset: ViewPreset, animate?: boolean): void;\n\t/**\n\t * Frame current content from an explicit world-space direction (target → camera) instead of a\n\t * named preset — used by the nav-cube, whose clicked axis is a world axis.\n\t */\n\tsetViewDirection(direction: THREE.Vector3, animate?: boolean): void;\n\t/** Frame a world-space box from the current view direction. No-op on an empty box. */\n\tframeBounds(box: THREE.Box3, animate?: boolean): void;\n\tsetRotateEnabled(enabled: boolean): void;\n\tisRotateEnabled(): boolean;\n\tupdateAspect(width: number, height: number): void;\n\t/** Cancel any in-flight camera tween. Call on viewer teardown so ticks can't touch disposed controls. */\n\tdispose(): void;\n}\n\ninterface CameraControllerDeps {\n\tscene: THREE.Scene;\n\tperspective: THREE.PerspectiveCamera;\n\tcontrols: OrbitControls;\n\tonActiveCameraChange: (camera: THREE.Camera) => void;\n\t/** Drives presets, ortho camera up, and iso direction. Falls back to `perspective.up`. */\n\tup?: THREE.Vector3;\n}\n\n/**\n * Seven preset view directions (target → camera, unit vectors), derived from `up` rather than a\n * fixed Y-up table so Top/Front/… stay meaningful for Z-up Rhino scenes.\n *\n * `buildUpBasis`'s `forward` is camera→model; these are camera positions relative to target, so\n * \"front\" is `-forward`. Flipping this puts the camera behind the model and swaps left/right.\n */\nfunction buildViewDirections(up: THREE.Vector3): Record<ViewPreset, THREE.Vector3> {\n\tconst { up: u, forward, right } = buildUpBasis(up);\n\n\t// Camera positions are opposite the look direction: Rhino's Front looks along +Y from -Y.\n\tconst frontPosition = forward.clone().negate();\n\tconst rightPosition = right.clone();\n\n\treturn {\n\t\ttop: u.clone(),\n\t\tbottom: u.clone().negate(),\n\t\tfront: frontPosition.clone(),\n\t\tback: frontPosition.clone().negate(),\n\t\tright: rightPosition.clone(),\n\t\tleft: rightPosition.clone().negate(),\n\t\tiso: frontPosition\n\t\t\t.clone()\n\t\t\t.multiplyScalar(1.2)\n\t\t\t.add(rightPosition.clone())\n\t\t\t.add(u.clone())\n\t\t\t.normalize()\n\t};\n}\n\nexport function createCameraController(deps: CameraControllerDeps): CameraController {\n\tconst { scene, perspective, controls, onActiveCameraChange } = deps;\n\n\tconst up = (deps.up ?? perspective.up).clone().normalize();\n\tconst VIEW_DIRECTIONS = buildViewDirections(up);\n\n\tconst ortho = new THREE.OrthographicCamera(-1, 1, 1, -1, perspective.near, perspective.far);\n\tortho.up.copy(up);\n\n\tlet projection: CameraProjection = 'perspective';\n\tlet aspect = perspective.aspect;\n\n\tconst active = (): THREE.Camera => (projection === 'perspective' ? perspective : ortho);\n\n\t// Starting a new tween cancels any prior one — two loops would otherwise fight over the camera.\n\tlet activeTween: TweenHandle | null = null;\n\tconst cancelTween = () => {\n\t\tactiveTween?.cancel();\n\t\tactiveTween = null;\n\t};\n\n\t// Sizes the ortho frustum to match perspective's apparent size at the current distance.\n\tconst syncOrthoFrustum = () => {\n\t\t// Measure whichever camera is live: while ortho is active, OrbitControls moves ortho's\n\t\t// position (only its zoom changes), leaving perspective's distance stale.\n\t\tconst reference = projection === 'orthographic' ? ortho : perspective;\n\t\tconst distance = reference.position.distanceTo(controls.target);\n\t\tconst halfH = distance * Math.tan((perspective.fov * Math.PI) / 360);\n\t\tconst halfW = halfH * aspect;\n\t\tortho.left = -halfW;\n\t\tortho.right = halfW;\n\t\tortho.top = halfH;\n\t\tortho.bottom = -halfH;\n\t\tortho.near = perspective.near;\n\t\tortho.far = perspective.far;\n\t\tortho.updateProjectionMatrix();\n\t};\n\n\tconst setProjection = (next: CameraProjection) => {\n\t\tif (next === projection) return;\n\t\t// A tween mid-flight would keep lerping the OLD active camera after the swap.\n\t\tcancelTween();\n\n\t\tif (next === 'orthographic') {\n\t\t\tortho.position.copy(perspective.position);\n\t\t\tortho.up.copy(perspective.up);\n\t\t\tortho.lookAt(controls.target);\n\t\t\t// OrbitControls dollies ortho via `zoom`, not position — reset to 1 so a leftover zoom\n\t\t\t// from a prior 2D session doesn't double up with the freshly-derived frustum.\n\t\t\tortho.zoom = 1;\n\t\t\tsyncOrthoFrustum();\n\t\t} else {\n\t\t\t// Convert ortho zoom back to perspective DISTANCE (halfH / tan(fov/2)) — copying position\n\t\t\t// alone would discard any zooming done in 2D.\n\t\t\tconst halfH = (ortho.top - ortho.bottom) / (2 * ortho.zoom);\n\t\t\tconst distance = halfH / Math.tan((perspective.fov * Math.PI) / 360);\n\t\t\tconst direction = ortho.position.clone().sub(controls.target);\n\t\t\tif (direction.lengthSq() < 1e-12) direction.copy(up);\n\t\t\tdirection.normalize();\n\t\t\tperspective.position.copy(controls.target).add(direction.multiplyScalar(distance));\n\t\t}\n\n\t\tprojection = next;\n\t\tcontrols.object = active();\n\t\tcontrols.update();\n\t\tonActiveCameraChange(active());\n\t};\n\n\t// Positions the active camera along `direction` at the distance fitting `maxDim`, retargeting\n\t// controls at `center`. Ortho zoom resets and the frustum re-derives via syncOrthoFrustum —\n\t// position alone wouldn't change an orthographic view's apparent size.\n\tconst frame = (\n\t\tcenter: THREE.Vector3,\n\t\tmaxDim: number,\n\t\tdirection: THREE.Vector3,\n\t\tanimate: boolean\n\t) => {\n\t\tconst fov = perspective.fov * (Math.PI / 180);\n\t\tconst distance = (maxDim / (2 * Math.tan(fov / 2))) * 1.5;\n\n\t\tconst dir = nudgeOffPole(direction, up);\n\t\tconst toPosition = center.clone().add(dir.clone().multiplyScalar(distance));\n\n\t\tconst cam = active();\n\t\t// Reset zoom before re-deriving the frustum, else it multiplies in and defeats the fit.\n\t\tif (projection === 'orthographic') ortho.zoom = 1;\n\n\t\tcancelTween();\n\t\tif (animate) {\n\t\t\tactiveTween = animateMove(cam, controls, toPosition, center, () => {\n\t\t\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t\t\t});\n\t\t} else {\n\t\t\tcam.position.copy(toPosition);\n\t\t\tcontrols.target.copy(center);\n\t\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t\t\tcontrols.update();\n\t\t}\n\t};\n\n\tconst setViewDirection = (direction: THREE.Vector3, animate = true) => {\n\t\tconst box = computeContentBounds(scene);\n\t\tconst center = box.isEmpty() ? controls.target.clone() : box.getCenter(new THREE.Vector3());\n\t\tconst size = box.isEmpty() ? new THREE.Vector3(1, 1, 1) : box.getSize(new THREE.Vector3());\n\t\tconst maxDim = Math.max(size.x, size.y, size.z) || 1;\n\t\tframe(center, maxDim, direction, animate);\n\t};\n\n\tconst frameBounds = (box: THREE.Box3, animate = true) => {\n\t\tif (box.isEmpty()) return;\n\t\tconst center = box.getCenter(new THREE.Vector3());\n\t\tconst size = box.getSize(new THREE.Vector3());\n\t\tconst maxDim = Math.max(size.x, size.y, size.z) || 1;\n\t\t// Keep the user's current viewing direction; only the distance/target change.\n\t\tconst direction = active().position.clone().sub(controls.target);\n\t\tif (direction.lengthSq() < 1e-12) direction.copy(VIEW_DIRECTIONS.iso);\n\t\tframe(center, maxDim, direction.normalize(), animate);\n\t};\n\n\tconst setView = (preset: ViewPreset, animate = true) => {\n\t\tsetViewDirection(VIEW_DIRECTIONS[preset], animate);\n\t};\n\n\tconst setRotateEnabled = (enabled: boolean) => {\n\t\tcontrols.enableRotate = enabled;\n\t};\n\n\tconst updateAspect = (width: number, height: number) => {\n\t\taspect = height === 0 ? aspect : width / height;\n\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t};\n\n\treturn {\n\t\tgetActiveCamera: active,\n\t\tgetProjection: () => projection,\n\t\tsetProjection,\n\t\ttoggleProjection: () => {\n\t\t\tsetProjection(projection === 'perspective' ? 'orthographic' : 'perspective');\n\t\t\treturn projection;\n\t\t},\n\t\tsetView,\n\t\tsetViewDirection,\n\t\tframeBounds,\n\t\tsetRotateEnabled,\n\t\tisRotateEnabled: () => controls.enableRotate,\n\t\tupdateAspect,\n\t\tdispose: cancelTween\n\t};\n}\n\n/**\n * Nudges a top/bottom view direction a ~0.5° tilt off the up axis; other presets pass through\n * unchanged. Looking exactly down `up` is an OrbitControls singularity: camera direction coincides\n * with `camera.up`, azimuth is undefined, and the first drag snaps the view.\n *\n * At the pole, `camera.up` can't define roll, so the tilt direction does instead. Both poles lean\n * toward `-forward` to reproduce Rhino's convention (Top has +forward at screen-top; Bottom mirrors\n * about the horizontal axis, matching Rhino where the far side reads backwards too) — leaning the\n * poles opposite ways also mirrors correctly, but rolled 180° from Rhino.\n */\nfunction nudgeOffPole(dir: THREE.Vector3, up: THREE.Vector3): THREE.Vector3 {\n\tconst { up: u, forward } = buildUpBasis(up);\n\tconst d = dir.clone().normalize();\n\tif (Math.abs(d.dot(u)) < 0.9999) return dir;\n\n\tconst inPlane = forward.clone().negate();\n\n\tconst tilt = (0.5 * Math.PI) / 180;\n\treturn d\n\t\t.multiplyScalar(Math.cos(tilt))\n\t\t.add(inPlane.multiplyScalar(Math.sin(tilt)))\n\t\t.normalize();\n}\n\nconst easeOut = (t: number) => 1 - Math.pow(1 - t, 3);\n\n/** Handle to a running camera tween, so callers can stop it (new move, projection swap, teardown). */\ninterface TweenHandle {\n\tcancel(): void;\n}\n\n/** Tweens camera position + controls target; returns a cancel handle for teardown/preemption. */\nfunction animateMove(\n\tcamera: THREE.Camera,\n\tcontrols: OrbitControls,\n\ttoPosition: THREE.Vector3,\n\ttoTarget: THREE.Vector3,\n\tonTick: () => void,\n\tdurationMs = 250\n): TweenHandle {\n\tconst fromPosition = camera.position.clone();\n\tconst fromTarget = controls.target.clone();\n\tconst startTime = performance.now();\n\tlet rafId: number | null = null;\n\n\tconst tick = () => {\n\t\trafId = null;\n\t\tconst t = easeOut(Math.min((performance.now() - startTime) / durationMs, 1));\n\t\tcamera.position.lerpVectors(fromPosition, toPosition, t);\n\t\tcontrols.target.lerpVectors(fromTarget, toTarget, t);\n\t\tonTick();\n\t\tcontrols.update();\n\t\tif (t < 1) rafId = requestAnimationFrame(tick);\n\t};\n\n\trafId = requestAnimationFrame(tick);\n\n\treturn {\n\t\tcancel: () => {\n\t\t\tif (rafId !== null) {\n\t\t\t\tcancelAnimationFrame(rafId);\n\t\t\t\trafId = null;\n\t\t\t}\n\t\t}\n\t};\n}\n","import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';\n\n// ============================================================================\n// Line geometry construction\n// ============================================================================\n\n// No cache here. An earlier WeakMap keyed per source BufferGeometry leaked ~400 live GPU entries\n// where 8 were expected — entries never vanished with their source — and measured 0/80 hits in a\n// real scrubbing loop. Every overlay builds and owns its own line geometry.\n\nexport interface EdgeGeometryEntry {\n\tgeometry: LineSegmentsGeometry;\n\tsegmentCount: number;\n\t/** {@link SPACING_PERCENTILE} quantile of segment length; drives the density fade in overlay.ts. */\n\tedgeSpacing: number;\n}\n\n// A low quantile (not mean) tracks the fine detail: real parts mix a few long silhouette edges\n// with many short ones at wildly different scale (e.g. 1mm laminations on a 10m part) — an\n// average would sit between the two and never trigger the fade for either.\nconst SPACING_PERCENTILE = 0.15;\n\n// Stride sampling keeps this O(1) on millions of segments.\nconst SPACING_SAMPLE_LIMIT = 4096;\n\nfunction edgeSpacingOf(segments: Float32Array): number {\n\tconst segmentCount = Math.floor(segments.length / 6);\n\tif (segmentCount === 0) return Infinity;\n\n\tconst stride = Math.max(1, Math.ceil(segmentCount / SPACING_SAMPLE_LIMIT));\n\tconst lengths: number[] = [];\n\tfor (let s = 0; s < segmentCount; s += stride) {\n\t\tconst i = s * 6;\n\t\tconst length = Math.hypot(\n\t\t\tsegments[i + 3] - segments[i],\n\t\t\tsegments[i + 4] - segments[i + 1],\n\t\t\tsegments[i + 5] - segments[i + 2]\n\t\t);\n\t\tif (length > 0) lengths.push(length);\n\t}\n\tif (lengths.length === 0) return Infinity;\n\n\tlengths.sort((a, b) => a - b);\n\treturn lengths[Math.min(lengths.length - 1, Math.floor(lengths.length * SPACING_PERCENTILE))]!;\n}\n\n// LineSegmentsGeometry adopts `segments` as its backing store without copying — treat it as\n// read-only from here on.\nexport function buildLineGeometry(segments: Float32Array): EdgeGeometryEntry {\n\tconst geometry = new LineSegmentsGeometry();\n\tgeometry.setPositions(segments);\n\treturn {\n\t\tgeometry,\n\t\tsegmentCount: segments.length / 6,\n\t\tedgeSpacing: edgeSpacingOf(segments)\n\t};\n}\n","/**\n * Dependency-free crease/boundary edge extraction — the hot core behind `addEdges`. Semantically\n * a drop-in for `THREE.EdgesGeometry(geometry, angle)` (same welding, crease test, boundary\n * handling, 3+-face quirks), but operates on raw typed arrays with numeric hashing instead of\n * three's per-vertex string keys, for speed and Worker portability — see the no-outer-captures\n * constraint on {@link extractEdgeSegments} itself.\n */\n\n/** Vertex ids pack two-per-double in edge keys; above 2^26 vertices the packing overflows. */\nexport const MAX_EXTRACT_VERTICES = 0x4000000; // 2^26\n\n/**\n * @param index - Triangle indices, or null for non-indexed soup.\n * @returns Segment endpoint pairs, same layout as `EdgesGeometry.attributes.position.array`.\n * @throws If `positions` holds ≥ 2^26 vertices ({@link MAX_EXTRACT_VERTICES}) — callers fall\n * back to `THREE.EdgesGeometry`.\n */\nexport function extractEdgeSegments(\n\tpositions: Float32Array,\n\tindex: Uint32Array | Uint16Array | null,\n\tthresholdAngleDeg: number\n): Float32Array {\n\t// No outer captures besides Math — this function is stringified via toString() to run inside\n\t// a Worker ({@link edgeExtractWorkerSource}). Don't reference anything outside this body.\n\tconst PRECISION = 1e4; // same quantization grid as THREE.EdgesGeometry\n\tconst ID_BITS = 0x4000000; // 2^26 — two ids pack into one float64-exact integer key\n\tconst thresholdDot = Math.cos((Math.PI / 180) * thresholdAngleDeg);\n\n\tconst vertexCount = positions.length / 3;\n\tif (vertexCount >= ID_BITS) {\n\t\tthrow new Error(`extractEdgeSegments: ${vertexCount} vertices exceeds 2^26 limit`);\n\t}\n\n\t// --- Weld vertices on the quantization grid → canonical id per vertex -------------------\n\t// Rounded coords stay float64 (huge coordinates stay exact where int32 would overflow); only\n\t// the hash truncates to int32 — equality always compares the exact float64 values.\n\tconst quantX = new Float64Array(vertexCount);\n\tconst quantY = new Float64Array(vertexCount);\n\tconst quantZ = new Float64Array(vertexCount);\n\tfor (let v = 0; v < vertexCount; v++) {\n\t\tquantX[v] = Math.round(positions[3 * v] * PRECISION);\n\t\tquantY[v] = Math.round(positions[3 * v + 1] * PRECISION);\n\t\tquantZ[v] = Math.round(positions[3 * v + 2] * PRECISION);\n\t}\n\n\t// Open-addressed table (linear probing): slot → first vertex id seen at that grid point.\n\tlet capacity = 16;\n\twhile (capacity < vertexCount * 2) capacity <<= 1;\n\tconst mask = capacity - 1;\n\tconst table = new Int32Array(capacity).fill(-1);\n\tconst canonical = new Int32Array(vertexCount);\n\tfor (let v = 0; v < vertexCount; v++) {\n\t\tlet slot =\n\t\t\t(Math.imul(quantX[v] | 0, 73856093) ^\n\t\t\t\tMath.imul(quantY[v] | 0, 19349663) ^\n\t\t\t\tMath.imul(quantZ[v] | 0, 83492791)) &\n\t\t\tmask;\n\t\tfor (;;) {\n\t\t\tconst existing = table[slot];\n\t\t\tif (existing === -1) {\n\t\t\t\ttable[slot] = v;\n\t\t\t\tcanonical[v] = v;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tquantX[existing] === quantX[v] &&\n\t\t\t\tquantY[existing] === quantY[v] &&\n\t\t\t\tquantZ[existing] === quantZ[v]\n\t\t\t) {\n\t\t\t\tcanonical[v] = existing;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tslot = (slot + 1) & mask;\n\t\t}\n\t}\n\n\t// --- Growable segment output ------------------------------------------------------------\n\tlet out = new Float32Array(4096);\n\tlet outLength = 0;\n\tconst emit = (i0: number, i1: number): void => {\n\t\tif (outLength + 6 > out.length) {\n\t\t\tconst grown = new Float32Array(out.length * 2);\n\t\t\tgrown.set(out);\n\t\t\tout = grown;\n\t\t}\n\t\tout[outLength++] = positions[3 * i0];\n\t\tout[outLength++] = positions[3 * i0 + 1];\n\t\tout[outLength++] = positions[3 * i0 + 2];\n\t\tout[outLength++] = positions[3 * i1];\n\t\tout[outLength++] = positions[3 * i1 + 1];\n\t\tout[outLength++] = positions[3 * i1 + 2];\n\t};\n\n\t// --- Walk triangles, pairing opposite-winding edges -------------------------------------\n\t// Mirrors EdgesGeometry: a directed edge a→b matches a pending b→a; on match the segment is\n\t// kept iff the face normals differ beyond the threshold, and the pending entry is tombstoned\n\t// (key kept, value -1) so a third face on the same edge re-registers it. Unmatched entries at\n\t// the end are boundary edges and always emitted.\n\tconst edgeSlots = new Map<number, number>(); // directed key → pending-edge slot, -1 = matched\n\tconst pendingIndex0: number[] = [];\n\tconst pendingIndex1: number[] = [];\n\tconst pendingNormals: number[] = [];\n\n\tconst triCount = (index ? index.length : vertexCount) / 3;\n\tfor (let t = 0; t < triCount; t++) {\n\t\tconst i0 = index ? index[3 * t] : 3 * t;\n\t\tconst i1 = index ? index[3 * t + 1] : 3 * t + 1;\n\t\tconst i2 = index ? index[3 * t + 2] : 3 * t + 2;\n\t\tconst a = canonical[i0];\n\t\tconst b = canonical[i1];\n\t\tconst c = canonical[i2];\n\n\t\t// Degenerate on the quantization grid — skip, as EdgesGeometry does.\n\t\tif (a === b || b === c || c === a) continue;\n\n\t\t// Face normal, computed exactly as Triangle.getNormal: normalize((c-b) × (a-b)).\n\t\tconst e0x = positions[3 * i2] - positions[3 * i1];\n\t\tconst e0y = positions[3 * i2 + 1] - positions[3 * i1 + 1];\n\t\tconst e0z = positions[3 * i2 + 2] - positions[3 * i1 + 2];\n\t\tconst e1x = positions[3 * i0] - positions[3 * i1];\n\t\tconst e1y = positions[3 * i0 + 1] - positions[3 * i1 + 1];\n\t\tconst e1z = positions[3 * i0 + 2] - positions[3 * i1 + 2];\n\t\tlet nx = e0y * e1z - e0z * e1y;\n\t\tlet ny = e0z * e1x - e0x * e1z;\n\t\tlet nz = e0x * e1y - e0y * e1x;\n\t\tconst lengthSq = nx * nx + ny * ny + nz * nz;\n\t\tif (lengthSq > 0) {\n\t\t\tconst inverseLength = 1 / Math.sqrt(lengthSq);\n\t\t\tnx *= inverseLength;\n\t\t\tny *= inverseLength;\n\t\t\tnz *= inverseLength;\n\t\t} else {\n\t\t\tnx = 0;\n\t\t\tny = 0;\n\t\t\tnz = 0;\n\t\t}\n\n\t\tfor (let j = 0; j < 3; j++) {\n\t\t\tlet from: number;\n\t\t\tlet to: number;\n\t\t\tlet fromCanonical: number;\n\t\t\tlet toCanonical: number;\n\t\t\tif (j === 0) {\n\t\t\t\tfrom = i0;\n\t\t\t\tto = i1;\n\t\t\t\tfromCanonical = a;\n\t\t\t\ttoCanonical = b;\n\t\t\t} else if (j === 1) {\n\t\t\t\tfrom = i1;\n\t\t\t\tto = i2;\n\t\t\t\tfromCanonical = b;\n\t\t\t\ttoCanonical = c;\n\t\t\t} else {\n\t\t\t\tfrom = i2;\n\t\t\t\tto = i0;\n\t\t\t\tfromCanonical = c;\n\t\t\t\ttoCanonical = a;\n\t\t\t}\n\n\t\t\tconst reverseKey = toCanonical * ID_BITS + fromCanonical;\n\t\t\tconst reverseSlot = edgeSlots.get(reverseKey);\n\t\t\tif (reverseSlot !== undefined && reverseSlot !== -1) {\n\t\t\t\tconst dot =\n\t\t\t\t\tnx * pendingNormals[3 * reverseSlot] +\n\t\t\t\t\tny * pendingNormals[3 * reverseSlot + 1] +\n\t\t\t\t\tnz * pendingNormals[3 * reverseSlot + 2];\n\t\t\t\tif (dot <= thresholdDot) emit(from, to);\n\t\t\t\tedgeSlots.set(reverseKey, -1);\n\t\t\t} else {\n\t\t\t\tconst forwardKey = fromCanonical * ID_BITS + toCanonical;\n\t\t\t\tif (!edgeSlots.has(forwardKey)) {\n\t\t\t\t\tconst slot = pendingIndex0.length;\n\t\t\t\t\tedgeSlots.set(forwardKey, slot);\n\t\t\t\t\tpendingIndex0.push(from);\n\t\t\t\t\tpendingIndex1.push(to);\n\t\t\t\t\tpendingNormals.push(nx, ny, nz);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Unmatched edges are boundaries — always kept ---------------------------------------\n\tfor (const slot of edgeSlots.values()) {\n\t\tif (slot !== -1) emit(pendingIndex0[slot], pendingIndex1[slot]);\n\t}\n\n\treturn out.slice(0, outLength);\n}\n\n/**\n * Worker source running {@link extractEdgeSegments} off the main thread. Protocol: receives\n * `{id, positions, index, thresholdAngle}`, replies `{id, segments}` (buffer transferred) or\n * `{id, error}`.\n *\n * Relies on `extractEdgeSegments` stringifying to standalone code — guarded by a unit test that\n * evals this source in isolation.\n */\nexport function edgeExtractWorkerSource(): string {\n\treturn [\n\t\t`const extract = ${extractEdgeSegments.toString()};`,\n\t\t`self.onmessage = (event) => {`,\n\t\t` const { id, positions, index, thresholdAngle } = event.data;`,\n\t\t` try {`,\n\t\t` const segments = extract(positions, index, thresholdAngle);`,\n\t\t` self.postMessage({ id, segments }, [segments.buffer]);`,\n\t\t` } catch (error) {`,\n\t\t` self.postMessage({ id, error: String((error && error.message) || error) });`,\n\t\t` }`,\n\t\t`};`\n\t].join('\\n');\n}\n","import * as THREE from 'three';\n\n/** Crisp boundary/crease edges overlaid on meshes. See the layer README for depth/perf strategy. */\nexport interface EdgeOptions {\n\t/** Default: each overlay derives its color from its own mesh's material (see {@link DEFAULT_EDGE_COLOR}). */\n\tcolor?: THREE.ColorRepresentation;\n\t/** How far to darken the derived edge color toward black, 0-1 (default 0.75). No-op when `color` is set. */\n\tdarken?: number;\n\t/** Edge thickness in CSS px. Default 1.5. */\n\twidth?: number;\n\t/** Crease angle in degrees; an edge survives only where its two faces differ by more. Default 44. */\n\tthresholdAngle?: number;\n\t/**\n\t * Fade an overlay out as its own edges crowd together on screen (default true). Edges draw at\n\t * constant pixel width, so dense edges (e.g. millimetre-pitch laminations on sheet goods) merge\n\t * into a dark smear at normal zoom; fading by edge density rather than mesh size catches that\n\t * while leaving sparsely-edged geometry fully drawn.\n\t */\n\tdistanceFade?: boolean;\n\t/**\n\t * Skip meshes above this triangle count entirely (default 4M) — extraction time is linear in\n\t * triangles, and past this bound even the worker path burns seconds for a look the screen-space\n\t * fallback approximates at constant cost. Skipped meshes are tagged\n\t * `userData.edgesSkipped = 'triangle-cap'`.\n\t */\n\tmaxTriangles?: number;\n\t/**\n\t * Above this many extracted segments (default 2M), an overlay drops the distance fade and renders\n\t * opaque instead — millions of blended fat-line quads are a fill-rate cliff; opaque ones aren't.\n\t */\n\tmaxSegments?: number;\n}\n\n/** Tag on edge overlays so pick/fit/clear logic can recognize and skip or dispose them. */\nexport const EDGE_USERDATA_KIND = 'edge-overlay';\n\n/** `userData.edgesSkipped` value; see {@link EdgeOptions.maxTriangles}. */\nexport const EDGES_SKIPPED_TRIANGLE_CAP = 'triangle-cap';\n\nexport const DEFAULT_EDGE_COLOR = 0x222222;\nconst DEFAULT_EDGE_WIDTH = 1.5;\nconst DEFAULT_THRESHOLD_ANGLE = 44;\nconst DEFAULT_DARKEN = 0.75;\nconst DEFAULT_MAX_TRIANGLES = 4_000_000;\nconst DEFAULT_MAX_SEGMENTS = 2_000_000;\n\n// Below this triangle count a worker round-trip would cost more than the extraction itself, so it\n// runs inline even on the async path.\nexport const INLINE_TRIANGLE_BUDGET = 25_000;\n\n// Fade band as mean on-screen gap between neighbouring edges, in px: opaque at/above\n// FADE_START_PX, gone at/below FADE_END_PX, linear between. Density-based rather than\n// mesh/bounding-sphere-based, because a big part with sub-pixel edge spacing still draws every\n// line at full width — the old bounding-sphere rule missed that (the sphere still covers most of\n// the viewport). Band sits just above 1px, where constant-width lines start visibly overlapping.\nexport const FADE_START_PX = 4;\nexport const FADE_END_PX = 1;\n\n// Units-only pull-forward, deliberately no slope (factor) term: a slope term scales with the\n// polygon's dZ/dpixel, huge at grazing angles, and applied to surfaces (the old strategy) it\n// pushed grazing faces back further than the mm-scale gaps between stacked parts — geometry\n// behind a wall then won the depth test and bled through the wall's own edges. A fixed\n// quantization-step bias on the lines instead lifts an edge off its own surface without reaching\n// across a gap to a neighbouring part.\n//\n// This bias is only safe as long as a depth ULP stays small, which is what near-plane.ts's dynamic\n// near-plane fitter guarantees. Weakening that fit makes this bias start to bleed.\nexport const EDGE_OFFSET_FACTOR = 0;\nexport const EDGE_OFFSET_UNITS = -1; // negative = toward the camera\n\nexport interface ResolvedOptions {\n\tforcedColor: THREE.Color | null;\n\tdarken: number;\n\twidth: number;\n\tthresholdAngle: number;\n\tdistanceFade: boolean;\n\tmaxTriangles: number;\n\tmaxSegments: number;\n}\n\nexport function resolveOptions(options: EdgeOptions): ResolvedOptions {\n\treturn {\n\t\tforcedColor: options.color != null ? new THREE.Color(options.color) : null,\n\t\tdarken: THREE.MathUtils.clamp(options.darken ?? DEFAULT_DARKEN, 0, 1),\n\t\twidth: options.width ?? DEFAULT_EDGE_WIDTH,\n\t\tthresholdAngle: options.thresholdAngle ?? DEFAULT_THRESHOLD_ANGLE,\n\t\tdistanceFade: options.distanceFade ?? true,\n\t\tmaxTriangles: options.maxTriangles ?? DEFAULT_MAX_TRIANGLES,\n\t\tmaxSegments: options.maxSegments ?? DEFAULT_MAX_SEGMENTS\n\t};\n}\n","import * as THREE from 'three';\n\nimport {\n\tMAX_EXTRACT_VERTICES,\n\tedgeExtractWorkerSource,\n\textractEdgeSegments\n} from '../edge-extract.js';\nimport { INLINE_TRIANGLE_BUDGET } from './options.js';\n\n// ============================================================================\n// Segment extraction — fast path, worker offload\n// ============================================================================\n\nexport function triangleCountOf(geometry: THREE.BufferGeometry): number {\n\tconst position = geometry.getAttribute('position');\n\tif (!position) return 0;\n\treturn (geometry.index ? geometry.index.count : position.count) / 3;\n}\n\ninterface FastPathData {\n\tpositions: Float32Array;\n\tindex: Uint32Array | Uint16Array | null;\n}\n\n// Fast extractor needs plain non-interleaved float32 xyz + typed index arrays. Anything exotic\n// (interleaved, float64, morphed) falls back to THREE.EdgesGeometry instead.\nfunction fastPathData(geometry: THREE.BufferGeometry): FastPathData | null {\n\tconst position = geometry.getAttribute('position');\n\tif (\n\t\t!position ||\n\t\t(position as THREE.InterleavedBufferAttribute).isInterleavedBufferAttribute ||\n\t\tposition.itemSize !== 3 ||\n\t\t!(position.array instanceof Float32Array) ||\n\t\tposition.count >= MAX_EXTRACT_VERTICES\n\t) {\n\t\treturn null;\n\t}\n\tconst index = geometry.index;\n\tif (index && !(index.array instanceof Uint32Array) && !(index.array instanceof Uint16Array)) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tpositions: position.array,\n\t\tindex: index ? (index.array as Uint32Array | Uint16Array) : null\n\t};\n}\n\n// FNV-1a over sampled head+tail words of position/index plus lengths and crease angle. Sampling\n// keeps this ~free at millions of vertices; a collision needs identical lengths AND sampled regions.\nfunction contentKey(data: FastPathData, thresholdAngle: number): string {\n\tconst SAMPLE_WORDS = 4096;\n\tlet hash = 0x811c9dc5;\n\tconst mix = (word: number): void => {\n\t\thash ^= word;\n\t\thash = Math.imul(hash, 0x01000193);\n\t};\n\n\tconst words = new Uint32Array(\n\t\tdata.positions.buffer,\n\t\tdata.positions.byteOffset,\n\t\tdata.positions.length\n\t);\n\tconst head = Math.min(SAMPLE_WORDS, words.length);\n\tfor (let i = 0; i < head; i++) mix(words[i]);\n\tfor (let i = Math.max(head, words.length - SAMPLE_WORDS); i < words.length; i++) mix(words[i]);\n\n\tlet indexLength = 0;\n\tif (data.index) {\n\t\tindexLength = data.index.length;\n\t\tconst headIndex = Math.min(SAMPLE_WORDS, indexLength);\n\t\tfor (let i = 0; i < headIndex; i++) mix(data.index[i]);\n\t\tfor (let i = Math.max(headIndex, indexLength - SAMPLE_WORDS); i < indexLength; i++) {\n\t\t\tmix(data.index[i]);\n\t\t}\n\t}\n\n\treturn `${thresholdAngle}:${data.positions.length}:${indexLength}:${hash >>> 0}`;\n}\n\nfunction extractViaThree(geometry: THREE.BufferGeometry, thresholdAngle: number): Float32Array {\n\tconst edges = new THREE.EdgesGeometry(geometry, thresholdAngle);\n\tconst positions = edges.attributes.position\n\t\t? (edges.attributes.position.array as Float32Array)\n\t\t: new Float32Array(0);\n\tedges.dispose(); // frees only GPU-side state; the CPU array is the return value\n\treturn positions;\n}\n\nexport function extractSegmentsSync(\n\tgeometry: THREE.BufferGeometry,\n\tthresholdAngle: number\n): Float32Array {\n\tconst data = fastPathData(geometry);\n\tif (!data) return extractViaThree(geometry, thresholdAngle);\n\n\treturn extractEdgeSegments(data.positions, data.index, thresholdAngle);\n}\n\n// --- Worker offload -----------------------------------------------------------------------------\n\ninterface PendingRequest {\n\tresolve: (segments: Float32Array) => void;\n\treject: (error: Error) => void;\n}\n\nlet extractionWorker: Worker | null | undefined; // undefined = not yet tried, null = unavailable\nconst pendingRequests = new Map<number, PendingRequest>();\nlet nextRequestId = 1;\n\nfunction getExtractionWorker(): Worker | null {\n\tif (extractionWorker !== undefined) return extractionWorker;\n\tif (\n\t\ttypeof Worker === 'undefined' ||\n\t\ttypeof Blob === 'undefined' ||\n\t\ttypeof URL === 'undefined' ||\n\t\ttypeof URL.createObjectURL !== 'function'\n\t) {\n\t\textractionWorker = null;\n\t\treturn null;\n\t}\n\ttry {\n\t\t// Blob URL keeps this bundler-agnostic (no `new Worker(new URL(...))`). Never revoked:\n\t\t// revoking before the worker finishes fetching is unspecified behavior, and this is a\n\t\t// process-lifetime singleton.\n\t\tconst url = URL.createObjectURL(\n\t\t\tnew Blob([edgeExtractWorkerSource()], { type: 'text/javascript' })\n\t\t);\n\t\tconst worker = new Worker(url);\n\t\tworker.onmessage = (event: MessageEvent) => {\n\t\t\tconst { id, segments, error } = event.data as {\n\t\t\t\tid: number;\n\t\t\t\tsegments?: Float32Array;\n\t\t\t\terror?: string;\n\t\t\t};\n\t\t\tconst pending = pendingRequests.get(id);\n\t\t\tif (!pending) return;\n\t\t\tpendingRequests.delete(id);\n\t\t\tif (segments) pending.resolve(segments);\n\t\t\telse pending.reject(new Error(error ?? 'edge extraction failed in worker'));\n\t\t};\n\t\tworker.onerror = () => {\n\t\t\t// Worker died (CSP, OOM, script error): fail everything in flight and never retry the\n\t\t\t// worker this session — callers fall back to inline extraction.\n\t\t\tfor (const pending of pendingRequests.values()) {\n\t\t\t\tpending.reject(new Error('edge extraction worker crashed'));\n\t\t\t}\n\t\t\tpendingRequests.clear();\n\t\t\tworker.terminate();\n\t\t\textractionWorker = null;\n\t\t};\n\t\textractionWorker = worker;\n\t} catch {\n\t\textractionWorker = null;\n\t}\n\treturn extractionWorker;\n}\n\nfunction extractInWorker(\n\tworker: Worker,\n\tdata: FastPathData,\n\tthresholdAngle: number\n): Promise<Float32Array> {\n\treturn new Promise<Float32Array>((resolve, reject) => {\n\t\tconst id = nextRequestId++;\n\t\tpendingRequests.set(id, { resolve, reject });\n\t\t// Copy before transfer — the originals back the render geometry.\n\t\tconst positions = data.positions.slice();\n\t\tconst index = data.index ? data.index.slice() : null;\n\t\tconst transfer: Transferable[] = [positions.buffer];\n\t\tif (index) transfer.push(index.buffer);\n\t\tworker.postMessage({ id, positions, index, thresholdAngle }, transfer);\n\t});\n}\n\n// In-flight dedupe: meshes with identical content share one worker round-trip.\nconst inFlightExtractions = new Map<string, Promise<Float32Array>>();\n\nexport function extractSegmentsAsync(\n\tgeometry: THREE.BufferGeometry,\n\tthresholdAngle: number\n): Promise<Float32Array> {\n\tconst data = fastPathData(geometry);\n\tif (!data || triangleCountOf(geometry) < INLINE_TRIANGLE_BUDGET) {\n\t\treturn Promise.resolve(extractSegmentsSync(geometry, thresholdAngle));\n\t}\n\n\tconst key = contentKey(data, thresholdAngle);\n\tconst inFlight = inFlightExtractions.get(key);\n\tif (inFlight) return inFlight;\n\n\tconst worker = getExtractionWorker();\n\tif (!worker) return Promise.resolve(extractSegmentsSync(geometry, thresholdAngle));\n\n\tconst request = extractInWorker(worker, data, thresholdAngle)\n\t\t.catch(() => extractEdgeSegments(data.positions, data.index, thresholdAngle))\n\t\t.finally(() => {\n\t\t\tinFlightExtractions.delete(key);\n\t\t});\n\tinFlightExtractions.set(key, request);\n\treturn request;\n}\n","import * as THREE from 'three';\nimport { LineMaterial } from 'three/addons/lines/LineMaterial.js';\nimport { LineSegments2 } from 'three/addons/lines/LineSegments2.js';\n\nimport type { EdgeGeometryEntry } from './line-geometry.js';\nimport {\n\tDEFAULT_EDGE_COLOR,\n\tEDGE_OFFSET_FACTOR,\n\tEDGE_OFFSET_UNITS,\n\tEDGE_USERDATA_KIND,\n\tFADE_END_PX,\n\tFADE_START_PX,\n\ttype ResolvedOptions\n} from './options.js';\n\n// ============================================================================\n// Overlay construction\n// ============================================================================\n\n// Multiplicative darkening (not lerp-to-black) preserves hue and desaturates gently; a near-black\n// surface just yields near-black edges.\nfunction deriveEdgeColor(mesh: THREE.Mesh, darken: number): THREE.Color {\n\tconst material = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;\n\tconst source = (material as { color?: THREE.Color } | null)?.color;\n\tif (!source) return new THREE.Color(DEFAULT_EDGE_COLOR);\n\treturn source.clone().multiplyScalar(1 - darken);\n}\n\n/** Pools materials by color+fade so overlays sharing both share one `LineMaterial` instance. */\nexport class MaterialPool {\n\tprivate readonly byKey = new Map<number, LineMaterial>();\n\tconstructor(private readonly options: ResolvedOptions) {}\n\n\tfor(mesh: THREE.Mesh, fade: boolean): LineMaterial {\n\t\tconst color = this.options.forcedColor ?? deriveEdgeColor(mesh, this.options.darken);\n\t\tconst key = color.getHex() * 2 + (fade ? 1 : 0);\n\t\tlet material = this.byKey.get(key);\n\t\tif (!material) {\n\t\t\tmaterial = createEdgeMaterial(color, this.options.width, fade);\n\t\t\tthis.byKey.set(key, material);\n\t\t}\n\t\treturn material;\n\t}\n\n\t/** Dispose any material no overlay adopted (e.g. every mesh was skipped or cancelled). */\n\tdisposeUnused(created: LineSegments2[]): void {\n\t\tconst used = new Set(created.map((overlay) => overlay.material));\n\t\tfor (const material of this.byKey.values()) {\n\t\t\tif (!used.has(material)) material.dispose();\n\t\t}\n\t}\n}\n\nfunction createEdgeMaterial(\n\tcolor: THREE.Color,\n\twidth: number,\n\tdistanceFade: boolean\n): LineMaterial {\n\t// LineMaterialParameters omits linewidth/opacity from its type though both exist at runtime.\n\tconst material = new LineMaterial({ color });\n\t(material as LineMaterial & { linewidth: number }).linewidth = width;\n\t// Lifts lines toward the camera by a couple of depth-quantization steps so they win z-fighting\n\t// against the surface they were extracted from, without moving the surface itself.\n\tmaterial.polygonOffset = true;\n\tmaterial.polygonOffsetFactor = EDGE_OFFSET_FACTOR;\n\tmaterial.polygonOffsetUnits = EDGE_OFFSET_UNITS;\n\t// Set once here, not per draw: flipping `transparent` after the render list is built wouldn't\n\t// re-sort the object into the transparent pass.\n\tif (distanceFade) material.transparent = true;\n\treturn material;\n}\n\nexport function buildEdgeOverlay(\n\tentry: EdgeGeometryEntry,\n\tmaterial: LineMaterial,\n\tdistanceFade: boolean\n): LineSegments2 {\n\tconst overlay = new LineSegments2(entry.geometry, material);\n\toverlay.userData.kind = EDGE_USERDATA_KIND;\n\toverlay.raycast = () => {}; // never pickable; clicks should hit the mesh, not its outline\n\tif (distanceFade) enableDistanceFade(overlay, entry.edgeSpacing);\n\treturn overlay;\n}\n\nconst _fadeCenter = new THREE.Vector3();\nconst _fadeCameraPos = new THREE.Vector3();\n\n// Returns Infinity (\"don't fade\") for an unknown/degenerate projection, or a camera inside the mesh.\nfunction pixelsPerWorldUnit(\n\toverlay: LineSegments2,\n\tcamera: THREE.Camera,\n\tviewportHeightPx: number\n): number {\n\tif (!overlay.geometry.boundingSphere) overlay.geometry.computeBoundingSphere();\n\tconst sphere = overlay.geometry.boundingSphere;\n\tif (!sphere) return Infinity;\n\n\tif ((camera as THREE.PerspectiveCamera).isPerspectiveCamera) {\n\t\tconst perspective = camera as THREE.PerspectiveCamera;\n\t\t_fadeCenter.copy(sphere.center).applyMatrix4(overlay.matrixWorld);\n\t\tconst distance = _fadeCameraPos\n\t\t\t.setFromMatrixPosition(camera.matrixWorld)\n\t\t\t.distanceTo(_fadeCenter);\n\t\tconst radius = sphere.radius * overlay.matrixWorld.getMaxScaleOnAxis();\n\t\tif (distance <= radius) return Infinity; // camera inside the mesh — no fade\n\t\tconst tanHalfFov = Math.tan(THREE.MathUtils.degToRad(perspective.fov) * 0.5);\n\t\tconst worldHeightAtCentre = 2 * distance * tanHalfFov;\n\t\treturn worldHeightAtCentre > 0 ? viewportHeightPx / worldHeightAtCentre : Infinity;\n\t}\n\tif ((camera as THREE.OrthographicCamera).isOrthographicCamera) {\n\t\tconst ortho = camera as THREE.OrthographicCamera;\n\t\tconst worldHeight = (ortho.top - ortho.bottom) / ortho.zoom;\n\t\treturn worldHeight > 0 ? viewportHeightPx / worldHeight : Infinity;\n\t}\n\treturn Infinity;\n}\n\n// Runs in onBeforeRender (not computed once) so opacity is written right before this overlay's\n// draw call — uniforms upload per draw, so overlays sharing one material still fade independently.\n// Chains LineSegments2's own onBeforeRender to keep its resolution uniform in sync.\nfunction enableDistanceFade(overlay: LineSegments2, edgeSpacing: number): void {\n\t// Assign via the Object3D base type: LineSegments2's typings narrow onBeforeRender to\n\t// (renderer) only, but the renderer actually calls it with (renderer, scene, camera, …).\n\t(overlay as THREE.Object3D).onBeforeRender = (renderer, _scene, camera) => {\n\t\tLineSegments2.prototype.onBeforeRender.call(overlay, renderer);\n\t\tconst material = overlay.material as LineMaterial;\n\t\tconst scale = pixelsPerWorldUnit(overlay, camera, material.resolution.y);\n\t\t// Screen-space gap between neighbouring edges. Infinity in, Infinity out — clamps to fully\n\t\t// opaque rather than fading on a guess.\n\t\tconst gapPx = edgeSpacing * scale;\n\t\tmaterial.opacity = THREE.MathUtils.clamp(\n\t\t\t(gapPx - FADE_END_PX) / (FADE_START_PX - FADE_END_PX),\n\t\t\t0,\n\t\t\t1\n\t\t);\n\t};\n}\n","import * as THREE from 'three';\nimport type { LineMaterial } from 'three/addons/lines/LineMaterial.js';\nimport { LineSegments2 } from 'three/addons/lines/LineSegments2.js';\n\nimport { buildLineGeometry, type EdgeGeometryEntry } from './edges/line-geometry.js';\nimport { extractSegmentsAsync, extractSegmentsSync, triangleCountOf } from './edges/extraction.js';\nimport {\n\tEDGES_SKIPPED_TRIANGLE_CAP,\n\tEDGE_USERDATA_KIND,\n\tresolveOptions,\n\ttype EdgeOptions,\n\ttype ResolvedOptions\n} from './edges/options.js';\nimport { MaterialPool, buildEdgeOverlay } from './edges/overlay.js';\n\n/**\n * Crisp boundary/crease edges overlaid on meshes, rendered as fat `LineSegments2` (controllable\n * thickness, unlike the 1px cap of `THREE.LineSegments`). Depth-offset rationale for the overlay\n * lines: see `EDGE_OFFSET_FACTOR`/`EDGE_OFFSET_UNITS` in `edges/options.ts`.\n */\nexport type { EdgeOptions };\nexport { EDGE_USERDATA_KIND, EDGES_SKIPPED_TRIANGLE_CAP };\n\n// ============================================================================\n// Public API — add / remove / query\n// ============================================================================\n\n/** For pick/fit filters elsewhere to exclude overlays from hit-testing. */\nexport function isEdgeOverlay(object: THREE.Object3D): boolean {\n\treturn object.userData?.kind === EDGE_USERDATA_KIND;\n}\n\n/** Meshes under `root` that should get an overlay: content meshes without one, caps applied. */\nfunction collectTargets(root: THREE.Object3D, maxTriangles: number): THREE.Mesh[] {\n\tconst targets: THREE.Mesh[] = [];\n\troot.traverse((object) => {\n\t\tif (!(object instanceof THREE.Mesh)) return;\n\t\tif (object.userData.id === 'floor' || object.userData.id === 'grid') return;\n\t\tif (object.userData.kind === EDGE_USERDATA_KIND) return;\n\t\tif (object.children.some((c) => c.userData?.kind === EDGE_USERDATA_KIND)) return; // already done\n\t\tif (!object.geometry) return;\n\n\t\tif (triangleCountOf(object.geometry) > maxTriangles) {\n\t\t\tobject.userData.edgesSkipped = EDGES_SKIPPED_TRIANGLE_CAP;\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(\n\t\t\t\t`[edges] skipping mesh over triangle cap (${triangleCountOf(object.geometry)} > ${maxTriangles})`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tdelete object.userData.edgesSkipped;\n\t\ttargets.push(object);\n\t});\n\treturn targets;\n}\n\nfunction attachOverlay(\n\tmesh: THREE.Mesh,\n\tentry: EdgeGeometryEntry,\n\tmaterials: MaterialPool,\n\tresolved: ResolvedOptions\n): LineSegments2 {\n\t// Distance fade needs the transparent pass; overlays over the segment cap stay opaque instead\n\t// of skipping outright — see EdgeOptions.maxSegments.\n\tconst fade = resolved.distanceFade && entry.segmentCount <= resolved.maxSegments;\n\tconst overlay = buildEdgeOverlay(entry, materials.for(mesh, fade), fade);\n\tmesh.add(overlay); // child → inherits transform, disposed with the parent subtree\n\treturn overlay;\n}\n\n/**\n * Attach an edge overlay to every `Mesh` in `root`'s subtree, returning the created overlays.\n * Idempotent — meshes that already carry an overlay are skipped. Skips the floor/grid aids.\n *\n * Fully synchronous, extraction included. Prefer {@link addEdgesAsync} for interactive hosts with\n * potentially-large meshes.\n */\nexport function addEdges(root: THREE.Object3D, options: EdgeOptions = {}): LineSegments2[] {\n\tconst resolved = resolveOptions(options);\n\tconst materials = new MaterialPool(resolved);\n\tconst created: LineSegments2[] = [];\n\n\tfor (const mesh of collectTargets(root, resolved.maxTriangles)) {\n\t\t// Extraction itself is cached by content (`extraction.ts`), which is where the savings are;\n\t\t// the line geometry is per-overlay and owned by it.\n\t\tconst segments = extractSegmentsSync(mesh.geometry, resolved.thresholdAngle);\n\t\tcreated.push(attachOverlay(mesh, buildLineGeometry(segments), materials, resolved));\n\t}\n\n\tmaterials.disposeUnused(created);\n\treturn created;\n}\n\n/**\n * {@link removeEdges} bumps this per root; async attaches landing after a bump are dropped, so\n * \"toggle off while extracting\" can't resurrect overlays.\n */\nconst rootGenerations = new WeakMap<THREE.Object3D, number>();\n\nfunction generationOf(root: THREE.Object3D): number {\n\treturn rootGenerations.get(root) ?? 0;\n}\n\n/** Is `mesh` still reachable from `root`? Guards attaches racing scene clears. */\nfunction isConnected(mesh: THREE.Object3D, root: THREE.Object3D): boolean {\n\tfor (let node: THREE.Object3D | null = mesh; node; node = node.parent) {\n\t\tif (node === root) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Like {@link addEdges}, but large-mesh extraction runs in a Worker so the main thread never\n * stalls; small meshes still attach synchronously before this resolves. Resolves with every\n * overlay actually attached — late results are dropped if the mesh left the subtree,\n * {@link removeEdges} ran for this root meanwhile, or another apply already attached one to that\n * mesh.\n */\nexport async function addEdgesAsync(\n\troot: THREE.Object3D,\n\toptions: EdgeOptions = {}\n): Promise<LineSegments2[]> {\n\tconst resolved = resolveOptions(options);\n\tconst materials = new MaterialPool(resolved);\n\tconst generation = generationOf(root);\n\tconst created: LineSegments2[] = [];\n\n\tconst attaches = collectTargets(root, resolved.maxTriangles).map(async (mesh) => {\n\t\tconst segments = await extractSegmentsAsync(mesh.geometry, resolved.thresholdAngle);\n\t\t// Things may have moved on while extracting — attach only if this apply is still wanted.\n\t\tif (generationOf(root) !== generation) return;\n\t\tif (!isConnected(mesh, root)) return;\n\t\tif (mesh.children.some((c) => c.userData?.kind === EDGE_USERDATA_KIND)) return;\n\t\tcreated.push(attachOverlay(mesh, buildLineGeometry(segments), materials, resolved));\n\t});\n\n\tawait Promise.all(attaches);\n\tmaterials.disposeUnused(created);\n\treturn created;\n}\n\n/**\n * Remove every edge overlay under `root`, disposing geometry and material, and cancels any\n * in-flight async attaches for `root`. Inverse of {@link addEdges}/{@link addEdgesAsync}. Returns\n * the count removed.\n */\nexport function removeEdges(root: THREE.Object3D): number {\n\trootGenerations.set(root, generationOf(root) + 1);\n\n\tconst overlays: LineSegments2[] = [];\n\troot.traverse((object) => {\n\t\tif (object instanceof LineSegments2 && isEdgeOverlay(object)) overlays.push(object);\n\t});\n\n\t// One addEdges call shares one material across its overlays — dispose each distinct one once.\n\t// (If `root` covers only part of a call's overlays, survivors self-heal: three recompiles a\n\t// disposed-but-still-referenced material on its next use.)\n\tconst materials = new Set<LineMaterial>();\n\tfor (const overlay of overlays) {\n\t\toverlay.geometry.dispose(); // each overlay owns its line geometry outright\n\t\tmaterials.add(overlay.material as LineMaterial);\n\t\t// Nothing to undo on the parent mesh: the depth bias lives entirely on the overlay's own\n\t\t// material, so surfaces keep whatever polygonOffset their look preset configured.\n\t\toverlay.removeFromParent();\n\t}\n\tmaterials.forEach((material) => material.dispose());\n\treturn overlays.length;\n}\n","import * as THREE from 'three';\n\n/**\n * An \"infinite\", distance-fading reference grid.\n *\n * `GridHelper` is a fixed-size square that visibly ends once you pan/zoom past it. This draws one\n * large plane and computes the grid in the fragment shader from world coordinates, fading with\n * distance so the edge is never a hard cutoff.\n *\n * Spacing is in world units (meters) — `cellSize` of 1 = 1m cells.\n */\n\nexport interface GridOptions {\n\t/** Minor cell size in world units (meters). Default 1. */\n\tcellSize?: number;\n\t/** How many minor cells per major line. Default 10. */\n\tmajorEvery?: number;\n\t/** Minor line color. Default 0x888888. */\n\tcellColor?: THREE.ColorRepresentation;\n\t/** Major line color. Default 0x444444. */\n\tmajorColor?: THREE.ColorRepresentation;\n\t/** World-space radius at which the grid has fully faded out. Default 100. */\n\tfadeDistance?: number;\n\t/**\n\t * Axis the grid is laid perpendicular to, i.e. the scene's up axis. Standalone default is `'y'`,\n\t * but `initThree` always passes a plane derived from the configured `sceneUp`, so a\n\t * viewer-created grid defaults to `'z'` (Rhino's ground plane).\n\t */\n\tplane?: 'x' | 'y' | 'z';\n}\n\nexport interface Grid {\n\t/** Tagged `userData.id = 'grid'` so pick/fit code skips it. */\n\treadonly object: THREE.Mesh;\n\t/** Re-centers the fade on the camera so the grid feels infinite as you move. Call per frame. */\n\tupdate(cameraPosition: THREE.Vector3): void;\n\t/**\n\t * Rescales cell spacing and fade radius to the content's extent, so a 3-unit or 3000-unit part\n\t * both get sensible cells. No-op for empty/degenerate bounds.\n\t */\n\tfitToContent(bounds: THREE.Box3): void;\n\tsetVisible(visible: boolean): void;\n\tdispose(): void;\n}\n\n/** Rounds to a \"nice\" 1/2/5 × 10ⁿ step (CAD ruler convention) so cells never come out finer than the target and alias into a solid sheet. */\nfunction niceStep(value: number): number {\n\tif (!(value > 0) || !Number.isFinite(value)) return 1;\n\tconst exponent = Math.floor(Math.log10(value));\n\tconst power = Math.pow(10, exponent);\n\tconst mantissa = value / power; // in [1, 10)\n\tconst niceMantissa = mantissa >= 5 ? 5 : mantissa >= 2 ? 2 : 1;\n\treturn niceMantissa * power;\n}\n\nconst GRID_VERTEX = /* glsl */ `\n\tvarying vec3 vWorldPos;\n\tvoid main() {\n\t\tvec4 world = modelMatrix * vec4(position, 1.0);\n\t\tvWorldPos = world.xyz;\n\t\tgl_Position = projectionMatrix * viewMatrix * world;\n\t}\n`;\n\nconst GRID_FRAGMENT = /* glsl */ `\n\tprecision highp float;\n\tvarying vec3 vWorldPos;\n\n\tuniform vec2 uAxes; // indices (0=x,1=y,2=z) of the two in-plane world axes\n\tuniform float uCell;\n\tuniform float uMajor;\n\tuniform vec3 uCellColor;\n\tuniform vec3 uMajorColor;\n\tuniform vec3 uCenter; // fade center (camera position projected onto the plane)\n\tuniform float uFade;\n\n\t// Screen-space derivatives keep grid lines ~1px regardless of zoom (\"pristine grid\" technique).\n\tfloat gridLine(vec2 coord, float spacing) {\n\t\tvec2 c = coord / spacing;\n\t\tvec2 d = fwidth(c);\n\t\tvec2 g = abs(fract(c - 0.5) - 0.5) / max(d, 1e-6);\n\t\tfloat line = min(g.x, g.y);\n\t\treturn 1.0 - clamp(line, 0.0, 1.0);\n\t}\n\n\t// Index a vec3 by a float axis id (0/1/2) without dynamic indexing (WebGL1-safe).\n\tfloat axis(vec3 v, float i) {\n\t\treturn i < 0.5 ? v.x : (i < 1.5 ? v.y : v.z);\n\t}\n\n\tvoid main() {\n\t\t// Pick the two in-plane world coordinates.\n\t\tvec2 coord = vec2(axis(vWorldPos, uAxes.x), axis(vWorldPos, uAxes.y));\n\n\t\tfloat minor = gridLine(coord, uCell);\n\t\tfloat major = gridLine(coord, uCell * uMajor);\n\n\t\tvec3 color = mix(uCellColor, uMajorColor, major);\n\t\tfloat alpha = max(minor, major);\n\n\t\t// Radial fade from the camera-projected center.\n\t\tfloat dist = distance(vWorldPos, uCenter);\n\t\tfloat fade = 1.0 - clamp(dist / uFade, 0.0, 1.0);\n\t\talpha *= fade * fade;\n\n\t\tif (alpha < 0.001) discard;\n\t\tgl_FragColor = vec4(color, alpha);\n\t}\n`;\n\nexport function createGrid(options: GridOptions = {}): Grid {\n\tconst {\n\t\tcellSize = 1,\n\t\tmajorEvery = 10,\n\t\tcellColor = 0x888888,\n\t\tmajorColor = 0x444444,\n\t\tfadeDistance = 100,\n\t\tplane = 'y'\n\t} = options;\n\n\t// In-plane world axes: ground 'y' grids over x,z; 'z' over x,y; 'x' over y,z.\n\tconst axes =\n\t\tplane === 'y'\n\t\t\t? new THREE.Vector2(0, 2) // x, z\n\t\t\t: plane === 'z'\n\t\t\t\t? new THREE.Vector2(0, 1) // x, y\n\t\t\t\t: new THREE.Vector2(1, 2); // y, z\n\n\t// Must comfortably outreach the fade radius, else the grid's rectangular edge shows before the\n\t// fade completes. Plane is unit-sized and grown purely via scale so fitToContent never\n\t// recreates geometry.\n\tconst PLANE_TO_FADE_RATIO = 2.5;\n\tconst geometry = new THREE.PlaneGeometry(1, 1);\n\n\t// PlaneGeometry is in the XY plane by default; rotate it onto the requested world plane.\n\tif (plane === 'y') geometry.rotateX(-Math.PI / 2);\n\telse if (plane === 'x') geometry.rotateY(Math.PI / 2);\n\n\tconst material = new THREE.ShaderMaterial({\n\t\tvertexShader: GRID_VERTEX,\n\t\tfragmentShader: GRID_FRAGMENT,\n\t\ttransparent: true,\n\t\tdepthWrite: false,\n\t\tside: THREE.DoubleSide,\n\t\tuniforms: {\n\t\t\tuAxes: { value: axes },\n\t\t\tuCell: { value: cellSize },\n\t\t\tuMajor: { value: majorEvery },\n\t\t\tuCellColor: { value: new THREE.Color(cellColor) },\n\t\t\tuMajorColor: { value: new THREE.Color(majorColor) },\n\t\t\tuCenter: { value: new THREE.Vector3() },\n\t\t\tuFade: { value: fadeDistance }\n\t\t}\n\t});\n\n\tconst mesh = new THREE.Mesh(geometry, material);\n\tmesh.name = 'grid';\n\tmesh.userData.id = 'grid';\n\tmesh.renderOrder = -1; // draw before content so transparent geometry blends over it\n\n\t// fitToContent mutates both; seeded from fadeDistance so an un-fitted grid still covers its fade.\n\tlet fadeRadius = fadeDistance;\n\tlet planeScale = fadeDistance * PLANE_TO_FADE_RATIO;\n\n\tconst center = new THREE.Vector3();\n\n\treturn {\n\t\tobject: mesh,\n\t\tupdate: (cameraPosition) => {\n\t\t\t// Re-center on the camera so the grid tracks the view; the plane's own axis stays fixed\n\t\t\t// (a ground grid shouldn't lift to the camera's height).\n\t\t\tif (plane === 'y') {\n\t\t\t\tmesh.position.set(cameraPosition.x, 0, cameraPosition.z);\n\t\t\t\tcenter.set(cameraPosition.x, 0, cameraPosition.z);\n\t\t\t} else if (plane === 'z') {\n\t\t\t\tmesh.position.set(cameraPosition.x, cameraPosition.y, 0);\n\t\t\t\tcenter.set(cameraPosition.x, cameraPosition.y, 0);\n\t\t\t} else {\n\t\t\t\tmesh.position.set(0, cameraPosition.y, cameraPosition.z);\n\t\t\t\tcenter.set(0, cameraPosition.y, cameraPosition.z);\n\t\t\t}\n\t\t\tmaterial.uniforms.uCenter.value.copy(center);\n\t\t\t// Rotation is baked into the geometry, so uniform scale works on any plane orientation.\n\t\t\tmesh.scale.setScalar(planeScale);\n\t\t},\n\t\tfitToContent: (bounds) => {\n\t\t\tif (bounds.isEmpty()) return;\n\t\t\t// In-plane extent only — a tall thin part shouldn't blow up the cell size by its height.\n\t\t\tconst sizeVec = bounds.getSize(new THREE.Vector3());\n\t\t\tconst axisComponent = (v: THREE.Vector3, i: number) => (i === 0 ? v.x : i === 1 ? v.y : v.z);\n\t\t\tconst inPlaneExtent = Math.max(\n\t\t\t\taxisComponent(sizeVec, axes.x),\n\t\t\t\taxisComponent(sizeVec, axes.y)\n\t\t\t);\n\t\t\tif (!(inPlaneExtent > 0) || !Number.isFinite(inPlaneExtent)) return;\n\n\t\t\t// ~20 minor cells across the part; fade reaches ~2x past it so the edge stays out of view.\n\t\t\tconst TARGET_CELLS_ACROSS = 20;\n\t\t\tmaterial.uniforms.uCell.value = niceStep(inPlaneExtent / TARGET_CELLS_ACROSS);\n\t\t\tfadeRadius = inPlaneExtent * 2;\n\t\t\tmaterial.uniforms.uFade.value = fadeRadius;\n\t\t\tplaneScale = fadeRadius * PLANE_TO_FADE_RATIO;\n\t\t},\n\t\tsetVisible: (visible) => {\n\t\t\tmesh.visible = visible;\n\t\t},\n\t\tdispose: () => {\n\t\t\tmesh.removeFromParent();\n\t\t\tgeometry.dispose();\n\t\t\tmaterial.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\nimport { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';\n\nexport interface LabelHandle {\n\treadonly object: CSS2DObject;\n\tsetPosition(position: THREE.Vector3): void;\n\tsetText(text: string): void;\n\tremove(): void;\n}\n\nexport interface LabelLayer {\n\taddLabel(text: string, position: THREE.Vector3, className?: string): LabelHandle;\n\t/** Call each frame after the WebGL render, with the active camera. */\n\trender(scene: THREE.Scene, camera: THREE.Camera): void;\n\tsetSize(width: number, height: number): void;\n\tdispose(): void;\n}\n\n// `container` is normally the canvas's parent, so both share a positioning context for the\n// absolutely-positioned label overlay.\nexport function createLabelLayer(container: HTMLElement, scene: THREE.Scene): LabelLayer {\n\tconst renderer = new CSS2DRenderer();\n\tconst dom = renderer.domElement;\n\tdom.style.position = 'absolute';\n\tdom.style.top = '0';\n\tdom.style.left = '0';\n\t// CSS2DRenderer sets width/height in pixels, so host must call `setSize` on every resize, same\n\t// as the WebGL renderer. overflow:hidden + pointerEvents:none: without both, the overlay can\n\t// cover the canvas and swallow orbit/clicks.\n\tdom.style.overflow = 'hidden';\n\tdom.style.pointerEvents = 'none';\n\tdom.style.zIndex = '30'; // above canvas/host overlays, below menus/popovers\n\tif (getComputedStyle(container).position === 'static') {\n\t\tcontainer.style.position = 'relative';\n\t}\n\tcontainer.appendChild(dom);\n\n\tconst size = { width: container.clientWidth || 1, height: container.clientHeight || 1 };\n\trenderer.setSize(size.width, size.height);\n\n\tconst group = new THREE.Group(); // pick/fit logic skips objects tagged 'label-layer'\n\tgroup.name = 'label-layer';\n\tgroup.userData.id = 'label-layer';\n\tscene.add(group);\n\n\tconst labels = new Set<CSS2DObject>();\n\n\tconst addLabel = (text: string, position: THREE.Vector3, className?: string): LabelHandle => {\n\t\tconst el = document.createElement('div');\n\t\tel.textContent = text;\n\t\tif (className) {\n\t\t\tel.className = className;\n\t\t} else {\n\t\t\t// Inline default so the layer needs no external stylesheet; pass className to opt out.\n\t\t\tObject.assign(el.style, {\n\t\t\t\tpadding: '2px 6px',\n\t\t\t\tborderRadius: '4px',\n\t\t\t\tbackground: 'rgba(20, 20, 20, 0.78)',\n\t\t\t\tcolor: '#fff',\n\t\t\t\tfont: '12px/1.3 system-ui, sans-serif',\n\t\t\t\t// `pre` preserves line breaks for multi-line readouts (e.g. total + per-axis deltas).\n\t\t\t\twhiteSpace: 'pre',\n\t\t\t\ttextAlign: 'center',\n\t\t\t\tuserSelect: 'none'\n\t\t\t} satisfies Partial<CSSStyleDeclaration>);\n\t\t}\n\t\tel.style.pointerEvents = 'none';\n\n\t\tconst object = new CSS2DObject(el);\n\t\tobject.position.copy(position);\n\t\tgroup.add(object);\n\t\tlabels.add(object);\n\n\t\treturn {\n\t\t\tobject,\n\t\t\tsetPosition: (p) => object.position.copy(p),\n\t\t\tsetText: (t) => {\n\t\t\t\tel.textContent = t;\n\t\t\t},\n\t\t\tremove: () => {\n\t\t\t\tobject.removeFromParent();\n\t\t\t\tel.remove();\n\t\t\t\tlabels.delete(object);\n\t\t\t}\n\t\t};\n\t};\n\n\treturn {\n\t\taddLabel,\n\t\trender: (scene, camera) => renderer.render(scene, camera),\n\t\tsetSize: (width, height) => renderer.setSize(width, height),\n\t\tdispose: () => {\n\t\t\tlabels.forEach((object) => {\n\t\t\t\tobject.removeFromParent();\n\t\t\t\t(object.element as HTMLElement).remove();\n\t\t\t});\n\t\t\tlabels.clear();\n\t\t\tgroup.removeFromParent();\n\t\t\tdom.remove();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\nimport { Line2 } from 'three/addons/lines/Line2.js';\nimport { LineGeometry } from 'three/addons/lines/LineGeometry.js';\nimport { LineMaterial } from 'three/addons/lines/LineMaterial.js';\n\nimport type { LabelLayer, LabelHandle } from './label-layer';\n\n/**\n * Two-click distance measurement. Click a point, click a second, read the distance off a label on\n * the connecting line; a third click starts fresh.\n *\n * Picking snaps to the nearest vertex within {@link MeasureOptions.snapPixels} so measurements\n * land exactly on vertices rather than wherever the ray happened to hit — a cheap local snap\n * against the struck primitive's own vertices, no spatial index.\n *\n * Dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks (caller\n * forwards them and swallows the event when {@link MeasureTool.handleClick} returns true) so\n * measuring doesn't also select objects.\n */\n\nexport interface MeasureTool {\n\tsetEnabled(enabled: boolean): void;\n\tisEnabled(): boolean;\n\t/** Returns true if the tool consumed the click (caller should not also select). */\n\thandleClick(event: MouseEvent): boolean;\n\t/** Preview the next snap point via a ghost marker. No-op when disabled; never consumes the event. */\n\thandleMove(event: MouseEvent): void;\n\tclear(): void;\n\tdispose(): void;\n}\n\nexport interface MeasureOptions {\n\t/** Snap to a vertex when the cursor is within this many screen pixels of it. Default 12. */\n\tsnapPixels?: number;\n\t/** Marker + line color. Default yellow. */\n\tcolor?: THREE.ColorRepresentation;\n\tlabelClassName?: string;\n\t/**\n\t * Pass `data.modelunits`. Scene is in meters; default formatter converts and labels in this unit\n\t * (e.g. \"25.0 mm\" not \"0.025 m\"). Defaults to meters. Ignored if `format` is given.\n\t */\n\tdisplayUnit?: string;\n\t/**\n\t * Format the measurement → label text. Receives `distance` and per-axis `delta` (|b − a|), both\n\t * in meters. May return multi-line text/HTML; default renders total + Δx/Δy/Δz in `displayUnit`.\n\t */\n\tformat?: (distance: number, delta: THREE.Vector3) => string;\n}\n\ninterface MeasureDeps {\n\tcanvas: HTMLCanvasElement;\n\tscene: THREE.Scene;\n\tgetActiveCamera: () => THREE.Camera;\n\t/**\n\t * The current orbit target (e.g. `controls.target`). Scales the line/point pick threshold as a\n\t * fraction of camera→target distance so it stays constant on screen regardless of framing.\n\t * Without it, the fallback is distance-to-origin, which misjudges off-origin content.\n\t */\n\tgetViewTarget?: () => THREE.Vector3;\n\tlabelLayer: LabelLayer;\n\toptions?: MeasureOptions;\n}\n\nconst DEFAULT_SNAP_PIXELS = 12;\nconst DEFAULT_COLOR = 0xffcc00;\n// Fraction of view distance used as the line/point raycast threshold: ~1.5% gives a comfortable\n// grab band at typical framing without snapping to far-off geometry.\nconst LINE_PICK_FRACTION = 0.015;\n\n// Scene geometry loads in meters (webdisplay parser scales when `allowScaling` is on). Keep in\n// sync with the webdisplay parser's SCALE_FACTORS.\nconst UNIT_DISPLAY: Record<string, { metersPerUnit: number; suffix: string }> = {\n\tMillimeters: { metersPerUnit: 1 / 1000, suffix: 'mm' },\n\tCentimeters: { metersPerUnit: 1 / 100, suffix: 'cm' },\n\tMeters: { metersPerUnit: 1, suffix: 'm' },\n\tInches: { metersPerUnit: 1 / 39.37, suffix: 'in' },\n\tFeet: { metersPerUnit: 1 / 3.28084, suffix: 'ft' }\n};\n\n/** @internal exported for tests */\nexport function makeFormatter(displayUnit?: string): (n: number) => string {\n\tconst unit = (displayUnit && UNIT_DISPLAY[displayUnit]) || UNIT_DISPLAY.Meters;\n\treturn (meters: number) => `${(meters / unit.metersPerUnit).toPrecision(3)} ${unit.suffix}`;\n}\n\n/**\n * Raycast threshold for picking lines/points, as a fixed fraction of view size so the grab band\n * stays constant on screen while zooming. Perspective uses camera→target distance (see\n * `MeasureDeps.getViewTarget`); orthographic uses frustum height `(top − bottom) / zoom`, since\n * ortho zoom changes `camera.zoom` rather than position.\n *\n * Shared with any tool doing its own picking, so grab bands stay consistent across tools.\n */\nexport function pickThreshold(camera: THREE.Camera, viewTarget?: THREE.Vector3): number {\n\tif ((camera as THREE.OrthographicCamera).isOrthographicCamera) {\n\t\tconst ortho = camera as THREE.OrthographicCamera;\n\t\tconst visibleHeight = Math.abs(ortho.top - ortho.bottom) / (ortho.zoom || 1);\n\t\treturn visibleHeight * LINE_PICK_FRACTION;\n\t}\n\tconst viewScale = viewTarget ? camera.position.distanceTo(viewTarget) : camera.position.length();\n\treturn (viewScale || 1) * LINE_PICK_FRACTION;\n}\n\n/**\n * Vertex indices to consider snapping to, by object type: Mesh → struck triangle's 3 vertices;\n * Line/LineSegments → struck segment's 2 endpoints; Points → the struck vertex. Null when the hit\n * carries no usable index (e.g. a fat `Line2`), so the caller keeps the raw hit point.\n */\nfunction snapCandidateIndices(hit: THREE.Intersection): number[] | null {\n\tconst obj = hit.object;\n\tif (obj instanceof THREE.Mesh) {\n\t\treturn hit.face ? [hit.face.a, hit.face.b, hit.face.c] : null;\n\t}\n\tif (obj instanceof THREE.Points) {\n\t\t// Points.raycast resolves indexed geometry itself: `hit.index` is always a position index.\n\t\treturn hit.index != null ? [hit.index] : null;\n\t}\n\t// THREE.Line / LineSegments / LineLoop. For non-indexed geometry `hit.index` is the first\n\t// vertex of the struck segment. For indexed geometry it's a cursor into the index buffer, not\n\t// the resolved vertex (three r184 Line.raycast reports the loop counter) — endpoints must be\n\t// looked up through the index before reading the position attribute.\n\tif (obj instanceof THREE.Line) {\n\t\tif (hit.index == null) return null;\n\t\tconst index = obj.geometry.index;\n\t\tif (index) {\n\t\t\tif (hit.index + 1 >= index.count) return null; // stale/inconsistent hit; keep raw point\n\t\t\treturn [index.getX(hit.index), index.getX(hit.index + 1)];\n\t\t}\n\t\treturn [hit.index, hit.index + 1];\n\t}\n\treturn null;\n}\n\n/** Snap a raycast hit to the nearest geometry vertex within `snapPixels` on screen, else the raw hit point. */\nexport function snapToVertex(\n\thit: THREE.Intersection,\n\tcamera: THREE.Camera,\n\tscreenSize: { width: number; height: number },\n\tsnapPixels: number\n): THREE.Vector3 {\n\tconst raw = hit.point.clone();\n\tconst obj = hit.object as THREE.Object3D & { geometry?: THREE.BufferGeometry };\n\tconst indices = snapCandidateIndices(hit);\n\tif (!indices || !obj.geometry) return raw;\n\n\tconst pos = obj.geometry.attributes.position as THREE.BufferAttribute | undefined;\n\tif (!pos) return raw;\n\n\tconst toScreen = (worldP: THREE.Vector3): THREE.Vector2 => {\n\t\tconst ndc = worldP.clone().project(camera);\n\t\treturn new THREE.Vector2(\n\t\t\t((ndc.x + 1) / 2) * screenSize.width,\n\t\t\t((1 - ndc.y) / 2) * screenSize.height\n\t\t);\n\t};\n\tconst rawScreen = toScreen(raw);\n\n\tlet best = raw;\n\tlet bestPx = snapPixels;\n\tfor (const idx of indices) {\n\t\tif (idx >= pos.count) continue; // guard the line `index + 1` against the geometry end\n\t\tconst local = new THREE.Vector3().fromBufferAttribute(pos, idx);\n\t\tconst world = local.applyMatrix4(obj.matrixWorld);\n\t\tconst px = toScreen(world).distanceTo(rawScreen);\n\t\tif (px < bestPx) {\n\t\t\tbestPx = px;\n\t\t\tbest = world;\n\t\t}\n\t}\n\treturn best;\n}\n\nexport function createMeasureTool(deps: MeasureDeps): MeasureTool {\n\tconst { canvas, scene, getActiveCamera, getViewTarget, labelLayer, options = {} } = deps;\n\tconst snapPixels = options.snapPixels ?? DEFAULT_SNAP_PIXELS;\n\tconst color = new THREE.Color(options.color ?? DEFAULT_COLOR);\n\tconst fmt = makeFormatter(options.displayUnit);\n\tconst defaultFormat = (d: number, delta: THREE.Vector3) =>\n\t\t`${fmt(d)}\\nΔx ${fmt(delta.x)} Δy ${fmt(delta.y)} Δz ${fmt(delta.z)}`;\n\tconst format = options.format ?? defaultFormat;\n\n\tconst raycaster = new THREE.Raycaster();\n\tconst pointer = new THREE.Vector2();\n\n\tlet enabled = false;\n\tconst points: THREE.Vector3[] = [];\n\n\tconst markers: THREE.Points[] = [];\n\tlet line: Line2 | null = null;\n\tlet label: LabelHandle | null = null;\n\n\tconst markerMaterial = new THREE.PointsMaterial({\n\t\tcolor,\n\t\tsize: 8,\n\t\tsizeAttenuation: false,\n\t\tdepthTest: false // markers stay visible through geometry, like CAD snap dots\n\t});\n\n\t// Dimmer + bigger than a committed marker so the next click's snap target is obvious before clicking.\n\tconst hoverMaterial = new THREE.PointsMaterial({\n\t\tcolor,\n\t\tsize: 11,\n\t\tsizeAttenuation: false,\n\t\tdepthTest: false,\n\t\ttransparent: true,\n\t\topacity: 0.5\n\t});\n\tlet hoverMarker: THREE.Points | null = null;\n\n\tconst showHover = (p: THREE.Vector3 | null) => {\n\t\tif (!p) {\n\t\t\tif (hoverMarker) hoverMarker.visible = false;\n\t\t\treturn;\n\t\t}\n\t\tif (!hoverMarker) {\n\t\t\tconst geometry = new THREE.BufferGeometry();\n\t\t\tgeometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));\n\t\t\thoverMarker = new THREE.Points(geometry, hoverMaterial);\n\t\t\thoverMarker.renderOrder = 1000;\n\t\t\thoverMarker.userData.id = 'measure';\n\t\t\thoverMarker.raycast = () => {};\n\t\t\tscene.add(hoverMarker);\n\t\t}\n\t\thoverMarker.position.copy(p);\n\t\thoverMarker.visible = true;\n\t};\n\n\tconst makeMarker = (p: THREE.Vector3): THREE.Points => {\n\t\tconst geometry = new THREE.BufferGeometry();\n\t\tgeometry.setAttribute('position', new THREE.Float32BufferAttribute([p.x, p.y, p.z], 3));\n\t\tconst marker = new THREE.Points(geometry, markerMaterial);\n\t\tmarker.renderOrder = 999;\n\t\tmarker.userData.id = 'measure';\n\t\tmarker.raycast = () => {}; // don't let markers be measure targets themselves\n\t\tscene.add(marker);\n\t\treturn marker;\n\t};\n\n\tconst clear = () => {\n\t\tpoints.length = 0;\n\t\tmarkers.forEach((m) => {\n\t\t\tm.geometry.dispose();\n\t\t\tm.removeFromParent();\n\t\t});\n\t\tmarkers.length = 0;\n\t\tif (line) {\n\t\t\tline.geometry.dispose();\n\t\t\t(line.material as LineMaterial).dispose();\n\t\t\tline.removeFromParent();\n\t\t\tline = null;\n\t\t}\n\t\tlabel?.remove();\n\t\tlabel = null;\n\t};\n\n\tconst drawMeasurement = () => {\n\t\tif (points.length !== 2) return;\n\t\tconst [a, b] = points;\n\n\t\tconst geometry = new LineGeometry();\n\t\tgeometry.setPositions([a.x, a.y, a.z, b.x, b.y, b.z]);\n\t\tconst material = new LineMaterial({ color });\n\t\t(material as LineMaterial & { linewidth: number; depthTest: boolean }).linewidth = 2;\n\t\tmaterial.depthTest = false;\n\n\t\tline = new Line2(geometry, material);\n\t\tline.renderOrder = 998;\n\t\tline.userData.id = 'measure';\n\t\tline.raycast = () => {};\n\t\tscene.add(line);\n\n\t\tconst mid = a.clone().add(b).multiplyScalar(0.5);\n\t\tconst delta = new THREE.Vector3(Math.abs(b.x - a.x), Math.abs(b.y - a.y), Math.abs(b.z - a.z));\n\t\tlabel = labelLayer.addLabel(format(a.distanceTo(b), delta), mid, options.labelClassName);\n\t};\n\n\tconst pickPoint = (event: MouseEvent): THREE.Vector3 | null => {\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tpointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tpointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tconst camera = getActiveCamera();\n\t\traycaster.setFromCamera(pointer, camera);\n\n\t\t// Lines/points have no surface area, so the raycast threshold matters — three's default is\n\t\t// nearly unclickable. pickThreshold scales it with the view so it stays constant on screen.\n\t\tconst threshold = pickThreshold(camera, getViewTarget?.());\n\t\traycaster.params.Line!.threshold = threshold;\n\t\traycaster.params.Points!.threshold = threshold;\n\n\t\tconst hits = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => i.object.userData.id !== 'measure' && i.object.userData.id !== 'grid');\n\n\t\tif (hits.length === 0) return null;\n\t\treturn snapToVertex(hits[0], camera, { width: rect.width, height: rect.height }, snapPixels);\n\t};\n\n\t// One raycast per animation frame, not per mousemove: a full-scene recursive raycast per event\n\t// hitches on large models, and only the latest event matters for the preview.\n\tlet pendingMove: MouseEvent | null = null;\n\tlet moveRaf = 0;\n\n\tconst cancelPendingMove = () => {\n\t\tif (moveRaf) {\n\t\t\tcancelAnimationFrame(moveRaf);\n\t\t\tmoveRaf = 0;\n\t\t}\n\t\tpendingMove = null;\n\t};\n\n\tconst handleMove = (event: MouseEvent): void => {\n\t\tif (!enabled) return;\n\t\tpendingMove = event;\n\t\tif (moveRaf) return;\n\t\tmoveRaf = requestAnimationFrame(() => {\n\t\t\tmoveRaf = 0;\n\t\t\tconst latest = pendingMove;\n\t\t\tpendingMove = null;\n\t\t\tif (!enabled || !latest) return;\n\t\t\tshowHover(pickPoint(latest));\n\t\t});\n\t};\n\n\tconst handleClick = (event: MouseEvent): boolean => {\n\t\tif (!enabled) return false;\n\n\t\t// A third click after a completed measurement starts fresh.\n\t\tif (points.length === 2) clear();\n\n\t\tconst point = pickPoint(event);\n\t\tif (point === null) return true; // consumed: a measuring click that missed still isn't a select\n\n\t\tpoints.push(point);\n\t\tmarkers.push(makeMarker(point));\n\n\t\tif (points.length === 2) drawMeasurement();\n\t\treturn true;\n\t};\n\n\treturn {\n\t\tsetEnabled: (value) => {\n\t\t\tenabled = value;\n\t\t\tif (!value) {\n\t\t\t\tcancelPendingMove();\n\t\t\t\tclear();\n\t\t\t\tshowHover(null);\n\t\t\t}\n\t\t},\n\t\tisEnabled: () => enabled,\n\t\thandleClick,\n\t\thandleMove,\n\t\tclear,\n\t\tdispose: () => {\n\t\t\tcancelPendingMove();\n\t\t\tclear();\n\t\t\tif (hoverMarker) {\n\t\t\t\thoverMarker.geometry.dispose();\n\t\t\t\thoverMarker.removeFromParent();\n\t\t\t\thoverMarker = null;\n\t\t\t}\n\t\t\tmarkerMaterial.dispose();\n\t\t\thoverMaterial.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\n\nimport { computeContentBounds } from './three-helpers';\n\n/**\n * Depth-buffer precision is ∝ near/z²: a fixed tiny near (0.01 m) grows to a ~0.25 m depth ULP at\n * 200 m, causing distant coplanar surfaces to z-fight. Pushing `near` up to a fraction of the\n * camera's gap to the nearest visible content recovers 10–100× precision when zoomed out, without\n * clipping anything.\n *\n * `far` stays owned by config/`updateScene` (must keep covering grid fade, floor). External writes\n * to `camera.near` are adopted as the new lower bound rather than fought — the fitter only ever\n * *raises* near above that floor.\n *\n * Ortho camera needs no fitting (linear depth) and is left untouched.\n */\n\n/** Headroom for the frame's camera motion and bounds staleness. */\nconst NEAR_GAP_FRACTION = 0.5;\n/** Caps near so the frustum stays sane if the camera flies out. */\nconst MAX_NEAR_TO_FAR = 0.01;\n/** Skip sub-5% changes so the projection matrix isn't rebuilt every frame while orbiting. */\nconst APPLY_THRESHOLD = 0.05;\n\nexport interface NearPlaneFitterOptions {\n\tcamera: THREE.PerspectiveCamera;\n\tscene: THREE.Scene;\n\t/**\n\t * Unit normals of ground planes through the origin carrying ground aids (grid, floor) — their\n\t * perpendicular distance to the camera also bounds near, since the aid re-centers under the\n\t * camera and content distance alone would clip it at grazing views.\n\t *\n\t * Pass a callback returning only planes whose aid is visible this frame: an aid that's hidden\n\t * (not merely absent) must not collapse `near` to protect geometry nobody can see.\n\t */\n\tgroundNormals?: () => THREE.Vector3[];\n}\n\nexport interface NearPlaneFitter {\n\tupdate: () => void;\n}\n\nconst NO_GROUND_NORMALS: THREE.Vector3[] = [];\n\nexport function createNearPlaneFitter({\n\tcamera,\n\tscene,\n\tgroundNormals = () => NO_GROUND_NORMALS\n}: NearPlaneFitterOptions): NearPlaneFitter {\n\tlet baseNear = camera.near;\n\tlet appliedNear = camera.near;\n\n\tconst center = new THREE.Vector3();\n\tconst size = new THREE.Vector3();\n\n\tconst update = () => {\n\t\tif (camera.near !== appliedNear) baseNear = camera.near; // external write → new floor\n\n\t\tconst bounds = computeContentBounds(scene);\n\t\tlet near = baseNear;\n\t\tif (!bounds.isEmpty()) {\n\t\t\t// Gap to content's bounding sphere: closest content can be to the camera.\n\t\t\tconst radius = bounds.getSize(size).length() * 0.5;\n\t\t\tlet gap = camera.position.distanceTo(bounds.getCenter(center)) - radius;\n\t\t\tfor (const normal of groundNormals()) {\n\t\t\t\tgap = Math.min(gap, Math.abs(camera.position.dot(normal)));\n\t\t\t}\n\t\t\tnear = THREE.MathUtils.clamp(gap * NEAR_GAP_FRACTION, baseNear, camera.far * MAX_NEAR_TO_FAR);\n\t\t}\n\n\t\tif (Math.abs(near - appliedNear) > appliedNear * APPLY_THRESHOLD) {\n\t\t\tcamera.near = near;\n\t\t\tcamera.updateProjectionMatrix();\n\t\t\tappliedNear = near;\n\t\t}\n\t};\n\n\treturn { update };\n}\n","// ============================================================================\n// Pointer tools: who gets the click first\n// ============================================================================\n//\n// A pointer tool claims canvas input ahead of object selection — measuring a distance or placing\n// a vertex must not also select the mesh under the cursor. The registry owns the ordering and the\n// single-active rule; `initThree` owns the DOM listeners and forwards to it.\n\n/**\n * A tool that can claim canvas pointer input.\n *\n * `handleClick` returning true means the tool consumed the event and the host stops dispatching —\n * no further tool sees it, and object selection doesn't run. `handleMove` never consumes: it runs\n * on every move regardless of which tool is active, so previews can't block orbit or pan.\n */\nexport interface PointerTool {\n\tsetEnabled?(enabled: boolean): void;\n\tisEnabled?(): boolean;\n\t/** Returns true if this tool consumed the click. */\n\thandleClick(event: MouseEvent): boolean;\n\t/** Preview only — must not consume. */\n\thandleMove?(event: MouseEvent): void;\n\tclear?(): void;\n\tdispose?(): void;\n}\n\nexport interface ToolRegistration {\n\t/** Unique within the registry; registering the same id twice replaces the earlier tool. */\n\tid: string;\n\ttool: PointerTool;\n\t/**\n\t * Higher runs first. The built-ins sit at 0 (measure) and -100 (gizmo); register above 0 to\n\t * claim clicks before measuring, below -100 to act only as a fallback.\n\t */\n\tpriority?: number;\n}\n\nexport interface ToolRegistry {\n\t/** Returns an unregister function. Does not dispose the tool — the registrant still owns it. */\n\tregister(registration: ToolRegistration): () => void;\n\tunregister(id: string): void;\n\tget(id: string): PointerTool | null;\n\t/**\n\t * Enables one tool and disables every other registered one. Pass null to disable all.\n\t * Tools without `setEnabled` are always live and unaffected.\n\t */\n\tsetActive(id: string | null): void;\n\t/** The id passed to the last `setActive`, or null. */\n\tgetActive(): string | null;\n\t/** @internal — `initThree` forwards DOM events here. */\n\thandleClick(event: MouseEvent): boolean;\n\t/** @internal */\n\thandleMove(event: MouseEvent): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n\tconst entries: Required<ToolRegistration>[] = [];\n\tlet activeId: string | null = null;\n\n\t// Descending priority, and registration order breaks ties — sort() is stable, so a tool\n\t// registered later never jumps ahead of an equal-priority one already there.\n\tconst sort = () => entries.sort((a, b) => b.priority - a.priority);\n\n\tconst indexOf = (id: string) => entries.findIndex((entry) => entry.id === id);\n\n\tconst unregister = (id: string) => {\n\t\tconst index = indexOf(id);\n\t\tif (index === -1) return;\n\t\tentries.splice(index, 1);\n\t\tif (activeId === id) activeId = null;\n\t};\n\n\tconst register = ({ id, tool, priority = 0 }: ToolRegistration) => {\n\t\tunregister(id);\n\t\tentries.push({ id, tool, priority });\n\t\tsort();\n\t\treturn () => unregister(id);\n\t};\n\n\tconst setActive = (id: string | null) => {\n\t\tactiveId = id;\n\t\tfor (const entry of entries) {\n\t\t\tentry.tool.setEnabled?.(entry.id === id);\n\t\t}\n\t};\n\n\treturn {\n\t\tregister,\n\t\tunregister,\n\t\tget: (id) => entries[indexOf(id)]?.tool ?? null,\n\t\tsetActive,\n\t\tgetActive: () => activeId,\n\t\thandleClick: (event) => {\n\t\t\t// Snapshot: a tool's handler may register or unregister during dispatch.\n\t\t\tfor (const entry of [...entries]) {\n\t\t\t\tif (entry.tool.handleClick(event)) return true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\thandleMove: (event) => {\n\t\t\tfor (const entry of [...entries]) {\n\t\t\t\tentry.tool.handleMove?.(event);\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Screen-space ray from a canvas mouse event, for tools doing their own picking. Handles the\n * canvas's position and size, so it stays correct under CSS scaling and in fullscreen.\n */\nexport function pointerToNdc(\n\tevent: MouseEvent,\n\tcanvas: HTMLCanvasElement\n): { x: number; y: number } {\n\tconst rect = canvas.getBoundingClientRect();\n\treturn {\n\t\tx: ((event.clientX - rect.left) / rect.width) * 2 - 1,\n\t\ty: -((event.clientY - rect.top) / rect.height) * 2 + 1\n\t};\n}\n","import * as THREE from 'three';\nimport { ViewHelper } from 'three/addons/helpers/ViewHelper.js';\n\nimport type { CameraController } from './camera-controller';\n\n/**\n * Corner nav-cube/axis gizmo. Uses three's {@link ViewHelper} only as the rendered widget, not its\n * click→animate behavior: ViewHelper's snap assumes Y-up and animates straight onto the up axis,\n * which rolls the view and jitters the gizmo at the pole in a Z-up scene. Instead this hit-tests\n * the axis sprites directly and drives the viewer's up-aware camera controller, which snaps\n * instantly with a pole nudge so the orbit basis never degenerates.\n *\n * A click frames the current orbit target (not the world origin) and switches back to perspective\n * first if orthographic — the cube is a 3D-orientation tool.\n *\n * Caller contract (mirrors ViewHelper's own): call {@link ViewGizmo.render} after the main scene\n * render each frame, and forward pointer clicks to {@link ViewGizmo.handleClick}.\n */\nexport interface ViewGizmo {\n\trender(renderer: THREE.WebGLRenderer): void;\n\t/** Returns true if it hit the gizmo (and a view change started). */\n\thandleClick(event: MouseEvent): boolean;\n\tsetVisible(visible: boolean): void;\n\tisVisible(): boolean;\n\tdispose(): void;\n}\n\ninterface ViewGizmoDeps {\n\tcamera: THREE.PerspectiveCamera;\n\tdomElement: HTMLElement;\n\tcontroller: CameraController;\n}\n\nexport function createViewGizmo(deps: ViewGizmoDeps): ViewGizmo {\n\tconst { camera, domElement, controller } = deps;\n\n\tconst helper = new ViewHelper(camera, domElement);\n\thelper.setLabels('X', 'Y', 'Z');\n\n\tlet visible = true;\n\n\t// Mirrors ViewHelper's internal `dim`×`dim` corner-viewport math.\n\tconst DIM = 128;\n\tconst raycaster = new THREE.Raycaster();\n\tconst gizmoCamera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 4);\n\tgizmoCamera.position.set(0, 0, 2);\n\t// This camera is never rendered, so nothing else computes its matrixWorld — and\n\t// Raycaster.setFromCamera doesn't either. Without this the ray originates at the identity\n\t// position (z = 0, the cube's mid-plane) and the camera-facing axis sprites sit behind it.\n\tgizmoCamera.updateMatrixWorld();\n\n\t// target → camera, matching CameraController.setViewDirection.\n\tconst AXIS_DIRECTIONS: Record<string, THREE.Vector3> = {\n\t\tposX: new THREE.Vector3(1, 0, 0),\n\t\tnegX: new THREE.Vector3(-1, 0, 0),\n\t\tposY: new THREE.Vector3(0, 1, 0),\n\t\tnegY: new THREE.Vector3(0, -1, 0),\n\t\tposZ: new THREE.Vector3(0, 0, 1),\n\t\tnegZ: new THREE.Vector3(0, 0, -1)\n\t};\n\n\t// Returns the hit sprite's `userData.type`, or null if the click missed the gizmo.\n\tconst pickAxis = (event: MouseEvent): string | null => {\n\t\tconst rect = domElement.getBoundingClientRect();\n\t\t// Gizmo viewport sits in the bottom-right corner (helper.location defaults: right/bottom 0).\n\t\tconst offsetX = rect.left + domElement.offsetWidth - DIM - helper.location.right;\n\t\tconst offsetY = rect.top + domElement.offsetHeight - DIM - helper.location.bottom;\n\n\t\tconst mouse = new THREE.Vector2(\n\t\t\t((event.clientX - offsetX) / DIM) * 2 - 1,\n\t\t\t-((event.clientY - offsetY) / DIM) * 2 + 1\n\t\t);\n\t\tif (Math.abs(mouse.x) > 1 || Math.abs(mouse.y) > 1) return null;\n\n\t\t// Orient the helper as rendered (inverse of the camera) so sprites match what's on screen.\n\t\thelper.quaternion.copy(camera.quaternion).invert();\n\t\thelper.updateMatrixWorld();\n\n\t\traycaster.setFromCamera(mouse, gizmoCamera);\n\t\tconst hits = raycaster.intersectObjects(helper.children, false);\n\t\tfor (const hit of hits) {\n\t\t\tconst type = hit.object.userData?.type;\n\t\t\tif (typeof type === 'string' && type in AXIS_DIRECTIONS) return type;\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst handleClick = (event: MouseEvent): boolean => {\n\t\tif (!visible) return false;\n\n\t\tconst axis = pickAxis(event);\n\t\tif (!axis) return false;\n\n\t\tif (controller.getProjection() === 'orthographic') {\n\t\t\tcontroller.setProjection('perspective');\n\t\t}\n\n\t\tcontroller.setViewDirection(AXIS_DIRECTIONS[axis]!, false);\n\t\treturn true;\n\t};\n\n\treturn {\n\t\trender: (renderer) => {\n\t\t\tif (!visible) return;\n\t\t\t// ViewHelper.render() calls renderer.render() with autoClear=true by default, which wipes\n\t\t\t// the FULL framebuffer before drawing the cube in its corner. Suppress it — ViewHelper\n\t\t\t// does its own depth clear internally.\n\t\t\tconst prevAutoClear = renderer.autoClear;\n\t\t\trenderer.autoClear = false;\n\t\t\thelper.render(renderer);\n\t\t\trenderer.autoClear = prevAutoClear;\n\t\t},\n\t\thandleClick,\n\t\tsetVisible: (value) => {\n\t\t\tvisible = value;\n\t\t},\n\t\tisVisible: () => visible,\n\t\tdispose: () => helper.dispose()\n\t};\n}\n","import * as THREE from 'three';\nimport type { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport type { CameraController } from '../camera-controller.js';\nimport type { Grid } from '../grid.js';\nimport type { LabelLayer } from '../label-layer.js';\nimport type { NearPlaneFitter } from '../near-plane.js';\nimport type { RenderPipeline } from '../render-pipeline.js';\nimport type { ViewGizmo } from '../view-gizmo.js';\n\n// Resize applied before render so buffer clear and draw happen in the same frame — avoids a\n// visible blank frame on resize.\nexport function createAnimationLoop(\n\trenderer: THREE.WebGLRenderer,\n\tscene: THREE.Scene,\n\tcamera: THREE.PerspectiveCamera,\n\tgetActiveCamera: () => THREE.Camera,\n\tcameraController: CameraController,\n\tcontrols: OrbitControls,\n\tgetCanvasSize: () => { width: number; height: number },\n\tpixelRatio: number,\n\tonFrame?: (delta: number) => void,\n\tgrid?: Grid | null,\n\tgizmo?: ViewGizmo | null,\n\tgetRenderPipeline?: () => RenderPipeline | null,\n\tlabelLayer?: LabelLayer | null,\n\tnearFitter?: NearPlaneFitter | null,\n\t// false = render every frame regardless of invalidate()/camera movement.\n\tonDemand: boolean = true\n): { animate: () => void; dispose: () => void; invalidate: () => void } {\n\tlet animationId: number | null = null;\n\tlet lastTime = performance.now();\n\n\t// The loop always *ticks* (cheap); it only *renders* when invalidate() was called, the active\n\t// camera moved (matrix compare catches damping/presets/gizmo/near-plane), or the idle-repaint\n\t// interval elapsed as a safety net for any mutation that forgot to invalidate.\n\tlet renderRequested = true; // first frame always renders\n\tlet lastRenderTime = 0;\n\tconst IDLE_REPAINT_INTERVAL_MS = 500;\n\tconst lastWorldMatrix = new THREE.Matrix4();\n\tconst lastProjectionMatrix = new THREE.Matrix4();\n\tlet lastCamera: THREE.Camera | null = null;\n\tconst invalidate = () => {\n\t\trenderRequested = true;\n\t};\n\n\tconst cameraMoved = (activeCamera: THREE.Camera): boolean => {\n\t\t// renderer.render normally refreshes matrixWorld, but we're deciding whether to call it — so\n\t\t// refresh here first (cheap: a camera has no deep subtree).\n\t\tactiveCamera.updateMatrixWorld();\n\t\tconst moved =\n\t\t\tlastCamera !== activeCamera ||\n\t\t\t!lastWorldMatrix.equals(activeCamera.matrixWorld) ||\n\t\t\t!lastProjectionMatrix.equals(activeCamera.projectionMatrix);\n\t\tif (moved) {\n\t\t\tlastCamera = activeCamera;\n\t\t\tlastWorldMatrix.copy(activeCamera.matrixWorld);\n\t\t\tlastProjectionMatrix.copy(activeCamera.projectionMatrix);\n\t\t}\n\t\treturn moved;\n\t};\n\n\t// Click-driven mutations (measure points, selection highlights) don't call invalidate() themselves.\n\tconst canvas = renderer.domElement;\n\tconst pointerEvents = ['pointerdown', 'pointerup', 'wheel'] as const;\n\tif (onDemand) {\n\t\tfor (const type of pointerEvents) {\n\t\t\tcanvas.addEventListener(type, invalidate, { passive: true });\n\t\t}\n\t}\n\n\tconst checkResize = () => {\n\t\tconst { width, height } = getCanvasSize();\n\t\tif (width === 0 || height === 0) return;\n\n\t\t// Must floor (not round) to match renderer.setSize's own flooring — otherwise the size\n\t\t// comparison below never settles and the resize branch runs every frame.\n\t\tconst newW = Math.floor(width * pixelRatio);\n\t\tconst newH = Math.floor(height * pixelRatio);\n\n\t\tif (renderer.domElement.width !== newW || renderer.domElement.height !== newH) {\n\t\t\trenderer.setPixelRatio(pixelRatio);\n\t\t\trenderer.setSize(width, height, false);\n\t\t\tcamera.aspect = width / height;\n\t\t\tcamera.updateProjectionMatrix();\n\t\t\tcameraController.updateAspect(width, height);\n\t\t\tgetRenderPipeline?.()?.setSize(width, height, pixelRatio);\n\t\t\tlabelLayer?.setSize(width, height); // CSS2D overlay uses CSS size, not the pixel-ratio buffer\n\t\t\tinvalidate();\n\t\t}\n\t};\n\n\tconst animate = function () {\n\t\tanimationId = requestAnimationFrame(animate);\n\n\t\tconst now = performance.now();\n\t\tconst delta = (now - lastTime) / 1000;\n\t\tlastTime = now;\n\n\t\tcheckResize();\n\n\t\tif (controls.enableDamping || controls.autoRotate) {\n\t\t\tcontrols.update();\n\t\t}\n\n\t\tif (grid) grid.update(getActiveCamera().position); // recenter on camera so it reads as infinite\n\n\t\t// Before render, so depth precision tracks the camera's current distance from content.\n\t\tif (nearFitter) nearFitter.update();\n\n\t\tonFrame?.(delta);\n\n\t\tconst activeCamera = getActiveCamera();\n\n\t\tif (onDemand) {\n\t\t\tconst shouldRender =\n\t\t\t\trenderRequested ||\n\t\t\t\tcameraMoved(activeCamera) ||\n\t\t\t\tnow - lastRenderTime >= IDLE_REPAINT_INTERVAL_MS;\n\t\t\tif (!shouldRender) return;\n\t\t\trenderRequested = false;\n\t\t\tlastRenderTime = now;\n\t\t}\n\n\t\tconst renderPipeline = getRenderPipeline?.();\n\t\tif (renderPipeline) {\n\t\t\trenderPipeline.setCamera(activeCamera); // retarget in case 2D/3D swapped\n\t\t\trenderPipeline.render(delta);\n\t\t} else {\n\t\t\trenderer.render(scene, activeCamera);\n\t\t}\n\n\t\tif (labelLayer) labelLayer.render(scene, activeCamera);\n\n\t\t// Corner-viewport overlay with its own clear; must render last to sit on top.\n\t\tif (gizmo) gizmo.render(renderer);\n\t};\n\n\tconst dispose = () => {\n\t\tif (animationId !== null) {\n\t\t\tcancelAnimationFrame(animationId);\n\t\t\tanimationId = null;\n\t\t}\n\t\tif (onDemand) {\n\t\t\tfor (const type of pointerEvents) {\n\t\t\t\tcanvas.removeEventListener(type, invalidate);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn { animate, dispose, invalidate };\n}\n","import * as THREE from 'three';\n\nimport { DEFAULT_LOOK, LOOK_PRESETS } from '../../shared/index.js';\nimport type { ThreeInitializerOptions } from '../types.js';\nimport { isoOffset, sunOffset, upToAxis } from '../up-axis.js';\n\n/** Rhino's convention, and the frame all geometry arrives in — Selva is Z-up end to end. */\nexport const defaultUp = new THREE.Vector3(0, 0, 1);\n\n// onMaxAnisotropy stays optional — a caller-supplied hook, not a config value with a default.\nexport type ResolvedOptions = Required<Omit<ThreeInitializerOptions, 'onMaxAnisotropy'>> &\n\tPick<ThreeInitializerOptions, 'onMaxAnisotropy'>;\n\nexport function applyDefaults(options: ThreeInitializerOptions): ResolvedOptions {\n\tconst scale = options.sceneScale || 'm';\n\n\t// Geometry is always in meters; sceneScale only changes camera/light/grid magnitudes.\n\tconst scaleDefaults = {\n\t\tmm: {\n\t\t\tcameraDistance: 20,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 100,\n\t\t\tlightDistance: 10,\n\t\t\tlightHeight: 20,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 1000\n\t\t},\n\t\tcm: {\n\t\t\tcameraDistance: 20,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 100,\n\t\t\tlightDistance: 25,\n\t\t\tlightHeight: 50,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 100\n\t\t},\n\t\tm: {\n\t\t\tcameraDistance: 10,\n\t\t\tnear: 0.01,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 50,\n\t\t\tlightDistance: 25,\n\t\t\tlightHeight: 50,\n\t\t\tminDistance: 0.001,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 1\n\t\t},\n\t\tinches: {\n\t\t\tcameraDistance: 15,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 80,\n\t\t\tlightDistance: 20,\n\t\t\tlightHeight: 40,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 80,\n\t\t\tscaleFactor: 39.37\n\t\t},\n\t\tfeet: {\n\t\t\tcameraDistance: 8,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 40,\n\t\t\tlightDistance: 15,\n\t\t\tlightHeight: 30,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 60,\n\t\t\tscaleFactor: 3.28084\n\t\t}\n\t};\n\n\tconst defaults = scaleDefaults[scale];\n\n\t// The look seeds lighting/material defaults (tone mapping, AO, IBL, fill), ranked below explicit\n\t// per-field options but above the plain defaults; it never touches edges/grid.\n\tconst look = options.look ?? DEFAULT_LOOK;\n\tconst preset = LOOK_PRESETS[look];\n\n\treturn {\n\t\tsceneScale: scale,\n\t\tlook,\n\t\tcamera: {\n\t\t\t// Default 3/4 iso (behind-left, above), derived from the scene up axis so a Y-up scene still\n\t\t\t// gets an overhead iso rather than a below-horizon view. cameraDistance*sqrt(3) preserves the\n\t\t\t// orbit radius of the old per-component (-d,-d,d) vector so this doesn't rezoom every scene.\n\t\t\tposition:\n\t\t\t\toptions.camera?.position ||\n\t\t\t\tisoOffset(\n\t\t\t\t\toptions.environment?.sceneUp ?? defaultUp,\n\t\t\t\t\tdefaults.cameraDistance * Math.sqrt(3)\n\t\t\t\t),\n\t\t\tfov: options.camera?.fov || 20,\n\t\t\tnear: options.camera?.near || defaults.near,\n\t\t\tfar: options.camera?.far || defaults.far,\n\t\t\ttarget: options.camera?.target || new THREE.Vector3(0, 0, 0),\n\t\t\tdynamicNear: options.camera?.dynamicNear ?? true\n\t\t},\n\t\tlighting: {\n\t\t\tenableSunlight: options.lighting?.enableSunlight ?? true,\n\t\t\tsunlightIntensity: options.lighting?.sunlightIntensity ?? 1,\n\t\t\t// Expressed in the scene basis so the sun stays overhead in any up convention.\n\t\t\tsunlightPosition:\n\t\t\t\toptions.lighting?.sunlightPosition ||\n\t\t\t\tsunOffset(\n\t\t\t\t\toptions.environment?.sceneUp ?? defaultUp,\n\t\t\t\t\tdefaults.lightDistance,\n\t\t\t\t\tdefaults.lightHeight\n\t\t\t\t),\n\t\t\tambientLightColor: options.lighting?.ambientLightColor || new THREE.Color(0x404040),\n\t\t\tambientLightIntensity: options.lighting?.ambientLightIntensity ?? preset.ambientIntensity,\n\t\t\tsunlightColor: options.lighting?.sunlightColor || 0xffffff,\n\t\t\t// A positive hemisphereIntensity is what actually creates the light in setupLighting.\n\t\t\tenableHemisphereLight:\n\t\t\t\toptions.lighting?.enableHemisphereLight ?? preset.hemisphereIntensity > 0,\n\t\t\themisphereSkyColor: options.lighting?.hemisphereSkyColor ?? 0xdfe6ff,\n\t\t\themisphereGroundColor: options.lighting?.hemisphereGroundColor ?? 0x6b5f52,\n\t\t\themisphereIntensity: options.lighting?.hemisphereIntensity ?? preset.hemisphereIntensity\n\t\t},\n\t\tenvironment: {\n\t\t\thdrPath: options.environment?.hdrPath || '/baseHDR.hdr',\n\t\t\tbackgroundColor: options.environment?.backgroundColor || new THREE.Color(0xf0f0f0),\n\t\t\tenableEnvironmentLighting: options.environment?.enableEnvironmentLighting ?? true,\n\t\t\tsceneUp: options.environment?.sceneUp || defaultUp,\n\t\t\tshowEnvironment: options.environment?.showEnvironment ?? false,\n\t\t\tenvironmentIntensity: options.environment?.environmentIntensity ?? preset.environmentIntensity\n\t\t},\n\t\tfloor: {\n\t\t\tenabled: options.floor?.enabled ?? false,\n\t\t\tsize: options.floor?.size || defaults.floorSize,\n\t\t\tcolor: options.floor?.color || new THREE.Color(0x808080),\n\t\t\troughness: options.floor?.roughness ?? 0.7,\n\t\t\tmetalness: options.floor?.metalness ?? 0.0,\n\t\t\treceiveShadow: options.floor?.receiveShadow ?? true\n\t\t},\n\t\trender: {\n\t\t\tenableShadows: options.render?.enableShadows ?? true,\n\t\t\tshadowMapSize: options.render?.shadowMapSize || 2048,\n\t\t\tantialias: options.render?.antialias ?? true,\n\t\t\tpixelRatio: options.render?.pixelRatio || Math.min(window.devicePixelRatio, 2),\n\t\t\t// ?? not ||: an explicit NoToneMapping (0) must be honoured, not fall through as falsy.\n\t\t\ttoneMapping: options.render?.toneMapping ?? preset.toneMapping,\n\t\t\ttoneMappingExposure: options.render?.toneMappingExposure ?? preset.toneMappingExposure,\n\t\t\tpreserveDrawingBuffer: options.render?.preserveDrawingBuffer ?? false,\n\t\t\tambientOcclusion: options.render?.ambientOcclusion ?? preset.ambientOcclusion,\n\t\t\taoIntensity: options.render?.aoIntensity ?? 1,\n\t\t\t// Default caps AO buffers at 1x — biggest lever on GTAO cost at high DPI.\n\t\t\taoPixelRatio: options.render?.aoPixelRatio ?? 1,\n\t\t\tonDemand: options.render?.onDemand ?? true\n\t\t},\n\t\tcontrols: {\n\t\t\tenableDamping: options.controls?.enableDamping ?? false,\n\t\t\tdampingFactor: options.controls?.dampingFactor || 0.05,\n\t\t\tautoRotate: options.controls?.autoRotate ?? false,\n\t\t\tautoRotateSpeed: options.controls?.autoRotateSpeed || 0.5,\n\t\t\tenableZoom: options.controls?.enableZoom ?? true,\n\t\t\tenablePan: options.controls?.enablePan ?? true,\n\t\t\tminDistance: options.controls?.minDistance || defaults.minDistance,\n\t\t\tmaxDistance: options.controls?.maxDistance || Infinity\n\t\t},\n\t\tgrid: {\n\t\t\t// Mirrors createGrid's own defaults so the two never drift.\n\t\t\tenabled: options.grid?.enabled ?? false,\n\t\t\tcellSize: options.grid?.cellSize ?? 1,\n\t\t\tmajorEvery: options.grid?.majorEvery ?? 10,\n\t\t\tcellColor: options.grid?.cellColor ?? 0x888888,\n\t\t\tmajorColor: options.grid?.majorColor ?? 0x444444,\n\t\t\tfadeDistance: options.grid?.fadeDistance ?? 100,\n\t\t\t// Orthogonal to the scene up axis: Z-up Rhino -> 'z', Y-up -> 'y'.\n\t\t\tplane: options.grid?.plane ?? upToAxis(options.environment?.sceneUp ?? defaultUp)\n\t\t},\n\t\tgizmo: {\n\t\t\tenabled: options.gizmo?.enabled ?? false\n\t\t},\n\t\tedges: {\n\t\t\t// Mirrors addEdges' own defaults so the two never drift.\n\t\t\tenabled: options.edges?.enabled ?? false,\n\t\t\t// Undefined lets addEdges derive each mesh's edge color from its own surface material.\n\t\t\tcolor: options.edges?.color,\n\t\t\tdarken: options.edges?.darken,\n\t\t\twidth: options.edges?.width ?? 1.5,\n\t\t\tthresholdAngle: options.edges?.thresholdAngle ?? 44,\n\t\t\tdistanceFade: options.edges?.distanceFade ?? true,\n\t\t\t// Passed through undefined on purpose: the caps' canonical defaults live in\n\t\t\t// `edges/options.ts` (resolveOptions), and applyEdges forwards these straight to it.\n\t\t\t// Restating 4M/2M here would be a second copy free to drift from the real one.\n\t\t\tmaxTriangles: options.edges?.maxTriangles,\n\t\t\tmaxSegments: options.edges?.maxSegments,\n\t\t\t// Read by init-three's updateEdgeFallback, which only checks for an explicit `false`.\n\t\t\tscreenSpaceFallback: options.edges?.screenSpaceFallback\n\t\t},\n\t\tmeasure: {\n\t\t\t// Visual defaults live in createMeasureTool; these pass through undefined to it.\n\t\t\tenabled: options.measure?.enabled ?? false,\n\t\t\tsnapPixels: options.measure?.snapPixels,\n\t\t\tcolor: options.measure?.color,\n\t\t\tlabelClassName: options.measure?.labelClassName,\n\t\t\tdisplayUnit: options.measure?.displayUnit,\n\t\t\tformat: options.measure?.format\n\t\t},\n\t\tevents: {\n\t\t\tonBackgroundClicked: options.events?.onBackgroundClicked,\n\t\t\tonObjectSelected: options.events?.onObjectSelected,\n\t\t\tonMeshMetadataClicked: options.events?.onMeshMetadataClicked,\n\t\t\tonMeshDoubleClicked: options.events?.onMeshDoubleClicked,\n\t\t\tselectionColor: options.events?.selectionColor || '#ff0000',\n\t\t\tenableEventHandlers: options.events?.enableEventHandlers ?? true,\n\t\t\tenableKeyboardControls: options.events?.enableKeyboardControls ?? true,\n\t\t\tenableClickToFocus: options.events?.enableClickToFocus ?? true,\n\t\t\tenableDoubleClickZoom: options.events?.enableDoubleClickZoom ?? true,\n\t\t\tonReady: options.events?.onReady,\n\t\t\tonFrame: options.events?.onFrame\n\t\t},\n\t\tonMaxAnisotropy: options.onMaxAnisotropy\n\t};\n}\n","import * as THREE from 'three';\n\nimport { LOOK_PRESETS, materialAppearanceForLook } from '../../shared/index.js';\nimport { SOURCE_COMPUTE } from '../scene-ownership.js';\nimport type { Look, MaterialAppearanceOptions } from '../types.js';\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\nimport type { PipelineController } from './pipeline-controller.js';\nimport type { SceneLights } from './setup-lighting.js';\n\n/** The runtime lighting/material dials — everything a host can retune without rebuilding the scene. */\nexport interface AppearanceController {\n\tsetFillLights(opts: {\n\t\themisphereIntensity?: number;\n\t\themisphereSkyColor?: THREE.Color | number;\n\t\themisphereGroundColor?: THREE.Color | number;\n\t\tambientIntensity?: number;\n\t}): void;\n\tsetEnvironmentIntensity(intensity: number): void;\n\tsetToneMappingExposure(exposure: number): void;\n\tsetAoIntensity(intensity: number): void;\n\tsetLook(look: Look): void;\n\tgetMaterialAppearance(): MaterialAppearanceOptions;\n}\n\n// setLook is built from the same setters a host would call directly, so construction-time defaults\n// (applyDefaults seeding from LOOK_PRESETS) can't drift from the runtime path.\nexport function createAppearanceController(params: {\n\tscene: THREE.Scene;\n\trenderer: THREE.WebGLRenderer;\n\tlights: SceneLights;\n\tconfig: ResolvedOptions;\n\tpipeline: PipelineController;\n\trequestRender: () => void;\n}): AppearanceController {\n\tconst { scene, renderer, lights, config, pipeline, requestRender } = params;\n\n\tlet activeLook: Look = config.look;\n\n\tconst setFillLights: AppearanceController['setFillLights'] = (opts) => {\n\t\tif (opts.ambientIntensity !== undefined) {\n\t\t\tlights.ambient.intensity = opts.ambientIntensity;\n\t\t}\n\t\tif (\n\t\t\topts.hemisphereIntensity !== undefined &&\n\t\t\t!lights.hemisphere &&\n\t\t\topts.hemisphereIntensity > 0\n\t\t) {\n\t\t\t// Lazily created so hosts can enable fill at runtime even if the scene was built without one.\n\t\t\tlights.hemisphere = new THREE.HemisphereLight(\n\t\t\t\topts.hemisphereSkyColor ?? config.lighting.hemisphereSkyColor,\n\t\t\t\topts.hemisphereGroundColor ?? config.lighting.hemisphereGroundColor,\n\t\t\t\topts.hemisphereIntensity\n\t\t\t);\n\t\t\tlights.hemisphere.position.copy(config.environment.sceneUp ?? defaultUp);\n\t\t\tscene.add(lights.hemisphere);\n\t\t}\n\t\tif (lights.hemisphere) {\n\t\t\tif (opts.hemisphereIntensity !== undefined)\n\t\t\t\tlights.hemisphere.intensity = opts.hemisphereIntensity;\n\t\t\tif (opts.hemisphereSkyColor !== undefined)\n\t\t\t\tlights.hemisphere.color.set(opts.hemisphereSkyColor);\n\t\t\tif (opts.hemisphereGroundColor !== undefined)\n\t\t\t\tlights.hemisphere.groundColor.set(opts.hemisphereGroundColor);\n\t\t}\n\t\trequestRender();\n\t};\n\n\tconst setEnvironmentIntensity = (intensity: number) => {\n\t\tconfig.environment.environmentIntensity = intensity;\n\t\tscene.environmentIntensity = intensity;\n\t\trequestRender();\n\t};\n\n\tconst setToneMappingExposure = (exposure: number) => {\n\t\tconfig.render.toneMappingExposure = exposure;\n\t\trenderer.toneMappingExposure = exposure;\n\t\t// Composer applies tone mapping via its own OutputPass, so it must rebuild to pick this up.\n\t\tif (pipeline.get()) pipeline.rebuild();\n\t};\n\n\tconst setAoIntensity = (intensity: number) => {\n\t\tconfig.render.aoIntensity = intensity;\n\t\tif (pipeline.get()) pipeline.rebuild();\n\t};\n\n\tconst setLook = (look: Look) => {\n\t\tconst preset = LOOK_PRESETS[look];\n\t\tactiveLook = look;\n\n\t\trenderer.toneMapping = preset.toneMapping;\n\t\trenderer.toneMappingExposure = preset.toneMappingExposure;\n\t\tconfig.render.toneMapping = preset.toneMapping;\n\t\tconfig.render.toneMappingExposure = preset.toneMappingExposure;\n\n\t\tsetFillLights({\n\t\t\themisphereIntensity: preset.hemisphereIntensity,\n\t\t\tambientIntensity: preset.ambientIntensity\n\t\t});\n\t\tsetEnvironmentIntensity(preset.environmentIntensity);\n\n\t\t// Rebuild on top of setAmbientOcclusion so an already-live composer's OutputPass adopts the\n\t\t// new tone mapping too.\n\t\tconst hadPipeline = pipeline.get() !== null;\n\t\tpipeline.setAmbientOcclusion(preset.ambientOcclusion);\n\t\tif (hadPipeline) pipeline.rebuild();\n\n\t\t// Solve output only. Host-added geometry (`user`/`app:` scopes) owns its own materials —\n\t\t// a point cloud or draft line has a deliberate look that a render-style switch must not\n\t\t// overwrite. Hosts that do want to follow the look read `getMaterialAppearance()`.\n\t\tscene.traverse((object) => {\n\t\t\tif (object.userData.source !== SOURCE_COMPUTE) return;\n\t\t\tconst mesh = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\t\tconst materials = Array.isArray(mesh.material)\n\t\t\t\t? mesh.material\n\t\t\t\t: mesh.material\n\t\t\t\t\t? [mesh.material]\n\t\t\t\t\t: [];\n\t\t\tfor (const material of materials) {\n\t\t\t\tif ('envMapIntensity' in material) {\n\t\t\t\t\t(material as THREE.MeshStandardMaterial).envMapIntensity = preset.envMapIntensity;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\trequestRender();\n\t};\n\n\treturn {\n\t\tsetFillLights,\n\t\tsetEnvironmentIntensity,\n\t\tsetToneMappingExposure,\n\t\tsetAoIntensity,\n\t\tsetLook,\n\t\tgetMaterialAppearance: () => materialAppearanceForLook(activeLook)\n\t};\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function createCamera(\n\tconfig: ResolvedOptions,\n\tcanvas: HTMLCanvasElement\n): THREE.PerspectiveCamera {\n\tconst parent = canvas.parentElement;\n\tconst width = parent ? parent.clientWidth : window.innerWidth;\n\tconst height = parent ? parent.clientHeight : window.innerHeight;\n\n\tconst camera = new THREE.PerspectiveCamera(\n\t\tconfig.camera.fov,\n\t\twidth / height,\n\t\tconfig.camera.near,\n\t\tconfig.camera.far\n\t);\n\n\tconst pos = config.camera.position;\n\tif (pos) {\n\t\tcamera.position.set(pos.x, pos.y, pos.z);\n\t}\n\n\treturn camera;\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function createScene(config: ResolvedOptions): THREE.Scene {\n\tconst scene = new THREE.Scene();\n\n\tconst bgColor =\n\t\ttypeof config.environment.backgroundColor === 'string'\n\t\t\t? new THREE.Color(config.environment.backgroundColor)\n\t\t\t: config.environment.backgroundColor;\n\tscene.background = bgColor || null;\n\n\treturn scene;\n}\n","import * as THREE from 'three';\n\nimport { disposeObjectTree, type DisposeOptions } from '../../shared/index.js';\n\nexport { disposeObjectTree };\nexport type { DisposeOptions };\n\n/** Sweeps every renderable plus the scene-level textures the object traversal can't reach. */\nexport function disposeSceneResources(scene: THREE.Scene, options?: DisposeOptions): void {\n\tdisposeObjectTree(scene, options);\n\n\tscene.environment?.dispose();\n\tif (scene.background instanceof THREE.Texture) {\n\t\tscene.background.dispose();\n\t}\n}\n","import * as THREE from 'three';\nimport { Pass, FullScreenQuad } from 'three/addons/postprocessing/Pass.js';\n\n/**\n * Screen-space edge detection (Roberts cross on depth + normal discontinuities), O(pixels)\n * regardless of triangle count. Fallback for meshes too heavy for geometry edge overlays\n * (over `EdgeOptions.maxTriangles`): uniform pixel width, one global color, view-dependent,\n * gentle creases below the normal threshold don't register.\n */\nexport interface EdgeDetectionOptions {\n\tcolor?: THREE.ColorRepresentation;\n\topacity?: number;\n\t/** Summed `1 - dot(n₁, n₂)` across the two diagonal pairs. Lower catches gentler creases. */\n\tnormalThreshold?: number;\n\t/** Relative view-depth discontinuity, fraction of center depth. */\n\tdepthThreshold?: number;\n\t/** Sample offset in device px — line thickness. */\n\tthickness?: number;\n}\n\nconst EDGE_SHADER = {\n\tuniforms: {\n\t\ttDiffuse: { value: null as THREE.Texture | null },\n\t\ttNormal: { value: null as THREE.Texture | null },\n\t\ttDepth: { value: null as THREE.Texture | null },\n\t\tuResolution: { value: new THREE.Vector2(1, 1) },\n\t\tuColor: { value: new THREE.Color(0x222222) },\n\t\tuOpacity: { value: 1 },\n\t\tuNormalThreshold: { value: 0.4 },\n\t\tuDepthThreshold: { value: 0.02 },\n\t\tuThickness: { value: 1 },\n\t\tuNear: { value: 0.1 },\n\t\tuFar: { value: 1000 },\n\t\tuPerspective: { value: 1 }\n\t},\n\tvertexShader: /* glsl */ `\n\t\tvarying vec2 vUv;\n\t\tvoid main() {\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n\t\t}\n\t`,\n\tfragmentShader: /* glsl */ `\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform sampler2D tNormal;\n\t\tuniform sampler2D tDepth;\n\t\tuniform vec2 uResolution;\n\t\tuniform vec3 uColor;\n\t\tuniform float uOpacity;\n\t\tuniform float uNormalThreshold;\n\t\tuniform float uDepthThreshold;\n\t\tuniform float uThickness;\n\t\tuniform float uNear;\n\t\tuniform float uFar;\n\t\tuniform float uPerspective;\n\t\tvarying vec2 vUv;\n\n\t\tfloat viewZOf(const in float depth) {\n\t\t\tfloat perspective = (uNear * uFar) / ((uFar - uNear) * depth - uFar);\n\t\t\tfloat orthographic = -(depth * (uFar - uNear) + uNear);\n\t\t\treturn mix(orthographic, perspective, uPerspective);\n\t\t}\n\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D(tDiffuse, vUv);\n\t\t\tvec2 texel = uThickness / uResolution;\n\n\t\t\tvec2 offsetA = vec2(texel.x, texel.y);\n\t\t\tvec2 offsetB = vec2(texel.x, -texel.y);\n\n\t\t\tfloat z0 = viewZOf(texture2D(tDepth, vUv + offsetA).x);\n\t\t\tfloat z1 = viewZOf(texture2D(tDepth, vUv - offsetA).x);\n\t\t\tfloat z2 = viewZOf(texture2D(tDepth, vUv + offsetB).x);\n\t\t\tfloat z3 = viewZOf(texture2D(tDepth, vUv - offsetB).x);\n\t\t\tfloat zCenter = viewZOf(texture2D(tDepth, vUv).x);\n\t\t\t// Normalized by center depth, not absolute Z: keeps the response scale-invariant (an\n\t\t\t// absolute threshold would be noise far away, blind up close).\n\t\t\tfloat depthDelta = (abs(z0 - z1) + abs(z2 - z3)) / max(abs(zCenter), 1e-6);\n\t\t\tfloat depthEdge = step(uDepthThreshold, depthDelta);\n\n\t\t\tvec3 n0 = texture2D(tNormal, vUv + offsetA).rgb * 2.0 - 1.0;\n\t\t\tvec3 n1 = texture2D(tNormal, vUv - offsetA).rgb * 2.0 - 1.0;\n\t\t\tvec3 n2 = texture2D(tNormal, vUv + offsetB).rgb * 2.0 - 1.0;\n\t\t\tvec3 n3 = texture2D(tNormal, vUv - offsetB).rgb * 2.0 - 1.0;\n\t\t\tfloat normalDelta = (1.0 - dot(n0, n1)) + (1.0 - dot(n2, n3));\n\t\t\tfloat normalEdge = step(uNormalThreshold, normalDelta);\n\n\t\t\tfloat edge = max(depthEdge, normalEdge) * uOpacity;\n\t\t\tgl_FragColor = vec4(mix(color.rgb, uColor, edge), color.a);\n\t\t}\n\t`\n};\n\nexport class EdgeDetectionPass extends Pass {\n\tcamera: THREE.Camera;\n\n\tprivate readonly scene: THREE.Scene;\n\tprivate readonly normalMaterial: THREE.MeshNormalMaterial;\n\tprivate readonly edgeMaterial: THREE.ShaderMaterial;\n\tprivate readonly fsQuad: FullScreenQuad;\n\tprivate normalTarget: THREE.WebGLRenderTarget | null = null;\n\tprivate width: number;\n\tprivate height: number;\n\n\tconstructor(\n\t\tscene: THREE.Scene,\n\t\tcamera: THREE.Camera,\n\t\twidth: number,\n\t\theight: number,\n\t\toptions: EdgeDetectionOptions = {}\n\t) {\n\t\tsuper();\n\t\tthis.scene = scene;\n\t\tthis.camera = camera;\n\t\tthis.width = Math.max(1, width);\n\t\tthis.height = Math.max(1, height);\n\n\t\tthis.normalMaterial = new THREE.MeshNormalMaterial();\n\t\tthis.normalMaterial.blending = THREE.NoBlending;\n\n\t\tthis.edgeMaterial = new THREE.ShaderMaterial({\n\t\t\tuniforms: THREE.UniformsUtils.clone(EDGE_SHADER.uniforms),\n\t\t\tvertexShader: EDGE_SHADER.vertexShader,\n\t\t\tfragmentShader: EDGE_SHADER.fragmentShader\n\t\t});\n\t\tconst uniforms = this.edgeMaterial.uniforms;\n\t\tuniforms.uColor.value = new THREE.Color(options.color ?? 0x222222);\n\t\tuniforms.uOpacity.value = options.opacity ?? 1;\n\t\tuniforms.uNormalThreshold.value = options.normalThreshold ?? 0.4;\n\t\tuniforms.uDepthThreshold.value = options.depthThreshold ?? 0.02;\n\t\tuniforms.uThickness.value = options.thickness ?? 1;\n\n\t\tthis.fsQuad = new FullScreenQuad(this.edgeMaterial);\n\t\tthis.needsSwap = true;\n\t}\n\n\tprivate acquireNormalTarget(): THREE.WebGLRenderTarget {\n\t\tif (!this.normalTarget) {\n\t\t\tconst depthTexture = new THREE.DepthTexture(this.width, this.height);\n\t\t\tthis.normalTarget = new THREE.WebGLRenderTarget(this.width, this.height, {\n\t\t\t\tminFilter: THREE.NearestFilter,\n\t\t\t\tmagFilter: THREE.NearestFilter,\n\t\t\t\tdepthTexture\n\t\t\t});\n\t\t}\n\t\treturn this.normalTarget;\n\t}\n\n\toverride setSize(width: number, height: number): void {\n\t\tthis.width = Math.max(1, width);\n\t\tthis.height = Math.max(1, height);\n\t\tthis.normalTarget?.setSize(this.width, this.height);\n\t}\n\n\toverride render(\n\t\trenderer: THREE.WebGLRenderer,\n\t\twriteBuffer: THREE.WebGLRenderTarget,\n\t\treadBuffer: THREE.WebGLRenderTarget\n\t): void {\n\t\tconst normalTarget = this.acquireNormalTarget();\n\n\t\t// --- Normals + depth prepass (override material) ---\n\t\tconst previousTarget = renderer.getRenderTarget();\n\t\tconst previousAutoClear = renderer.autoClear;\n\t\tconst previousClearColor = renderer.getClearColor(new THREE.Color());\n\t\tconst previousClearAlpha = renderer.getClearAlpha();\n\t\tconst previousOverride = this.scene.overrideMaterial;\n\n\t\trenderer.setRenderTarget(normalTarget);\n\t\t// 0x7777ff is packed +Z: background pixels get a uniform normal, so only depth silhouettes\n\t\t// (not normal noise) separate objects from empty space.\n\t\trenderer.setClearColor(0x7777ff, 1);\n\t\trenderer.autoClear = true;\n\t\tthis.scene.overrideMaterial = this.normalMaterial;\n\t\trenderer.render(this.scene, this.camera);\n\t\tthis.scene.overrideMaterial = previousOverride;\n\t\trenderer.setClearColor(previousClearColor, previousClearAlpha);\n\t\trenderer.autoClear = previousAutoClear;\n\n\t\t// --- Edge composite ---\n\t\tconst uniforms = this.edgeMaterial.uniforms;\n\t\tuniforms.tDiffuse.value = readBuffer.texture;\n\t\tuniforms.tNormal.value = normalTarget.texture;\n\t\tuniforms.tDepth.value = normalTarget.depthTexture;\n\t\tuniforms.uResolution.value.set(this.width, this.height);\n\t\tconst perspective = this.camera as Partial<THREE.PerspectiveCamera>;\n\t\tuniforms.uPerspective.value = perspective.isPerspectiveCamera ? 1 : 0;\n\t\tuniforms.uNear.value = (this.camera as THREE.PerspectiveCamera).near ?? 0.1;\n\t\tuniforms.uFar.value = (this.camera as THREE.PerspectiveCamera).far ?? 1000;\n\n\t\trenderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);\n\t\tthis.fsQuad.render(renderer);\n\t\trenderer.setRenderTarget(previousTarget);\n\t}\n\n\toverride dispose(): void {\n\t\tthis.normalTarget?.dispose();\n\t\tthis.normalMaterial.dispose();\n\t\tthis.edgeMaterial.dispose();\n\t\tthis.fsQuad.dispose();\n\t}\n}\n","import * as THREE from 'three';\nimport { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/addons/postprocessing/RenderPass.js';\nimport { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';\nimport { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';\nimport { OutputPass } from 'three/addons/postprocessing/OutputPass.js';\n\nimport { EdgeDetectionPass, type EdgeDetectionOptions } from './edge-detection-pass';\n\n/**\n * Pipeline: RenderPass → GTAOPass? → EdgeDetectionPass? → SMAAPass → OutputPass. EdgeDetectionPass\n * sits before SMAA so its 1px lines get antialiased. SMAA is required because EffectComposer\n * renders offscreen, so the renderer's own MSAA does nothing here; SMAA over TAA because TAA's\n * temporal jitter smears during OrbitControls drags. OutputPass applies tone mapping and color\n * space last, so SMAA operates on the pre-tonemapped image.\n */\n\nexport interface RenderPipeline {\n\trender(deltaTime: number): void;\n\tsetSize(width: number, height: number, pixelRatio: number): void;\n\t/** Call when the camera's projection changes (e.g. perspective↔ortho). */\n\tsetCamera(camera: THREE.Camera): void;\n\tsetEdgeDetection(enabled: boolean): void;\n\tedgeDetectionEnabled(): boolean;\n\tdispose(): void;\n}\n\nexport interface RenderPipelineOptions {\n\t/** Must mirror the renderer's own tone mapping — OutputPass applies it once composited, not the renderer. */\n\ttoneMapping: THREE.ToneMapping;\n\ttoneMappingExposure: number;\n\t/** Default true. */\n\tambientOcclusion?: boolean;\n\t/** AO strength 0–1. Default 1. */\n\taoIntensity?: number;\n\t/** DPR cap for the composer's AO buffers; clamps `setSize`'s pixelRatio. Default 1. */\n\taoPixelRatio?: number;\n\t/** Start with the screen-space edge pass enabled; pass an object to tune it. */\n\tedgeDetection?: boolean | EdgeDetectionOptions;\n}\n\nexport function createRenderPipeline(\n\trenderer: THREE.WebGLRenderer,\n\tscene: THREE.Scene,\n\tcamera: THREE.Camera,\n\twidth: number,\n\theight: number,\n\toptions: RenderPipelineOptions\n): RenderPipeline {\n\tconst composer = new EffectComposer(renderer);\n\n\tconst renderPass = new RenderPass(scene, camera);\n\tcomposer.addPass(renderPass);\n\n\tlet gtaoPass: GTAOPass | null = null;\n\tif (options.ambientOcclusion ?? true) {\n\t\tgtaoPass = new GTAOPass(scene, camera, width, height);\n\t\tgtaoPass.blendIntensity = options.aoIntensity ?? 1;\n\t\tgtaoPass.updateGtaoMaterial({ screenSpaceRadius: true });\n\t\tcomposer.addPass(gtaoPass);\n\t}\n\n\tconst edgeOptions = typeof options.edgeDetection === 'object' ? options.edgeDetection : {};\n\tconst edgePass = new EdgeDetectionPass(scene, camera, width, height, edgeOptions);\n\tedgePass.enabled = !!options.edgeDetection;\n\tcomposer.addPass(edgePass);\n\n\tconst smaaPass = new SMAAPass();\n\tcomposer.addPass(smaaPass);\n\n\tconst outputPass = new OutputPass();\n\tcomposer.addPass(outputPass);\n\n\trenderer.toneMapping = options.toneMapping;\n\trenderer.toneMappingExposure = options.toneMappingExposure;\n\n\tconst aoPixelRatioCap = options.aoPixelRatio ?? 1;\n\tcomposer.setSize(width, height);\n\n\treturn {\n\t\trender: (deltaTime) => composer.render(deltaTime),\n\t\t// composer.setSize only — calling individual pass.setSize would reset AO/AA targets back to\n\t\t// logical CSS size, undoing the pixel-ratio scaling.\n\t\tsetSize: (w, h, pixelRatio) => {\n\t\t\tcomposer.setPixelRatio(Math.min(pixelRatio, aoPixelRatioCap));\n\t\t\tcomposer.setSize(w, h);\n\t\t},\n\t\tsetCamera: (cam) => {\n\t\t\trenderPass.camera = cam;\n\t\t\tedgePass.camera = cam;\n\t\t\tif (!gtaoPass) return;\n\t\t\tgtaoPass.camera = cam;\n\t\t\t// GTAOPass bakes camera type into its AO shader as a construction-time define; reassigning\n\t\t\t// `camera` alone leaves the old projection's depth reconstruction active — garbage AO after\n\t\t\t// a perspective⇄ortho toggle. Force a recompile when the type actually changes.\n\t\t\tconst isPerspective = (cam as Partial<THREE.PerspectiveCamera>).isPerspectiveCamera ? 1 : 0;\n\t\t\tif (gtaoPass.gtaoMaterial.defines.PERSPECTIVE_CAMERA !== isPerspective) {\n\t\t\t\tgtaoPass.gtaoMaterial.defines.PERSPECTIVE_CAMERA = isPerspective;\n\t\t\t\tgtaoPass.gtaoMaterial.needsUpdate = true;\n\t\t\t}\n\t\t},\n\t\tsetEdgeDetection: (enabled) => {\n\t\t\tedgePass.enabled = enabled;\n\t\t},\n\t\tedgeDetectionEnabled: () => edgePass.enabled,\n\t\t// composer.dispose() doesn't free passes.\n\t\tdispose: () => {\n\t\t\tcomposer.dispose();\n\t\t\tgtaoPass?.dispose();\n\t\t\tedgePass.dispose();\n\t\t\tsmaaPass.dispose();\n\t\t\toutputPass.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\n\nimport { createRenderPipeline, type RenderPipeline } from '../render-pipeline.js';\nimport type { ResolvedOptions } from './defaults.js';\n\n/**\n * Owns the optional postprocessing composer and the two independent reasons to want one: ambient\n * occlusion (a user/look choice) and the screen-space edge fallback (forced on while meshes over\n * the triangle cap are in the scene). Neither knows about the other, so \"is a pipeline wanted, and\n * does it need rebuilding\" is reconciled here instead of at each caller.\n */\nexport interface PipelineController {\n\tget(): RenderPipeline | null;\n\tsync(): void;\n\t/** Dispose and rebuild (if one is wanted) so construction-time options re-apply. */\n\trebuild(): void;\n\tsetAmbientOcclusion(enabled: boolean): void;\n\tsetEdgeFallback(active: boolean): void;\n\tisEdgeFallbackActive(): boolean;\n\tdispose(): void;\n}\n\nexport function createPipelineController(params: {\n\trenderer: THREE.WebGLRenderer;\n\tscene: THREE.Scene;\n\tgetActiveCamera: () => THREE.Camera;\n\tgetCanvasSize: () => { width: number; height: number };\n\tpixelRatio: number;\n\tconfig: ResolvedOptions;\n\trequestRender: () => void;\n}): PipelineController {\n\tconst { renderer, scene, getActiveCamera, getCanvasSize, pixelRatio, config, requestRender } =\n\t\tparams;\n\n\tlet pipeline: RenderPipeline | null = null;\n\tlet aoEnabled = !!config.render.ambientOcclusion;\n\tlet edgeFallbackActive = false;\n\tlet builtWithAo = false;\n\n\tconst build = (withAo: boolean): RenderPipeline => {\n\t\tconst { width, height } = getCanvasSize();\n\t\tconst built = createRenderPipeline(\n\t\t\trenderer,\n\t\t\tscene,\n\t\t\tgetActiveCamera(),\n\t\t\tMath.max(1, width),\n\t\t\tMath.max(1, height),\n\t\t\t{\n\t\t\t\ttoneMapping: config.render.toneMapping ?? THREE.NeutralToneMapping,\n\t\t\t\ttoneMappingExposure: config.render.toneMappingExposure ?? 1,\n\t\t\t\tambientOcclusion: withAo,\n\t\t\t\taoIntensity: config.render.aoIntensity,\n\t\t\t\taoPixelRatio: config.render.aoPixelRatio,\n\t\t\t\t// Always built disabled; sync() flips it live via setEdgeDetection.\n\t\t\t\tedgeDetection: false\n\t\t\t}\n\t\t);\n\t\tbuilt.setSize(Math.max(1, width), Math.max(1, height), pixelRatio);\n\t\treturn built;\n\t};\n\n\tconst sync = () => {\n\t\tconst wantPipeline = aoEnabled || edgeFallbackActive;\n\t\tif (!wantPipeline) {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t\trequestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (!pipeline || builtWithAo !== aoEnabled) {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = build(aoEnabled);\n\t\t\tbuiltWithAo = aoEnabled;\n\t\t}\n\t\tpipeline.setEdgeDetection(edgeFallbackActive);\n\t\trequestRender();\n\t};\n\n\treturn {\n\t\tget: () => pipeline,\n\t\tsync,\n\t\trebuild: () => {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t\tsync();\n\t\t},\n\t\tsetAmbientOcclusion: (enabled: boolean) => {\n\t\t\taoEnabled = enabled;\n\t\t\tsync();\n\t\t},\n\t\tsetEdgeFallback: (active: boolean) => {\n\t\t\tif (active === edgeFallbackActive) return;\n\t\t\tedgeFallbackActive = active;\n\t\t\tsync();\n\t\t},\n\t\tisEdgeFallbackActive: () => edgeFallbackActive,\n\t\tdispose: () => {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t}\n\t};\n}\n","import type * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupControls(\n\tcamera: THREE.PerspectiveCamera,\n\tcanvas: HTMLCanvasElement,\n\tconfig: ResolvedOptions\n): OrbitControls {\n\tconst controls = new OrbitControls(camera, canvas);\n\n\tconst target = config.camera.target;\n\tif (target) {\n\t\tcontrols.target.set(target.x, target.y, target.z);\n\t}\n\n\tcontrols.enableDamping = config.controls.enableDamping || false;\n\tcontrols.dampingFactor = config.controls.dampingFactor || 0.05;\n\n\tcontrols.autoRotate = config.controls.autoRotate || false;\n\tcontrols.autoRotateSpeed = config.controls.autoRotateSpeed || 0.5;\n\n\tcontrols.enableZoom = config.controls.enableZoom ?? true;\n\tcontrols.enablePan = config.controls.enablePan ?? true;\n\tcontrols.minDistance = config.controls.minDistance || 0.001;\n\tcontrols.maxDistance = config.controls.maxDistance || Infinity;\n\n\tcontrols.screenSpacePanning = false;\n\tcontrols.maxPolarAngle = Math.PI;\n\n\tcontrols.update();\n\treturn controls;\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from '../../shared/index.js';\nimport { HDRLoader } from 'three/addons/loaders/HDRLoader.js';\n\nimport { environmentRotationFor } from '../up-axis.js';\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\n\nexport function setupEnvironment(\n\tscene: THREE.Scene,\n\trenderer: THREE.WebGLRenderer,\n\tconfig: ResolvedOptions,\n\tisDisposed: () => boolean\n) {\n\tif (config.environment.enableEnvironmentLighting) {\n\t\tnew HDRLoader().load(\n\t\t\tconfig.environment.hdrPath || '/baseHDR.hdr',\n\t\t\tfunction (envMap) {\n\t\t\t\t// Viewer may be torn down mid-fetch (fast mount/unmount); dispose() already swept the\n\t\t\t\t// scene, so adopting the texture now would leak it, and onReady must not fire.\n\t\t\t\tif (isDisposed()) {\n\t\t\t\t\tenvMap.dispose();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!envMap?.image) {\n\t\t\t\t\tgetLogger().warn('HDR loaded without image data; skipping environment map.');\n\t\t\t\t\tenvMap?.dispose();\n\t\t\t\t\tconfig.events.onReady?.();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tenvMap.mapping = THREE.EquirectangularReflectionMapping;\n\n\t\t\t\t// PMREM builds the roughness-aware mip chain MeshStandardMaterial samples for IBL;\n\t\t\t\t// without it, rough surfaces read a near-mirror level (sharp reflections, sparkly highlights).\n\t\t\t\tconst pmrem = new THREE.PMREMGenerator(renderer);\n\t\t\t\tpmrem.compileEquirectangularShader();\n\t\t\t\tconst prefiltered = pmrem.fromEquirectangular(envMap).texture;\n\t\t\t\tpmrem.dispose();\n\n\t\t\t\tscene.environment = prefiltered;\n\t\t\t\t// Normalizes IBL contribution so brightness is consistent across HDRs of differing exposure.\n\t\t\t\tscene.environmentIntensity = config.environment.environmentIntensity ?? 1;\n\t\t\t\t// Equirect mapping assumes the horizon lies in the XZ plane (Y-up); without rotating for\n\t\t\t\t// a Z-up scene the sky lights the model from +Y instead of from above.\n\t\t\t\tconst envRotation = environmentRotationFor(config.environment.sceneUp ?? defaultUp);\n\t\t\t\tscene.environmentRotation.copy(envRotation);\n\t\t\t\tif (config.environment.showEnvironment) {\n\t\t\t\t\t// Background wants the full-res equirect, not the low-res prefiltered probe.\n\t\t\t\t\tscene.background = envMap;\n\t\t\t\t\t// Separate property from environmentRotation — drifts apart if only one is set.\n\t\t\t\t\tscene.backgroundRotation.copy(envRotation);\n\t\t\t\t} else {\n\t\t\t\t\t// Raw equirect was only PMREM input; the prefiltered probe has superseded it.\n\t\t\t\t\tenvMap.dispose();\n\t\t\t\t}\n\t\t\t\tconfig.events.onReady?.();\n\t\t\t},\n\t\t\tundefined,\n\t\t\tfunction (error) {\n\t\t\t\tif (isDisposed()) return;\n\t\t\t\tgetLogger().warn('HDR texture could not be loaded, falling back to basic lighting:', error);\n\t\t\t\tconfig.events.onReady?.();\n\t\t\t}\n\t\t);\n\t} else {\n\t\tconfig.events.onReady?.();\n\t}\n}\n\nexport function addFloor(scene: THREE.Scene, config: ResolvedOptions) {\n\tconst floorSize = config.floor.size;\n\tconst floorGeometry = new THREE.PlaneGeometry(floorSize, floorSize);\n\n\tconst floorColor =\n\t\ttypeof config.floor.color === 'string'\n\t\t\t? new THREE.Color(config.floor.color)\n\t\t\t: config.floor.color;\n\n\tconst floorMaterial = new THREE.MeshStandardMaterial({\n\t\tcolor: floorColor,\n\t\troughness: config.floor.roughness,\n\t\tmetalness: config.floor.metalness,\n\t\tside: THREE.DoubleSide\n\t});\n\n\tconst floor = new THREE.Mesh(floorGeometry, floorMaterial);\n\tfloor.userData.id = 'floor';\n\tfloor.name = 'floor';\n\t// PlaneGeometry's default +Z normal is already correct for Z-up; orient to scene up for any other.\n\tconst up = (config.environment?.sceneUp || defaultUp).clone().normalize();\n\tfloor.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), up);\n\tfloor.position.set(0, 0, 0);\n\n\tif (config.floor.receiveShadow && config.render.enableShadows) {\n\t\tfloor.receiveShadow = true;\n\t}\n\n\tscene.add(floor);\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from '../../shared/index.js';\n\nimport type { CameraController } from '../camera-controller.js';\nimport { computeContentBounds } from '../three-helpers.js';\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupEventHandlers(\n\tcanvas: HTMLCanvasElement,\n\tscene: THREE.Scene,\n\tcameraController: CameraController,\n\tconfig: ResolvedOptions\n): {\n\tdispose: () => void;\n\tfitToView: () => void;\n\tclearSelection: () => void;\n} {\n\tconst selectedObjects = new Set<THREE.Object3D>();\n\tconst originalMaterials = new Map<THREE.Object3D, THREE.Material | THREE.Material[]>();\n\tconst raycaster = new THREE.Raycaster();\n\tconst mouse = new THREE.Vector2();\n\tconst mouseDownPosition = new THREE.Vector2();\n\tconst getActiveCamera = () => cameraController.getActiveCamera();\n\n\t// Three.js's recursive intersect hits a visible Mesh inside a hidden Group; this enforces that\n\t// every ancestor must also be visible.\n\tconst isFullyVisible = (object: THREE.Object3D): boolean => {\n\t\tlet current: THREE.Object3D | null = object;\n\t\twhile (current) {\n\t\t\tif (!current.visible) return false;\n\t\t\tcurrent = current.parent;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst fitToView = () => {\n\t\tconst box = computeContentBounds(scene);\n\n\t\tif (box.isEmpty()) {\n\t\t\tgetLogger().warn('No objects to fit to view');\n\t\t\treturn;\n\t\t}\n\n\t\t// Via the controller, not the perspective camera directly: it repositions whichever camera is\n\t\t// live and re-derives the ortho frustum in 2D mode.\n\t\tcameraController.frameBounds(box, false);\n\t};\n\n\tconst selectionColorObj =\n\t\ttypeof config.events.selectionColor === 'string'\n\t\t\t? new THREE.Color(config.events.selectionColor)\n\t\t\t: config.events.selectionColor instanceof THREE.Color\n\t\t\t\t? config.events.selectionColor\n\t\t\t\t: new THREE.Color('#ff0000');\n\n\tconst clearSelection = () => {\n\t\tselectedObjects.forEach((obj) => {\n\t\t\tconst restorable = obj as THREE.Object3D & {\n\t\t\t\tmaterial?: THREE.Material | THREE.Material[];\n\t\t\t};\n\t\t\tif (originalMaterials.has(obj)) {\n\t\t\t\tconst original = originalMaterials.get(obj)!;\n\t\t\t\tconst clone = restorable.material; // dispose the highlight clone before restoring\n\t\t\t\tif (clone instanceof THREE.Material) clone.dispose();\n\t\t\t\telse if (Array.isArray(clone)) clone.forEach((m) => m.dispose());\n\t\t\t\trestorable.material = original;\n\t\t\t\toriginalMaterials.delete(obj);\n\n\t\t\t\t// If the object left the scene while selected, no traversal can reach the original\n\t\t\t\t// material we just restored — dispose it here. Compute content is cleared wholesale\n\t\t\t\t// per solve, so a detached object's material has no surviving sharers.\n\t\t\t\tlet root: THREE.Object3D = obj;\n\t\t\t\twhile (root.parent) root = root.parent;\n\t\t\t\tif (root !== scene) {\n\t\t\t\t\tif (original instanceof THREE.Material) original.dispose();\n\t\t\t\t\telse original.forEach((m) => m.dispose());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tselectedObjects.clear();\n\t};\n\n\t// Meshes tint via `emissive` (keeps base color); lines/points have no emissive channel, so\n\t// `color` is recolored directly instead.\n\tconst applyHighlight = (object: THREE.Object3D): boolean => {\n\t\tconst target = object as THREE.Object3D & { material?: THREE.Material | THREE.Material[] };\n\t\tif (!(target.material instanceof THREE.Material)) return false;\n\n\t\toriginalMaterials.set(object, target.material);\n\t\tconst clone = target.material.clone();\n\n\t\tif (object instanceof THREE.Mesh && 'emissive' in clone) {\n\t\t\t(clone as THREE.MeshStandardMaterial).emissive = selectionColorObj.clone();\n\t\t} else if ('color' in clone) {\n\t\t\t(clone as THREE.LineBasicMaterial).color = selectionColorObj.clone();\n\t\t}\n\n\t\ttarget.material = clone;\n\t\treturn true;\n\t};\n\n\t// Points picking tolerance, scaled to scene size so it holds at any zoom. Fat Line2 uses its own\n\t// material linewidth instead, so no separate threshold is needed for lines.\n\tconst updatePickThresholds = () => {\n\t\tconst box = computeContentBounds(scene);\n\t\tconst diagonal = box.isEmpty() ? 1 : box.getSize(new THREE.Vector3()).length();\n\t\traycaster.params.Points.threshold = diagonal * 0.01;\n\t};\n\n\tconst handleMouseDown = (event: MouseEvent) => {\n\t\tmouseDownPosition.set(event.clientX, event.clientY);\n\t};\n\n\tconst handleCanvasClick = (event: MouseEvent) => {\n\t\tconst currentMousePosition = new THREE.Vector2(event.clientX, event.clientY);\n\t\tif (mouseDownPosition.distanceTo(currentMousePosition) > 5) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tmouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tmouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tupdatePickThresholds();\n\t\traycaster.setFromCamera(mouse, getActiveCamera());\n\t\tconst intersects = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => isFullyVisible(i.object));\n\n\t\tif (intersects.length > 0) {\n\t\t\tconst clickedObject = intersects[0].object;\n\n\t\t\tif (!selectedObjects.has(clickedObject)) {\n\t\t\t\tclearSelection();\n\t\t\t\tselectedObjects.add(clickedObject);\n\t\t\t\tapplyHighlight(clickedObject);\n\n\t\t\t\tconfig.events?.onObjectSelected?.(clickedObject);\n\n\t\t\t\tif (clickedObject instanceof THREE.Mesh && Object.keys(clickedObject.userData).length > 0) {\n\t\t\t\t\tconfig.events?.onMeshMetadataClicked?.(clickedObject.userData);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tclearSelection();\n\t\t\tconfig.events?.onBackgroundClicked?.({ x: mouse.x, y: mouse.y });\n\t\t}\n\t};\n\n\tconst handleDoubleClick = (event: MouseEvent) => {\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tmouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tmouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tupdatePickThresholds();\n\t\traycaster.setFromCamera(mouse, getActiveCamera());\n\t\tconst intersects = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => isFullyVisible(i.object));\n\n\t\tif (intersects.length === 0) return;\n\n\t\tconst target = intersects[0].object;\n\t\tconfig.events?.onMeshDoubleClicked?.(target);\n\n\t\tif (!config.events?.enableDoubleClickZoom) return;\n\n\t\tconst box = new THREE.Box3().setFromObject(target);\n\t\tif (box.isEmpty()) return;\n\n\t\t// Via the controller so the active camera moves (translating an ortho camera alone zooms\n\t\t// nothing). The resulting tween is cancellable — a rapid second double-click replaces it\n\t\t// rather than racing it.\n\t\tcameraController.frameBounds(box, true);\n\t};\n\n\tconst handleKeydown = (event: KeyboardEvent) => {\n\t\tif (!config.events?.enableKeyboardControls) return;\n\n\t\tswitch (event.key.toLowerCase()) {\n\t\t\tcase 'f':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfitToView();\n\t\t\t\tbreak;\n\t\t\tcase 'escape':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tclearSelection();\n\t\t\t\tbreak;\n\t\t\tcase ' ':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfitToView();\n\t\t\t\tbreak;\n\t\t}\n\t};\n\n\tif (config.events?.enableClickToFocus) {\n\t\tcanvas.addEventListener('mousedown', handleMouseDown);\n\t\tcanvas.addEventListener('click', handleCanvasClick);\n\t\tcanvas.addEventListener('dblclick', handleDoubleClick);\n\t}\n\n\tif (config.events?.enableKeyboardControls) {\n\t\tcanvas.setAttribute('tabindex', '0');\n\t\tcanvas.addEventListener('keydown', handleKeydown);\n\t}\n\n\tconst dispose = () => {\n\t\tcanvas.removeEventListener('mousedown', handleMouseDown);\n\t\tcanvas.removeEventListener('click', handleCanvasClick);\n\t\tcanvas.removeEventListener('dblclick', handleDoubleClick);\n\t\tcanvas.removeEventListener('keydown', handleKeydown);\n\t\tclearSelection();\n\t};\n\n\treturn { dispose, fitToView, clearSelection };\n}\n","import * as THREE from 'three';\n\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\n\nexport type SceneLights = {\n\tambient: THREE.AmbientLight;\n\t/** Null unless `lighting.enableHemisphereLight`. */\n\themisphere: THREE.HemisphereLight | null;\n\t/** Null when sunlight or shadows are disabled. */\n\tsun: THREE.DirectionalLight | null;\n};\n\nexport function setupLighting(scene: THREE.Scene, config: ResolvedOptions): SceneLights {\n\tconst ambient = new THREE.AmbientLight(\n\t\tconfig.lighting.ambientLightColor,\n\t\tconfig.lighting.ambientLightIntensity\n\t);\n\tscene.add(ambient);\n\n\t// HemisphereLight defaults to +Y up, which is wrong for a Z-up scene — align to scene up instead.\n\tlet hemisphere: THREE.HemisphereLight | null = null;\n\tif (config.lighting.enableHemisphereLight) {\n\t\themisphere = new THREE.HemisphereLight(\n\t\t\tconfig.lighting.hemisphereSkyColor,\n\t\t\tconfig.lighting.hemisphereGroundColor,\n\t\t\tconfig.lighting.hemisphereIntensity\n\t\t);\n\t\tconst up = config.environment.sceneUp ?? defaultUp;\n\t\themisphere.position.copy(up);\n\t\tscene.add(hemisphere);\n\t}\n\n\tif (!config.lighting.enableSunlight) return { ambient, hemisphere, sun: null };\n\n\tconst sunlight = new THREE.DirectionalLight(\n\t\tconfig.lighting.sunlightColor ?? 0xffffff,\n\t\tconfig.lighting.sunlightIntensity\n\t);\n\tconst pos = config.lighting.sunlightPosition;\n\tif (pos) {\n\t\tsunlight.position.set(pos.x, pos.y, pos.z);\n\t}\n\n\tif (!config.render.enableShadows) {\n\t\tscene.add(sunlight);\n\t\treturn { ambient, hemisphere, sun: null };\n\t}\n\n\tsunlight.castShadow = true;\n\n\t// Frustum bounds are not set here — fitShadowToContent sizes them to scene content instead.\n\tsunlight.shadow.mapSize.width = config.render.shadowMapSize || 2048;\n\tsunlight.shadow.mapSize.height = config.render.shadowMapSize || 2048;\n\n\tsunlight.shadow.bias = -0.0001;\n\tsunlight.shadow.normalBias = 0.02;\n\tsunlight.shadow.radius = 4;\n\n\tscene.add(sunlight);\n\t// A DirectionalLight aims at its target's world position, so the target must be in the scene\n\t// graph for its matrix to update.\n\tscene.add(sunlight.target);\n\treturn { ambient, hemisphere, sun: sunlight };\n}\n\n/**\n * Sizes a directional light's orthographic shadow frustum to the scene content's bounding sphere —\n * the dominant lever on shadow crispness. No-op on an empty box (would collapse the frustum to a point).\n */\nexport function fitShadowToContent(light: THREE.DirectionalLight, bounds: THREE.Box3): void {\n\tif (bounds.isEmpty()) return;\n\n\tconst center = bounds.getCenter(new THREE.Vector3());\n\t// Bounding-sphere radius keeps the frustum rotation-invariant; padded so grazing-angle casters\n\t// and VSM blur near the edges don't clip.\n\tconst radius = bounds.getSize(new THREE.Vector3()).length() * 0.5 * 1.2;\n\n\tconst cam = light.shadow.camera;\n\tcam.left = -radius;\n\tcam.right = radius;\n\tcam.top = radius;\n\tcam.bottom = -radius;\n\n\t// Only the target moves to the content centre, preserving the light's configured direction.\n\tlight.target.position.copy(center);\n\tlight.target.updateMatrixWorld();\n\n\t// Clamp near above 0 so a light sitting inside the bounds can't invert the frustum.\n\tconst lightDistance = light.position.distanceTo(center);\n\tcam.near = Math.max(radius * 0.01, lightDistance - radius);\n\tcam.far = lightDistance + radius;\n\tcam.updateProjectionMatrix();\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupRenderer(\n\tcanvas: HTMLCanvasElement,\n\tconfig: ResolvedOptions,\n\tpixelRatio: number\n): THREE.WebGLRenderer {\n\tconst renderer = new THREE.WebGLRenderer({\n\t\tantialias: config.render.antialias,\n\t\tcanvas,\n\t\talpha: true,\n\t\tpowerPreference: 'high-performance',\n\t\tpreserveDrawingBuffer: config.render.preserveDrawingBuffer,\n\t\t// Deliberately NOT logarithmic: the GTAO pipeline reconstructs view-space positions assuming\n\t\t// standard perspective depth and doesn't support log-encoded depth (haloing, wrong-scale\n\t\t// occlusion if enabled). If log depth is ever needed, AO must be disabled alongside it.\n\t\tlogarithmicDepthBuffer: false\n\t});\n\n\tconst parent = canvas.parentElement;\n\tconst width = parent ? parent.clientWidth : window.innerWidth;\n\tconst height = parent ? parent.clientHeight : window.innerHeight;\n\n\tif (parent) {\n\t\tcanvas.style.width = '100%';\n\t\tcanvas.style.height = '100%';\n\t\tcanvas.style.display = 'block';\n\t}\n\n\trenderer.setSize(width, height, false);\n\trenderer.setPixelRatio(pixelRatio);\n\n\tif (config.render.enableShadows) {\n\t\trenderer.shadowMap.enabled = true;\n\t\trenderer.shadowMap.type = THREE.VSMShadowMap;\n\t}\n\n\trenderer.toneMapping = config.render.toneMapping!;\n\trenderer.toneMappingExposure = config.render.toneMappingExposure ?? 1.0;\n\trenderer.outputColorSpace = THREE.SRGBColorSpace;\n\n\trenderer.sortObjects = true;\n\n\treturn renderer;\n}\n","import * as THREE from 'three';\n\nimport { publishMaxAnisotropy } from '../../shared/index.js';\nimport { createCameraController } from '../camera-controller.js';\nimport { EDGES_SKIPPED_TRIANGLE_CAP, addEdgesAsync, removeEdges } from '../edges.js';\nimport { createGrid } from '../grid.js';\nimport { createLabelLayer, type LabelLayer } from '../label-layer.js';\nimport { createMeasureTool, type MeasureTool } from '../measure.js';\nimport { createNearPlaneFitter, type NearPlaneFitter } from '../near-plane.js';\nimport { SOURCE_USER, appSource, isOwnedBy } from '../scene-ownership.js';\nimport { computeContentBounds } from '../three-helpers.js';\nimport { createToolRegistry } from '../tool-registry.js';\nimport type { ThreeInitializerOptions } from '../types.js';\nimport { upToAxis } from '../up-axis.js';\nimport { createViewGizmo } from '../view-gizmo.js';\nimport { createAnimationLoop } from './animation-loop.js';\nimport { createAppearanceController } from './appearance.js';\nimport { createCamera } from './create-camera.js';\nimport { createScene } from './create-scene.js';\nimport { applyDefaults, defaultUp } from './defaults.js';\nimport { disposeObjectTree, disposeSceneResources } from './dispose.js';\nimport { createPipelineController } from './pipeline-controller.js';\nimport { setupControls } from './setup-controls.js';\nimport { addFloor, setupEnvironment } from './setup-environment.js';\nimport { setupEventHandlers } from './setup-events.js';\nimport { fitShadowToContent, setupLighting } from './setup-lighting.js';\nimport { setupRenderer } from './setup-renderer.js';\nimport type { ThreeViewer } from './viewer.js';\n\nexport const initThree = function (\n\tcanvas: HTMLCanvasElement,\n\toptions?: ThreeInitializerOptions\n): ThreeViewer {\n\tconst config = applyDefaults(options || {});\n\n\tconst sceneUp = config.environment?.sceneUp || defaultUp;\n\n\t// Single source of truth for DPR (renderer, resize check, AO pipeline); applyDefaults always\n\t// sets it, the fallback here is just type narrowing.\n\tconst pixelRatio = config.render.pixelRatio ?? Math.min(window.devicePixelRatio, 2);\n\n\tconst scene = createScene(config);\n\tconst camera = createCamera(config, canvas);\n\t// Must happen before OrbitControls/the controller read camera.up (captured at construction), or\n\t// a Z-up scene orbits and frames as if Y-up.\n\tcamera.up.copy(sceneUp);\n\tconst renderer = setupRenderer(canvas, config, pixelRatio);\n\t// Published to a shared sink rather than imported, so render/ stays independent of parse/.\n\tpublishMaxAnisotropy(renderer.capabilities.getMaxAnisotropy());\n\toptions?.onMaxAnisotropy?.(renderer.capabilities.getMaxAnisotropy());\n\n\tconst controls = setupControls(camera, canvas, config);\n\n\t// Render loop, resize, and raycasting all read through getActiveCamera so 2D/3D stays coherent.\n\tconst cameraController = createCameraController({\n\t\tscene,\n\t\tperspective: camera,\n\t\tcontrols,\n\t\tonActiveCameraChange: () => {},\n\t\tup: sceneUp\n\t});\n\tconst getActiveCamera = () => cameraController.getActiveCamera();\n\n\t// HDR decodes asynchronously; setupEnvironment's load callback checks this to drop (and dispose)\n\t// the texture instead of attaching it to a scene torn down mid-fetch.\n\tlet disposed = false;\n\tsetupEnvironment(scene, renderer, config, () => disposed);\n\tconst lights = setupLighting(scene, config);\n\tconst sunlight = lights.sun;\n\n\tconst updateShadowBounds = () => {\n\t\tif (sunlight) fitShadowToContent(sunlight, computeContentBounds(scene));\n\t};\n\n\tif (config.floor?.enabled) {\n\t\taddFloor(scene, config);\n\t}\n\t// So the near-plane fitter below can consult the floor's live visibility.\n\tconst floorMesh = config.floor?.enabled\n\t\t? (scene.children.find((child) => child.userData.id === 'floor') ?? null)\n\t\t: null;\n\n\tconst grid = config.grid.enabled\n\t\t? createGrid({\n\t\t\t\tcellSize: config.grid.cellSize,\n\t\t\t\tmajorEvery: config.grid.majorEvery,\n\t\t\t\tcellColor: config.grid.cellColor,\n\t\t\t\tmajorColor: config.grid.majorColor,\n\t\t\t\tfadeDistance: config.grid.fadeDistance,\n\t\t\t\tplane: config.grid.plane\n\t\t\t})\n\t\t: null;\n\tif (grid) scene.add(grid.object);\n\n\tconst updateGridScale = () => {\n\t\tif (grid) grid.fitToContent(computeContentBounds(scene));\n\t};\n\n\tconst gizmo = config.gizmo.enabled\n\t\t? createViewGizmo({ camera, domElement: canvas, controller: cameraController })\n\t\t: null;\n\n\t// Only VISIBLE ground aids feed the near-plane fitter's clamp: the grid is commonly built hidden\n\t// so hosts can toggle it, and the clamp is the camera's height above the plane — a hidden grid\n\t// would still drive near→0 at grazing views and crater depth precision, punching hidden edges\n\t// through geometry.\n\tconst gridPlane = config.grid.plane ?? upToAxis(sceneUp);\n\tconst gridNormal = new THREE.Vector3(\n\t\tgridPlane === 'x' ? 1 : 0,\n\t\tgridPlane === 'y' ? 1 : 0,\n\t\tgridPlane === 'z' ? 1 : 0\n\t);\n\tconst floorNormal = sceneUp.clone().normalize();\n\tconst groundNormals = (): THREE.Vector3[] => {\n\t\tconst normals: THREE.Vector3[] = [];\n\t\tif (grid?.object.visible) normals.push(gridNormal);\n\t\tif (config.floor.enabled && floorMesh?.visible) normals.push(floorNormal);\n\t\treturn normals;\n\t};\n\tconst nearFitter: NearPlaneFitter | null = config.camera.dynamicNear\n\t\t? createNearPlaneFitter({ camera, scene, groundNormals })\n\t\t: null;\n\n\t// Built unconditionally: measure was the first consumer, but any tool or app annotating the\n\t// scene needs it, and gating it behind measure.enabled left them with no way to get one.\n\tconst labelContainer = canvas.parentElement ?? canvas;\n\tconst labelLayer: LabelLayer = createLabelLayer(labelContainer, scene);\n\tconst measureTool: MeasureTool | null = config.measure.enabled\n\t\t? createMeasureTool({\n\t\t\t\tcanvas,\n\t\t\t\tscene,\n\t\t\t\tgetActiveCamera,\n\t\t\t\tlabelLayer,\n\t\t\t\toptions: {\n\t\t\t\t\tsnapPixels: config.measure.snapPixels,\n\t\t\t\t\tcolor: config.measure.color,\n\t\t\t\t\tlabelClassName: config.measure.labelClassName,\n\t\t\t\t\tdisplayUnit: config.measure.displayUnit,\n\t\t\t\t\tformat: config.measure.format\n\t\t\t\t}\n\t\t\t})\n\t\t: null;\n\n\tconst eventHandlers =\n\t\tconfig.events.enableEventHandlers !== false\n\t\t\t? setupEventHandlers(canvas, scene, cameraController, config)\n\t\t\t: { dispose: () => {}, fitToView: () => {}, clearSelection: () => {} };\n\n\t// Built-ins register at the priorities documented on ToolRegistration, so a host tool can slot\n\t// above or below them. Listeners are attached unconditionally — a tool can register at any time.\n\tconst tools = createToolRegistry();\n\tif (measureTool) tools.register({ id: 'measure', tool: measureTool, priority: 0 });\n\tif (gizmo) tools.register({ id: 'gizmo', tool: gizmo, priority: -100 });\n\n\t// A drag to orbit/pan ends with a `click` on mouseup; without this guard that release would be\n\t// mistaken for a measurement point.\n\tconst DRAG_SLOP_PX = 5;\n\tlet pressX = 0;\n\tlet pressY = 0;\n\tconst handlePointerDown = (event: MouseEvent) => {\n\t\tpressX = event.clientX;\n\t\tpressY = event.clientY;\n\t};\n\tconst wasDrag = (event: MouseEvent) =>\n\t\tMath.hypot(event.clientX - pressX, event.clientY - pressY) > DRAG_SLOP_PX;\n\n\t// Capture-phase so tools see the click before bubble-phase selection; the first to claim it\n\t// wins, and stopImmediatePropagation keeps selection from also firing.\n\tconst handleToolClick = (event: MouseEvent) => {\n\t\tif (wasDrag(event)) return;\n\t\tif (tools.handleClick(event)) event.stopImmediatePropagation();\n\t};\n\tcanvas.addEventListener('mousedown', handlePointerDown, { capture: true });\n\tcanvas.addEventListener('click', handleToolClick, { capture: true });\n\n\t// Passive: moves only drive previews, never consume, so they can't interfere with orbit/pan.\n\tconst handleToolMove = (event: MouseEvent) => tools.handleMove(event);\n\tcanvas.addEventListener('mousemove', handleToolMove, { passive: true });\n\n\t// Rebound to the animation loop's real invalidate once it's created below.\n\tlet requestRender: () => void = () => {};\n\n\t// Applies regardless of edges.enabled — an explicit call should never be silently ignored.\n\t// Meshes over the triangle cap switch the screen-space edge fallback on; a later solve without\n\t// such meshes switches it back off.\n\tconst applyEdges = (root: THREE.Object3D) => {\n\t\tvoid addEdgesAsync(root, {\n\t\t\tcolor: config.edges.color,\n\t\t\tdarken: config.edges.darken,\n\t\t\twidth: config.edges.width,\n\t\t\tthresholdAngle: config.edges.thresholdAngle,\n\t\t\tdistanceFade: config.edges.distanceFade,\n\t\t\tmaxTriangles: config.edges.maxTriangles,\n\t\t\tmaxSegments: config.edges.maxSegments\n\t\t}).then(() => {\n\t\t\tupdateEdgeFallback(root);\n\t\t\trequestRender(); // async attach may land after the solve's own repaint\n\t\t});\n\t};\n\n\tconst updateEdgeFallback = (root: THREE.Object3D) => {\n\t\tif (config.edges.screenSpaceFallback === false) return;\n\t\tlet hasSkippedMeshes = false;\n\t\troot.traverse((object) => {\n\t\t\tif (object.userData?.edgesSkipped === EDGES_SKIPPED_TRIANGLE_CAP) hasSkippedMeshes = true;\n\t\t});\n\t\tpipeline.setEdgeFallback(hasSkippedMeshes);\n\t};\n\n\t// Also stands down the screen-space fallback — bare removeEdges alone would keep drawing lines\n\t// for capped meshes.\n\tconst clearEdges = (root: THREE.Object3D) => {\n\t\tremoveEdges(root);\n\t\tpipeline.setEdgeFallback(false);\n\t\trequestRender();\n\t};\n\n\tconst parent = canvas.parentElement;\n\tconst getCanvasSize = () =>\n\t\tparent\n\t\t\t? { width: parent.clientWidth, height: parent.clientHeight }\n\t\t\t: { width: window.innerWidth, height: window.innerHeight };\n\n\tconst pipeline = createPipelineController({\n\t\trenderer,\n\t\tscene,\n\t\tgetActiveCamera,\n\t\tgetCanvasSize,\n\t\tpixelRatio,\n\t\tconfig,\n\t\trequestRender: () => requestRender()\n\t});\n\tpipeline.sync();\n\n\tconst appearance = createAppearanceController({\n\t\tscene,\n\t\trenderer,\n\t\tlights,\n\t\tconfig,\n\t\tpipeline,\n\t\trequestRender: () => requestRender()\n\t});\n\n\tconst {\n\t\tanimate,\n\t\tdispose: disposeAnimation,\n\t\tinvalidate\n\t} = createAnimationLoop(\n\t\trenderer,\n\t\tscene,\n\t\tcamera,\n\t\tgetActiveCamera,\n\t\tcameraController,\n\t\tcontrols,\n\t\tgetCanvasSize,\n\t\tpixelRatio,\n\t\tconfig.events.onFrame,\n\t\tgrid,\n\t\tgizmo,\n\t\t() => pipeline.get(),\n\t\tlabelLayer,\n\t\tnearFitter,\n\t\tconfig.render.onDemand ?? true\n\t);\n\trequestRender = invalidate;\n\tanimate();\n\n\tscene.up.set(sceneUp.x, sceneUp.y, sceneUp.z);\n\n\t// Initial fit for geometry already present; hosts loading more later via updateScene should\n\t// call these again.\n\tupdateShadowBounds();\n\tupdateGridScale();\n\n\tconst addUserGeometry = (object: THREE.Object3D, appId?: string) => {\n\t\tobject.userData.source = appId === undefined ? SOURCE_USER : appSource(appId);\n\t\tscene.add(object);\n\t\trequestRender();\n\t};\n\n\tconst removeUserGeometry = (object: THREE.Object3D) => {\n\t\tobject.removeFromParent();\n\t\tdisposeObjectTree(object);\n\t\trequestRender();\n\t};\n\n\tconst clearUserGeometry = (appId?: string) => {\n\t\t// Snapshot first: removeFromParent would mutate scene.children mid-iteration otherwise.\n\t\tconst owned = scene.children.filter((child) => isOwnedBy(child, appId));\n\t\towned.forEach((object) => {\n\t\t\tobject.removeFromParent();\n\t\t\tdisposeObjectTree(object);\n\t\t});\n\t\trequestRender();\n\t};\n\n\tconst dispose = () => {\n\t\t// Idempotent: a second call would re-run forceContextLoss() on an already-lost context and\n\t\t// throw (double-dispose happens naturally under React StrictMode).\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tdisposeAnimation();\n\t\teventHandlers.dispose();\n\t\tcanvas.removeEventListener('mousedown', handlePointerDown, { capture: true });\n\t\tcanvas.removeEventListener('click', handleToolClick, { capture: true });\n\t\tcanvas.removeEventListener('mousemove', handleToolMove);\n\t\tmeasureTool?.dispose();\n\t\tlabelLayer?.dispose();\n\t\tgizmo?.dispose();\n\t\tgrid?.dispose();\n\t\tpipeline.dispose();\n\t\t// Stops any in-flight camera tween — its rAF ticks would otherwise keep touching the\n\t\t// disposed controls after teardown.\n\t\tcameraController.dispose();\n\t\tcontrols.dispose();\n\t\trenderer.dispose();\n\t\t// Frees the GL context itself: browsers cap live WebGL contexts (~16), and otherwise it won't\n\t\t// be reclaimed until GC, which can lag across rapid mount/unmount cycles.\n\t\trenderer.forceContextLoss();\n\n\t\tdisposeSceneResources(scene);\n\n\t\t// Cross-solve caches (parse/'s, reached via a registry rather than an import — layer rule)\n\t\t// outlive any single scene but not the GL context just destroyed. Refcounted: only the last\n\t\t// live viewer actually frees, and this must run after the scene sweep above.\n\t};\n\n\treturn {\n\t\tscene,\n\t\tcamera,\n\t\tcontrols,\n\t\trenderer,\n\t\tcameraController,\n\t\tgrid,\n\t\tgizmo,\n\t\tmeasureTool,\n\t\tlabelLayer,\n\t\ttools,\n\t\tapplyEdges,\n\t\tclearEdges,\n\t\tinvalidate,\n\t\tsetAmbientOcclusion: pipeline.setAmbientOcclusion,\n\t\tsetLook: appearance.setLook,\n\t\tsetFillLights: appearance.setFillLights,\n\t\tsetEnvironmentIntensity: appearance.setEnvironmentIntensity,\n\t\tsetToneMappingExposure: appearance.setToneMappingExposure,\n\t\tsetAoIntensity: appearance.setAoIntensity,\n\t\tgetMaterialAppearance: appearance.getMaterialAppearance,\n\t\tupdateShadowBounds,\n\t\tupdateGridScale,\n\t\tdispose,\n\t\tfitToView: eventHandlers.fitToView,\n\t\tclearSelection: eventHandlers.clearSelection,\n\t\taddUserGeometry,\n\t\tremoveUserGeometry,\n\t\tclearUserGeometry\n\t};\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"render.cjs","names":["THREE","THREE","computeCombinedBoundingBox","THREE","THREE","LineSegmentsGeometry","THREE","THREE","THREE","LineMaterial","LineSegments2","THREE","LineSegments2","THREE","CSS2DRenderer","THREE","CSS2DObject","THREE","LineGeometry","LineMaterial","Line2","THREE","ViewHelper","THREE","THREE","THREE","THREE","THREE","THREE","THREE","THREE","Pass","FullScreenQuad","EffectComposer","RenderPass","GTAOPass","SMAAPass","OutputPass","THREE","OrbitControls","HDRLoader","THREE","THREE","THREE","THREE","THREE"],"sources":["../src/shared/types.ts","../src/shared/looks.ts","../src/render/scene-ownership.ts","../src/render/up-axis.ts","../src/render/three-helpers.ts","../src/render/camera-controller.ts","../src/render/edges/line-geometry.ts","../src/render/edge-extract.ts","../src/render/edges/options.ts","../src/render/edges/extraction.ts","../src/render/edges/overlay.ts","../src/render/edges.ts","../src/render/grid.ts","../src/render/label-layer.ts","../src/render/measure.ts","../src/render/near-plane.ts","../src/render/tool-registry.ts","../src/render/view-gizmo.ts","../src/render/scene-setup/animation-loop.ts","../src/render/scene-setup/defaults.ts","../src/render/scene-setup/appearance.ts","../src/render/scene-setup/create-camera.ts","../src/render/scene-setup/create-scene.ts","../src/render/scene-setup/dispose.ts","../src/render/edge-detection-pass.ts","../src/render/render-pipeline.ts","../src/render/scene-setup/pipeline-controller.ts","../src/render/scene-setup/setup-controls.ts","../src/render/scene-setup/setup-environment.ts","../src/render/scene-setup/setup-events.ts","../src/render/scene-setup/setup-lighting.ts","../src/render/scene-setup/setup-renderer.ts","../src/render/scene-setup/init-three.ts"],"sourcesContent":["import type * as THREE from 'three';\n\n// ============================================================================\n// LOOKS\n// ============================================================================\n\n/** Source of truth for {@link Look} — lets consumers (e.g. a style picker) iterate instead of hardcoding names. */\nexport const LOOKS = ['technical', 'studio', 'showcase'] as const;\n\nexport type Look = (typeof LOOKS)[number];\n\n/**\n * The lighting/material dials a {@link Look} sets. Never carries edges or grid — those are\n * independent overlays.\n */\nexport type LookPreset = {\n\ttoneMapping: THREE.ToneMapping;\n\ttoneMappingExposure: number;\n\tenvMapIntensity: number;\n\t/** Multiplier on the HDR's IBL (`scene.environmentIntensity`). */\n\tenvironmentIntensity: number;\n\themisphereIntensity: number;\n\tambientIntensity: number;\n\tcullBackfaces: boolean;\n\tambientOcclusion: boolean;\n};\n\n/** How compute meshes read visually — the parse-time material choices baked from a {@link Look}. */\nexport interface MaterialAppearanceOptions {\n\t/** Default 1 (three.js's own material default) when omitted. */\n\tenvMapIntensity?: number;\n\t/**\n\t * `THREE.FrontSide` instead of `THREE.DoubleSide` — crisper silhouette on closed solids, but open\n\t * surfaces (which Rhino also emits) vanish when viewed from behind. Default false (DoubleSide) to\n\t * stay safe for surface geometry.\n\t */\n\tcullBackfaces?: boolean;\n}\n","import * as THREE from 'three';\n\nimport type { Look, LookPreset, MaterialAppearanceOptions } from './types.js';\n\n/** The look applied when the caller passes no `look` option. */\nexport const DEFAULT_LOOK: Look = 'technical';\n\n/**\n * Single source of truth for both `applyDefaults` (construction) and `setLook` (runtime), so the two\n * can't drift.\n *\n * `ambientOcclusion: false` on every look: GTAO is a heavy full-screen pass, so it stays opt-in\n * (`render.ambientOcclusion` or `setAmbientOcclusion(true)`) rather than costing every viewer 60fps.\n */\nexport const LOOK_PRESETS: Record<Look, LookPreset> = {\n\t// Fills shadows in (vs. showcase, which lets them fall off). IBL/fill kept modest so\n\t// env+hemisphere+ambient don't triple-stack into an ACES-washed look.\n\tstudio: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.75,\n\t\tambientIntensity: 0.4,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t},\n\t// Flat ambient kept low — a full flat ambient flattens shading toward milky grey regardless of\n\t// face orientation — with IBL near full and a little hemisphere fill for under-facing surfaces.\n\ttechnical: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 0.9,\n\t\tenvironmentIntensity: 1,\n\t\themisphereIntensity: 0.35,\n\t\tambientIntensity: 0.25,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t},\n\t// Fill pulled DOWN (low ambient/hemisphere) so light-to-shadow falloff stays strong for a\n\t// dramatic hero shot, unlike `studio` which fills shadows in.\n\tshowcase: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1.15,\n\t\tenvMapIntensity: 1.4,\n\t\tenvironmentIntensity: 1.25,\n\t\themisphereIntensity: 0.35,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false\n\t}\n};\n\n/** Baked at parse time (not toggleable at runtime). */\nexport function materialAppearanceForLook(look: Look): MaterialAppearanceOptions {\n\tconst preset = LOOK_PRESETS[look];\n\treturn {\n\t\tenvMapIntensity: preset.envMapIntensity,\n\t\tcullBackfaces: preset.cullBackfaces\n\t};\n}\n","// ============================================================================\n// Scene ownership: who put an object in the scene, and what may remove it\n// ============================================================================\n//\n// A live scene mixes content from three owners: the solve (replaced wholesale every solve),\n// viewer aids (grid/floor/labels, never replaced), and host apps drawing their own geometry\n// alongside the solve. `userData.source` records which, and `clearScene` consults it to decide\n// what a solve is allowed to destroy.\n//\n// App content carries an owner id (`app:<id>`) rather than a flat `'user'` so a host running\n// more than one app can clear its own geometry without touching another's, and so a scoped\n// solve can replace one app's results while leaving the rest standing.\n\nimport type * as THREE from 'three';\n\n/** Geometry produced by a solve. Replaced wholesale on the next one. */\nexport const SOURCE_COMPUTE = 'compute';\n\n/**\n * Host-added geometry with no owner id. Predates scoped ownership and still honoured everywhere\n * an `app:` scope is — new code should prefer {@link appSource}.\n */\nexport const SOURCE_USER = 'user';\n\nconst APP_PREFIX = 'app:';\n\n/** The `userData.source` tag for geometry owned by app `id` (`'pointcloud'` → `'app:pointcloud'`). */\nexport function appSource(id: string): string {\n\treturn `${APP_PREFIX}${id}`;\n}\n\n/** The app id from a source tag, or null if the tag isn't app-owned. */\nexport function appIdFromSource(source: unknown): string | null {\n\tif (typeof source !== 'string' || !source.startsWith(APP_PREFIX)) return null;\n\treturn source.slice(APP_PREFIX.length) || null;\n}\n\n/**\n * True for anything a host added rather than the solve — `'user'` or any `app:` scope. This is\n * the predicate `clearScene` uses, so it decides what survives a solve.\n */\nexport function isHostOwned(object: THREE.Object3D): boolean {\n\tconst source = object.userData?.source;\n\treturn source === SOURCE_USER || appIdFromSource(source) !== null;\n}\n\n/**\n * True for objects owned by `id`. Passing no id matches every host-owned object, which is what\n * `clearUserGeometry()` does.\n */\nexport function isOwnedBy(object: THREE.Object3D, id?: string): boolean {\n\tif (id === undefined) return isHostOwned(object);\n\treturn object.userData?.source === appSource(id);\n}\n","import * as THREE from 'three';\n\n/**\n * Single source of truth for \"which way is up, and what do front/right mean\" — camera framing, sun\n * position, ground offset, and view presets all derive from {@link buildUpBasis} rather than\n * hardcoding an axis, so a Y-up scene gets a correct horizon and sun instead of the below-horizon\n * result a hardcoded Z-up vector would give.\n *\n * `forward` is the camera's look direction (camera → model); a view preset's camera position is the\n * reverse (target → camera), see `camera-controller.ts`. `right` is `seed x up`, not `up x seed`, to\n * match Rhino's handedness: for Z-up this makes the Front-view camera look along +Y and the\n * Right-view camera look along -X (it sits at `right` = +X, facing back toward the origin).\n */\n\n/** Orthonormal frame derived from a scene up axis. All vectors are unit length. */\nexport interface UpBasis {\n\tup: THREE.Vector3;\n\tforward: THREE.Vector3;\n\tright: THREE.Vector3;\n}\n\n/** Ground-plane axes for a given up vector: Z-up yields forward = +Y, right = +X (Rhino's Front/Right). */\nexport function buildUpBasis(up: THREE.Vector3): UpBasis {\n\tconst u = up.clone().normalize();\n\n\t// Seed must not be (nearly) parallel to up, or the cross product is unstable.\n\tconst worldZ = new THREE.Vector3(0, 0, 1);\n\tconst worldY = new THREE.Vector3(0, 1, 0);\n\tconst seed = Math.abs(u.dot(worldZ)) > 0.9 ? worldY : worldZ;\n\n\tconst right = new THREE.Vector3().crossVectors(seed, u).normalize();\n\tconst forward = new THREE.Vector3().crossVectors(u, right).normalize();\n\n\treturn { up: u, forward, right };\n}\n\n/** Default 3/4 iso camera offset from the target (behind-left, above), scaled to `distance`. */\nexport function isoOffset(up: THREE.Vector3, distance: number): THREE.Vector3 {\n\tconst { forward, right, up: u } = buildUpBasis(up);\n\t// Normalize before scaling so `distance` is the true radius, not the diagonal of the raw sum.\n\treturn forward\n\t\t.clone()\n\t\t.multiplyScalar(-1)\n\t\t.add(right.clone().multiplyScalar(-1))\n\t\t.add(u)\n\t\t.normalize()\n\t\t.multiplyScalar(distance);\n}\n\n/** Default sun position: high above the model, offset to one side for a directional gradient. */\nexport function sunOffset(up: THREE.Vector3, sideDistance: number, height: number): THREE.Vector3 {\n\tconst { forward, right, up: u } = buildUpBasis(up);\n\treturn right\n\t\t.clone()\n\t\t.multiplyScalar(sideDistance)\n\t\t.add(forward.clone().multiplyScalar(sideDistance))\n\t\t.add(u.clone().multiplyScalar(height));\n}\n\n/**\n * Rotates an equirectangular environment map's horizon onto the scene's ground plane.\n *\n * Three's equirect mapping is hardcoded to Y-up: the HDR's horizon is assumed to lie in the XZ\n * plane with zenith along +Y. In a Z-up scene that leaves the environment on its side — horizon\n * vertical, lighting arriving from +Y instead of overhead. A neutral studio HDR hides this; any\n * sky/ground HDR makes it obvious.\n *\n * Returns the Euler rotating the map's native +Y zenith onto `up` (identity for Y-up). Apply to\n * BOTH `scene.environmentRotation` and `scene.backgroundRotation` — they're independent, and\n * setting only one desyncs background from lighting.\n */\nexport function environmentRotationFor(up: THREE.Vector3): THREE.Euler {\n\tconst u = up.clone().normalize();\n\tconst mapZenith = new THREE.Vector3(0, 1, 0);\n\n\tif (u.dot(mapZenith) > 0.9999) return new THREE.Euler();\n\n\t// Upside-down (-Y): setFromUnitVectors picks an arbitrary perpendicular axis for a 180° flip,\n\t// spinning the horizon. Roll about X instead so the horizon stays put.\n\tif (u.dot(mapZenith) < -0.9999) return new THREE.Euler(Math.PI, 0, 0);\n\n\tconst quaternion = new THREE.Quaternion().setFromUnitVectors(mapZenith, u);\n\treturn new THREE.Euler().setFromQuaternion(quaternion);\n}\n\n/** Which world axis the up vector most closely aligns with. */\nexport function upToAxis(up: THREE.Vector3): 'x' | 'y' | 'z' {\n\tconst ax = Math.abs(up.x);\n\tconst ay = Math.abs(up.y);\n\tconst az = Math.abs(up.z);\n\tif (ax >= ay && ax >= az) return 'x';\n\tif (ay >= az) return 'y';\n\treturn 'z';\n}\n","import * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport { computeCombinedBoundingBox, disposeObjectTree } from '../shared/index.js';\nimport { isHostOwned } from './scene-ownership.js';\nimport { isoOffset } from './up-axis';\n\nconst CAMERA_CONFIG = {\n\tHUGE_THRESHOLD: 10000,\n\tLARGE_THRESHOLD: 1000,\n\tSCALE_RATIO_THRESHOLD: 100,\n\tNEAR_PLANE_FACTOR: {\n\t\tTINY: 0.0001,\n\t\tSMALL: 0.001,\n\t\tNORMAL: 0.01\n\t},\n\tFAR_PLANE_FACTOR: {\n\t\tHUGE: 100,\n\t\tLARGE: 50,\n\t\tNORMAL: 20\n\t},\n\tINITIAL_DISTANCE_MULTIPLIER: 4\n};\n\n/** Replaces scene content with `meshes`, rescales the camera frustum to fit, and (first call only) positions the camera/controls. */\nexport function updateScene(\n\tscene: THREE.Scene,\n\tmeshes: THREE.Object3D[],\n\tcamera: THREE.PerspectiveCamera,\n\tcontrols: OrbitControls,\n\tinitialPositionSet: boolean\n) {\n\tclearScene(scene);\n\n\tif (meshes.length === 0) return;\n\n\tmeshes.forEach((mesh) => {\n\t\tscene.add(mesh);\n\t});\n\n\tconst unionBoundingBox = computeCombinedBoundingBox(meshes);\n\tconst center = unionBoundingBox.getCenter(new THREE.Vector3());\n\tconst size = unionBoundingBox.getSize(new THREE.Vector3());\n\tconst maxDim = Math.max(size.x, size.y, size.z);\n\n\t// Rescaled every call, not just the first, so near/far stay well-conditioned when geometry\n\t// size changes drastically between solves.\n\tconst scaleRatio = maxDim / Math.min(size.x || 1, size.y || 1, size.z || 1);\n\n\tif (scaleRatio > CAMERA_CONFIG.SCALE_RATIO_THRESHOLD || maxDim > CAMERA_CONFIG.HUGE_THRESHOLD) {\n\t\tcamera.near = maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.TINY;\n\t\tcamera.far = maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.HUGE;\n\t} else if (maxDim > CAMERA_CONFIG.LARGE_THRESHOLD) {\n\t\tcamera.near = maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.SMALL;\n\t\tcamera.far = maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.LARGE;\n\t} else {\n\t\tcamera.near = Math.max(0.01, maxDim * CAMERA_CONFIG.NEAR_PLANE_FACTOR.NORMAL);\n\t\tcamera.far = Math.max(2000, maxDim * CAMERA_CONFIG.FAR_PLANE_FACTOR.NORMAL);\n\t}\n\n\tcamera.updateProjectionMatrix();\n\n\t// Camera/controls are repositioned on first frame only. Zoom limits (min/maxDistance) are\n\t// deliberately NOT touched here: they're owned by the host via setupControls, and overwriting\n\t// them per solve would silently discard user-supplied configuration after the first update.\n\tif (!initialPositionSet) {\n\t\tconst distance = maxDim * CAMERA_CONFIG.INITIAL_DISTANCE_MULTIPLIER;\n\n\t\t// camera.up is already the configured sceneUp (initThree sets it before this runs), so the\n\t\t// iso offset stays consistent with whatever up-axis the viewer opened at.\n\t\tcamera.position.copy(center).add(isoOffset(camera.up, distance));\n\t\tcontrols.target.copy(center);\n\n\t\tcontrols.update();\n\t}\n}\n\n// Excluded from every content-bounds query: the grid is a huge plane that re-centers on the\n// camera each frame, so including it would make fit-to-view frame the camera's position instead\n// of the geometry.\nconst VIEWER_AID_IDS = new Set(['grid', 'floor', 'label-layer', 'measure']);\n\nfunction isViewerAid(object: THREE.Object3D): boolean {\n\tlet current: THREE.Object3D | null = object;\n\twhile (current) {\n\t\tif (typeof current.userData.id === 'string' && VIEWER_AID_IDS.has(current.userData.id)) {\n\t\t\treturn true;\n\t\t}\n\t\tcurrent = current.parent;\n\t}\n\treturn false;\n}\n\n/**\n * Bounds of the scene's renderable content, excluding viewer aids (grid/floor/labels/measure).\n * Shared by fit-to-view, pick-threshold scaling, camera framing (`setView`), and shadow-frustum\n * fitting so they all measure exactly the same box.\n */\nexport function computeContentBounds(scene: THREE.Scene): THREE.Box3 {\n\t// Refresh world matrices once up front so expandByObject reads current transforms, regardless\n\t// of when the caller invokes this.\n\tscene.updateMatrixWorld(true);\n\tconst box = new THREE.Box3();\n\tscene.traverse((object) => {\n\t\tconst renderable = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\tif (object.visible && !isViewerAid(object) && renderable.geometry) {\n\t\t\tbox.expandByObject(object);\n\t\t}\n\t});\n\treturn box;\n}\n\nconst PERSISTENT_SCENE_IDS = new Set(['floor', 'grid', 'label-layer']);\n\nexport function clearScene(scene: THREE.Scene): void {\n\t// Snapshot — removeFromParent below mutates scene.children during iteration.\n\tconst topLevel = [...scene.children];\n\n\ttopLevel.forEach((object) => {\n\t\t// Removing the label-layer group here would orphan it: the CSS2D renderer only finds labels\n\t\t// by walking the live scene, so labels added afterwards would never render.\n\t\tif (PERSISTENT_SCENE_IDS.has(object.userData.id)) return;\n\n\t\t// Host-added geometry (tagged by addUserGeometry, either plain 'user' or an app: scope)\n\t\t// persists across solves so it isn't lost when compute content is replaced.\n\t\tif (isHostOwned(object)) return;\n\n\t\t// Edge overlays are children of the meshes they outline, so this traversal disposes their\n\t\t// line geometries too — each overlay owns its geometry outright.\n\t\tdisposeObjectTree(object);\n\n\t\tobject.removeFromParent();\n\t});\n}\n","import * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport { computeContentBounds } from './three-helpers';\nimport { buildUpBasis } from './up-axis';\n\n/**\n * Runtime camera control: preset views, perspective⇄orthographic toggle, rotate lock.\n *\n * Centralized because projection switching swaps the camera object that OrbitControls drives, the\n * render loop renders, resize reshapes, and the raycaster picks with — {@link getActiveCamera} is\n * the one source of truth for all four call sites.\n *\n * Orthographic mirrors perspective's position/target with a frustum derived from perspective's FOV\n * and distance, so switching projections doesn't visually jump.\n */\n\nexport type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';\n\nexport type CameraProjection = 'perspective' | 'orthographic';\n\nexport interface CameraController {\n\t/** Swaps identity on {@link setProjection}. */\n\tgetActiveCamera(): THREE.Camera;\n\tgetProjection(): CameraProjection;\n\tsetProjection(projection: CameraProjection): void;\n\ttoggleProjection(): CameraProjection;\n\tsetView(preset: ViewPreset, animate?: boolean): void;\n\t/**\n\t * Frame current content from an explicit world-space direction (target → camera) instead of a\n\t * named preset — used by the nav-cube, whose clicked axis is a world axis.\n\t */\n\tsetViewDirection(direction: THREE.Vector3, animate?: boolean): void;\n\t/** Frame a world-space box from the current view direction. No-op on an empty box. */\n\tframeBounds(box: THREE.Box3, animate?: boolean): void;\n\tsetRotateEnabled(enabled: boolean): void;\n\tisRotateEnabled(): boolean;\n\tupdateAspect(width: number, height: number): void;\n\t/** Cancel any in-flight camera tween. Call on viewer teardown so ticks can't touch disposed controls. */\n\tdispose(): void;\n}\n\ninterface CameraControllerDeps {\n\tscene: THREE.Scene;\n\tperspective: THREE.PerspectiveCamera;\n\tcontrols: OrbitControls;\n\tonActiveCameraChange: (camera: THREE.Camera) => void;\n\t/** Drives presets, ortho camera up, and iso direction. Falls back to `perspective.up`. */\n\tup?: THREE.Vector3;\n}\n\n/**\n * Seven preset view directions (target → camera, unit vectors), derived from `up` rather than a\n * fixed Y-up table so Top/Front/… stay meaningful for Z-up Rhino scenes.\n *\n * `buildUpBasis`'s `forward` is camera→model; these are camera positions relative to target, so\n * \"front\" is `-forward`. Flipping this puts the camera behind the model and swaps left/right.\n */\nfunction buildViewDirections(up: THREE.Vector3): Record<ViewPreset, THREE.Vector3> {\n\tconst { up: u, forward, right } = buildUpBasis(up);\n\n\t// Camera positions are opposite the look direction: Rhino's Front looks along +Y from -Y.\n\tconst frontPosition = forward.clone().negate();\n\tconst rightPosition = right.clone();\n\n\treturn {\n\t\ttop: u.clone(),\n\t\tbottom: u.clone().negate(),\n\t\tfront: frontPosition.clone(),\n\t\tback: frontPosition.clone().negate(),\n\t\tright: rightPosition.clone(),\n\t\tleft: rightPosition.clone().negate(),\n\t\tiso: frontPosition\n\t\t\t.clone()\n\t\t\t.multiplyScalar(1.2)\n\t\t\t.add(rightPosition.clone())\n\t\t\t.add(u.clone())\n\t\t\t.normalize()\n\t};\n}\n\nexport function createCameraController(deps: CameraControllerDeps): CameraController {\n\tconst { scene, perspective, controls, onActiveCameraChange } = deps;\n\n\tconst up = (deps.up ?? perspective.up).clone().normalize();\n\tconst VIEW_DIRECTIONS = buildViewDirections(up);\n\n\tconst ortho = new THREE.OrthographicCamera(-1, 1, 1, -1, perspective.near, perspective.far);\n\tortho.up.copy(up);\n\n\tlet projection: CameraProjection = 'perspective';\n\tlet aspect = perspective.aspect;\n\n\tconst active = (): THREE.Camera => (projection === 'perspective' ? perspective : ortho);\n\n\t// Starting a new tween cancels any prior one — two loops would otherwise fight over the camera.\n\tlet activeTween: TweenHandle | null = null;\n\tconst cancelTween = () => {\n\t\tactiveTween?.cancel();\n\t\tactiveTween = null;\n\t};\n\n\t// Sizes the ortho frustum to match perspective's apparent size at the current distance.\n\tconst syncOrthoFrustum = () => {\n\t\t// Measure whichever camera is live: while ortho is active, OrbitControls moves ortho's\n\t\t// position (only its zoom changes), leaving perspective's distance stale.\n\t\tconst reference = projection === 'orthographic' ? ortho : perspective;\n\t\tconst distance = reference.position.distanceTo(controls.target);\n\t\tconst halfH = distance * Math.tan((perspective.fov * Math.PI) / 360);\n\t\tconst halfW = halfH * aspect;\n\t\tortho.left = -halfW;\n\t\tortho.right = halfW;\n\t\tortho.top = halfH;\n\t\tortho.bottom = -halfH;\n\t\tortho.near = perspective.near;\n\t\tortho.far = perspective.far;\n\t\tortho.updateProjectionMatrix();\n\t};\n\n\tconst setProjection = (next: CameraProjection) => {\n\t\tif (next === projection) return;\n\t\t// A tween mid-flight would keep lerping the OLD active camera after the swap.\n\t\tcancelTween();\n\n\t\tif (next === 'orthographic') {\n\t\t\tortho.position.copy(perspective.position);\n\t\t\tortho.up.copy(perspective.up);\n\t\t\tortho.lookAt(controls.target);\n\t\t\t// OrbitControls dollies ortho via `zoom`, not position — reset to 1 so a leftover zoom\n\t\t\t// from a prior 2D session doesn't double up with the freshly-derived frustum.\n\t\t\tortho.zoom = 1;\n\t\t\tsyncOrthoFrustum();\n\t\t} else {\n\t\t\t// Convert ortho zoom back to perspective DISTANCE (halfH / tan(fov/2)) — copying position\n\t\t\t// alone would discard any zooming done in 2D.\n\t\t\tconst halfH = (ortho.top - ortho.bottom) / (2 * ortho.zoom);\n\t\t\tconst distance = halfH / Math.tan((perspective.fov * Math.PI) / 360);\n\t\t\tconst direction = ortho.position.clone().sub(controls.target);\n\t\t\tif (direction.lengthSq() < 1e-12) direction.copy(up);\n\t\t\tdirection.normalize();\n\t\t\tperspective.position.copy(controls.target).add(direction.multiplyScalar(distance));\n\t\t}\n\n\t\tprojection = next;\n\t\tcontrols.object = active();\n\t\tcontrols.update();\n\t\tonActiveCameraChange(active());\n\t};\n\n\t// Positions the active camera along `direction` at the distance fitting `maxDim`, retargeting\n\t// controls at `center`. Ortho zoom resets and the frustum re-derives via syncOrthoFrustum —\n\t// position alone wouldn't change an orthographic view's apparent size.\n\tconst frame = (\n\t\tcenter: THREE.Vector3,\n\t\tmaxDim: number,\n\t\tdirection: THREE.Vector3,\n\t\tanimate: boolean\n\t) => {\n\t\tconst fov = perspective.fov * (Math.PI / 180);\n\t\tconst distance = (maxDim / (2 * Math.tan(fov / 2))) * 1.5;\n\n\t\tconst dir = nudgeOffPole(direction, up);\n\t\tconst toPosition = center.clone().add(dir.clone().multiplyScalar(distance));\n\n\t\tconst cam = active();\n\t\t// Reset zoom before re-deriving the frustum, else it multiplies in and defeats the fit.\n\t\tif (projection === 'orthographic') ortho.zoom = 1;\n\n\t\tcancelTween();\n\t\tif (animate) {\n\t\t\tactiveTween = animateMove(cam, controls, toPosition, center, () => {\n\t\t\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t\t\t});\n\t\t} else {\n\t\t\tcam.position.copy(toPosition);\n\t\t\tcontrols.target.copy(center);\n\t\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t\t\tcontrols.update();\n\t\t}\n\t};\n\n\tconst setViewDirection = (direction: THREE.Vector3, animate = true) => {\n\t\tconst box = computeContentBounds(scene);\n\t\tconst center = box.isEmpty() ? controls.target.clone() : box.getCenter(new THREE.Vector3());\n\t\tconst size = box.isEmpty() ? new THREE.Vector3(1, 1, 1) : box.getSize(new THREE.Vector3());\n\t\tconst maxDim = Math.max(size.x, size.y, size.z) || 1;\n\t\tframe(center, maxDim, direction, animate);\n\t};\n\n\tconst frameBounds = (box: THREE.Box3, animate = true) => {\n\t\tif (box.isEmpty()) return;\n\t\tconst center = box.getCenter(new THREE.Vector3());\n\t\tconst size = box.getSize(new THREE.Vector3());\n\t\tconst maxDim = Math.max(size.x, size.y, size.z) || 1;\n\t\t// Keep the user's current viewing direction; only the distance/target change.\n\t\tconst direction = active().position.clone().sub(controls.target);\n\t\tif (direction.lengthSq() < 1e-12) direction.copy(VIEW_DIRECTIONS.iso);\n\t\tframe(center, maxDim, direction.normalize(), animate);\n\t};\n\n\tconst setView = (preset: ViewPreset, animate = true) => {\n\t\tsetViewDirection(VIEW_DIRECTIONS[preset], animate);\n\t};\n\n\tconst setRotateEnabled = (enabled: boolean) => {\n\t\tcontrols.enableRotate = enabled;\n\t};\n\n\tconst updateAspect = (width: number, height: number) => {\n\t\taspect = height === 0 ? aspect : width / height;\n\t\tif (projection === 'orthographic') syncOrthoFrustum();\n\t};\n\n\treturn {\n\t\tgetActiveCamera: active,\n\t\tgetProjection: () => projection,\n\t\tsetProjection,\n\t\ttoggleProjection: () => {\n\t\t\tsetProjection(projection === 'perspective' ? 'orthographic' : 'perspective');\n\t\t\treturn projection;\n\t\t},\n\t\tsetView,\n\t\tsetViewDirection,\n\t\tframeBounds,\n\t\tsetRotateEnabled,\n\t\tisRotateEnabled: () => controls.enableRotate,\n\t\tupdateAspect,\n\t\tdispose: cancelTween\n\t};\n}\n\n/**\n * Nudges a top/bottom view direction a ~0.5° tilt off the up axis; other presets pass through\n * unchanged. Looking exactly down `up` is an OrbitControls singularity: camera direction coincides\n * with `camera.up`, azimuth is undefined, and the first drag snaps the view.\n *\n * At the pole, `camera.up` can't define roll, so the tilt direction does instead. Both poles lean\n * toward `-forward` to reproduce Rhino's convention (Top has +forward at screen-top; Bottom mirrors\n * about the horizontal axis, matching Rhino where the far side reads backwards too) — leaning the\n * poles opposite ways also mirrors correctly, but rolled 180° from Rhino.\n */\nfunction nudgeOffPole(dir: THREE.Vector3, up: THREE.Vector3): THREE.Vector3 {\n\tconst { up: u, forward } = buildUpBasis(up);\n\tconst d = dir.clone().normalize();\n\tif (Math.abs(d.dot(u)) < 0.9999) return dir;\n\n\tconst inPlane = forward.clone().negate();\n\n\tconst tilt = (0.5 * Math.PI) / 180;\n\treturn d\n\t\t.multiplyScalar(Math.cos(tilt))\n\t\t.add(inPlane.multiplyScalar(Math.sin(tilt)))\n\t\t.normalize();\n}\n\nconst easeOut = (t: number) => 1 - Math.pow(1 - t, 3);\n\n/** Handle to a running camera tween, so callers can stop it (new move, projection swap, teardown). */\ninterface TweenHandle {\n\tcancel(): void;\n}\n\n/** Tweens camera position + controls target; returns a cancel handle for teardown/preemption. */\nfunction animateMove(\n\tcamera: THREE.Camera,\n\tcontrols: OrbitControls,\n\ttoPosition: THREE.Vector3,\n\ttoTarget: THREE.Vector3,\n\tonTick: () => void,\n\tdurationMs = 250\n): TweenHandle {\n\tconst fromPosition = camera.position.clone();\n\tconst fromTarget = controls.target.clone();\n\tconst startTime = performance.now();\n\tlet rafId: number | null = null;\n\n\tconst tick = () => {\n\t\trafId = null;\n\t\tconst t = easeOut(Math.min((performance.now() - startTime) / durationMs, 1));\n\t\tcamera.position.lerpVectors(fromPosition, toPosition, t);\n\t\tcontrols.target.lerpVectors(fromTarget, toTarget, t);\n\t\tonTick();\n\t\tcontrols.update();\n\t\tif (t < 1) rafId = requestAnimationFrame(tick);\n\t};\n\n\trafId = requestAnimationFrame(tick);\n\n\treturn {\n\t\tcancel: () => {\n\t\t\tif (rafId !== null) {\n\t\t\t\tcancelAnimationFrame(rafId);\n\t\t\t\trafId = null;\n\t\t\t}\n\t\t}\n\t};\n}\n","import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';\n\n// ============================================================================\n// Line geometry construction\n// ============================================================================\n\n// No cache here. An earlier WeakMap keyed per source BufferGeometry leaked ~400 live GPU entries\n// where 8 were expected — entries never vanished with their source — and measured 0/80 hits in a\n// real scrubbing loop. Every overlay builds and owns its own line geometry.\n\nexport interface EdgeGeometryEntry {\n\tgeometry: LineSegmentsGeometry;\n\tsegmentCount: number;\n\t/** {@link SPACING_PERCENTILE} quantile of segment length; drives the density fade in overlay.ts. */\n\tedgeSpacing: number;\n}\n\n// A low quantile (not mean) tracks the fine detail: real parts mix a few long silhouette edges\n// with many short ones at wildly different scale (e.g. 1mm laminations on a 10m part) — an\n// average would sit between the two and never trigger the fade for either.\nconst SPACING_PERCENTILE = 0.15;\n\n// Stride sampling keeps this O(1) on millions of segments.\nconst SPACING_SAMPLE_LIMIT = 4096;\n\nfunction edgeSpacingOf(segments: Float32Array): number {\n\tconst segmentCount = Math.floor(segments.length / 6);\n\tif (segmentCount === 0) return Infinity;\n\n\tconst stride = Math.max(1, Math.ceil(segmentCount / SPACING_SAMPLE_LIMIT));\n\tconst lengths: number[] = [];\n\tfor (let s = 0; s < segmentCount; s += stride) {\n\t\tconst i = s * 6;\n\t\tconst length = Math.hypot(\n\t\t\tsegments[i + 3] - segments[i],\n\t\t\tsegments[i + 4] - segments[i + 1],\n\t\t\tsegments[i + 5] - segments[i + 2]\n\t\t);\n\t\tif (length > 0) lengths.push(length);\n\t}\n\tif (lengths.length === 0) return Infinity;\n\n\tlengths.sort((a, b) => a - b);\n\treturn lengths[Math.min(lengths.length - 1, Math.floor(lengths.length * SPACING_PERCENTILE))]!;\n}\n\n// LineSegmentsGeometry adopts `segments` as its backing store without copying — treat it as\n// read-only from here on.\nexport function buildLineGeometry(segments: Float32Array): EdgeGeometryEntry {\n\tconst geometry = new LineSegmentsGeometry();\n\tgeometry.setPositions(segments);\n\treturn {\n\t\tgeometry,\n\t\tsegmentCount: segments.length / 6,\n\t\tedgeSpacing: edgeSpacingOf(segments)\n\t};\n}\n","/**\n * Dependency-free crease/boundary edge extraction — the hot core behind `addEdges`. Semantically\n * a drop-in for `THREE.EdgesGeometry(geometry, angle)` (same welding, crease test, boundary\n * handling, 3+-face quirks), but operates on raw typed arrays with numeric hashing instead of\n * three's per-vertex string keys, for speed and Worker portability — see the no-outer-captures\n * constraint on {@link extractEdgeSegments} itself.\n */\n\n/** Vertex ids pack two-per-double in edge keys; above 2^26 vertices the packing overflows. */\nexport const MAX_EXTRACT_VERTICES = 0x4000000; // 2^26\n\n/**\n * @param index - Triangle indices, or null for non-indexed soup.\n * @returns Segment endpoint pairs, same layout as `EdgesGeometry.attributes.position.array`.\n * @throws If `positions` holds ≥ 2^26 vertices ({@link MAX_EXTRACT_VERTICES}) — callers fall\n * back to `THREE.EdgesGeometry`.\n */\nexport function extractEdgeSegments(\n\tpositions: Float32Array,\n\tindex: Uint32Array | Uint16Array | null,\n\tthresholdAngleDeg: number\n): Float32Array {\n\t// No outer captures besides Math — this function is stringified via toString() to run inside\n\t// a Worker ({@link edgeExtractWorkerSource}). Don't reference anything outside this body.\n\tconst PRECISION = 1e4; // same quantization grid as THREE.EdgesGeometry\n\tconst ID_BITS = 0x4000000; // 2^26 — two ids pack into one float64-exact integer key\n\tconst thresholdDot = Math.cos((Math.PI / 180) * thresholdAngleDeg);\n\n\tconst vertexCount = positions.length / 3;\n\tif (vertexCount >= ID_BITS) {\n\t\tthrow new Error(`extractEdgeSegments: ${vertexCount} vertices exceeds 2^26 limit`);\n\t}\n\n\t// --- Weld vertices on the quantization grid → canonical id per vertex -------------------\n\t// Rounded coords stay float64 (huge coordinates stay exact where int32 would overflow); only\n\t// the hash truncates to int32 — equality always compares the exact float64 values.\n\tconst quantX = new Float64Array(vertexCount);\n\tconst quantY = new Float64Array(vertexCount);\n\tconst quantZ = new Float64Array(vertexCount);\n\tfor (let v = 0; v < vertexCount; v++) {\n\t\tquantX[v] = Math.round(positions[3 * v] * PRECISION);\n\t\tquantY[v] = Math.round(positions[3 * v + 1] * PRECISION);\n\t\tquantZ[v] = Math.round(positions[3 * v + 2] * PRECISION);\n\t}\n\n\t// Open-addressed table (linear probing): slot → first vertex id seen at that grid point.\n\tlet capacity = 16;\n\twhile (capacity < vertexCount * 2) capacity <<= 1;\n\tconst mask = capacity - 1;\n\tconst table = new Int32Array(capacity).fill(-1);\n\tconst canonical = new Int32Array(vertexCount);\n\tfor (let v = 0; v < vertexCount; v++) {\n\t\tlet slot =\n\t\t\t(Math.imul(quantX[v] | 0, 73856093) ^\n\t\t\t\tMath.imul(quantY[v] | 0, 19349663) ^\n\t\t\t\tMath.imul(quantZ[v] | 0, 83492791)) &\n\t\t\tmask;\n\t\tfor (;;) {\n\t\t\tconst existing = table[slot];\n\t\t\tif (existing === -1) {\n\t\t\t\ttable[slot] = v;\n\t\t\t\tcanonical[v] = v;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tquantX[existing] === quantX[v] &&\n\t\t\t\tquantY[existing] === quantY[v] &&\n\t\t\t\tquantZ[existing] === quantZ[v]\n\t\t\t) {\n\t\t\t\tcanonical[v] = existing;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tslot = (slot + 1) & mask;\n\t\t}\n\t}\n\n\t// --- Growable segment output ------------------------------------------------------------\n\tlet out = new Float32Array(4096);\n\tlet outLength = 0;\n\tconst emit = (i0: number, i1: number): void => {\n\t\tif (outLength + 6 > out.length) {\n\t\t\tconst grown = new Float32Array(out.length * 2);\n\t\t\tgrown.set(out);\n\t\t\tout = grown;\n\t\t}\n\t\tout[outLength++] = positions[3 * i0];\n\t\tout[outLength++] = positions[3 * i0 + 1];\n\t\tout[outLength++] = positions[3 * i0 + 2];\n\t\tout[outLength++] = positions[3 * i1];\n\t\tout[outLength++] = positions[3 * i1 + 1];\n\t\tout[outLength++] = positions[3 * i1 + 2];\n\t};\n\n\t// --- Walk triangles, pairing opposite-winding edges -------------------------------------\n\t// Mirrors EdgesGeometry: a directed edge a→b matches a pending b→a; on match the segment is\n\t// kept iff the face normals differ beyond the threshold, and the pending entry is tombstoned\n\t// (key kept, value -1) so a third face on the same edge re-registers it. Unmatched entries at\n\t// the end are boundary edges and always emitted.\n\tconst edgeSlots = new Map<number, number>(); // directed key → pending-edge slot, -1 = matched\n\tconst pendingIndex0: number[] = [];\n\tconst pendingIndex1: number[] = [];\n\tconst pendingNormals: number[] = [];\n\n\tconst triCount = (index ? index.length : vertexCount) / 3;\n\tfor (let t = 0; t < triCount; t++) {\n\t\tconst i0 = index ? index[3 * t] : 3 * t;\n\t\tconst i1 = index ? index[3 * t + 1] : 3 * t + 1;\n\t\tconst i2 = index ? index[3 * t + 2] : 3 * t + 2;\n\t\tconst a = canonical[i0];\n\t\tconst b = canonical[i1];\n\t\tconst c = canonical[i2];\n\n\t\t// Degenerate on the quantization grid — skip, as EdgesGeometry does.\n\t\tif (a === b || b === c || c === a) continue;\n\n\t\t// Face normal, computed exactly as Triangle.getNormal: normalize((c-b) × (a-b)).\n\t\tconst e0x = positions[3 * i2] - positions[3 * i1];\n\t\tconst e0y = positions[3 * i2 + 1] - positions[3 * i1 + 1];\n\t\tconst e0z = positions[3 * i2 + 2] - positions[3 * i1 + 2];\n\t\tconst e1x = positions[3 * i0] - positions[3 * i1];\n\t\tconst e1y = positions[3 * i0 + 1] - positions[3 * i1 + 1];\n\t\tconst e1z = positions[3 * i0 + 2] - positions[3 * i1 + 2];\n\t\tlet nx = e0y * e1z - e0z * e1y;\n\t\tlet ny = e0z * e1x - e0x * e1z;\n\t\tlet nz = e0x * e1y - e0y * e1x;\n\t\tconst lengthSq = nx * nx + ny * ny + nz * nz;\n\t\tif (lengthSq > 0) {\n\t\t\tconst inverseLength = 1 / Math.sqrt(lengthSq);\n\t\t\tnx *= inverseLength;\n\t\t\tny *= inverseLength;\n\t\t\tnz *= inverseLength;\n\t\t} else {\n\t\t\tnx = 0;\n\t\t\tny = 0;\n\t\t\tnz = 0;\n\t\t}\n\n\t\tfor (let j = 0; j < 3; j++) {\n\t\t\tlet from: number;\n\t\t\tlet to: number;\n\t\t\tlet fromCanonical: number;\n\t\t\tlet toCanonical: number;\n\t\t\tif (j === 0) {\n\t\t\t\tfrom = i0;\n\t\t\t\tto = i1;\n\t\t\t\tfromCanonical = a;\n\t\t\t\ttoCanonical = b;\n\t\t\t} else if (j === 1) {\n\t\t\t\tfrom = i1;\n\t\t\t\tto = i2;\n\t\t\t\tfromCanonical = b;\n\t\t\t\ttoCanonical = c;\n\t\t\t} else {\n\t\t\t\tfrom = i2;\n\t\t\t\tto = i0;\n\t\t\t\tfromCanonical = c;\n\t\t\t\ttoCanonical = a;\n\t\t\t}\n\n\t\t\tconst reverseKey = toCanonical * ID_BITS + fromCanonical;\n\t\t\tconst reverseSlot = edgeSlots.get(reverseKey);\n\t\t\tif (reverseSlot !== undefined && reverseSlot !== -1) {\n\t\t\t\tconst dot =\n\t\t\t\t\tnx * pendingNormals[3 * reverseSlot] +\n\t\t\t\t\tny * pendingNormals[3 * reverseSlot + 1] +\n\t\t\t\t\tnz * pendingNormals[3 * reverseSlot + 2];\n\t\t\t\tif (dot <= thresholdDot) emit(from, to);\n\t\t\t\tedgeSlots.set(reverseKey, -1);\n\t\t\t} else {\n\t\t\t\tconst forwardKey = fromCanonical * ID_BITS + toCanonical;\n\t\t\t\tif (!edgeSlots.has(forwardKey)) {\n\t\t\t\t\tconst slot = pendingIndex0.length;\n\t\t\t\t\tedgeSlots.set(forwardKey, slot);\n\t\t\t\t\tpendingIndex0.push(from);\n\t\t\t\t\tpendingIndex1.push(to);\n\t\t\t\t\tpendingNormals.push(nx, ny, nz);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Unmatched edges are boundaries — always kept ---------------------------------------\n\tfor (const slot of edgeSlots.values()) {\n\t\tif (slot !== -1) emit(pendingIndex0[slot], pendingIndex1[slot]);\n\t}\n\n\treturn out.slice(0, outLength);\n}\n\n/**\n * Worker source running {@link extractEdgeSegments} off the main thread. Protocol: receives\n * `{id, positions, index, thresholdAngle}`, replies `{id, segments}` (buffer transferred) or\n * `{id, error}`.\n *\n * Relies on `extractEdgeSegments` stringifying to standalone code — guarded by a unit test that\n * evals this source in isolation.\n */\nexport function edgeExtractWorkerSource(): string {\n\treturn [\n\t\t`const extract = ${extractEdgeSegments.toString()};`,\n\t\t`self.onmessage = (event) => {`,\n\t\t` const { id, positions, index, thresholdAngle } = event.data;`,\n\t\t` try {`,\n\t\t` const segments = extract(positions, index, thresholdAngle);`,\n\t\t` self.postMessage({ id, segments }, [segments.buffer]);`,\n\t\t` } catch (error) {`,\n\t\t` self.postMessage({ id, error: String((error && error.message) || error) });`,\n\t\t` }`,\n\t\t`};`\n\t].join('\\n');\n}\n","import * as THREE from 'three';\n\n/** Crisp boundary/crease edges overlaid on meshes. See the layer README for depth/perf strategy. */\nexport interface EdgeOptions {\n\t/** Default: each overlay derives its color from its own mesh's material (see {@link DEFAULT_EDGE_COLOR}). */\n\tcolor?: THREE.ColorRepresentation;\n\t/** How far to darken the derived edge color toward black, 0-1 (default 0.75). No-op when `color` is set. */\n\tdarken?: number;\n\t/** Edge thickness in CSS px. Default 1.5. */\n\twidth?: number;\n\t/** Crease angle in degrees; an edge survives only where its two faces differ by more. Default 44. */\n\tthresholdAngle?: number;\n\t/**\n\t * Fade an overlay out as its own edges crowd together on screen (default true). Edges draw at\n\t * constant pixel width, so dense edges (e.g. millimetre-pitch laminations on sheet goods) merge\n\t * into a dark smear at normal zoom; fading by edge density rather than mesh size catches that\n\t * while leaving sparsely-edged geometry fully drawn.\n\t */\n\tdistanceFade?: boolean;\n\t/**\n\t * Skip meshes above this triangle count entirely (default 4M) — extraction time is linear in\n\t * triangles, and past this bound even the worker path burns seconds for a look the screen-space\n\t * fallback approximates at constant cost. Skipped meshes are tagged\n\t * `userData.edgesSkipped = 'triangle-cap'`.\n\t */\n\tmaxTriangles?: number;\n\t/**\n\t * Above this many extracted segments (default 2M), an overlay drops the distance fade and renders\n\t * opaque instead — millions of blended fat-line quads are a fill-rate cliff; opaque ones aren't.\n\t */\n\tmaxSegments?: number;\n}\n\n/** Tag on edge overlays so pick/fit/clear logic can recognize and skip or dispose them. */\nexport const EDGE_USERDATA_KIND = 'edge-overlay';\n\n/** `userData.edgesSkipped` value; see {@link EdgeOptions.maxTriangles}. */\nexport const EDGES_SKIPPED_TRIANGLE_CAP = 'triangle-cap';\n\nexport const DEFAULT_EDGE_COLOR = 0x222222;\nconst DEFAULT_EDGE_WIDTH = 1.5;\nconst DEFAULT_THRESHOLD_ANGLE = 44;\nconst DEFAULT_DARKEN = 0.75;\nconst DEFAULT_MAX_TRIANGLES = 4_000_000;\nconst DEFAULT_MAX_SEGMENTS = 2_000_000;\n\n// Below this triangle count a worker round-trip would cost more than the extraction itself, so it\n// runs inline even on the async path.\nexport const INLINE_TRIANGLE_BUDGET = 25_000;\n\n// Fade band as mean on-screen gap between neighbouring edges, in px: opaque at/above\n// FADE_START_PX, gone at/below FADE_END_PX, linear between. Density-based rather than\n// mesh/bounding-sphere-based, because a big part with sub-pixel edge spacing still draws every\n// line at full width — the old bounding-sphere rule missed that (the sphere still covers most of\n// the viewport). Band sits just above 1px, where constant-width lines start visibly overlapping.\nexport const FADE_START_PX = 4;\nexport const FADE_END_PX = 1;\n\n// Units-only pull-forward, deliberately no slope (factor) term: a slope term scales with the\n// polygon's dZ/dpixel, huge at grazing angles, and applied to surfaces (the old strategy) it\n// pushed grazing faces back further than the mm-scale gaps between stacked parts — geometry\n// behind a wall then won the depth test and bled through the wall's own edges. A fixed\n// quantization-step bias on the lines instead lifts an edge off its own surface without reaching\n// across a gap to a neighbouring part.\n//\n// This bias is only safe as long as a depth ULP stays small, which is what near-plane.ts's dynamic\n// near-plane fitter guarantees. Weakening that fit makes this bias start to bleed.\nexport const EDGE_OFFSET_FACTOR = 0;\nexport const EDGE_OFFSET_UNITS = -1; // negative = toward the camera\n\nexport interface ResolvedOptions {\n\tforcedColor: THREE.Color | null;\n\tdarken: number;\n\twidth: number;\n\tthresholdAngle: number;\n\tdistanceFade: boolean;\n\tmaxTriangles: number;\n\tmaxSegments: number;\n}\n\nexport function resolveOptions(options: EdgeOptions): ResolvedOptions {\n\treturn {\n\t\tforcedColor: options.color != null ? new THREE.Color(options.color) : null,\n\t\tdarken: THREE.MathUtils.clamp(options.darken ?? DEFAULT_DARKEN, 0, 1),\n\t\twidth: options.width ?? DEFAULT_EDGE_WIDTH,\n\t\tthresholdAngle: options.thresholdAngle ?? DEFAULT_THRESHOLD_ANGLE,\n\t\tdistanceFade: options.distanceFade ?? true,\n\t\tmaxTriangles: options.maxTriangles ?? DEFAULT_MAX_TRIANGLES,\n\t\tmaxSegments: options.maxSegments ?? DEFAULT_MAX_SEGMENTS\n\t};\n}\n","import * as THREE from 'three';\n\nimport {\n\tMAX_EXTRACT_VERTICES,\n\tedgeExtractWorkerSource,\n\textractEdgeSegments\n} from '../edge-extract.js';\nimport { INLINE_TRIANGLE_BUDGET } from './options.js';\n\n// ============================================================================\n// Segment extraction — fast path, worker offload\n// ============================================================================\n\nexport function triangleCountOf(geometry: THREE.BufferGeometry): number {\n\tconst position = geometry.getAttribute('position');\n\tif (!position) return 0;\n\treturn (geometry.index ? geometry.index.count : position.count) / 3;\n}\n\ninterface FastPathData {\n\tpositions: Float32Array;\n\tindex: Uint32Array | Uint16Array | null;\n}\n\n// Fast extractor needs plain non-interleaved float32 xyz + typed index arrays. Anything exotic\n// (interleaved, float64, morphed) falls back to THREE.EdgesGeometry instead.\nfunction fastPathData(geometry: THREE.BufferGeometry): FastPathData | null {\n\tconst position = geometry.getAttribute('position');\n\tif (\n\t\t!position ||\n\t\t(position as THREE.InterleavedBufferAttribute).isInterleavedBufferAttribute ||\n\t\tposition.itemSize !== 3 ||\n\t\t!(position.array instanceof Float32Array) ||\n\t\tposition.count >= MAX_EXTRACT_VERTICES\n\t) {\n\t\treturn null;\n\t}\n\tconst index = geometry.index;\n\tif (index && !(index.array instanceof Uint32Array) && !(index.array instanceof Uint16Array)) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tpositions: position.array,\n\t\tindex: index ? (index.array as Uint32Array | Uint16Array) : null\n\t};\n}\n\n// FNV-1a over sampled head+tail words of position/index plus lengths and crease angle. Sampling\n// keeps this ~free at millions of vertices; a collision needs identical lengths AND sampled regions.\nfunction contentKey(data: FastPathData, thresholdAngle: number): string {\n\tconst SAMPLE_WORDS = 4096;\n\tlet hash = 0x811c9dc5;\n\tconst mix = (word: number): void => {\n\t\thash ^= word;\n\t\thash = Math.imul(hash, 0x01000193);\n\t};\n\n\tconst words = new Uint32Array(\n\t\tdata.positions.buffer,\n\t\tdata.positions.byteOffset,\n\t\tdata.positions.length\n\t);\n\tconst head = Math.min(SAMPLE_WORDS, words.length);\n\tfor (let i = 0; i < head; i++) mix(words[i]);\n\tfor (let i = Math.max(head, words.length - SAMPLE_WORDS); i < words.length; i++) mix(words[i]);\n\n\tlet indexLength = 0;\n\tif (data.index) {\n\t\tindexLength = data.index.length;\n\t\tconst headIndex = Math.min(SAMPLE_WORDS, indexLength);\n\t\tfor (let i = 0; i < headIndex; i++) mix(data.index[i]);\n\t\tfor (let i = Math.max(headIndex, indexLength - SAMPLE_WORDS); i < indexLength; i++) {\n\t\t\tmix(data.index[i]);\n\t\t}\n\t}\n\n\treturn `${thresholdAngle}:${data.positions.length}:${indexLength}:${hash >>> 0}`;\n}\n\nfunction extractViaThree(geometry: THREE.BufferGeometry, thresholdAngle: number): Float32Array {\n\tconst edges = new THREE.EdgesGeometry(geometry, thresholdAngle);\n\tconst positions = edges.attributes.position\n\t\t? (edges.attributes.position.array as Float32Array)\n\t\t: new Float32Array(0);\n\tedges.dispose(); // frees only GPU-side state; the CPU array is the return value\n\treturn positions;\n}\n\nexport function extractSegmentsSync(\n\tgeometry: THREE.BufferGeometry,\n\tthresholdAngle: number\n): Float32Array {\n\tconst data = fastPathData(geometry);\n\tif (!data) return extractViaThree(geometry, thresholdAngle);\n\n\treturn extractEdgeSegments(data.positions, data.index, thresholdAngle);\n}\n\n// --- Worker offload -----------------------------------------------------------------------------\n\ninterface PendingRequest {\n\tresolve: (segments: Float32Array) => void;\n\treject: (error: Error) => void;\n}\n\nlet extractionWorker: Worker | null | undefined; // undefined = not yet tried, null = unavailable\nconst pendingRequests = new Map<number, PendingRequest>();\nlet nextRequestId = 1;\n\nfunction getExtractionWorker(): Worker | null {\n\tif (extractionWorker !== undefined) return extractionWorker;\n\tif (\n\t\ttypeof Worker === 'undefined' ||\n\t\ttypeof Blob === 'undefined' ||\n\t\ttypeof URL === 'undefined' ||\n\t\ttypeof URL.createObjectURL !== 'function'\n\t) {\n\t\textractionWorker = null;\n\t\treturn null;\n\t}\n\ttry {\n\t\t// Blob URL keeps this bundler-agnostic (no `new Worker(new URL(...))`). Never revoked:\n\t\t// revoking before the worker finishes fetching is unspecified behavior, and this is a\n\t\t// process-lifetime singleton.\n\t\tconst url = URL.createObjectURL(\n\t\t\tnew Blob([edgeExtractWorkerSource()], { type: 'text/javascript' })\n\t\t);\n\t\tconst worker = new Worker(url);\n\t\tworker.onmessage = (event: MessageEvent) => {\n\t\t\tconst { id, segments, error } = event.data as {\n\t\t\t\tid: number;\n\t\t\t\tsegments?: Float32Array;\n\t\t\t\terror?: string;\n\t\t\t};\n\t\t\tconst pending = pendingRequests.get(id);\n\t\t\tif (!pending) return;\n\t\t\tpendingRequests.delete(id);\n\t\t\tif (segments) pending.resolve(segments);\n\t\t\telse pending.reject(new Error(error ?? 'edge extraction failed in worker'));\n\t\t};\n\t\tworker.onerror = () => {\n\t\t\t// Worker died (CSP, OOM, script error): fail everything in flight and never retry the\n\t\t\t// worker this session — callers fall back to inline extraction.\n\t\t\tfor (const pending of pendingRequests.values()) {\n\t\t\t\tpending.reject(new Error('edge extraction worker crashed'));\n\t\t\t}\n\t\t\tpendingRequests.clear();\n\t\t\tworker.terminate();\n\t\t\textractionWorker = null;\n\t\t};\n\t\textractionWorker = worker;\n\t} catch {\n\t\textractionWorker = null;\n\t}\n\treturn extractionWorker;\n}\n\nfunction extractInWorker(\n\tworker: Worker,\n\tdata: FastPathData,\n\tthresholdAngle: number\n): Promise<Float32Array> {\n\treturn new Promise<Float32Array>((resolve, reject) => {\n\t\tconst id = nextRequestId++;\n\t\tpendingRequests.set(id, { resolve, reject });\n\t\t// Copy before transfer — the originals back the render geometry.\n\t\tconst positions = data.positions.slice();\n\t\tconst index = data.index ? data.index.slice() : null;\n\t\tconst transfer: Transferable[] = [positions.buffer];\n\t\tif (index) transfer.push(index.buffer);\n\t\tworker.postMessage({ id, positions, index, thresholdAngle }, transfer);\n\t});\n}\n\n// In-flight dedupe: meshes with identical content share one worker round-trip.\nconst inFlightExtractions = new Map<string, Promise<Float32Array>>();\n\nexport function extractSegmentsAsync(\n\tgeometry: THREE.BufferGeometry,\n\tthresholdAngle: number\n): Promise<Float32Array> {\n\tconst data = fastPathData(geometry);\n\tif (!data || triangleCountOf(geometry) < INLINE_TRIANGLE_BUDGET) {\n\t\treturn Promise.resolve(extractSegmentsSync(geometry, thresholdAngle));\n\t}\n\n\tconst key = contentKey(data, thresholdAngle);\n\tconst inFlight = inFlightExtractions.get(key);\n\tif (inFlight) return inFlight;\n\n\tconst worker = getExtractionWorker();\n\tif (!worker) return Promise.resolve(extractSegmentsSync(geometry, thresholdAngle));\n\n\tconst request = extractInWorker(worker, data, thresholdAngle)\n\t\t.catch(() => extractEdgeSegments(data.positions, data.index, thresholdAngle))\n\t\t.finally(() => {\n\t\t\tinFlightExtractions.delete(key);\n\t\t});\n\tinFlightExtractions.set(key, request);\n\treturn request;\n}\n","import * as THREE from 'three';\nimport { LineMaterial } from 'three/addons/lines/LineMaterial.js';\nimport { LineSegments2 } from 'three/addons/lines/LineSegments2.js';\n\nimport type { EdgeGeometryEntry } from './line-geometry.js';\nimport {\n\tDEFAULT_EDGE_COLOR,\n\tEDGE_OFFSET_FACTOR,\n\tEDGE_OFFSET_UNITS,\n\tEDGE_USERDATA_KIND,\n\tFADE_END_PX,\n\tFADE_START_PX,\n\ttype ResolvedOptions\n} from './options.js';\n\n// ============================================================================\n// Overlay construction\n// ============================================================================\n\n// Multiplicative darkening (not lerp-to-black) preserves hue and desaturates gently; a near-black\n// surface just yields near-black edges.\nfunction deriveEdgeColor(mesh: THREE.Mesh, darken: number): THREE.Color {\n\tconst material = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;\n\tconst source = (material as { color?: THREE.Color } | null)?.color;\n\tif (!source) return new THREE.Color(DEFAULT_EDGE_COLOR);\n\treturn source.clone().multiplyScalar(1 - darken);\n}\n\n/** Pools materials by color+fade so overlays sharing both share one `LineMaterial` instance. */\nexport class MaterialPool {\n\tprivate readonly byKey = new Map<number, LineMaterial>();\n\tconstructor(private readonly options: ResolvedOptions) {}\n\n\tfor(mesh: THREE.Mesh, fade: boolean): LineMaterial {\n\t\tconst color = this.options.forcedColor ?? deriveEdgeColor(mesh, this.options.darken);\n\t\tconst key = color.getHex() * 2 + (fade ? 1 : 0);\n\t\tlet material = this.byKey.get(key);\n\t\tif (!material) {\n\t\t\tmaterial = createEdgeMaterial(color, this.options.width, fade);\n\t\t\tthis.byKey.set(key, material);\n\t\t}\n\t\treturn material;\n\t}\n\n\t/** Dispose any material no overlay adopted (e.g. every mesh was skipped or cancelled). */\n\tdisposeUnused(created: LineSegments2[]): void {\n\t\tconst used = new Set(created.map((overlay) => overlay.material));\n\t\tfor (const material of this.byKey.values()) {\n\t\t\tif (!used.has(material)) material.dispose();\n\t\t}\n\t}\n}\n\nfunction createEdgeMaterial(\n\tcolor: THREE.Color,\n\twidth: number,\n\tdistanceFade: boolean\n): LineMaterial {\n\t// LineMaterialParameters omits linewidth/opacity from its type though both exist at runtime.\n\tconst material = new LineMaterial({ color });\n\t(material as LineMaterial & { linewidth: number }).linewidth = width;\n\t// Lifts lines toward the camera by a couple of depth-quantization steps so they win z-fighting\n\t// against the surface they were extracted from, without moving the surface itself.\n\tmaterial.polygonOffset = true;\n\tmaterial.polygonOffsetFactor = EDGE_OFFSET_FACTOR;\n\tmaterial.polygonOffsetUnits = EDGE_OFFSET_UNITS;\n\t// Set once here, not per draw: flipping `transparent` after the render list is built wouldn't\n\t// re-sort the object into the transparent pass.\n\tif (distanceFade) material.transparent = true;\n\treturn material;\n}\n\nexport function buildEdgeOverlay(\n\tentry: EdgeGeometryEntry,\n\tmaterial: LineMaterial,\n\tdistanceFade: boolean\n): LineSegments2 {\n\tconst overlay = new LineSegments2(entry.geometry, material);\n\toverlay.userData.kind = EDGE_USERDATA_KIND;\n\toverlay.raycast = () => {}; // never pickable; clicks should hit the mesh, not its outline\n\tif (distanceFade) enableDistanceFade(overlay, entry.edgeSpacing);\n\treturn overlay;\n}\n\nconst _fadeCenter = new THREE.Vector3();\nconst _fadeCameraPos = new THREE.Vector3();\n\n// Returns Infinity (\"don't fade\") for an unknown/degenerate projection, or a camera inside the mesh.\nfunction pixelsPerWorldUnit(\n\toverlay: LineSegments2,\n\tcamera: THREE.Camera,\n\tviewportHeightPx: number\n): number {\n\tif (!overlay.geometry.boundingSphere) overlay.geometry.computeBoundingSphere();\n\tconst sphere = overlay.geometry.boundingSphere;\n\tif (!sphere) return Infinity;\n\n\tif ((camera as THREE.PerspectiveCamera).isPerspectiveCamera) {\n\t\tconst perspective = camera as THREE.PerspectiveCamera;\n\t\t_fadeCenter.copy(sphere.center).applyMatrix4(overlay.matrixWorld);\n\t\tconst distance = _fadeCameraPos\n\t\t\t.setFromMatrixPosition(camera.matrixWorld)\n\t\t\t.distanceTo(_fadeCenter);\n\t\tconst radius = sphere.radius * overlay.matrixWorld.getMaxScaleOnAxis();\n\t\tif (distance <= radius) return Infinity; // camera inside the mesh — no fade\n\t\tconst tanHalfFov = Math.tan(THREE.MathUtils.degToRad(perspective.fov) * 0.5);\n\t\tconst worldHeightAtCentre = 2 * distance * tanHalfFov;\n\t\treturn worldHeightAtCentre > 0 ? viewportHeightPx / worldHeightAtCentre : Infinity;\n\t}\n\tif ((camera as THREE.OrthographicCamera).isOrthographicCamera) {\n\t\tconst ortho = camera as THREE.OrthographicCamera;\n\t\tconst worldHeight = (ortho.top - ortho.bottom) / ortho.zoom;\n\t\treturn worldHeight > 0 ? viewportHeightPx / worldHeight : Infinity;\n\t}\n\treturn Infinity;\n}\n\n// Runs in onBeforeRender (not computed once) so opacity is written right before this overlay's\n// draw call — uniforms upload per draw, so overlays sharing one material still fade independently.\n// Chains LineSegments2's own onBeforeRender to keep its resolution uniform in sync.\nfunction enableDistanceFade(overlay: LineSegments2, edgeSpacing: number): void {\n\t// Assign via the Object3D base type: LineSegments2's typings narrow onBeforeRender to\n\t// (renderer) only, but the renderer actually calls it with (renderer, scene, camera, …).\n\t(overlay as THREE.Object3D).onBeforeRender = (renderer, _scene, camera) => {\n\t\tLineSegments2.prototype.onBeforeRender.call(overlay, renderer);\n\t\tconst material = overlay.material as LineMaterial;\n\t\tconst scale = pixelsPerWorldUnit(overlay, camera, material.resolution.y);\n\t\t// Screen-space gap between neighbouring edges. Infinity in, Infinity out — clamps to fully\n\t\t// opaque rather than fading on a guess.\n\t\tconst gapPx = edgeSpacing * scale;\n\t\tmaterial.opacity = THREE.MathUtils.clamp(\n\t\t\t(gapPx - FADE_END_PX) / (FADE_START_PX - FADE_END_PX),\n\t\t\t0,\n\t\t\t1\n\t\t);\n\t};\n}\n","import * as THREE from 'three';\nimport type { LineMaterial } from 'three/addons/lines/LineMaterial.js';\nimport { LineSegments2 } from 'three/addons/lines/LineSegments2.js';\n\nimport { buildLineGeometry, type EdgeGeometryEntry } from './edges/line-geometry.js';\nimport { extractSegmentsAsync, extractSegmentsSync, triangleCountOf } from './edges/extraction.js';\nimport {\n\tEDGES_SKIPPED_TRIANGLE_CAP,\n\tEDGE_USERDATA_KIND,\n\tresolveOptions,\n\ttype EdgeOptions,\n\ttype ResolvedOptions\n} from './edges/options.js';\nimport { MaterialPool, buildEdgeOverlay } from './edges/overlay.js';\n\n/**\n * Crisp boundary/crease edges overlaid on meshes, rendered as fat `LineSegments2` (controllable\n * thickness, unlike the 1px cap of `THREE.LineSegments`). Depth-offset rationale for the overlay\n * lines: see `EDGE_OFFSET_FACTOR`/`EDGE_OFFSET_UNITS` in `edges/options.ts`.\n */\nexport type { EdgeOptions };\nexport { EDGE_USERDATA_KIND, EDGES_SKIPPED_TRIANGLE_CAP };\n\n// ============================================================================\n// Public API — add / remove / query\n// ============================================================================\n\n/** For pick/fit filters elsewhere to exclude overlays from hit-testing. */\nexport function isEdgeOverlay(object: THREE.Object3D): boolean {\n\treturn object.userData?.kind === EDGE_USERDATA_KIND;\n}\n\n/** Meshes under `root` that should get an overlay: content meshes without one, caps applied. */\nfunction collectTargets(root: THREE.Object3D, maxTriangles: number): THREE.Mesh[] {\n\tconst targets: THREE.Mesh[] = [];\n\troot.traverse((object) => {\n\t\tif (!(object instanceof THREE.Mesh)) return;\n\t\tif (object.userData.id === 'floor' || object.userData.id === 'grid') return;\n\t\tif (object.userData.kind === EDGE_USERDATA_KIND) return;\n\t\tif (object.children.some((c) => c.userData?.kind === EDGE_USERDATA_KIND)) return; // already done\n\t\tif (!object.geometry) return;\n\n\t\tif (triangleCountOf(object.geometry) > maxTriangles) {\n\t\t\tobject.userData.edgesSkipped = EDGES_SKIPPED_TRIANGLE_CAP;\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(\n\t\t\t\t`[edges] skipping mesh over triangle cap (${triangleCountOf(object.geometry)} > ${maxTriangles})`\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tdelete object.userData.edgesSkipped;\n\t\ttargets.push(object);\n\t});\n\treturn targets;\n}\n\nfunction attachOverlay(\n\tmesh: THREE.Mesh,\n\tentry: EdgeGeometryEntry,\n\tmaterials: MaterialPool,\n\tresolved: ResolvedOptions\n): LineSegments2 {\n\t// Distance fade needs the transparent pass; overlays over the segment cap stay opaque instead\n\t// of skipping outright — see EdgeOptions.maxSegments.\n\tconst fade = resolved.distanceFade && entry.segmentCount <= resolved.maxSegments;\n\tconst overlay = buildEdgeOverlay(entry, materials.for(mesh, fade), fade);\n\tmesh.add(overlay); // child → inherits transform, disposed with the parent subtree\n\treturn overlay;\n}\n\n/**\n * Attach an edge overlay to every `Mesh` in `root`'s subtree, returning the created overlays.\n * Idempotent — meshes that already carry an overlay are skipped. Skips the floor/grid aids.\n *\n * Fully synchronous, extraction included. Prefer {@link addEdgesAsync} for interactive hosts with\n * potentially-large meshes.\n */\nexport function addEdges(root: THREE.Object3D, options: EdgeOptions = {}): LineSegments2[] {\n\tconst resolved = resolveOptions(options);\n\tconst materials = new MaterialPool(resolved);\n\tconst created: LineSegments2[] = [];\n\n\tfor (const mesh of collectTargets(root, resolved.maxTriangles)) {\n\t\t// Extraction itself is cached by content (`extraction.ts`), which is where the savings are;\n\t\t// the line geometry is per-overlay and owned by it.\n\t\tconst segments = extractSegmentsSync(mesh.geometry, resolved.thresholdAngle);\n\t\tcreated.push(attachOverlay(mesh, buildLineGeometry(segments), materials, resolved));\n\t}\n\n\tmaterials.disposeUnused(created);\n\treturn created;\n}\n\n/**\n * {@link removeEdges} bumps this per root; async attaches landing after a bump are dropped, so\n * \"toggle off while extracting\" can't resurrect overlays.\n */\nconst rootGenerations = new WeakMap<THREE.Object3D, number>();\n\nfunction generationOf(root: THREE.Object3D): number {\n\treturn rootGenerations.get(root) ?? 0;\n}\n\n/** Is `mesh` still reachable from `root`? Guards attaches racing scene clears. */\nfunction isConnected(mesh: THREE.Object3D, root: THREE.Object3D): boolean {\n\tfor (let node: THREE.Object3D | null = mesh; node; node = node.parent) {\n\t\tif (node === root) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Like {@link addEdges}, but large-mesh extraction runs in a Worker so the main thread never\n * stalls; small meshes still attach synchronously before this resolves. Resolves with every\n * overlay actually attached — late results are dropped if the mesh left the subtree,\n * {@link removeEdges} ran for this root meanwhile, or another apply already attached one to that\n * mesh.\n */\nexport async function addEdgesAsync(\n\troot: THREE.Object3D,\n\toptions: EdgeOptions = {}\n): Promise<LineSegments2[]> {\n\tconst resolved = resolveOptions(options);\n\tconst materials = new MaterialPool(resolved);\n\tconst generation = generationOf(root);\n\tconst created: LineSegments2[] = [];\n\n\tconst attaches = collectTargets(root, resolved.maxTriangles).map(async (mesh) => {\n\t\tconst segments = await extractSegmentsAsync(mesh.geometry, resolved.thresholdAngle);\n\t\t// Things may have moved on while extracting — attach only if this apply is still wanted.\n\t\tif (generationOf(root) !== generation) return;\n\t\tif (!isConnected(mesh, root)) return;\n\t\tif (mesh.children.some((c) => c.userData?.kind === EDGE_USERDATA_KIND)) return;\n\t\tcreated.push(attachOverlay(mesh, buildLineGeometry(segments), materials, resolved));\n\t});\n\n\tawait Promise.all(attaches);\n\tmaterials.disposeUnused(created);\n\treturn created;\n}\n\n/**\n * Remove every edge overlay under `root`, disposing geometry and material, and cancels any\n * in-flight async attaches for `root`. Inverse of {@link addEdges}/{@link addEdgesAsync}. Returns\n * the count removed.\n */\nexport function removeEdges(root: THREE.Object3D): number {\n\trootGenerations.set(root, generationOf(root) + 1);\n\n\tconst overlays: LineSegments2[] = [];\n\troot.traverse((object) => {\n\t\tif (object instanceof LineSegments2 && isEdgeOverlay(object)) overlays.push(object);\n\t});\n\n\t// One addEdges call shares one material across its overlays — dispose each distinct one once.\n\t// (If `root` covers only part of a call's overlays, survivors self-heal: three recompiles a\n\t// disposed-but-still-referenced material on its next use.)\n\tconst materials = new Set<LineMaterial>();\n\tfor (const overlay of overlays) {\n\t\toverlay.geometry.dispose(); // each overlay owns its line geometry outright\n\t\tmaterials.add(overlay.material as LineMaterial);\n\t\t// Nothing to undo on the parent mesh: the depth bias lives entirely on the overlay's own\n\t\t// material, so surfaces keep whatever polygonOffset their look preset configured.\n\t\toverlay.removeFromParent();\n\t}\n\tmaterials.forEach((material) => material.dispose());\n\treturn overlays.length;\n}\n","import * as THREE from 'three';\n\n/**\n * An \"infinite\", distance-fading reference grid.\n *\n * `GridHelper` is a fixed-size square that visibly ends once you pan/zoom past it. This draws one\n * large plane and computes the grid in the fragment shader from world coordinates, fading with\n * distance so the edge is never a hard cutoff.\n *\n * Spacing is in world units (meters) — `cellSize` of 1 = 1m cells.\n */\n\nexport interface GridOptions {\n\t/** Minor cell size in world units (meters). Default 1. */\n\tcellSize?: number;\n\t/** How many minor cells per major line. Default 10. */\n\tmajorEvery?: number;\n\t/** Minor line color. Default 0x888888. */\n\tcellColor?: THREE.ColorRepresentation;\n\t/** Major line color. Default 0x444444. */\n\tmajorColor?: THREE.ColorRepresentation;\n\t/** World-space radius at which the grid has fully faded out. Default 100. */\n\tfadeDistance?: number;\n\t/**\n\t * Axis the grid is laid perpendicular to, i.e. the scene's up axis. Standalone default is `'y'`,\n\t * but `initThree` always passes a plane derived from the configured `sceneUp`, so a\n\t * viewer-created grid defaults to `'z'` (Rhino's ground plane).\n\t */\n\tplane?: 'x' | 'y' | 'z';\n}\n\nexport interface Grid {\n\t/** Tagged `userData.id = 'grid'` so pick/fit code skips it. */\n\treadonly object: THREE.Mesh;\n\t/** Re-centers the fade on the camera so the grid feels infinite as you move. Call per frame. */\n\tupdate(cameraPosition: THREE.Vector3): void;\n\t/**\n\t * Rescales cell spacing and fade radius to the content's extent, so a 3-unit or 3000-unit part\n\t * both get sensible cells. No-op for empty/degenerate bounds.\n\t */\n\tfitToContent(bounds: THREE.Box3): void;\n\tsetVisible(visible: boolean): void;\n\tdispose(): void;\n}\n\n/** Rounds to a \"nice\" 1/2/5 × 10ⁿ step (CAD ruler convention) so cells never come out finer than the target and alias into a solid sheet. */\nfunction niceStep(value: number): number {\n\tif (!(value > 0) || !Number.isFinite(value)) return 1;\n\tconst exponent = Math.floor(Math.log10(value));\n\tconst power = Math.pow(10, exponent);\n\tconst mantissa = value / power; // in [1, 10)\n\tconst niceMantissa = mantissa >= 5 ? 5 : mantissa >= 2 ? 2 : 1;\n\treturn niceMantissa * power;\n}\n\nconst GRID_VERTEX = /* glsl */ `\n\tvarying vec3 vWorldPos;\n\tvoid main() {\n\t\tvec4 world = modelMatrix * vec4(position, 1.0);\n\t\tvWorldPos = world.xyz;\n\t\tgl_Position = projectionMatrix * viewMatrix * world;\n\t}\n`;\n\nconst GRID_FRAGMENT = /* glsl */ `\n\tprecision highp float;\n\tvarying vec3 vWorldPos;\n\n\tuniform vec2 uAxes; // indices (0=x,1=y,2=z) of the two in-plane world axes\n\tuniform float uCell;\n\tuniform float uMajor;\n\tuniform vec3 uCellColor;\n\tuniform vec3 uMajorColor;\n\tuniform vec3 uCenter; // fade center (camera position projected onto the plane)\n\tuniform float uFade;\n\n\t// Screen-space derivatives keep grid lines ~1px regardless of zoom (\"pristine grid\" technique).\n\tfloat gridLine(vec2 coord, float spacing) {\n\t\tvec2 c = coord / spacing;\n\t\tvec2 d = fwidth(c);\n\t\tvec2 g = abs(fract(c - 0.5) - 0.5) / max(d, 1e-6);\n\t\tfloat line = min(g.x, g.y);\n\t\treturn 1.0 - clamp(line, 0.0, 1.0);\n\t}\n\n\t// Index a vec3 by a float axis id (0/1/2) without dynamic indexing (WebGL1-safe).\n\tfloat axis(vec3 v, float i) {\n\t\treturn i < 0.5 ? v.x : (i < 1.5 ? v.y : v.z);\n\t}\n\n\tvoid main() {\n\t\t// Pick the two in-plane world coordinates.\n\t\tvec2 coord = vec2(axis(vWorldPos, uAxes.x), axis(vWorldPos, uAxes.y));\n\n\t\tfloat minor = gridLine(coord, uCell);\n\t\tfloat major = gridLine(coord, uCell * uMajor);\n\n\t\tvec3 color = mix(uCellColor, uMajorColor, major);\n\t\tfloat alpha = max(minor, major);\n\n\t\t// Radial fade from the camera-projected center.\n\t\tfloat dist = distance(vWorldPos, uCenter);\n\t\tfloat fade = 1.0 - clamp(dist / uFade, 0.0, 1.0);\n\t\talpha *= fade * fade;\n\n\t\tif (alpha < 0.001) discard;\n\t\tgl_FragColor = vec4(color, alpha);\n\t}\n`;\n\nexport function createGrid(options: GridOptions = {}): Grid {\n\tconst {\n\t\tcellSize = 1,\n\t\tmajorEvery = 10,\n\t\tcellColor = 0x888888,\n\t\tmajorColor = 0x444444,\n\t\tfadeDistance = 100,\n\t\tplane = 'y'\n\t} = options;\n\n\t// In-plane world axes: ground 'y' grids over x,z; 'z' over x,y; 'x' over y,z.\n\tconst axes =\n\t\tplane === 'y'\n\t\t\t? new THREE.Vector2(0, 2) // x, z\n\t\t\t: plane === 'z'\n\t\t\t\t? new THREE.Vector2(0, 1) // x, y\n\t\t\t\t: new THREE.Vector2(1, 2); // y, z\n\n\t// Must comfortably outreach the fade radius, else the grid's rectangular edge shows before the\n\t// fade completes. Plane is unit-sized and grown purely via scale so fitToContent never\n\t// recreates geometry.\n\tconst PLANE_TO_FADE_RATIO = 2.5;\n\tconst geometry = new THREE.PlaneGeometry(1, 1);\n\n\t// PlaneGeometry is in the XY plane by default; rotate it onto the requested world plane.\n\tif (plane === 'y') geometry.rotateX(-Math.PI / 2);\n\telse if (plane === 'x') geometry.rotateY(Math.PI / 2);\n\n\tconst material = new THREE.ShaderMaterial({\n\t\tvertexShader: GRID_VERTEX,\n\t\tfragmentShader: GRID_FRAGMENT,\n\t\ttransparent: true,\n\t\tdepthWrite: false,\n\t\tside: THREE.DoubleSide,\n\t\tuniforms: {\n\t\t\tuAxes: { value: axes },\n\t\t\tuCell: { value: cellSize },\n\t\t\tuMajor: { value: majorEvery },\n\t\t\tuCellColor: { value: new THREE.Color(cellColor) },\n\t\t\tuMajorColor: { value: new THREE.Color(majorColor) },\n\t\t\tuCenter: { value: new THREE.Vector3() },\n\t\t\tuFade: { value: fadeDistance }\n\t\t}\n\t});\n\n\tconst mesh = new THREE.Mesh(geometry, material);\n\tmesh.name = 'grid';\n\tmesh.userData.id = 'grid';\n\tmesh.renderOrder = -1; // draw before content so transparent geometry blends over it\n\n\t// fitToContent mutates both; seeded from fadeDistance so an un-fitted grid still covers its fade.\n\tlet fadeRadius = fadeDistance;\n\tlet planeScale = fadeDistance * PLANE_TO_FADE_RATIO;\n\n\tconst center = new THREE.Vector3();\n\n\treturn {\n\t\tobject: mesh,\n\t\tupdate: (cameraPosition) => {\n\t\t\t// Re-center on the camera so the grid tracks the view; the plane's own axis stays fixed\n\t\t\t// (a ground grid shouldn't lift to the camera's height).\n\t\t\tif (plane === 'y') {\n\t\t\t\tmesh.position.set(cameraPosition.x, 0, cameraPosition.z);\n\t\t\t\tcenter.set(cameraPosition.x, 0, cameraPosition.z);\n\t\t\t} else if (plane === 'z') {\n\t\t\t\tmesh.position.set(cameraPosition.x, cameraPosition.y, 0);\n\t\t\t\tcenter.set(cameraPosition.x, cameraPosition.y, 0);\n\t\t\t} else {\n\t\t\t\tmesh.position.set(0, cameraPosition.y, cameraPosition.z);\n\t\t\t\tcenter.set(0, cameraPosition.y, cameraPosition.z);\n\t\t\t}\n\t\t\tmaterial.uniforms.uCenter.value.copy(center);\n\t\t\t// Rotation is baked into the geometry, so uniform scale works on any plane orientation.\n\t\t\tmesh.scale.setScalar(planeScale);\n\t\t},\n\t\tfitToContent: (bounds) => {\n\t\t\tif (bounds.isEmpty()) return;\n\t\t\t// In-plane extent only — a tall thin part shouldn't blow up the cell size by its height.\n\t\t\tconst sizeVec = bounds.getSize(new THREE.Vector3());\n\t\t\tconst axisComponent = (v: THREE.Vector3, i: number) => (i === 0 ? v.x : i === 1 ? v.y : v.z);\n\t\t\tconst inPlaneExtent = Math.max(\n\t\t\t\taxisComponent(sizeVec, axes.x),\n\t\t\t\taxisComponent(sizeVec, axes.y)\n\t\t\t);\n\t\t\tif (!(inPlaneExtent > 0) || !Number.isFinite(inPlaneExtent)) return;\n\n\t\t\t// ~20 minor cells across the part; fade reaches ~2x past it so the edge stays out of view.\n\t\t\tconst TARGET_CELLS_ACROSS = 20;\n\t\t\tmaterial.uniforms.uCell.value = niceStep(inPlaneExtent / TARGET_CELLS_ACROSS);\n\t\t\tfadeRadius = inPlaneExtent * 2;\n\t\t\tmaterial.uniforms.uFade.value = fadeRadius;\n\t\t\tplaneScale = fadeRadius * PLANE_TO_FADE_RATIO;\n\t\t},\n\t\tsetVisible: (visible) => {\n\t\t\tmesh.visible = visible;\n\t\t},\n\t\tdispose: () => {\n\t\t\tmesh.removeFromParent();\n\t\t\tgeometry.dispose();\n\t\t\tmaterial.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\nimport { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';\n\nexport interface LabelHandle {\n\treadonly object: CSS2DObject;\n\tsetPosition(position: THREE.Vector3): void;\n\tsetText(text: string): void;\n\tremove(): void;\n}\n\nexport interface LabelLayer {\n\taddLabel(text: string, position: THREE.Vector3, className?: string): LabelHandle;\n\t/** Call each frame after the WebGL render, with the active camera. */\n\trender(scene: THREE.Scene, camera: THREE.Camera): void;\n\tsetSize(width: number, height: number): void;\n\tdispose(): void;\n}\n\n// `container` is normally the canvas's parent, so both share a positioning context for the\n// absolutely-positioned label overlay.\nexport function createLabelLayer(container: HTMLElement, scene: THREE.Scene): LabelLayer {\n\tconst renderer = new CSS2DRenderer();\n\tconst dom = renderer.domElement;\n\tdom.style.position = 'absolute';\n\tdom.style.top = '0';\n\tdom.style.left = '0';\n\t// CSS2DRenderer sets width/height in pixels, so host must call `setSize` on every resize, same\n\t// as the WebGL renderer. overflow:hidden + pointerEvents:none: without both, the overlay can\n\t// cover the canvas and swallow orbit/clicks.\n\tdom.style.overflow = 'hidden';\n\tdom.style.pointerEvents = 'none';\n\tdom.style.zIndex = '30'; // above canvas/host overlays, below menus/popovers\n\tif (getComputedStyle(container).position === 'static') {\n\t\tcontainer.style.position = 'relative';\n\t}\n\tcontainer.appendChild(dom);\n\n\tconst size = { width: container.clientWidth || 1, height: container.clientHeight || 1 };\n\trenderer.setSize(size.width, size.height);\n\n\tconst group = new THREE.Group(); // pick/fit logic skips objects tagged 'label-layer'\n\tgroup.name = 'label-layer';\n\tgroup.userData.id = 'label-layer';\n\tscene.add(group);\n\n\tconst labels = new Set<CSS2DObject>();\n\n\tconst addLabel = (text: string, position: THREE.Vector3, className?: string): LabelHandle => {\n\t\tconst el = document.createElement('div');\n\t\tel.textContent = text;\n\t\tif (className) {\n\t\t\tel.className = className;\n\t\t} else {\n\t\t\t// Inline default so the layer needs no external stylesheet; pass className to opt out.\n\t\t\tObject.assign(el.style, {\n\t\t\t\tpadding: '2px 6px',\n\t\t\t\tborderRadius: '4px',\n\t\t\t\tbackground: 'rgba(20, 20, 20, 0.78)',\n\t\t\t\tcolor: '#fff',\n\t\t\t\tfont: '12px/1.3 system-ui, sans-serif',\n\t\t\t\t// `pre` preserves line breaks for multi-line readouts (e.g. total + per-axis deltas).\n\t\t\t\twhiteSpace: 'pre',\n\t\t\t\ttextAlign: 'center',\n\t\t\t\tuserSelect: 'none'\n\t\t\t} satisfies Partial<CSSStyleDeclaration>);\n\t\t}\n\t\tel.style.pointerEvents = 'none';\n\n\t\tconst object = new CSS2DObject(el);\n\t\tobject.position.copy(position);\n\t\tgroup.add(object);\n\t\tlabels.add(object);\n\n\t\treturn {\n\t\t\tobject,\n\t\t\tsetPosition: (p) => object.position.copy(p),\n\t\t\tsetText: (t) => {\n\t\t\t\tel.textContent = t;\n\t\t\t},\n\t\t\tremove: () => {\n\t\t\t\tobject.removeFromParent();\n\t\t\t\tel.remove();\n\t\t\t\tlabels.delete(object);\n\t\t\t}\n\t\t};\n\t};\n\n\treturn {\n\t\taddLabel,\n\t\trender: (scene, camera) => renderer.render(scene, camera),\n\t\tsetSize: (width, height) => renderer.setSize(width, height),\n\t\tdispose: () => {\n\t\t\tlabels.forEach((object) => {\n\t\t\t\tobject.removeFromParent();\n\t\t\t\t(object.element as HTMLElement).remove();\n\t\t\t});\n\t\t\tlabels.clear();\n\t\t\tgroup.removeFromParent();\n\t\t\tdom.remove();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\nimport { Line2 } from 'three/addons/lines/Line2.js';\nimport { LineGeometry } from 'three/addons/lines/LineGeometry.js';\nimport { LineMaterial } from 'three/addons/lines/LineMaterial.js';\n\nimport type { LabelLayer, LabelHandle } from './label-layer';\n\n/**\n * Two-click distance measurement. Click a point, click a second, read the distance off a label on\n * the connecting line; a third click starts fresh.\n *\n * Picking snaps to the nearest vertex within {@link MeasureOptions.snapPixels} so measurements\n * land exactly on vertices rather than wherever the ray happened to hit — a cheap local snap\n * against the struck primitive's own vertices, no spatial index.\n *\n * Dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks (caller\n * forwards them and swallows the event when {@link MeasureTool.handleClick} returns true) so\n * measuring doesn't also select objects.\n */\n\nexport interface MeasureTool {\n\tsetEnabled(enabled: boolean): void;\n\tisEnabled(): boolean;\n\t/** Returns true if the tool consumed the click (caller should not also select). */\n\thandleClick(event: MouseEvent): boolean;\n\t/** Preview the next snap point via a ghost marker. No-op when disabled; never consumes the event. */\n\thandleMove(event: MouseEvent): void;\n\tclear(): void;\n\tdispose(): void;\n}\n\nexport interface MeasureOptions {\n\t/** Snap to a vertex when the cursor is within this many screen pixels of it. Default 12. */\n\tsnapPixels?: number;\n\t/** Marker + line color. Default yellow. */\n\tcolor?: THREE.ColorRepresentation;\n\tlabelClassName?: string;\n\t/**\n\t * Pass `data.modelunits`. Scene is in meters; default formatter converts and labels in this unit\n\t * (e.g. \"25.0 mm\" not \"0.025 m\"). Defaults to meters. Ignored if `format` is given.\n\t */\n\tdisplayUnit?: string;\n\t/**\n\t * Format the measurement → label text. Receives `distance` and per-axis `delta` (|b − a|), both\n\t * in meters. May return multi-line text/HTML; default renders total + Δx/Δy/Δz in `displayUnit`.\n\t */\n\tformat?: (distance: number, delta: THREE.Vector3) => string;\n}\n\ninterface MeasureDeps {\n\tcanvas: HTMLCanvasElement;\n\tscene: THREE.Scene;\n\tgetActiveCamera: () => THREE.Camera;\n\t/**\n\t * The current orbit target (e.g. `controls.target`). Scales the line/point pick threshold as a\n\t * fraction of camera→target distance so it stays constant on screen regardless of framing.\n\t * Without it, the fallback is distance-to-origin, which misjudges off-origin content.\n\t */\n\tgetViewTarget?: () => THREE.Vector3;\n\tlabelLayer: LabelLayer;\n\toptions?: MeasureOptions;\n}\n\nconst DEFAULT_SNAP_PIXELS = 12;\nconst DEFAULT_COLOR = 0xffcc00;\n// Fraction of view distance used as the line/point raycast threshold: ~1.5% gives a comfortable\n// grab band at typical framing without snapping to far-off geometry.\nconst LINE_PICK_FRACTION = 0.015;\n\n// Scene geometry loads in meters (webdisplay parser scales when `allowScaling` is on). Keep in\n// sync with the webdisplay parser's SCALE_FACTORS.\nconst UNIT_DISPLAY: Record<string, { metersPerUnit: number; suffix: string }> = {\n\tMillimeters: { metersPerUnit: 1 / 1000, suffix: 'mm' },\n\tCentimeters: { metersPerUnit: 1 / 100, suffix: 'cm' },\n\tMeters: { metersPerUnit: 1, suffix: 'm' },\n\tInches: { metersPerUnit: 1 / 39.37, suffix: 'in' },\n\tFeet: { metersPerUnit: 1 / 3.28084, suffix: 'ft' }\n};\n\n/** @internal exported for tests */\nexport function makeFormatter(displayUnit?: string): (n: number) => string {\n\tconst unit = (displayUnit && UNIT_DISPLAY[displayUnit]) || UNIT_DISPLAY.Meters;\n\treturn (meters: number) => `${(meters / unit.metersPerUnit).toPrecision(3)} ${unit.suffix}`;\n}\n\n/**\n * Raycast threshold for picking lines/points, as a fixed fraction of view size so the grab band\n * stays constant on screen while zooming. Perspective uses camera→target distance (see\n * `MeasureDeps.getViewTarget`); orthographic uses frustum height `(top − bottom) / zoom`, since\n * ortho zoom changes `camera.zoom` rather than position.\n *\n * Shared with any tool doing its own picking, so grab bands stay consistent across tools.\n */\nexport function pickThreshold(camera: THREE.Camera, viewTarget?: THREE.Vector3): number {\n\tif ((camera as THREE.OrthographicCamera).isOrthographicCamera) {\n\t\tconst ortho = camera as THREE.OrthographicCamera;\n\t\tconst visibleHeight = Math.abs(ortho.top - ortho.bottom) / (ortho.zoom || 1);\n\t\treturn visibleHeight * LINE_PICK_FRACTION;\n\t}\n\tconst viewScale = viewTarget ? camera.position.distanceTo(viewTarget) : camera.position.length();\n\treturn (viewScale || 1) * LINE_PICK_FRACTION;\n}\n\n/**\n * Vertex indices to consider snapping to, by object type: Mesh → struck triangle's 3 vertices;\n * Line/LineSegments → struck segment's 2 endpoints; Points → the struck vertex. Null when the hit\n * carries no usable index (e.g. a fat `Line2`), so the caller keeps the raw hit point.\n */\nfunction snapCandidateIndices(hit: THREE.Intersection): number[] | null {\n\tconst obj = hit.object;\n\tif (obj instanceof THREE.Mesh) {\n\t\treturn hit.face ? [hit.face.a, hit.face.b, hit.face.c] : null;\n\t}\n\tif (obj instanceof THREE.Points) {\n\t\t// Points.raycast resolves indexed geometry itself: `hit.index` is always a position index.\n\t\treturn hit.index != null ? [hit.index] : null;\n\t}\n\t// THREE.Line / LineSegments / LineLoop. For non-indexed geometry `hit.index` is the first\n\t// vertex of the struck segment. For indexed geometry it's a cursor into the index buffer, not\n\t// the resolved vertex (three r184 Line.raycast reports the loop counter) — endpoints must be\n\t// looked up through the index before reading the position attribute.\n\tif (obj instanceof THREE.Line) {\n\t\tif (hit.index == null) return null;\n\t\tconst index = obj.geometry.index;\n\t\tif (index) {\n\t\t\tif (hit.index + 1 >= index.count) return null; // stale/inconsistent hit; keep raw point\n\t\t\treturn [index.getX(hit.index), index.getX(hit.index + 1)];\n\t\t}\n\t\treturn [hit.index, hit.index + 1];\n\t}\n\treturn null;\n}\n\n/** Snap a raycast hit to the nearest geometry vertex within `snapPixels` on screen, else the raw hit point. */\nexport function snapToVertex(\n\thit: THREE.Intersection,\n\tcamera: THREE.Camera,\n\tscreenSize: { width: number; height: number },\n\tsnapPixels: number\n): THREE.Vector3 {\n\tconst raw = hit.point.clone();\n\tconst obj = hit.object as THREE.Object3D & { geometry?: THREE.BufferGeometry };\n\tconst indices = snapCandidateIndices(hit);\n\tif (!indices || !obj.geometry) return raw;\n\n\tconst pos = obj.geometry.attributes.position as THREE.BufferAttribute | undefined;\n\tif (!pos) return raw;\n\n\tconst toScreen = (worldP: THREE.Vector3): THREE.Vector2 => {\n\t\tconst ndc = worldP.clone().project(camera);\n\t\treturn new THREE.Vector2(\n\t\t\t((ndc.x + 1) / 2) * screenSize.width,\n\t\t\t((1 - ndc.y) / 2) * screenSize.height\n\t\t);\n\t};\n\tconst rawScreen = toScreen(raw);\n\n\tlet best = raw;\n\tlet bestPx = snapPixels;\n\tfor (const idx of indices) {\n\t\tif (idx >= pos.count) continue; // guard the line `index + 1` against the geometry end\n\t\tconst local = new THREE.Vector3().fromBufferAttribute(pos, idx);\n\t\tconst world = local.applyMatrix4(obj.matrixWorld);\n\t\tconst px = toScreen(world).distanceTo(rawScreen);\n\t\tif (px < bestPx) {\n\t\t\tbestPx = px;\n\t\t\tbest = world;\n\t\t}\n\t}\n\treturn best;\n}\n\nexport function createMeasureTool(deps: MeasureDeps): MeasureTool {\n\tconst { canvas, scene, getActiveCamera, getViewTarget, labelLayer, options = {} } = deps;\n\tconst snapPixels = options.snapPixels ?? DEFAULT_SNAP_PIXELS;\n\tconst color = new THREE.Color(options.color ?? DEFAULT_COLOR);\n\tconst fmt = makeFormatter(options.displayUnit);\n\tconst defaultFormat = (d: number, delta: THREE.Vector3) =>\n\t\t`${fmt(d)}\\nΔx ${fmt(delta.x)} Δy ${fmt(delta.y)} Δz ${fmt(delta.z)}`;\n\tconst format = options.format ?? defaultFormat;\n\n\tconst raycaster = new THREE.Raycaster();\n\tconst pointer = new THREE.Vector2();\n\n\tlet enabled = false;\n\tconst points: THREE.Vector3[] = [];\n\n\tconst markers: THREE.Points[] = [];\n\tlet line: Line2 | null = null;\n\tlet label: LabelHandle | null = null;\n\n\tconst markerMaterial = new THREE.PointsMaterial({\n\t\tcolor,\n\t\tsize: 8,\n\t\tsizeAttenuation: false,\n\t\tdepthTest: false // markers stay visible through geometry, like CAD snap dots\n\t});\n\n\t// Dimmer + bigger than a committed marker so the next click's snap target is obvious before clicking.\n\tconst hoverMaterial = new THREE.PointsMaterial({\n\t\tcolor,\n\t\tsize: 11,\n\t\tsizeAttenuation: false,\n\t\tdepthTest: false,\n\t\ttransparent: true,\n\t\topacity: 0.5\n\t});\n\tlet hoverMarker: THREE.Points | null = null;\n\n\tconst showHover = (p: THREE.Vector3 | null) => {\n\t\tif (!p) {\n\t\t\tif (hoverMarker) hoverMarker.visible = false;\n\t\t\treturn;\n\t\t}\n\t\tif (!hoverMarker) {\n\t\t\tconst geometry = new THREE.BufferGeometry();\n\t\t\tgeometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));\n\t\t\thoverMarker = new THREE.Points(geometry, hoverMaterial);\n\t\t\thoverMarker.renderOrder = 1000;\n\t\t\thoverMarker.userData.id = 'measure';\n\t\t\thoverMarker.raycast = () => {};\n\t\t\tscene.add(hoverMarker);\n\t\t}\n\t\thoverMarker.position.copy(p);\n\t\thoverMarker.visible = true;\n\t};\n\n\tconst makeMarker = (p: THREE.Vector3): THREE.Points => {\n\t\tconst geometry = new THREE.BufferGeometry();\n\t\tgeometry.setAttribute('position', new THREE.Float32BufferAttribute([p.x, p.y, p.z], 3));\n\t\tconst marker = new THREE.Points(geometry, markerMaterial);\n\t\tmarker.renderOrder = 999;\n\t\tmarker.userData.id = 'measure';\n\t\tmarker.raycast = () => {}; // don't let markers be measure targets themselves\n\t\tscene.add(marker);\n\t\treturn marker;\n\t};\n\n\tconst clear = () => {\n\t\tpoints.length = 0;\n\t\tmarkers.forEach((m) => {\n\t\t\tm.geometry.dispose();\n\t\t\tm.removeFromParent();\n\t\t});\n\t\tmarkers.length = 0;\n\t\tif (line) {\n\t\t\tline.geometry.dispose();\n\t\t\t(line.material as LineMaterial).dispose();\n\t\t\tline.removeFromParent();\n\t\t\tline = null;\n\t\t}\n\t\tlabel?.remove();\n\t\tlabel = null;\n\t};\n\n\tconst drawMeasurement = () => {\n\t\tif (points.length !== 2) return;\n\t\tconst [a, b] = points;\n\n\t\tconst geometry = new LineGeometry();\n\t\tgeometry.setPositions([a.x, a.y, a.z, b.x, b.y, b.z]);\n\t\tconst material = new LineMaterial({ color });\n\t\t(material as LineMaterial & { linewidth: number; depthTest: boolean }).linewidth = 2;\n\t\tmaterial.depthTest = false;\n\n\t\tline = new Line2(geometry, material);\n\t\tline.renderOrder = 998;\n\t\tline.userData.id = 'measure';\n\t\tline.raycast = () => {};\n\t\tscene.add(line);\n\n\t\tconst mid = a.clone().add(b).multiplyScalar(0.5);\n\t\tconst delta = new THREE.Vector3(Math.abs(b.x - a.x), Math.abs(b.y - a.y), Math.abs(b.z - a.z));\n\t\tlabel = labelLayer.addLabel(format(a.distanceTo(b), delta), mid, options.labelClassName);\n\t};\n\n\tconst pickPoint = (event: MouseEvent): THREE.Vector3 | null => {\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tpointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tpointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tconst camera = getActiveCamera();\n\t\traycaster.setFromCamera(pointer, camera);\n\n\t\t// Lines/points have no surface area, so the raycast threshold matters — three's default is\n\t\t// nearly unclickable. pickThreshold scales it with the view so it stays constant on screen.\n\t\tconst threshold = pickThreshold(camera, getViewTarget?.());\n\t\traycaster.params.Line!.threshold = threshold;\n\t\traycaster.params.Points!.threshold = threshold;\n\n\t\tconst hits = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => i.object.userData.id !== 'measure' && i.object.userData.id !== 'grid');\n\n\t\tif (hits.length === 0) return null;\n\t\treturn snapToVertex(hits[0], camera, { width: rect.width, height: rect.height }, snapPixels);\n\t};\n\n\t// One raycast per animation frame, not per mousemove: a full-scene recursive raycast per event\n\t// hitches on large models, and only the latest event matters for the preview.\n\tlet pendingMove: MouseEvent | null = null;\n\tlet moveRaf = 0;\n\n\tconst cancelPendingMove = () => {\n\t\tif (moveRaf) {\n\t\t\tcancelAnimationFrame(moveRaf);\n\t\t\tmoveRaf = 0;\n\t\t}\n\t\tpendingMove = null;\n\t};\n\n\tconst handleMove = (event: MouseEvent): void => {\n\t\tif (!enabled) return;\n\t\tpendingMove = event;\n\t\tif (moveRaf) return;\n\t\tmoveRaf = requestAnimationFrame(() => {\n\t\t\tmoveRaf = 0;\n\t\t\tconst latest = pendingMove;\n\t\t\tpendingMove = null;\n\t\t\tif (!enabled || !latest) return;\n\t\t\tshowHover(pickPoint(latest));\n\t\t});\n\t};\n\n\tconst handleClick = (event: MouseEvent): boolean => {\n\t\tif (!enabled) return false;\n\n\t\t// A third click after a completed measurement starts fresh.\n\t\tif (points.length === 2) clear();\n\n\t\tconst point = pickPoint(event);\n\t\tif (point === null) return true; // consumed: a measuring click that missed still isn't a select\n\n\t\tpoints.push(point);\n\t\tmarkers.push(makeMarker(point));\n\n\t\tif (points.length === 2) drawMeasurement();\n\t\treturn true;\n\t};\n\n\treturn {\n\t\tsetEnabled: (value) => {\n\t\t\tenabled = value;\n\t\t\tif (!value) {\n\t\t\t\tcancelPendingMove();\n\t\t\t\tclear();\n\t\t\t\tshowHover(null);\n\t\t\t}\n\t\t},\n\t\tisEnabled: () => enabled,\n\t\thandleClick,\n\t\thandleMove,\n\t\tclear,\n\t\tdispose: () => {\n\t\t\tcancelPendingMove();\n\t\t\tclear();\n\t\t\tif (hoverMarker) {\n\t\t\t\thoverMarker.geometry.dispose();\n\t\t\t\thoverMarker.removeFromParent();\n\t\t\t\thoverMarker = null;\n\t\t\t}\n\t\t\tmarkerMaterial.dispose();\n\t\t\thoverMaterial.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\n\nimport { computeContentBounds } from './three-helpers';\n\n/**\n * Depth-buffer precision is ∝ near/z²: a fixed tiny near (0.01 m) grows to a ~0.25 m depth ULP at\n * 200 m, causing distant coplanar surfaces to z-fight. Pushing `near` up to a fraction of the\n * camera's gap to the nearest visible content recovers 10–100× precision when zoomed out, without\n * clipping anything.\n *\n * `far` stays owned by config/`updateScene` (must keep covering grid fade, floor). External writes\n * to `camera.near` are adopted as the new lower bound rather than fought — the fitter only ever\n * *raises* near above that floor.\n *\n * Ortho camera needs no fitting (linear depth) and is left untouched.\n */\n\n/** Headroom for the frame's camera motion and bounds staleness. */\nconst NEAR_GAP_FRACTION = 0.5;\n/** Caps near so the frustum stays sane if the camera flies out. */\nconst MAX_NEAR_TO_FAR = 0.01;\n/** Skip sub-5% changes so the projection matrix isn't rebuilt every frame while orbiting. */\nconst APPLY_THRESHOLD = 0.05;\n\nexport interface NearPlaneFitterOptions {\n\tcamera: THREE.PerspectiveCamera;\n\tscene: THREE.Scene;\n\t/**\n\t * Unit normals of ground planes through the origin carrying ground aids (grid, floor) — their\n\t * perpendicular distance to the camera also bounds near, since the aid re-centers under the\n\t * camera and content distance alone would clip it at grazing views.\n\t *\n\t * Pass a callback returning only planes whose aid is visible this frame: an aid that's hidden\n\t * (not merely absent) must not collapse `near` to protect geometry nobody can see.\n\t */\n\tgroundNormals?: () => THREE.Vector3[];\n}\n\nexport interface NearPlaneFitter {\n\tupdate: () => void;\n}\n\nconst NO_GROUND_NORMALS: THREE.Vector3[] = [];\n\nexport function createNearPlaneFitter({\n\tcamera,\n\tscene,\n\tgroundNormals = () => NO_GROUND_NORMALS\n}: NearPlaneFitterOptions): NearPlaneFitter {\n\tlet baseNear = camera.near;\n\tlet appliedNear = camera.near;\n\n\tconst center = new THREE.Vector3();\n\tconst size = new THREE.Vector3();\n\n\tconst update = () => {\n\t\tif (camera.near !== appliedNear) baseNear = camera.near; // external write → new floor\n\n\t\tconst bounds = computeContentBounds(scene);\n\t\tlet near = baseNear;\n\t\tif (!bounds.isEmpty()) {\n\t\t\t// Gap to content's bounding sphere: closest content can be to the camera.\n\t\t\tconst radius = bounds.getSize(size).length() * 0.5;\n\t\t\tlet gap = camera.position.distanceTo(bounds.getCenter(center)) - radius;\n\t\t\tfor (const normal of groundNormals()) {\n\t\t\t\tgap = Math.min(gap, Math.abs(camera.position.dot(normal)));\n\t\t\t}\n\t\t\tnear = THREE.MathUtils.clamp(gap * NEAR_GAP_FRACTION, baseNear, camera.far * MAX_NEAR_TO_FAR);\n\t\t}\n\n\t\tif (Math.abs(near - appliedNear) > appliedNear * APPLY_THRESHOLD) {\n\t\t\tcamera.near = near;\n\t\t\tcamera.updateProjectionMatrix();\n\t\t\tappliedNear = near;\n\t\t}\n\t};\n\n\treturn { update };\n}\n","// ============================================================================\n// Pointer tools: who gets the click first\n// ============================================================================\n//\n// A pointer tool claims canvas input ahead of object selection — measuring a distance or placing\n// a vertex must not also select the mesh under the cursor. The registry owns the ordering and the\n// single-active rule; `initThree` owns the DOM listeners and forwards to it.\n\n/**\n * A tool that can claim canvas pointer input.\n *\n * `handleClick` returning true means the tool consumed the event and the host stops dispatching —\n * no further tool sees it, and object selection doesn't run. `handleMove` never consumes: it runs\n * on every move regardless of which tool is active, so previews can't block orbit or pan.\n */\nexport interface PointerTool {\n\tsetEnabled?(enabled: boolean): void;\n\tisEnabled?(): boolean;\n\t/** Returns true if this tool consumed the click. */\n\thandleClick(event: MouseEvent): boolean;\n\t/** Preview only — must not consume. */\n\thandleMove?(event: MouseEvent): void;\n\tclear?(): void;\n\tdispose?(): void;\n}\n\nexport interface ToolRegistration {\n\t/** Unique within the registry; registering the same id twice replaces the earlier tool. */\n\tid: string;\n\ttool: PointerTool;\n\t/**\n\t * Higher runs first. The built-ins sit at 0 (measure) and -100 (gizmo); register above 0 to\n\t * claim clicks before measuring, below -100 to act only as a fallback.\n\t */\n\tpriority?: number;\n}\n\nexport interface ToolRegistry {\n\t/** Returns an unregister function. Does not dispose the tool — the registrant still owns it. */\n\tregister(registration: ToolRegistration): () => void;\n\tunregister(id: string): void;\n\tget(id: string): PointerTool | null;\n\t/**\n\t * Enables one tool and disables every other registered one. Pass null to disable all.\n\t * Tools without `setEnabled` are always live and unaffected.\n\t */\n\tsetActive(id: string | null): void;\n\t/** The id passed to the last `setActive`, or null. */\n\tgetActive(): string | null;\n\t/** @internal — `initThree` forwards DOM events here. */\n\thandleClick(event: MouseEvent): boolean;\n\t/** @internal */\n\thandleMove(event: MouseEvent): void;\n}\n\nexport function createToolRegistry(): ToolRegistry {\n\tconst entries: Required<ToolRegistration>[] = [];\n\tlet activeId: string | null = null;\n\n\t// Descending priority, and registration order breaks ties — sort() is stable, so a tool\n\t// registered later never jumps ahead of an equal-priority one already there.\n\tconst sort = () => entries.sort((a, b) => b.priority - a.priority);\n\n\tconst indexOf = (id: string) => entries.findIndex((entry) => entry.id === id);\n\n\tconst unregister = (id: string) => {\n\t\tconst index = indexOf(id);\n\t\tif (index === -1) return;\n\t\tentries.splice(index, 1);\n\t\tif (activeId === id) activeId = null;\n\t};\n\n\tconst register = ({ id, tool, priority = 0 }: ToolRegistration) => {\n\t\tunregister(id);\n\t\tentries.push({ id, tool, priority });\n\t\tsort();\n\t\treturn () => unregister(id);\n\t};\n\n\tconst setActive = (id: string | null) => {\n\t\tactiveId = id;\n\t\tfor (const entry of entries) {\n\t\t\tentry.tool.setEnabled?.(entry.id === id);\n\t\t}\n\t};\n\n\treturn {\n\t\tregister,\n\t\tunregister,\n\t\tget: (id) => entries[indexOf(id)]?.tool ?? null,\n\t\tsetActive,\n\t\tgetActive: () => activeId,\n\t\thandleClick: (event) => {\n\t\t\t// Snapshot: a tool's handler may register or unregister during dispatch.\n\t\t\tfor (const entry of [...entries]) {\n\t\t\t\tif (entry.tool.handleClick(event)) return true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\thandleMove: (event) => {\n\t\t\tfor (const entry of [...entries]) {\n\t\t\t\tentry.tool.handleMove?.(event);\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * Screen-space ray from a canvas mouse event, for tools doing their own picking. Handles the\n * canvas's position and size, so it stays correct under CSS scaling and in fullscreen.\n */\nexport function pointerToNdc(\n\tevent: MouseEvent,\n\tcanvas: HTMLCanvasElement\n): { x: number; y: number } {\n\tconst rect = canvas.getBoundingClientRect();\n\treturn {\n\t\tx: ((event.clientX - rect.left) / rect.width) * 2 - 1,\n\t\ty: -((event.clientY - rect.top) / rect.height) * 2 + 1\n\t};\n}\n","import * as THREE from 'three';\nimport { ViewHelper } from 'three/addons/helpers/ViewHelper.js';\n\nimport type { CameraController } from './camera-controller';\n\n/**\n * Corner nav-cube/axis gizmo. Uses three's {@link ViewHelper} only as the rendered widget, not its\n * click→animate behavior: ViewHelper's snap assumes Y-up and animates straight onto the up axis,\n * which rolls the view and jitters the gizmo at the pole in a Z-up scene. Instead this hit-tests\n * the axis sprites directly and drives the viewer's up-aware camera controller, which snaps\n * instantly with a pole nudge so the orbit basis never degenerates.\n *\n * A click frames the current orbit target (not the world origin) and switches back to perspective\n * first if orthographic — the cube is a 3D-orientation tool.\n *\n * Caller contract (mirrors ViewHelper's own): call {@link ViewGizmo.render} after the main scene\n * render each frame, and forward pointer clicks to {@link ViewGizmo.handleClick}.\n */\nexport interface ViewGizmo {\n\trender(renderer: THREE.WebGLRenderer): void;\n\t/** Returns true if it hit the gizmo (and a view change started). */\n\thandleClick(event: MouseEvent): boolean;\n\tsetVisible(visible: boolean): void;\n\tisVisible(): boolean;\n\tdispose(): void;\n}\n\ninterface ViewGizmoDeps {\n\tcamera: THREE.PerspectiveCamera;\n\tdomElement: HTMLElement;\n\tcontroller: CameraController;\n}\n\nexport function createViewGizmo(deps: ViewGizmoDeps): ViewGizmo {\n\tconst { camera, domElement, controller } = deps;\n\n\tconst helper = new ViewHelper(camera, domElement);\n\thelper.setLabels('X', 'Y', 'Z');\n\n\tlet visible = true;\n\n\t// Mirrors ViewHelper's internal `dim`×`dim` corner-viewport math.\n\tconst DIM = 128;\n\tconst raycaster = new THREE.Raycaster();\n\tconst gizmoCamera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 4);\n\tgizmoCamera.position.set(0, 0, 2);\n\t// This camera is never rendered, so nothing else computes its matrixWorld — and\n\t// Raycaster.setFromCamera doesn't either. Without this the ray originates at the identity\n\t// position (z = 0, the cube's mid-plane) and the camera-facing axis sprites sit behind it.\n\tgizmoCamera.updateMatrixWorld();\n\n\t// target → camera, matching CameraController.setViewDirection.\n\tconst AXIS_DIRECTIONS: Record<string, THREE.Vector3> = {\n\t\tposX: new THREE.Vector3(1, 0, 0),\n\t\tnegX: new THREE.Vector3(-1, 0, 0),\n\t\tposY: new THREE.Vector3(0, 1, 0),\n\t\tnegY: new THREE.Vector3(0, -1, 0),\n\t\tposZ: new THREE.Vector3(0, 0, 1),\n\t\tnegZ: new THREE.Vector3(0, 0, -1)\n\t};\n\n\t// Returns the hit sprite's `userData.type`, or null if the click missed the gizmo.\n\tconst pickAxis = (event: MouseEvent): string | null => {\n\t\tconst rect = domElement.getBoundingClientRect();\n\t\t// Gizmo viewport sits in the bottom-right corner (helper.location defaults: right/bottom 0).\n\t\tconst offsetX = rect.left + domElement.offsetWidth - DIM - helper.location.right;\n\t\tconst offsetY = rect.top + domElement.offsetHeight - DIM - helper.location.bottom;\n\n\t\tconst mouse = new THREE.Vector2(\n\t\t\t((event.clientX - offsetX) / DIM) * 2 - 1,\n\t\t\t-((event.clientY - offsetY) / DIM) * 2 + 1\n\t\t);\n\t\tif (Math.abs(mouse.x) > 1 || Math.abs(mouse.y) > 1) return null;\n\n\t\t// Orient the helper as rendered (inverse of the camera) so sprites match what's on screen.\n\t\thelper.quaternion.copy(camera.quaternion).invert();\n\t\thelper.updateMatrixWorld();\n\n\t\traycaster.setFromCamera(mouse, gizmoCamera);\n\t\tconst hits = raycaster.intersectObjects(helper.children, false);\n\t\tfor (const hit of hits) {\n\t\t\tconst type = hit.object.userData?.type;\n\t\t\tif (typeof type === 'string' && type in AXIS_DIRECTIONS) return type;\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst handleClick = (event: MouseEvent): boolean => {\n\t\tif (!visible) return false;\n\n\t\tconst axis = pickAxis(event);\n\t\tif (!axis) return false;\n\n\t\tif (controller.getProjection() === 'orthographic') {\n\t\t\tcontroller.setProjection('perspective');\n\t\t}\n\n\t\tcontroller.setViewDirection(AXIS_DIRECTIONS[axis]!, false);\n\t\treturn true;\n\t};\n\n\treturn {\n\t\trender: (renderer) => {\n\t\t\tif (!visible) return;\n\t\t\t// ViewHelper.render() calls renderer.render() with autoClear=true by default, which wipes\n\t\t\t// the FULL framebuffer before drawing the cube in its corner. Suppress it — ViewHelper\n\t\t\t// does its own depth clear internally.\n\t\t\tconst prevAutoClear = renderer.autoClear;\n\t\t\trenderer.autoClear = false;\n\t\t\thelper.render(renderer);\n\t\t\trenderer.autoClear = prevAutoClear;\n\t\t},\n\t\thandleClick,\n\t\tsetVisible: (value) => {\n\t\t\tvisible = value;\n\t\t},\n\t\tisVisible: () => visible,\n\t\tdispose: () => helper.dispose()\n\t};\n}\n","import * as THREE from 'three';\nimport type { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport type { CameraController } from '../camera-controller.js';\nimport type { Grid } from '../grid.js';\nimport type { LabelLayer } from '../label-layer.js';\nimport type { NearPlaneFitter } from '../near-plane.js';\nimport type { RenderPipeline } from '../render-pipeline.js';\nimport type { ViewGizmo } from '../view-gizmo.js';\n\n// Resize applied before render so buffer clear and draw happen in the same frame — avoids a\n// visible blank frame on resize.\nexport function createAnimationLoop(\n\trenderer: THREE.WebGLRenderer,\n\tscene: THREE.Scene,\n\tcamera: THREE.PerspectiveCamera,\n\tgetActiveCamera: () => THREE.Camera,\n\tcameraController: CameraController,\n\tcontrols: OrbitControls,\n\tgetCanvasSize: () => { width: number; height: number },\n\tpixelRatio: number,\n\tonFrame?: (delta: number) => void,\n\tgrid?: Grid | null,\n\tgizmo?: ViewGizmo | null,\n\tgetRenderPipeline?: () => RenderPipeline | null,\n\tlabelLayer?: LabelLayer | null,\n\tnearFitter?: NearPlaneFitter | null,\n\t// false = render every frame regardless of invalidate()/camera movement.\n\tonDemand: boolean = true\n): { animate: () => void; dispose: () => void; invalidate: () => void; renderNow: () => void } {\n\tlet animationId: number | null = null;\n\tlet lastTime = performance.now();\n\n\t// The loop always *ticks* (cheap); it only *renders* when invalidate() was called, the active\n\t// camera moved (matrix compare catches damping/presets/gizmo/near-plane), or the idle-repaint\n\t// interval elapsed as a safety net for any mutation that forgot to invalidate.\n\tlet renderRequested = true; // first frame always renders\n\tlet lastRenderTime = 0;\n\tconst IDLE_REPAINT_INTERVAL_MS = 500;\n\tconst lastWorldMatrix = new THREE.Matrix4();\n\tconst lastProjectionMatrix = new THREE.Matrix4();\n\tlet lastCamera: THREE.Camera | null = null;\n\tconst invalidate = () => {\n\t\trenderRequested = true;\n\t};\n\n\tconst cameraMoved = (activeCamera: THREE.Camera): boolean => {\n\t\t// renderer.render normally refreshes matrixWorld, but we're deciding whether to call it — so\n\t\t// refresh here first (cheap: a camera has no deep subtree).\n\t\tactiveCamera.updateMatrixWorld();\n\t\tconst moved =\n\t\t\tlastCamera !== activeCamera ||\n\t\t\t!lastWorldMatrix.equals(activeCamera.matrixWorld) ||\n\t\t\t!lastProjectionMatrix.equals(activeCamera.projectionMatrix);\n\t\tif (moved) {\n\t\t\tlastCamera = activeCamera;\n\t\t\tlastWorldMatrix.copy(activeCamera.matrixWorld);\n\t\t\tlastProjectionMatrix.copy(activeCamera.projectionMatrix);\n\t\t}\n\t\treturn moved;\n\t};\n\n\t// Click-driven mutations (measure points, selection highlights) don't call invalidate() themselves.\n\tconst canvas = renderer.domElement;\n\tconst pointerEvents = ['pointerdown', 'pointerup', 'wheel'] as const;\n\tif (onDemand) {\n\t\tfor (const type of pointerEvents) {\n\t\t\tcanvas.addEventListener(type, invalidate, { passive: true });\n\t\t}\n\t}\n\n\tconst checkResize = () => {\n\t\tconst { width, height } = getCanvasSize();\n\t\tif (width === 0 || height === 0) return;\n\n\t\t// Must floor (not round) to match renderer.setSize's own flooring — otherwise the size\n\t\t// comparison below never settles and the resize branch runs every frame.\n\t\tconst newW = Math.floor(width * pixelRatio);\n\t\tconst newH = Math.floor(height * pixelRatio);\n\n\t\tif (renderer.domElement.width !== newW || renderer.domElement.height !== newH) {\n\t\t\trenderer.setPixelRatio(pixelRatio);\n\t\t\trenderer.setSize(width, height, false);\n\t\t\tcamera.aspect = width / height;\n\t\t\tcamera.updateProjectionMatrix();\n\t\t\tcameraController.updateAspect(width, height);\n\t\t\tgetRenderPipeline?.()?.setSize(width, height, pixelRatio);\n\t\t\tlabelLayer?.setSize(width, height); // CSS2D overlay uses CSS size, not the pixel-ratio buffer\n\t\t\tinvalidate();\n\t\t}\n\t};\n\n\tconst drawFrame = (activeCamera: THREE.Camera, delta: number) => {\n\t\tconst renderPipeline = getRenderPipeline?.();\n\t\tif (renderPipeline) {\n\t\t\trenderPipeline.setCamera(activeCamera); // retarget in case 2D/3D swapped\n\t\t\trenderPipeline.render(delta);\n\t\t} else {\n\t\t\trenderer.render(scene, activeCamera);\n\t\t}\n\n\t\tif (labelLayer) labelLayer.render(scene, activeCamera);\n\n\t\t// Corner-viewport overlay with its own clear; must render last to sit on top.\n\t\tif (gizmo) gizmo.render(renderer);\n\t};\n\n\t// Without preserveDrawingBuffer the colour buffer is cleared once the browser composites, so a\n\t// canvas read (toBlob/toDataURL) only sees pixels if it happens in the same task as a draw.\n\tconst renderNow = () => {\n\t\tdrawFrame(getActiveCamera(), 0);\n\t};\n\n\tconst animate = function () {\n\t\tanimationId = requestAnimationFrame(animate);\n\n\t\tconst now = performance.now();\n\t\tconst delta = (now - lastTime) / 1000;\n\t\tlastTime = now;\n\n\t\tcheckResize();\n\n\t\tif (controls.enableDamping || controls.autoRotate) {\n\t\t\tcontrols.update();\n\t\t}\n\n\t\tif (grid) grid.update(getActiveCamera().position); // recenter on camera so it reads as infinite\n\n\t\t// Before render, so depth precision tracks the camera's current distance from content.\n\t\tif (nearFitter) nearFitter.update();\n\n\t\tonFrame?.(delta);\n\n\t\tconst activeCamera = getActiveCamera();\n\n\t\tif (onDemand) {\n\t\t\tconst shouldRender =\n\t\t\t\trenderRequested ||\n\t\t\t\tcameraMoved(activeCamera) ||\n\t\t\t\tnow - lastRenderTime >= IDLE_REPAINT_INTERVAL_MS;\n\t\t\tif (!shouldRender) return;\n\t\t\trenderRequested = false;\n\t\t\tlastRenderTime = now;\n\t\t}\n\n\t\tdrawFrame(activeCamera, delta);\n\t};\n\n\tconst dispose = () => {\n\t\tif (animationId !== null) {\n\t\t\tcancelAnimationFrame(animationId);\n\t\t\tanimationId = null;\n\t\t}\n\t\tif (onDemand) {\n\t\t\tfor (const type of pointerEvents) {\n\t\t\t\tcanvas.removeEventListener(type, invalidate);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn { animate, dispose, invalidate, renderNow };\n}\n","import * as THREE from 'three';\n\nimport { DEFAULT_LOOK, LOOK_PRESETS } from '../../shared/index.js';\nimport type { ThreeInitializerOptions } from '../types.js';\nimport { isoOffset, sunOffset, upToAxis } from '../up-axis.js';\n\n/** Rhino's convention, and the frame all geometry arrives in — Selva is Z-up end to end. */\nexport const defaultUp = new THREE.Vector3(0, 0, 1);\n\n// onMaxAnisotropy stays optional — a caller-supplied hook, not a config value with a default.\nexport type ResolvedOptions = Required<Omit<ThreeInitializerOptions, 'onMaxAnisotropy'>> &\n\tPick<ThreeInitializerOptions, 'onMaxAnisotropy'>;\n\nexport function applyDefaults(options: ThreeInitializerOptions): ResolvedOptions {\n\tconst scale = options.sceneScale || 'm';\n\n\t// Geometry is always in meters; sceneScale only changes camera/light/grid magnitudes.\n\tconst scaleDefaults = {\n\t\tmm: {\n\t\t\tcameraDistance: 20,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 100,\n\t\t\tlightDistance: 10,\n\t\t\tlightHeight: 20,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 1000\n\t\t},\n\t\tcm: {\n\t\t\tcameraDistance: 20,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 100,\n\t\t\tlightDistance: 25,\n\t\t\tlightHeight: 50,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 100\n\t\t},\n\t\tm: {\n\t\t\tcameraDistance: 10,\n\t\t\tnear: 0.01,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 50,\n\t\t\tlightDistance: 25,\n\t\t\tlightHeight: 50,\n\t\t\tminDistance: 0.001,\n\t\t\tshadowSize: 100,\n\t\t\tscaleFactor: 1\n\t\t},\n\t\tinches: {\n\t\t\tcameraDistance: 15,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 80,\n\t\t\tlightDistance: 20,\n\t\t\tlightHeight: 40,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 80,\n\t\t\tscaleFactor: 39.37\n\t\t},\n\t\tfeet: {\n\t\t\tcameraDistance: 8,\n\t\t\tnear: 0.1,\n\t\t\tfar: 2000,\n\t\t\tfloorSize: 40,\n\t\t\tlightDistance: 15,\n\t\t\tlightHeight: 30,\n\t\t\tminDistance: 0.1,\n\t\t\tshadowSize: 60,\n\t\t\tscaleFactor: 3.28084\n\t\t}\n\t};\n\n\tconst defaults = scaleDefaults[scale];\n\n\t// The look seeds lighting/material defaults (tone mapping, AO, IBL, fill), ranked below explicit\n\t// per-field options but above the plain defaults; it never touches edges/grid.\n\tconst look = options.look ?? DEFAULT_LOOK;\n\tconst preset = LOOK_PRESETS[look];\n\n\treturn {\n\t\tsceneScale: scale,\n\t\tlook,\n\t\tcamera: {\n\t\t\t// Default 3/4 iso (behind-left, above), derived from the scene up axis so a Y-up scene still\n\t\t\t// gets an overhead iso rather than a below-horizon view. cameraDistance*sqrt(3) preserves the\n\t\t\t// orbit radius of the old per-component (-d,-d,d) vector so this doesn't rezoom every scene.\n\t\t\tposition:\n\t\t\t\toptions.camera?.position ||\n\t\t\t\tisoOffset(\n\t\t\t\t\toptions.environment?.sceneUp ?? defaultUp,\n\t\t\t\t\tdefaults.cameraDistance * Math.sqrt(3)\n\t\t\t\t),\n\t\t\tfov: options.camera?.fov || 20,\n\t\t\tnear: options.camera?.near || defaults.near,\n\t\t\tfar: options.camera?.far || defaults.far,\n\t\t\ttarget: options.camera?.target || new THREE.Vector3(0, 0, 0),\n\t\t\tdynamicNear: options.camera?.dynamicNear ?? true\n\t\t},\n\t\tlighting: {\n\t\t\tenableSunlight: options.lighting?.enableSunlight ?? true,\n\t\t\tsunlightIntensity: options.lighting?.sunlightIntensity ?? 1,\n\t\t\t// Expressed in the scene basis so the sun stays overhead in any up convention.\n\t\t\tsunlightPosition:\n\t\t\t\toptions.lighting?.sunlightPosition ||\n\t\t\t\tsunOffset(\n\t\t\t\t\toptions.environment?.sceneUp ?? defaultUp,\n\t\t\t\t\tdefaults.lightDistance,\n\t\t\t\t\tdefaults.lightHeight\n\t\t\t\t),\n\t\t\tambientLightColor: options.lighting?.ambientLightColor || new THREE.Color(0x404040),\n\t\t\tambientLightIntensity: options.lighting?.ambientLightIntensity ?? preset.ambientIntensity,\n\t\t\tsunlightColor: options.lighting?.sunlightColor || 0xffffff,\n\t\t\t// A positive hemisphereIntensity is what actually creates the light in setupLighting.\n\t\t\tenableHemisphereLight:\n\t\t\t\toptions.lighting?.enableHemisphereLight ?? preset.hemisphereIntensity > 0,\n\t\t\themisphereSkyColor: options.lighting?.hemisphereSkyColor ?? 0xdfe6ff,\n\t\t\themisphereGroundColor: options.lighting?.hemisphereGroundColor ?? 0x6b5f52,\n\t\t\themisphereIntensity: options.lighting?.hemisphereIntensity ?? preset.hemisphereIntensity\n\t\t},\n\t\tenvironment: {\n\t\t\thdrPath: options.environment?.hdrPath || '/baseHDR.hdr',\n\t\t\tbackgroundColor: options.environment?.backgroundColor || new THREE.Color(0xf0f0f0),\n\t\t\tenableEnvironmentLighting: options.environment?.enableEnvironmentLighting ?? true,\n\t\t\tsceneUp: options.environment?.sceneUp || defaultUp,\n\t\t\tshowEnvironment: options.environment?.showEnvironment ?? false,\n\t\t\tenvironmentIntensity: options.environment?.environmentIntensity ?? preset.environmentIntensity\n\t\t},\n\t\tfloor: {\n\t\t\tenabled: options.floor?.enabled ?? false,\n\t\t\tsize: options.floor?.size || defaults.floorSize,\n\t\t\tcolor: options.floor?.color || new THREE.Color(0x808080),\n\t\t\troughness: options.floor?.roughness ?? 0.7,\n\t\t\tmetalness: options.floor?.metalness ?? 0.0,\n\t\t\treceiveShadow: options.floor?.receiveShadow ?? true\n\t\t},\n\t\trender: {\n\t\t\tenableShadows: options.render?.enableShadows ?? true,\n\t\t\tshadowMapSize: options.render?.shadowMapSize || 2048,\n\t\t\tantialias: options.render?.antialias ?? true,\n\t\t\tpixelRatio: options.render?.pixelRatio || Math.min(window.devicePixelRatio, 2),\n\t\t\t// ?? not ||: an explicit NoToneMapping (0) must be honoured, not fall through as falsy.\n\t\t\ttoneMapping: options.render?.toneMapping ?? preset.toneMapping,\n\t\t\ttoneMappingExposure: options.render?.toneMappingExposure ?? preset.toneMappingExposure,\n\t\t\tpreserveDrawingBuffer: options.render?.preserveDrawingBuffer ?? false,\n\t\t\tambientOcclusion: options.render?.ambientOcclusion ?? preset.ambientOcclusion,\n\t\t\taoIntensity: options.render?.aoIntensity ?? 1,\n\t\t\t// Default caps AO buffers at 1x — biggest lever on GTAO cost at high DPI.\n\t\t\taoPixelRatio: options.render?.aoPixelRatio ?? 1,\n\t\t\tonDemand: options.render?.onDemand ?? true\n\t\t},\n\t\tcontrols: {\n\t\t\tenableDamping: options.controls?.enableDamping ?? false,\n\t\t\tdampingFactor: options.controls?.dampingFactor || 0.05,\n\t\t\tautoRotate: options.controls?.autoRotate ?? false,\n\t\t\tautoRotateSpeed: options.controls?.autoRotateSpeed || 0.5,\n\t\t\tenableZoom: options.controls?.enableZoom ?? true,\n\t\t\tenablePan: options.controls?.enablePan ?? true,\n\t\t\tminDistance: options.controls?.minDistance || defaults.minDistance,\n\t\t\tmaxDistance: options.controls?.maxDistance || Infinity\n\t\t},\n\t\tgrid: {\n\t\t\t// Mirrors createGrid's own defaults so the two never drift.\n\t\t\tenabled: options.grid?.enabled ?? false,\n\t\t\tcellSize: options.grid?.cellSize ?? 1,\n\t\t\tmajorEvery: options.grid?.majorEvery ?? 10,\n\t\t\tcellColor: options.grid?.cellColor ?? 0x888888,\n\t\t\tmajorColor: options.grid?.majorColor ?? 0x444444,\n\t\t\tfadeDistance: options.grid?.fadeDistance ?? 100,\n\t\t\t// Orthogonal to the scene up axis: Z-up Rhino -> 'z', Y-up -> 'y'.\n\t\t\tplane: options.grid?.plane ?? upToAxis(options.environment?.sceneUp ?? defaultUp)\n\t\t},\n\t\tgizmo: {\n\t\t\tenabled: options.gizmo?.enabled ?? false\n\t\t},\n\t\tedges: {\n\t\t\t// Mirrors addEdges' own defaults so the two never drift.\n\t\t\tenabled: options.edges?.enabled ?? false,\n\t\t\t// Undefined lets addEdges derive each mesh's edge color from its own surface material.\n\t\t\tcolor: options.edges?.color,\n\t\t\tdarken: options.edges?.darken,\n\t\t\twidth: options.edges?.width ?? 1.5,\n\t\t\tthresholdAngle: options.edges?.thresholdAngle ?? 44,\n\t\t\tdistanceFade: options.edges?.distanceFade ?? true,\n\t\t\t// Passed through undefined on purpose: the caps' canonical defaults live in\n\t\t\t// `edges/options.ts` (resolveOptions), and applyEdges forwards these straight to it.\n\t\t\t// Restating 4M/2M here would be a second copy free to drift from the real one.\n\t\t\tmaxTriangles: options.edges?.maxTriangles,\n\t\t\tmaxSegments: options.edges?.maxSegments,\n\t\t\t// Read by init-three's updateEdgeFallback, which only checks for an explicit `false`.\n\t\t\tscreenSpaceFallback: options.edges?.screenSpaceFallback\n\t\t},\n\t\tmeasure: {\n\t\t\t// Visual defaults live in createMeasureTool; these pass through undefined to it.\n\t\t\tenabled: options.measure?.enabled ?? false,\n\t\t\tsnapPixels: options.measure?.snapPixels,\n\t\t\tcolor: options.measure?.color,\n\t\t\tlabelClassName: options.measure?.labelClassName,\n\t\t\tdisplayUnit: options.measure?.displayUnit,\n\t\t\tformat: options.measure?.format\n\t\t},\n\t\tevents: {\n\t\t\tonBackgroundClicked: options.events?.onBackgroundClicked,\n\t\t\tonObjectSelected: options.events?.onObjectSelected,\n\t\t\tonMeshMetadataClicked: options.events?.onMeshMetadataClicked,\n\t\t\tonMeshDoubleClicked: options.events?.onMeshDoubleClicked,\n\t\t\tselectionColor: options.events?.selectionColor || '#ff0000',\n\t\t\tenableEventHandlers: options.events?.enableEventHandlers ?? true,\n\t\t\tenableKeyboardControls: options.events?.enableKeyboardControls ?? true,\n\t\t\tenableClickToFocus: options.events?.enableClickToFocus ?? true,\n\t\t\tenableDoubleClickZoom: options.events?.enableDoubleClickZoom ?? true,\n\t\t\tonReady: options.events?.onReady,\n\t\t\tonFrame: options.events?.onFrame\n\t\t},\n\t\tonMaxAnisotropy: options.onMaxAnisotropy\n\t};\n}\n","import * as THREE from 'three';\n\nimport { LOOK_PRESETS, materialAppearanceForLook } from '../../shared/index.js';\nimport { SOURCE_COMPUTE } from '../scene-ownership.js';\nimport type { Look, MaterialAppearanceOptions } from '../types.js';\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\nimport type { PipelineController } from './pipeline-controller.js';\nimport type { SceneLights } from './setup-lighting.js';\n\n/** The runtime lighting/material dials — everything a host can retune without rebuilding the scene. */\nexport interface AppearanceController {\n\tsetFillLights(opts: {\n\t\themisphereIntensity?: number;\n\t\themisphereSkyColor?: THREE.Color | number;\n\t\themisphereGroundColor?: THREE.Color | number;\n\t\tambientIntensity?: number;\n\t}): void;\n\tsetEnvironmentIntensity(intensity: number): void;\n\tsetToneMappingExposure(exposure: number): void;\n\tsetAoIntensity(intensity: number): void;\n\tsetLook(look: Look): void;\n\tgetMaterialAppearance(): MaterialAppearanceOptions;\n}\n\n// setLook is built from the same setters a host would call directly, so construction-time defaults\n// (applyDefaults seeding from LOOK_PRESETS) can't drift from the runtime path.\nexport function createAppearanceController(params: {\n\tscene: THREE.Scene;\n\trenderer: THREE.WebGLRenderer;\n\tlights: SceneLights;\n\tconfig: ResolvedOptions;\n\tpipeline: PipelineController;\n\trequestRender: () => void;\n}): AppearanceController {\n\tconst { scene, renderer, lights, config, pipeline, requestRender } = params;\n\n\tlet activeLook: Look = config.look;\n\n\tconst setFillLights: AppearanceController['setFillLights'] = (opts) => {\n\t\tif (opts.ambientIntensity !== undefined) {\n\t\t\tlights.ambient.intensity = opts.ambientIntensity;\n\t\t}\n\t\tif (\n\t\t\topts.hemisphereIntensity !== undefined &&\n\t\t\t!lights.hemisphere &&\n\t\t\topts.hemisphereIntensity > 0\n\t\t) {\n\t\t\t// Lazily created so hosts can enable fill at runtime even if the scene was built without one.\n\t\t\tlights.hemisphere = new THREE.HemisphereLight(\n\t\t\t\topts.hemisphereSkyColor ?? config.lighting.hemisphereSkyColor,\n\t\t\t\topts.hemisphereGroundColor ?? config.lighting.hemisphereGroundColor,\n\t\t\t\topts.hemisphereIntensity\n\t\t\t);\n\t\t\tlights.hemisphere.position.copy(config.environment.sceneUp ?? defaultUp);\n\t\t\tscene.add(lights.hemisphere);\n\t\t}\n\t\tif (lights.hemisphere) {\n\t\t\tif (opts.hemisphereIntensity !== undefined)\n\t\t\t\tlights.hemisphere.intensity = opts.hemisphereIntensity;\n\t\t\tif (opts.hemisphereSkyColor !== undefined)\n\t\t\t\tlights.hemisphere.color.set(opts.hemisphereSkyColor);\n\t\t\tif (opts.hemisphereGroundColor !== undefined)\n\t\t\t\tlights.hemisphere.groundColor.set(opts.hemisphereGroundColor);\n\t\t}\n\t\trequestRender();\n\t};\n\n\tconst setEnvironmentIntensity = (intensity: number) => {\n\t\tconfig.environment.environmentIntensity = intensity;\n\t\tscene.environmentIntensity = intensity;\n\t\trequestRender();\n\t};\n\n\tconst setToneMappingExposure = (exposure: number) => {\n\t\tconfig.render.toneMappingExposure = exposure;\n\t\trenderer.toneMappingExposure = exposure;\n\t\t// Composer applies tone mapping via its own OutputPass, so it must rebuild to pick this up.\n\t\tif (pipeline.get()) pipeline.rebuild();\n\t};\n\n\tconst setAoIntensity = (intensity: number) => {\n\t\tconfig.render.aoIntensity = intensity;\n\t\tif (pipeline.get()) pipeline.rebuild();\n\t};\n\n\tconst setLook = (look: Look) => {\n\t\tconst preset = LOOK_PRESETS[look];\n\t\tactiveLook = look;\n\n\t\trenderer.toneMapping = preset.toneMapping;\n\t\trenderer.toneMappingExposure = preset.toneMappingExposure;\n\t\tconfig.render.toneMapping = preset.toneMapping;\n\t\tconfig.render.toneMappingExposure = preset.toneMappingExposure;\n\n\t\tsetFillLights({\n\t\t\themisphereIntensity: preset.hemisphereIntensity,\n\t\t\tambientIntensity: preset.ambientIntensity\n\t\t});\n\t\tsetEnvironmentIntensity(preset.environmentIntensity);\n\n\t\t// Rebuild on top of setAmbientOcclusion so an already-live composer's OutputPass adopts the\n\t\t// new tone mapping too.\n\t\tconst hadPipeline = pipeline.get() !== null;\n\t\tpipeline.setAmbientOcclusion(preset.ambientOcclusion);\n\t\tif (hadPipeline) pipeline.rebuild();\n\n\t\t// Solve output only. Host-added geometry (`user`/`app:` scopes) owns its own materials —\n\t\t// a point cloud or draft line has a deliberate look that a render-style switch must not\n\t\t// overwrite. Hosts that do want to follow the look read `getMaterialAppearance()`.\n\t\tscene.traverse((object) => {\n\t\t\tif (object.userData.source !== SOURCE_COMPUTE) return;\n\t\t\tconst mesh = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\t\tconst materials = Array.isArray(mesh.material)\n\t\t\t\t? mesh.material\n\t\t\t\t: mesh.material\n\t\t\t\t\t? [mesh.material]\n\t\t\t\t\t: [];\n\t\t\tfor (const material of materials) {\n\t\t\t\tif ('envMapIntensity' in material) {\n\t\t\t\t\t(material as THREE.MeshStandardMaterial).envMapIntensity = preset.envMapIntensity;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\trequestRender();\n\t};\n\n\treturn {\n\t\tsetFillLights,\n\t\tsetEnvironmentIntensity,\n\t\tsetToneMappingExposure,\n\t\tsetAoIntensity,\n\t\tsetLook,\n\t\tgetMaterialAppearance: () => materialAppearanceForLook(activeLook)\n\t};\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function createCamera(\n\tconfig: ResolvedOptions,\n\tcanvas: HTMLCanvasElement\n): THREE.PerspectiveCamera {\n\tconst parent = canvas.parentElement;\n\tconst width = parent ? parent.clientWidth : window.innerWidth;\n\tconst height = parent ? parent.clientHeight : window.innerHeight;\n\n\tconst camera = new THREE.PerspectiveCamera(\n\t\tconfig.camera.fov,\n\t\twidth / height,\n\t\tconfig.camera.near,\n\t\tconfig.camera.far\n\t);\n\n\tconst pos = config.camera.position;\n\tif (pos) {\n\t\tcamera.position.set(pos.x, pos.y, pos.z);\n\t}\n\n\treturn camera;\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function createScene(config: ResolvedOptions): THREE.Scene {\n\tconst scene = new THREE.Scene();\n\n\tconst bgColor =\n\t\ttypeof config.environment.backgroundColor === 'string'\n\t\t\t? new THREE.Color(config.environment.backgroundColor)\n\t\t\t: config.environment.backgroundColor;\n\tscene.background = bgColor || null;\n\n\treturn scene;\n}\n","import * as THREE from 'three';\n\nimport { disposeObjectTree, type DisposeOptions } from '../../shared/index.js';\n\nexport { disposeObjectTree };\nexport type { DisposeOptions };\n\n/** Sweeps every renderable plus the scene-level textures the object traversal can't reach. */\nexport function disposeSceneResources(scene: THREE.Scene, options?: DisposeOptions): void {\n\tdisposeObjectTree(scene, options);\n\n\tscene.environment?.dispose();\n\tif (scene.background instanceof THREE.Texture) {\n\t\tscene.background.dispose();\n\t}\n}\n","import * as THREE from 'three';\nimport { Pass, FullScreenQuad } from 'three/addons/postprocessing/Pass.js';\n\n/**\n * Screen-space edge detection (Roberts cross on depth + normal discontinuities), O(pixels)\n * regardless of triangle count. Fallback for meshes too heavy for geometry edge overlays\n * (over `EdgeOptions.maxTriangles`): uniform pixel width, one global color, view-dependent,\n * gentle creases below the normal threshold don't register.\n */\nexport interface EdgeDetectionOptions {\n\tcolor?: THREE.ColorRepresentation;\n\topacity?: number;\n\t/** Summed `1 - dot(n₁, n₂)` across the two diagonal pairs. Lower catches gentler creases. */\n\tnormalThreshold?: number;\n\t/** Relative view-depth discontinuity, fraction of center depth. */\n\tdepthThreshold?: number;\n\t/** Sample offset in device px — line thickness. */\n\tthickness?: number;\n}\n\nconst EDGE_SHADER = {\n\tuniforms: {\n\t\ttDiffuse: { value: null as THREE.Texture | null },\n\t\ttNormal: { value: null as THREE.Texture | null },\n\t\ttDepth: { value: null as THREE.Texture | null },\n\t\tuResolution: { value: new THREE.Vector2(1, 1) },\n\t\tuColor: { value: new THREE.Color(0x222222) },\n\t\tuOpacity: { value: 1 },\n\t\tuNormalThreshold: { value: 0.4 },\n\t\tuDepthThreshold: { value: 0.02 },\n\t\tuThickness: { value: 1 },\n\t\tuNear: { value: 0.1 },\n\t\tuFar: { value: 1000 },\n\t\tuPerspective: { value: 1 }\n\t},\n\tvertexShader: /* glsl */ `\n\t\tvarying vec2 vUv;\n\t\tvoid main() {\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n\t\t}\n\t`,\n\tfragmentShader: /* glsl */ `\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform sampler2D tNormal;\n\t\tuniform sampler2D tDepth;\n\t\tuniform vec2 uResolution;\n\t\tuniform vec3 uColor;\n\t\tuniform float uOpacity;\n\t\tuniform float uNormalThreshold;\n\t\tuniform float uDepthThreshold;\n\t\tuniform float uThickness;\n\t\tuniform float uNear;\n\t\tuniform float uFar;\n\t\tuniform float uPerspective;\n\t\tvarying vec2 vUv;\n\n\t\tfloat viewZOf(const in float depth) {\n\t\t\tfloat perspective = (uNear * uFar) / ((uFar - uNear) * depth - uFar);\n\t\t\tfloat orthographic = -(depth * (uFar - uNear) + uNear);\n\t\t\treturn mix(orthographic, perspective, uPerspective);\n\t\t}\n\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D(tDiffuse, vUv);\n\t\t\tvec2 texel = uThickness / uResolution;\n\n\t\t\tvec2 offsetA = vec2(texel.x, texel.y);\n\t\t\tvec2 offsetB = vec2(texel.x, -texel.y);\n\n\t\t\tfloat z0 = viewZOf(texture2D(tDepth, vUv + offsetA).x);\n\t\t\tfloat z1 = viewZOf(texture2D(tDepth, vUv - offsetA).x);\n\t\t\tfloat z2 = viewZOf(texture2D(tDepth, vUv + offsetB).x);\n\t\t\tfloat z3 = viewZOf(texture2D(tDepth, vUv - offsetB).x);\n\t\t\tfloat zCenter = viewZOf(texture2D(tDepth, vUv).x);\n\t\t\t// Normalized by center depth, not absolute Z: keeps the response scale-invariant (an\n\t\t\t// absolute threshold would be noise far away, blind up close).\n\t\t\tfloat depthDelta = (abs(z0 - z1) + abs(z2 - z3)) / max(abs(zCenter), 1e-6);\n\t\t\tfloat depthEdge = step(uDepthThreshold, depthDelta);\n\n\t\t\tvec3 n0 = texture2D(tNormal, vUv + offsetA).rgb * 2.0 - 1.0;\n\t\t\tvec3 n1 = texture2D(tNormal, vUv - offsetA).rgb * 2.0 - 1.0;\n\t\t\tvec3 n2 = texture2D(tNormal, vUv + offsetB).rgb * 2.0 - 1.0;\n\t\t\tvec3 n3 = texture2D(tNormal, vUv - offsetB).rgb * 2.0 - 1.0;\n\t\t\tfloat normalDelta = (1.0 - dot(n0, n1)) + (1.0 - dot(n2, n3));\n\t\t\tfloat normalEdge = step(uNormalThreshold, normalDelta);\n\n\t\t\tfloat edge = max(depthEdge, normalEdge) * uOpacity;\n\t\t\tgl_FragColor = vec4(mix(color.rgb, uColor, edge), color.a);\n\t\t}\n\t`\n};\n\nexport class EdgeDetectionPass extends Pass {\n\tcamera: THREE.Camera;\n\n\tprivate readonly scene: THREE.Scene;\n\tprivate readonly normalMaterial: THREE.MeshNormalMaterial;\n\tprivate readonly edgeMaterial: THREE.ShaderMaterial;\n\tprivate readonly fsQuad: FullScreenQuad;\n\tprivate normalTarget: THREE.WebGLRenderTarget | null = null;\n\tprivate width: number;\n\tprivate height: number;\n\n\tconstructor(\n\t\tscene: THREE.Scene,\n\t\tcamera: THREE.Camera,\n\t\twidth: number,\n\t\theight: number,\n\t\toptions: EdgeDetectionOptions = {}\n\t) {\n\t\tsuper();\n\t\tthis.scene = scene;\n\t\tthis.camera = camera;\n\t\tthis.width = Math.max(1, width);\n\t\tthis.height = Math.max(1, height);\n\n\t\tthis.normalMaterial = new THREE.MeshNormalMaterial();\n\t\tthis.normalMaterial.blending = THREE.NoBlending;\n\n\t\tthis.edgeMaterial = new THREE.ShaderMaterial({\n\t\t\tuniforms: THREE.UniformsUtils.clone(EDGE_SHADER.uniforms),\n\t\t\tvertexShader: EDGE_SHADER.vertexShader,\n\t\t\tfragmentShader: EDGE_SHADER.fragmentShader\n\t\t});\n\t\tconst uniforms = this.edgeMaterial.uniforms;\n\t\tuniforms.uColor.value = new THREE.Color(options.color ?? 0x222222);\n\t\tuniforms.uOpacity.value = options.opacity ?? 1;\n\t\tuniforms.uNormalThreshold.value = options.normalThreshold ?? 0.4;\n\t\tuniforms.uDepthThreshold.value = options.depthThreshold ?? 0.02;\n\t\tuniforms.uThickness.value = options.thickness ?? 1;\n\n\t\tthis.fsQuad = new FullScreenQuad(this.edgeMaterial);\n\t\tthis.needsSwap = true;\n\t}\n\n\tprivate acquireNormalTarget(): THREE.WebGLRenderTarget {\n\t\tif (!this.normalTarget) {\n\t\t\tconst depthTexture = new THREE.DepthTexture(this.width, this.height);\n\t\t\tthis.normalTarget = new THREE.WebGLRenderTarget(this.width, this.height, {\n\t\t\t\tminFilter: THREE.NearestFilter,\n\t\t\t\tmagFilter: THREE.NearestFilter,\n\t\t\t\tdepthTexture\n\t\t\t});\n\t\t}\n\t\treturn this.normalTarget;\n\t}\n\n\toverride setSize(width: number, height: number): void {\n\t\tthis.width = Math.max(1, width);\n\t\tthis.height = Math.max(1, height);\n\t\tthis.normalTarget?.setSize(this.width, this.height);\n\t}\n\n\toverride render(\n\t\trenderer: THREE.WebGLRenderer,\n\t\twriteBuffer: THREE.WebGLRenderTarget,\n\t\treadBuffer: THREE.WebGLRenderTarget\n\t): void {\n\t\tconst normalTarget = this.acquireNormalTarget();\n\n\t\t// --- Normals + depth prepass (override material) ---\n\t\tconst previousTarget = renderer.getRenderTarget();\n\t\tconst previousAutoClear = renderer.autoClear;\n\t\tconst previousClearColor = renderer.getClearColor(new THREE.Color());\n\t\tconst previousClearAlpha = renderer.getClearAlpha();\n\t\tconst previousOverride = this.scene.overrideMaterial;\n\n\t\trenderer.setRenderTarget(normalTarget);\n\t\t// 0x7777ff is packed +Z: background pixels get a uniform normal, so only depth silhouettes\n\t\t// (not normal noise) separate objects from empty space.\n\t\trenderer.setClearColor(0x7777ff, 1);\n\t\trenderer.autoClear = true;\n\t\tthis.scene.overrideMaterial = this.normalMaterial;\n\t\trenderer.render(this.scene, this.camera);\n\t\tthis.scene.overrideMaterial = previousOverride;\n\t\trenderer.setClearColor(previousClearColor, previousClearAlpha);\n\t\trenderer.autoClear = previousAutoClear;\n\n\t\t// --- Edge composite ---\n\t\tconst uniforms = this.edgeMaterial.uniforms;\n\t\tuniforms.tDiffuse.value = readBuffer.texture;\n\t\tuniforms.tNormal.value = normalTarget.texture;\n\t\tuniforms.tDepth.value = normalTarget.depthTexture;\n\t\tuniforms.uResolution.value.set(this.width, this.height);\n\t\tconst perspective = this.camera as Partial<THREE.PerspectiveCamera>;\n\t\tuniforms.uPerspective.value = perspective.isPerspectiveCamera ? 1 : 0;\n\t\tuniforms.uNear.value = (this.camera as THREE.PerspectiveCamera).near ?? 0.1;\n\t\tuniforms.uFar.value = (this.camera as THREE.PerspectiveCamera).far ?? 1000;\n\n\t\trenderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);\n\t\tthis.fsQuad.render(renderer);\n\t\trenderer.setRenderTarget(previousTarget);\n\t}\n\n\toverride dispose(): void {\n\t\tthis.normalTarget?.dispose();\n\t\tthis.normalMaterial.dispose();\n\t\tthis.edgeMaterial.dispose();\n\t\tthis.fsQuad.dispose();\n\t}\n}\n","import * as THREE from 'three';\nimport { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/addons/postprocessing/RenderPass.js';\nimport { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';\nimport { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';\nimport { OutputPass } from 'three/addons/postprocessing/OutputPass.js';\n\nimport { EdgeDetectionPass, type EdgeDetectionOptions } from './edge-detection-pass';\n\n/**\n * Pipeline: RenderPass → GTAOPass? → EdgeDetectionPass? → SMAAPass → OutputPass. EdgeDetectionPass\n * sits before SMAA so its 1px lines get antialiased. SMAA is required because EffectComposer\n * renders offscreen, so the renderer's own MSAA does nothing here; SMAA over TAA because TAA's\n * temporal jitter smears during OrbitControls drags. OutputPass applies tone mapping and color\n * space last, so SMAA operates on the pre-tonemapped image.\n */\n\nexport interface RenderPipeline {\n\trender(deltaTime: number): void;\n\tsetSize(width: number, height: number, pixelRatio: number): void;\n\t/** Call when the camera's projection changes (e.g. perspective↔ortho). */\n\tsetCamera(camera: THREE.Camera): void;\n\tsetEdgeDetection(enabled: boolean): void;\n\tedgeDetectionEnabled(): boolean;\n\tdispose(): void;\n}\n\nexport interface RenderPipelineOptions {\n\t/** Must mirror the renderer's own tone mapping — OutputPass applies it once composited, not the renderer. */\n\ttoneMapping: THREE.ToneMapping;\n\ttoneMappingExposure: number;\n\t/** Default true. */\n\tambientOcclusion?: boolean;\n\t/** AO strength 0–1. Default 1. */\n\taoIntensity?: number;\n\t/** DPR cap for the composer's AO buffers; clamps `setSize`'s pixelRatio. Default 1. */\n\taoPixelRatio?: number;\n\t/** Start with the screen-space edge pass enabled; pass an object to tune it. */\n\tedgeDetection?: boolean | EdgeDetectionOptions;\n}\n\nexport function createRenderPipeline(\n\trenderer: THREE.WebGLRenderer,\n\tscene: THREE.Scene,\n\tcamera: THREE.Camera,\n\twidth: number,\n\theight: number,\n\toptions: RenderPipelineOptions\n): RenderPipeline {\n\tconst composer = new EffectComposer(renderer);\n\n\tconst renderPass = new RenderPass(scene, camera);\n\tcomposer.addPass(renderPass);\n\n\tlet gtaoPass: GTAOPass | null = null;\n\tif (options.ambientOcclusion ?? true) {\n\t\tgtaoPass = new GTAOPass(scene, camera, width, height);\n\t\tgtaoPass.blendIntensity = options.aoIntensity ?? 1;\n\t\tgtaoPass.updateGtaoMaterial({ screenSpaceRadius: true });\n\t\tcomposer.addPass(gtaoPass);\n\t}\n\n\tconst edgeOptions = typeof options.edgeDetection === 'object' ? options.edgeDetection : {};\n\tconst edgePass = new EdgeDetectionPass(scene, camera, width, height, edgeOptions);\n\tedgePass.enabled = !!options.edgeDetection;\n\tcomposer.addPass(edgePass);\n\n\tconst smaaPass = new SMAAPass();\n\tcomposer.addPass(smaaPass);\n\n\tconst outputPass = new OutputPass();\n\tcomposer.addPass(outputPass);\n\n\trenderer.toneMapping = options.toneMapping;\n\trenderer.toneMappingExposure = options.toneMappingExposure;\n\n\tconst aoPixelRatioCap = options.aoPixelRatio ?? 1;\n\tcomposer.setSize(width, height);\n\n\treturn {\n\t\trender: (deltaTime) => composer.render(deltaTime),\n\t\t// composer.setSize only — calling individual pass.setSize would reset AO/AA targets back to\n\t\t// logical CSS size, undoing the pixel-ratio scaling.\n\t\tsetSize: (w, h, pixelRatio) => {\n\t\t\tcomposer.setPixelRatio(Math.min(pixelRatio, aoPixelRatioCap));\n\t\t\tcomposer.setSize(w, h);\n\t\t},\n\t\tsetCamera: (cam) => {\n\t\t\trenderPass.camera = cam;\n\t\t\tedgePass.camera = cam;\n\t\t\tif (!gtaoPass) return;\n\t\t\tgtaoPass.camera = cam;\n\t\t\t// GTAOPass bakes camera type into its AO shader as a construction-time define; reassigning\n\t\t\t// `camera` alone leaves the old projection's depth reconstruction active — garbage AO after\n\t\t\t// a perspective⇄ortho toggle. Force a recompile when the type actually changes.\n\t\t\tconst isPerspective = (cam as Partial<THREE.PerspectiveCamera>).isPerspectiveCamera ? 1 : 0;\n\t\t\tif (gtaoPass.gtaoMaterial.defines.PERSPECTIVE_CAMERA !== isPerspective) {\n\t\t\t\tgtaoPass.gtaoMaterial.defines.PERSPECTIVE_CAMERA = isPerspective;\n\t\t\t\tgtaoPass.gtaoMaterial.needsUpdate = true;\n\t\t\t}\n\t\t},\n\t\tsetEdgeDetection: (enabled) => {\n\t\t\tedgePass.enabled = enabled;\n\t\t},\n\t\tedgeDetectionEnabled: () => edgePass.enabled,\n\t\t// composer.dispose() doesn't free passes.\n\t\tdispose: () => {\n\t\t\tcomposer.dispose();\n\t\t\tgtaoPass?.dispose();\n\t\t\tedgePass.dispose();\n\t\t\tsmaaPass.dispose();\n\t\t\toutputPass.dispose();\n\t\t}\n\t};\n}\n","import * as THREE from 'three';\n\nimport { createRenderPipeline, type RenderPipeline } from '../render-pipeline.js';\nimport type { ResolvedOptions } from './defaults.js';\n\n/**\n * Owns the optional postprocessing composer and the two independent reasons to want one: ambient\n * occlusion (a user/look choice) and the screen-space edge fallback (forced on while meshes over\n * the triangle cap are in the scene). Neither knows about the other, so \"is a pipeline wanted, and\n * does it need rebuilding\" is reconciled here instead of at each caller.\n */\nexport interface PipelineController {\n\tget(): RenderPipeline | null;\n\tsync(): void;\n\t/** Dispose and rebuild (if one is wanted) so construction-time options re-apply. */\n\trebuild(): void;\n\tsetAmbientOcclusion(enabled: boolean): void;\n\tsetEdgeFallback(active: boolean): void;\n\tisEdgeFallbackActive(): boolean;\n\tdispose(): void;\n}\n\nexport function createPipelineController(params: {\n\trenderer: THREE.WebGLRenderer;\n\tscene: THREE.Scene;\n\tgetActiveCamera: () => THREE.Camera;\n\tgetCanvasSize: () => { width: number; height: number };\n\tpixelRatio: number;\n\tconfig: ResolvedOptions;\n\trequestRender: () => void;\n}): PipelineController {\n\tconst { renderer, scene, getActiveCamera, getCanvasSize, pixelRatio, config, requestRender } =\n\t\tparams;\n\n\tlet pipeline: RenderPipeline | null = null;\n\tlet aoEnabled = !!config.render.ambientOcclusion;\n\tlet edgeFallbackActive = false;\n\tlet builtWithAo = false;\n\n\tconst build = (withAo: boolean): RenderPipeline => {\n\t\tconst { width, height } = getCanvasSize();\n\t\tconst built = createRenderPipeline(\n\t\t\trenderer,\n\t\t\tscene,\n\t\t\tgetActiveCamera(),\n\t\t\tMath.max(1, width),\n\t\t\tMath.max(1, height),\n\t\t\t{\n\t\t\t\ttoneMapping: config.render.toneMapping ?? THREE.NeutralToneMapping,\n\t\t\t\ttoneMappingExposure: config.render.toneMappingExposure ?? 1,\n\t\t\t\tambientOcclusion: withAo,\n\t\t\t\taoIntensity: config.render.aoIntensity,\n\t\t\t\taoPixelRatio: config.render.aoPixelRatio,\n\t\t\t\t// Always built disabled; sync() flips it live via setEdgeDetection.\n\t\t\t\tedgeDetection: false\n\t\t\t}\n\t\t);\n\t\tbuilt.setSize(Math.max(1, width), Math.max(1, height), pixelRatio);\n\t\treturn built;\n\t};\n\n\tconst sync = () => {\n\t\tconst wantPipeline = aoEnabled || edgeFallbackActive;\n\t\tif (!wantPipeline) {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t\trequestRender();\n\t\t\treturn;\n\t\t}\n\t\tif (!pipeline || builtWithAo !== aoEnabled) {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = build(aoEnabled);\n\t\t\tbuiltWithAo = aoEnabled;\n\t\t}\n\t\tpipeline.setEdgeDetection(edgeFallbackActive);\n\t\trequestRender();\n\t};\n\n\treturn {\n\t\tget: () => pipeline,\n\t\tsync,\n\t\trebuild: () => {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t\tsync();\n\t\t},\n\t\tsetAmbientOcclusion: (enabled: boolean) => {\n\t\t\taoEnabled = enabled;\n\t\t\tsync();\n\t\t},\n\t\tsetEdgeFallback: (active: boolean) => {\n\t\t\tif (active === edgeFallbackActive) return;\n\t\t\tedgeFallbackActive = active;\n\t\t\tsync();\n\t\t},\n\t\tisEdgeFallbackActive: () => edgeFallbackActive,\n\t\tdispose: () => {\n\t\t\tpipeline?.dispose();\n\t\t\tpipeline = null;\n\t\t}\n\t};\n}\n","import type * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupControls(\n\tcamera: THREE.PerspectiveCamera,\n\tcanvas: HTMLCanvasElement,\n\tconfig: ResolvedOptions\n): OrbitControls {\n\tconst controls = new OrbitControls(camera, canvas);\n\n\tconst target = config.camera.target;\n\tif (target) {\n\t\tcontrols.target.set(target.x, target.y, target.z);\n\t}\n\n\tcontrols.enableDamping = config.controls.enableDamping || false;\n\tcontrols.dampingFactor = config.controls.dampingFactor || 0.05;\n\n\tcontrols.autoRotate = config.controls.autoRotate || false;\n\tcontrols.autoRotateSpeed = config.controls.autoRotateSpeed || 0.5;\n\n\tcontrols.enableZoom = config.controls.enableZoom ?? true;\n\tcontrols.enablePan = config.controls.enablePan ?? true;\n\tcontrols.minDistance = config.controls.minDistance || 0.001;\n\tcontrols.maxDistance = config.controls.maxDistance || Infinity;\n\n\tcontrols.screenSpacePanning = false;\n\tcontrols.maxPolarAngle = Math.PI;\n\n\tcontrols.update();\n\treturn controls;\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from '../../shared/index.js';\nimport { HDRLoader } from 'three/addons/loaders/HDRLoader.js';\n\nimport { environmentRotationFor } from '../up-axis.js';\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\n\nexport function setupEnvironment(\n\tscene: THREE.Scene,\n\trenderer: THREE.WebGLRenderer,\n\tconfig: ResolvedOptions,\n\tisDisposed: () => boolean\n) {\n\tif (config.environment.enableEnvironmentLighting) {\n\t\tnew HDRLoader().load(\n\t\t\tconfig.environment.hdrPath || '/baseHDR.hdr',\n\t\t\tfunction (envMap) {\n\t\t\t\t// Viewer may be torn down mid-fetch (fast mount/unmount); dispose() already swept the\n\t\t\t\t// scene, so adopting the texture now would leak it, and onReady must not fire.\n\t\t\t\tif (isDisposed()) {\n\t\t\t\t\tenvMap.dispose();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!envMap?.image) {\n\t\t\t\t\tgetLogger().warn('HDR loaded without image data; skipping environment map.');\n\t\t\t\t\tenvMap?.dispose();\n\t\t\t\t\tconfig.events.onReady?.();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tenvMap.mapping = THREE.EquirectangularReflectionMapping;\n\n\t\t\t\t// PMREM builds the roughness-aware mip chain MeshStandardMaterial samples for IBL;\n\t\t\t\t// without it, rough surfaces read a near-mirror level (sharp reflections, sparkly highlights).\n\t\t\t\tconst pmrem = new THREE.PMREMGenerator(renderer);\n\t\t\t\tpmrem.compileEquirectangularShader();\n\t\t\t\tconst prefiltered = pmrem.fromEquirectangular(envMap).texture;\n\t\t\t\tpmrem.dispose();\n\n\t\t\t\tscene.environment = prefiltered;\n\t\t\t\t// Normalizes IBL contribution so brightness is consistent across HDRs of differing exposure.\n\t\t\t\tscene.environmentIntensity = config.environment.environmentIntensity ?? 1;\n\t\t\t\t// Equirect mapping assumes the horizon lies in the XZ plane (Y-up); without rotating for\n\t\t\t\t// a Z-up scene the sky lights the model from +Y instead of from above.\n\t\t\t\tconst envRotation = environmentRotationFor(config.environment.sceneUp ?? defaultUp);\n\t\t\t\tscene.environmentRotation.copy(envRotation);\n\t\t\t\tif (config.environment.showEnvironment) {\n\t\t\t\t\t// Background wants the full-res equirect, not the low-res prefiltered probe.\n\t\t\t\t\tscene.background = envMap;\n\t\t\t\t\t// Separate property from environmentRotation — drifts apart if only one is set.\n\t\t\t\t\tscene.backgroundRotation.copy(envRotation);\n\t\t\t\t} else {\n\t\t\t\t\t// Raw equirect was only PMREM input; the prefiltered probe has superseded it.\n\t\t\t\t\tenvMap.dispose();\n\t\t\t\t}\n\t\t\t\tconfig.events.onReady?.();\n\t\t\t},\n\t\t\tundefined,\n\t\t\tfunction (error) {\n\t\t\t\tif (isDisposed()) return;\n\t\t\t\tgetLogger().warn('HDR texture could not be loaded, falling back to basic lighting:', error);\n\t\t\t\tconfig.events.onReady?.();\n\t\t\t}\n\t\t);\n\t} else {\n\t\tconfig.events.onReady?.();\n\t}\n}\n\nexport function addFloor(scene: THREE.Scene, config: ResolvedOptions) {\n\tconst floorSize = config.floor.size;\n\tconst floorGeometry = new THREE.PlaneGeometry(floorSize, floorSize);\n\n\tconst floorColor =\n\t\ttypeof config.floor.color === 'string'\n\t\t\t? new THREE.Color(config.floor.color)\n\t\t\t: config.floor.color;\n\n\tconst floorMaterial = new THREE.MeshStandardMaterial({\n\t\tcolor: floorColor,\n\t\troughness: config.floor.roughness,\n\t\tmetalness: config.floor.metalness,\n\t\tside: THREE.DoubleSide\n\t});\n\n\tconst floor = new THREE.Mesh(floorGeometry, floorMaterial);\n\tfloor.userData.id = 'floor';\n\tfloor.name = 'floor';\n\t// PlaneGeometry's default +Z normal is already correct for Z-up; orient to scene up for any other.\n\tconst up = (config.environment?.sceneUp || defaultUp).clone().normalize();\n\tfloor.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), up);\n\tfloor.position.set(0, 0, 0);\n\n\tif (config.floor.receiveShadow && config.render.enableShadows) {\n\t\tfloor.receiveShadow = true;\n\t}\n\n\tscene.add(floor);\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from '../../shared/index.js';\n\nimport type { CameraController } from '../camera-controller.js';\nimport { computeContentBounds } from '../three-helpers.js';\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupEventHandlers(\n\tcanvas: HTMLCanvasElement,\n\tscene: THREE.Scene,\n\tcameraController: CameraController,\n\tconfig: ResolvedOptions\n): {\n\tdispose: () => void;\n\tfitToView: () => void;\n\tclearSelection: () => void;\n} {\n\tconst selectedObjects = new Set<THREE.Object3D>();\n\tconst originalMaterials = new Map<THREE.Object3D, THREE.Material | THREE.Material[]>();\n\tconst raycaster = new THREE.Raycaster();\n\tconst mouse = new THREE.Vector2();\n\tconst mouseDownPosition = new THREE.Vector2();\n\tconst getActiveCamera = () => cameraController.getActiveCamera();\n\n\t// Three.js's recursive intersect hits a visible Mesh inside a hidden Group; this enforces that\n\t// every ancestor must also be visible.\n\tconst isFullyVisible = (object: THREE.Object3D): boolean => {\n\t\tlet current: THREE.Object3D | null = object;\n\t\twhile (current) {\n\t\t\tif (!current.visible) return false;\n\t\t\tcurrent = current.parent;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst fitToView = () => {\n\t\tconst box = computeContentBounds(scene);\n\n\t\tif (box.isEmpty()) {\n\t\t\tgetLogger().warn('No objects to fit to view');\n\t\t\treturn;\n\t\t}\n\n\t\t// Via the controller, not the perspective camera directly: it repositions whichever camera is\n\t\t// live and re-derives the ortho frustum in 2D mode.\n\t\tcameraController.frameBounds(box, false);\n\t};\n\n\tconst selectionColorObj =\n\t\ttypeof config.events.selectionColor === 'string'\n\t\t\t? new THREE.Color(config.events.selectionColor)\n\t\t\t: config.events.selectionColor instanceof THREE.Color\n\t\t\t\t? config.events.selectionColor\n\t\t\t\t: new THREE.Color('#ff0000');\n\n\tconst clearSelection = () => {\n\t\tselectedObjects.forEach((obj) => {\n\t\t\tconst restorable = obj as THREE.Object3D & {\n\t\t\t\tmaterial?: THREE.Material | THREE.Material[];\n\t\t\t};\n\t\t\tif (originalMaterials.has(obj)) {\n\t\t\t\tconst original = originalMaterials.get(obj)!;\n\t\t\t\tconst clone = restorable.material; // dispose the highlight clone before restoring\n\t\t\t\tif (clone instanceof THREE.Material) clone.dispose();\n\t\t\t\telse if (Array.isArray(clone)) clone.forEach((m) => m.dispose());\n\t\t\t\trestorable.material = original;\n\t\t\t\toriginalMaterials.delete(obj);\n\n\t\t\t\t// If the object left the scene while selected, no traversal can reach the original\n\t\t\t\t// material we just restored — dispose it here. Compute content is cleared wholesale\n\t\t\t\t// per solve, so a detached object's material has no surviving sharers.\n\t\t\t\tlet root: THREE.Object3D = obj;\n\t\t\t\twhile (root.parent) root = root.parent;\n\t\t\t\tif (root !== scene) {\n\t\t\t\t\tif (original instanceof THREE.Material) original.dispose();\n\t\t\t\t\telse original.forEach((m) => m.dispose());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tselectedObjects.clear();\n\t};\n\n\t// Meshes tint via `emissive` (keeps base color); lines/points have no emissive channel, so\n\t// `color` is recolored directly instead.\n\tconst applyHighlight = (object: THREE.Object3D): boolean => {\n\t\tconst target = object as THREE.Object3D & { material?: THREE.Material | THREE.Material[] };\n\t\tif (!(target.material instanceof THREE.Material)) return false;\n\n\t\toriginalMaterials.set(object, target.material);\n\t\tconst clone = target.material.clone();\n\n\t\tif (object instanceof THREE.Mesh && 'emissive' in clone) {\n\t\t\t(clone as THREE.MeshStandardMaterial).emissive = selectionColorObj.clone();\n\t\t} else if ('color' in clone) {\n\t\t\t(clone as THREE.LineBasicMaterial).color = selectionColorObj.clone();\n\t\t}\n\n\t\ttarget.material = clone;\n\t\treturn true;\n\t};\n\n\t// Points picking tolerance, scaled to scene size so it holds at any zoom. Fat Line2 uses its own\n\t// material linewidth instead, so no separate threshold is needed for lines.\n\tconst updatePickThresholds = () => {\n\t\tconst box = computeContentBounds(scene);\n\t\tconst diagonal = box.isEmpty() ? 1 : box.getSize(new THREE.Vector3()).length();\n\t\traycaster.params.Points.threshold = diagonal * 0.01;\n\t};\n\n\tconst handleMouseDown = (event: MouseEvent) => {\n\t\tmouseDownPosition.set(event.clientX, event.clientY);\n\t};\n\n\tconst handleCanvasClick = (event: MouseEvent) => {\n\t\tconst currentMousePosition = new THREE.Vector2(event.clientX, event.clientY);\n\t\tif (mouseDownPosition.distanceTo(currentMousePosition) > 5) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tmouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tmouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tupdatePickThresholds();\n\t\traycaster.setFromCamera(mouse, getActiveCamera());\n\t\tconst intersects = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => isFullyVisible(i.object));\n\n\t\tif (intersects.length > 0) {\n\t\t\tconst clickedObject = intersects[0].object;\n\n\t\t\tif (!selectedObjects.has(clickedObject)) {\n\t\t\t\tclearSelection();\n\t\t\t\tselectedObjects.add(clickedObject);\n\t\t\t\tapplyHighlight(clickedObject);\n\n\t\t\t\tconfig.events?.onObjectSelected?.(clickedObject);\n\n\t\t\t\tif (clickedObject instanceof THREE.Mesh && Object.keys(clickedObject.userData).length > 0) {\n\t\t\t\t\tconfig.events?.onMeshMetadataClicked?.(clickedObject.userData);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tclearSelection();\n\t\t\tconfig.events?.onBackgroundClicked?.({ x: mouse.x, y: mouse.y });\n\t\t}\n\t};\n\n\tconst handleDoubleClick = (event: MouseEvent) => {\n\t\tconst rect = canvas.getBoundingClientRect();\n\t\tmouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n\t\tmouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;\n\n\t\tupdatePickThresholds();\n\t\traycaster.setFromCamera(mouse, getActiveCamera());\n\t\tconst intersects = raycaster\n\t\t\t.intersectObjects(scene.children, true)\n\t\t\t.filter((i) => isFullyVisible(i.object));\n\n\t\tif (intersects.length === 0) return;\n\n\t\tconst target = intersects[0].object;\n\t\tconfig.events?.onMeshDoubleClicked?.(target);\n\n\t\tif (!config.events?.enableDoubleClickZoom) return;\n\n\t\tconst box = new THREE.Box3().setFromObject(target);\n\t\tif (box.isEmpty()) return;\n\n\t\t// Via the controller so the active camera moves (translating an ortho camera alone zooms\n\t\t// nothing). The resulting tween is cancellable — a rapid second double-click replaces it\n\t\t// rather than racing it.\n\t\tcameraController.frameBounds(box, true);\n\t};\n\n\tconst handleKeydown = (event: KeyboardEvent) => {\n\t\tif (!config.events?.enableKeyboardControls) return;\n\n\t\tswitch (event.key.toLowerCase()) {\n\t\t\tcase 'f':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfitToView();\n\t\t\t\tbreak;\n\t\t\tcase 'escape':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tclearSelection();\n\t\t\t\tbreak;\n\t\t\tcase ' ':\n\t\t\t\tevent.preventDefault();\n\t\t\t\tfitToView();\n\t\t\t\tbreak;\n\t\t}\n\t};\n\n\tif (config.events?.enableClickToFocus) {\n\t\tcanvas.addEventListener('mousedown', handleMouseDown);\n\t\tcanvas.addEventListener('click', handleCanvasClick);\n\t\tcanvas.addEventListener('dblclick', handleDoubleClick);\n\t}\n\n\tif (config.events?.enableKeyboardControls) {\n\t\tcanvas.setAttribute('tabindex', '0');\n\t\tcanvas.addEventListener('keydown', handleKeydown);\n\t}\n\n\tconst dispose = () => {\n\t\tcanvas.removeEventListener('mousedown', handleMouseDown);\n\t\tcanvas.removeEventListener('click', handleCanvasClick);\n\t\tcanvas.removeEventListener('dblclick', handleDoubleClick);\n\t\tcanvas.removeEventListener('keydown', handleKeydown);\n\t\tclearSelection();\n\t};\n\n\treturn { dispose, fitToView, clearSelection };\n}\n","import * as THREE from 'three';\n\nimport { defaultUp, type ResolvedOptions } from './defaults.js';\n\nexport type SceneLights = {\n\tambient: THREE.AmbientLight;\n\t/** Null unless `lighting.enableHemisphereLight`. */\n\themisphere: THREE.HemisphereLight | null;\n\t/** Null when sunlight or shadows are disabled. */\n\tsun: THREE.DirectionalLight | null;\n};\n\nexport function setupLighting(scene: THREE.Scene, config: ResolvedOptions): SceneLights {\n\tconst ambient = new THREE.AmbientLight(\n\t\tconfig.lighting.ambientLightColor,\n\t\tconfig.lighting.ambientLightIntensity\n\t);\n\tscene.add(ambient);\n\n\t// HemisphereLight defaults to +Y up, which is wrong for a Z-up scene — align to scene up instead.\n\tlet hemisphere: THREE.HemisphereLight | null = null;\n\tif (config.lighting.enableHemisphereLight) {\n\t\themisphere = new THREE.HemisphereLight(\n\t\t\tconfig.lighting.hemisphereSkyColor,\n\t\t\tconfig.lighting.hemisphereGroundColor,\n\t\t\tconfig.lighting.hemisphereIntensity\n\t\t);\n\t\tconst up = config.environment.sceneUp ?? defaultUp;\n\t\themisphere.position.copy(up);\n\t\tscene.add(hemisphere);\n\t}\n\n\tif (!config.lighting.enableSunlight) return { ambient, hemisphere, sun: null };\n\n\tconst sunlight = new THREE.DirectionalLight(\n\t\tconfig.lighting.sunlightColor ?? 0xffffff,\n\t\tconfig.lighting.sunlightIntensity\n\t);\n\tconst pos = config.lighting.sunlightPosition;\n\tif (pos) {\n\t\tsunlight.position.set(pos.x, pos.y, pos.z);\n\t}\n\n\tif (!config.render.enableShadows) {\n\t\tscene.add(sunlight);\n\t\treturn { ambient, hemisphere, sun: null };\n\t}\n\n\tsunlight.castShadow = true;\n\n\t// Frustum bounds are not set here — fitShadowToContent sizes them to scene content instead.\n\tsunlight.shadow.mapSize.width = config.render.shadowMapSize || 2048;\n\tsunlight.shadow.mapSize.height = config.render.shadowMapSize || 2048;\n\n\tsunlight.shadow.bias = -0.0001;\n\tsunlight.shadow.normalBias = 0.02;\n\tsunlight.shadow.radius = 4;\n\n\tscene.add(sunlight);\n\t// A DirectionalLight aims at its target's world position, so the target must be in the scene\n\t// graph for its matrix to update.\n\tscene.add(sunlight.target);\n\treturn { ambient, hemisphere, sun: sunlight };\n}\n\n/**\n * Sizes a directional light's orthographic shadow frustum to the scene content's bounding sphere —\n * the dominant lever on shadow crispness. No-op on an empty box (would collapse the frustum to a point).\n */\nexport function fitShadowToContent(light: THREE.DirectionalLight, bounds: THREE.Box3): void {\n\tif (bounds.isEmpty()) return;\n\n\tconst center = bounds.getCenter(new THREE.Vector3());\n\t// Bounding-sphere radius keeps the frustum rotation-invariant; padded so grazing-angle casters\n\t// and VSM blur near the edges don't clip.\n\tconst radius = bounds.getSize(new THREE.Vector3()).length() * 0.5 * 1.2;\n\n\tconst cam = light.shadow.camera;\n\tcam.left = -radius;\n\tcam.right = radius;\n\tcam.top = radius;\n\tcam.bottom = -radius;\n\n\t// Only the target moves to the content centre, preserving the light's configured direction.\n\tlight.target.position.copy(center);\n\tlight.target.updateMatrixWorld();\n\n\t// Clamp near above 0 so a light sitting inside the bounds can't invert the frustum.\n\tconst lightDistance = light.position.distanceTo(center);\n\tcam.near = Math.max(radius * 0.01, lightDistance - radius);\n\tcam.far = lightDistance + radius;\n\tcam.updateProjectionMatrix();\n}\n","import * as THREE from 'three';\n\nimport type { ResolvedOptions } from './defaults.js';\n\nexport function setupRenderer(\n\tcanvas: HTMLCanvasElement,\n\tconfig: ResolvedOptions,\n\tpixelRatio: number\n): THREE.WebGLRenderer {\n\tconst renderer = new THREE.WebGLRenderer({\n\t\tantialias: config.render.antialias,\n\t\tcanvas,\n\t\talpha: true,\n\t\tpowerPreference: 'high-performance',\n\t\tpreserveDrawingBuffer: config.render.preserveDrawingBuffer,\n\t\t// Deliberately NOT logarithmic: the GTAO pipeline reconstructs view-space positions assuming\n\t\t// standard perspective depth and doesn't support log-encoded depth (haloing, wrong-scale\n\t\t// occlusion if enabled). If log depth is ever needed, AO must be disabled alongside it.\n\t\tlogarithmicDepthBuffer: false\n\t});\n\n\tconst parent = canvas.parentElement;\n\tconst width = parent ? parent.clientWidth : window.innerWidth;\n\tconst height = parent ? parent.clientHeight : window.innerHeight;\n\n\tif (parent) {\n\t\tcanvas.style.width = '100%';\n\t\tcanvas.style.height = '100%';\n\t\tcanvas.style.display = 'block';\n\t}\n\n\trenderer.setSize(width, height, false);\n\trenderer.setPixelRatio(pixelRatio);\n\n\tif (config.render.enableShadows) {\n\t\trenderer.shadowMap.enabled = true;\n\t\trenderer.shadowMap.type = THREE.VSMShadowMap;\n\t}\n\n\trenderer.toneMapping = config.render.toneMapping!;\n\trenderer.toneMappingExposure = config.render.toneMappingExposure ?? 1.0;\n\trenderer.outputColorSpace = THREE.SRGBColorSpace;\n\n\trenderer.sortObjects = true;\n\n\treturn renderer;\n}\n","import * as THREE from 'three';\n\nimport { publishMaxAnisotropy } from '../../shared/index.js';\nimport { createCameraController } from '../camera-controller.js';\nimport { EDGES_SKIPPED_TRIANGLE_CAP, addEdgesAsync, removeEdges } from '../edges.js';\nimport { createGrid } from '../grid.js';\nimport { createLabelLayer, type LabelLayer } from '../label-layer.js';\nimport { createMeasureTool, type MeasureTool } from '../measure.js';\nimport { createNearPlaneFitter, type NearPlaneFitter } from '../near-plane.js';\nimport { SOURCE_USER, appSource, isOwnedBy } from '../scene-ownership.js';\nimport { computeContentBounds } from '../three-helpers.js';\nimport { createToolRegistry } from '../tool-registry.js';\nimport type { ThreeInitializerOptions } from '../types.js';\nimport { upToAxis } from '../up-axis.js';\nimport { createViewGizmo } from '../view-gizmo.js';\nimport { createAnimationLoop } from './animation-loop.js';\nimport { createAppearanceController } from './appearance.js';\nimport { createCamera } from './create-camera.js';\nimport { createScene } from './create-scene.js';\nimport { applyDefaults, defaultUp } from './defaults.js';\nimport { disposeObjectTree, disposeSceneResources } from './dispose.js';\nimport { createPipelineController } from './pipeline-controller.js';\nimport { setupControls } from './setup-controls.js';\nimport { addFloor, setupEnvironment } from './setup-environment.js';\nimport { setupEventHandlers } from './setup-events.js';\nimport { fitShadowToContent, setupLighting } from './setup-lighting.js';\nimport { setupRenderer } from './setup-renderer.js';\nimport type { ThreeViewer } from './viewer.js';\n\nexport const initThree = function (\n\tcanvas: HTMLCanvasElement,\n\toptions?: ThreeInitializerOptions\n): ThreeViewer {\n\tconst config = applyDefaults(options || {});\n\n\tconst sceneUp = config.environment?.sceneUp || defaultUp;\n\n\t// Single source of truth for DPR (renderer, resize check, AO pipeline); applyDefaults always\n\t// sets it, the fallback here is just type narrowing.\n\tconst pixelRatio = config.render.pixelRatio ?? Math.min(window.devicePixelRatio, 2);\n\n\tconst scene = createScene(config);\n\tconst camera = createCamera(config, canvas);\n\t// Must happen before OrbitControls/the controller read camera.up (captured at construction), or\n\t// a Z-up scene orbits and frames as if Y-up.\n\tcamera.up.copy(sceneUp);\n\tconst renderer = setupRenderer(canvas, config, pixelRatio);\n\t// Published to a shared sink rather than imported, so render/ stays independent of parse/.\n\tpublishMaxAnisotropy(renderer.capabilities.getMaxAnisotropy());\n\toptions?.onMaxAnisotropy?.(renderer.capabilities.getMaxAnisotropy());\n\n\tconst controls = setupControls(camera, canvas, config);\n\n\t// Render loop, resize, and raycasting all read through getActiveCamera so 2D/3D stays coherent.\n\tconst cameraController = createCameraController({\n\t\tscene,\n\t\tperspective: camera,\n\t\tcontrols,\n\t\tonActiveCameraChange: () => {},\n\t\tup: sceneUp\n\t});\n\tconst getActiveCamera = () => cameraController.getActiveCamera();\n\n\t// HDR decodes asynchronously; setupEnvironment's load callback checks this to drop (and dispose)\n\t// the texture instead of attaching it to a scene torn down mid-fetch.\n\tlet disposed = false;\n\tsetupEnvironment(scene, renderer, config, () => disposed);\n\tconst lights = setupLighting(scene, config);\n\tconst sunlight = lights.sun;\n\n\tconst updateShadowBounds = () => {\n\t\tif (sunlight) fitShadowToContent(sunlight, computeContentBounds(scene));\n\t};\n\n\tif (config.floor?.enabled) {\n\t\taddFloor(scene, config);\n\t}\n\t// So the near-plane fitter below can consult the floor's live visibility.\n\tconst floorMesh = config.floor?.enabled\n\t\t? (scene.children.find((child) => child.userData.id === 'floor') ?? null)\n\t\t: null;\n\n\tconst grid = config.grid.enabled\n\t\t? createGrid({\n\t\t\t\tcellSize: config.grid.cellSize,\n\t\t\t\tmajorEvery: config.grid.majorEvery,\n\t\t\t\tcellColor: config.grid.cellColor,\n\t\t\t\tmajorColor: config.grid.majorColor,\n\t\t\t\tfadeDistance: config.grid.fadeDistance,\n\t\t\t\tplane: config.grid.plane\n\t\t\t})\n\t\t: null;\n\tif (grid) scene.add(grid.object);\n\n\tconst updateGridScale = () => {\n\t\tif (grid) grid.fitToContent(computeContentBounds(scene));\n\t};\n\n\tconst gizmo = config.gizmo.enabled\n\t\t? createViewGizmo({ camera, domElement: canvas, controller: cameraController })\n\t\t: null;\n\n\t// Only VISIBLE ground aids feed the near-plane fitter's clamp: the grid is commonly built hidden\n\t// so hosts can toggle it, and the clamp is the camera's height above the plane — a hidden grid\n\t// would still drive near→0 at grazing views and crater depth precision, punching hidden edges\n\t// through geometry.\n\tconst gridPlane = config.grid.plane ?? upToAxis(sceneUp);\n\tconst gridNormal = new THREE.Vector3(\n\t\tgridPlane === 'x' ? 1 : 0,\n\t\tgridPlane === 'y' ? 1 : 0,\n\t\tgridPlane === 'z' ? 1 : 0\n\t);\n\tconst floorNormal = sceneUp.clone().normalize();\n\tconst groundNormals = (): THREE.Vector3[] => {\n\t\tconst normals: THREE.Vector3[] = [];\n\t\tif (grid?.object.visible) normals.push(gridNormal);\n\t\tif (config.floor.enabled && floorMesh?.visible) normals.push(floorNormal);\n\t\treturn normals;\n\t};\n\tconst nearFitter: NearPlaneFitter | null = config.camera.dynamicNear\n\t\t? createNearPlaneFitter({ camera, scene, groundNormals })\n\t\t: null;\n\n\t// Built unconditionally: measure was the first consumer, but any tool or app annotating the\n\t// scene needs it, and gating it behind measure.enabled left them with no way to get one.\n\tconst labelContainer = canvas.parentElement ?? canvas;\n\tconst labelLayer: LabelLayer = createLabelLayer(labelContainer, scene);\n\tconst measureTool: MeasureTool | null = config.measure.enabled\n\t\t? createMeasureTool({\n\t\t\t\tcanvas,\n\t\t\t\tscene,\n\t\t\t\tgetActiveCamera,\n\t\t\t\tlabelLayer,\n\t\t\t\toptions: {\n\t\t\t\t\tsnapPixels: config.measure.snapPixels,\n\t\t\t\t\tcolor: config.measure.color,\n\t\t\t\t\tlabelClassName: config.measure.labelClassName,\n\t\t\t\t\tdisplayUnit: config.measure.displayUnit,\n\t\t\t\t\tformat: config.measure.format\n\t\t\t\t}\n\t\t\t})\n\t\t: null;\n\n\tconst eventHandlers =\n\t\tconfig.events.enableEventHandlers !== false\n\t\t\t? setupEventHandlers(canvas, scene, cameraController, config)\n\t\t\t: { dispose: () => {}, fitToView: () => {}, clearSelection: () => {} };\n\n\t// Built-ins register at the priorities documented on ToolRegistration, so a host tool can slot\n\t// above or below them. Listeners are attached unconditionally — a tool can register at any time.\n\tconst tools = createToolRegistry();\n\tif (measureTool) tools.register({ id: 'measure', tool: measureTool, priority: 0 });\n\tif (gizmo) tools.register({ id: 'gizmo', tool: gizmo, priority: -100 });\n\n\t// A drag to orbit/pan ends with a `click` on mouseup; without this guard that release would be\n\t// mistaken for a measurement point.\n\tconst DRAG_SLOP_PX = 5;\n\tlet pressX = 0;\n\tlet pressY = 0;\n\tconst handlePointerDown = (event: MouseEvent) => {\n\t\tpressX = event.clientX;\n\t\tpressY = event.clientY;\n\t};\n\tconst wasDrag = (event: MouseEvent) =>\n\t\tMath.hypot(event.clientX - pressX, event.clientY - pressY) > DRAG_SLOP_PX;\n\n\t// Capture-phase so tools see the click before bubble-phase selection; the first to claim it\n\t// wins, and stopImmediatePropagation keeps selection from also firing.\n\tconst handleToolClick = (event: MouseEvent) => {\n\t\tif (wasDrag(event)) return;\n\t\tif (tools.handleClick(event)) event.stopImmediatePropagation();\n\t};\n\tcanvas.addEventListener('mousedown', handlePointerDown, { capture: true });\n\tcanvas.addEventListener('click', handleToolClick, { capture: true });\n\n\t// Passive: moves only drive previews, never consume, so they can't interfere with orbit/pan.\n\tconst handleToolMove = (event: MouseEvent) => tools.handleMove(event);\n\tcanvas.addEventListener('mousemove', handleToolMove, { passive: true });\n\n\t// Rebound to the animation loop's real invalidate once it's created below.\n\tlet requestRender: () => void = () => {};\n\n\t// Applies regardless of edges.enabled — an explicit call should never be silently ignored.\n\t// Meshes over the triangle cap switch the screen-space edge fallback on; a later solve without\n\t// such meshes switches it back off.\n\tconst applyEdges = (root: THREE.Object3D) => {\n\t\tvoid addEdgesAsync(root, {\n\t\t\tcolor: config.edges.color,\n\t\t\tdarken: config.edges.darken,\n\t\t\twidth: config.edges.width,\n\t\t\tthresholdAngle: config.edges.thresholdAngle,\n\t\t\tdistanceFade: config.edges.distanceFade,\n\t\t\tmaxTriangles: config.edges.maxTriangles,\n\t\t\tmaxSegments: config.edges.maxSegments\n\t\t}).then(() => {\n\t\t\tupdateEdgeFallback(root);\n\t\t\trequestRender(); // async attach may land after the solve's own repaint\n\t\t});\n\t};\n\n\tconst updateEdgeFallback = (root: THREE.Object3D) => {\n\t\tif (config.edges.screenSpaceFallback === false) return;\n\t\tlet hasSkippedMeshes = false;\n\t\troot.traverse((object) => {\n\t\t\tif (object.userData?.edgesSkipped === EDGES_SKIPPED_TRIANGLE_CAP) hasSkippedMeshes = true;\n\t\t});\n\t\tpipeline.setEdgeFallback(hasSkippedMeshes);\n\t};\n\n\t// Also stands down the screen-space fallback — bare removeEdges alone would keep drawing lines\n\t// for capped meshes.\n\tconst clearEdges = (root: THREE.Object3D) => {\n\t\tremoveEdges(root);\n\t\tpipeline.setEdgeFallback(false);\n\t\trequestRender();\n\t};\n\n\tconst parent = canvas.parentElement;\n\tconst getCanvasSize = () =>\n\t\tparent\n\t\t\t? { width: parent.clientWidth, height: parent.clientHeight }\n\t\t\t: { width: window.innerWidth, height: window.innerHeight };\n\n\tconst pipeline = createPipelineController({\n\t\trenderer,\n\t\tscene,\n\t\tgetActiveCamera,\n\t\tgetCanvasSize,\n\t\tpixelRatio,\n\t\tconfig,\n\t\trequestRender: () => requestRender()\n\t});\n\tpipeline.sync();\n\n\tconst appearance = createAppearanceController({\n\t\tscene,\n\t\trenderer,\n\t\tlights,\n\t\tconfig,\n\t\tpipeline,\n\t\trequestRender: () => requestRender()\n\t});\n\n\tconst {\n\t\tanimate,\n\t\tdispose: disposeAnimation,\n\t\tinvalidate,\n\t\trenderNow\n\t} = createAnimationLoop(\n\t\trenderer,\n\t\tscene,\n\t\tcamera,\n\t\tgetActiveCamera,\n\t\tcameraController,\n\t\tcontrols,\n\t\tgetCanvasSize,\n\t\tpixelRatio,\n\t\tconfig.events.onFrame,\n\t\tgrid,\n\t\tgizmo,\n\t\t() => pipeline.get(),\n\t\tlabelLayer,\n\t\tnearFitter,\n\t\tconfig.render.onDemand ?? true\n\t);\n\trequestRender = invalidate;\n\tanimate();\n\n\tscene.up.set(sceneUp.x, sceneUp.y, sceneUp.z);\n\n\t// Initial fit for geometry already present; hosts loading more later via updateScene should\n\t// call these again.\n\tupdateShadowBounds();\n\tupdateGridScale();\n\n\tconst captureImage = (type = 'image/png', quality?: number): Promise<Blob | null> => {\n\t\t// Draw and read in the same task: without preserveDrawingBuffer the colour buffer is gone\n\t\t// once the browser composites, and a deferred toBlob returns a blank image.\n\t\trenderNow();\n\t\treturn new Promise((resolve) => renderer.domElement.toBlob(resolve, type, quality));\n\t};\n\n\tconst addUserGeometry = (object: THREE.Object3D, appId?: string) => {\n\t\tobject.userData.source = appId === undefined ? SOURCE_USER : appSource(appId);\n\t\tscene.add(object);\n\t\trequestRender();\n\t};\n\n\tconst removeUserGeometry = (object: THREE.Object3D) => {\n\t\tobject.removeFromParent();\n\t\tdisposeObjectTree(object);\n\t\trequestRender();\n\t};\n\n\tconst clearUserGeometry = (appId?: string) => {\n\t\t// Snapshot first: removeFromParent would mutate scene.children mid-iteration otherwise.\n\t\tconst owned = scene.children.filter((child) => isOwnedBy(child, appId));\n\t\towned.forEach((object) => {\n\t\t\tobject.removeFromParent();\n\t\t\tdisposeObjectTree(object);\n\t\t});\n\t\trequestRender();\n\t};\n\n\tconst dispose = () => {\n\t\t// Idempotent: a second call would re-run forceContextLoss() on an already-lost context and\n\t\t// throw (double-dispose happens naturally under React StrictMode).\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tdisposeAnimation();\n\t\teventHandlers.dispose();\n\t\tcanvas.removeEventListener('mousedown', handlePointerDown, { capture: true });\n\t\tcanvas.removeEventListener('click', handleToolClick, { capture: true });\n\t\tcanvas.removeEventListener('mousemove', handleToolMove);\n\t\tmeasureTool?.dispose();\n\t\tlabelLayer?.dispose();\n\t\tgizmo?.dispose();\n\t\tgrid?.dispose();\n\t\tpipeline.dispose();\n\t\t// Stops any in-flight camera tween — its rAF ticks would otherwise keep touching the\n\t\t// disposed controls after teardown.\n\t\tcameraController.dispose();\n\t\tcontrols.dispose();\n\t\trenderer.dispose();\n\t\t// Frees the GL context itself: browsers cap live WebGL contexts (~16), and otherwise it won't\n\t\t// be reclaimed until GC, which can lag across rapid mount/unmount cycles.\n\t\trenderer.forceContextLoss();\n\n\t\tdisposeSceneResources(scene);\n\n\t\t// Cross-solve caches (parse/'s, reached via a registry rather than an import — layer rule)\n\t\t// outlive any single scene but not the GL context just destroyed. Refcounted: only the last\n\t\t// live viewer actually frees, and this must run after the scene sweep above.\n\t};\n\n\treturn {\n\t\tscene,\n\t\tcamera,\n\t\tcontrols,\n\t\trenderer,\n\t\tcameraController,\n\t\tgrid,\n\t\tgizmo,\n\t\tmeasureTool,\n\t\tlabelLayer,\n\t\ttools,\n\t\tapplyEdges,\n\t\tclearEdges,\n\t\tinvalidate,\n\t\tcaptureImage,\n\t\tsetAmbientOcclusion: pipeline.setAmbientOcclusion,\n\t\tsetLook: appearance.setLook,\n\t\tsetFillLights: appearance.setFillLights,\n\t\tsetEnvironmentIntensity: appearance.setEnvironmentIntensity,\n\t\tsetToneMappingExposure: appearance.setToneMappingExposure,\n\t\tsetAoIntensity: appearance.setAoIntensity,\n\t\tgetMaterialAppearance: appearance.getMaterialAppearance,\n\t\tupdateShadowBounds,\n\t\tupdateGridScale,\n\t\tdispose,\n\t\tfitToView: eventHandlers.fitToView,\n\t\tclearSelection: eventHandlers.clearSelection,\n\t\taddUserGeometry,\n\t\tremoveUserGeometry,\n\t\tclearUserGeometry\n\t};\n};\n"],"mappings":"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"}
|