@selvajs/visualization 1.0.0-beta.0 → 1.0.0-beta.1
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 +4 -3
- package/dist/chunk-AZ4GBXXL.cjs +2 -0
- package/dist/chunk-AZ4GBXXL.cjs.map +1 -0
- package/dist/chunk-BYLIBOAU.cjs.map +1 -1
- package/dist/chunk-KRA5RVHM.js +2 -0
- package/dist/chunk-KRA5RVHM.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/parse.cjs +3 -3
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.d.cts +31 -45
- package/dist/parse.d.ts +31 -45
- package/dist/parse.js +3 -3
- package/dist/parse.js.map +1 -1
- package/dist/render.cjs +9 -10
- package/dist/render.cjs.map +1 -1
- package/dist/render.d.cts +36 -44
- package/dist/render.d.ts +36 -44
- package/dist/render.js +9 -10
- package/dist/render.js.map +1 -1
- package/dist/scene.cjs.map +1 -1
- package/dist/scene.d.cts +4 -9
- package/dist/scene.d.ts +4 -9
- package/dist/scene.js.map +1 -1
- package/dist/{types-CdF9R3qA.d.cts → types-DCuos3gI.d.cts} +4 -12
- package/dist/{types-CdF9R3qA.d.ts → types-DCuos3gI.d.ts} +4 -12
- package/package.json +2 -3
- package/dist/chunk-AQJPVUH3.cjs +0 -2
- package/dist/chunk-AQJPVUH3.cjs.map +0 -1
- package/dist/chunk-EXAI6IC5.js +0 -2
- package/dist/chunk-EXAI6IC5.js.map +0 -1
package/dist/render.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["c:\\Users\\felix\\coding\\selva\\packages\\visualization\\dist\\render.cjs"],"names":[],"mappings":"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CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,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AAqB,EAAE,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAA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","file":"C:\\Users\\felix\\coding\\selva\\packages\\visualization\\dist\\render.cjs","sourcesContent":[null]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/selva/selva/packages/visualization/dist/render.cjs","../src/render/scene-setup/init-three.ts","../src/render/up-axis.ts","../src/render/three-helpers.ts","../src/render/camera-controller.ts","../src/render/edges.ts","../src/render/edges/line-geometry.ts","../src/render/edges/extraction.ts","../src/render/edge-extract.ts","../src/render/edges/options.ts","../src/render/grid.ts","../src/render/measure.ts","../src/render/edge-detection-pass.ts"],"names":["buildUpBasis","up","u","worldZ","worldY","seed","right","forward","isoOffset","distance","sunOffset","sideDistance","height","environmentRotationFor","mapZenith","quaternion","upToAxis","ax","ay","az","CAMERA_CONFIG","updateScene","scene","meshes","camera","controls","initialPositionSet","clearScene","mesh","unionBoundingBox","computeCombinedBoundingBox","center","size","maxDim","VIEWER_AID_IDS","isViewerAid","object","current","computeContentBounds","box","renderable","PERSISTENT_SCENE_IDS","disposeObjectTree","buildViewDirections","frontPosition","rightPosition","createCameraController","deps","perspective","onActiveCameraChange","VIEW_DIRECTIONS","ortho","projection","aspect","active","activeTween","cancelTween","syncOrthoFrustum","halfH","halfW","setProjection","next","direction","frame","animate","fov","dir","nudgeOffPole","toPosition","cam","animateMove","setViewDirection","preset","enabled","width","d","inPlane","tilt","easeOut","t","toTarget","onTick","durationMs","fromPosition","fromTarget","startTime","rafId","tick","SPACING_PERCENTILE","SPACING_SAMPLE_LIMIT","edgeSpacingOf","segments","segmentCount","stride","lengths","s","i","length","a","b","buildLineGeometry","geometry","LineSegmentsGeometry","extractEdgeSegments","positions","index","thresholdAngleDeg","thresholdDot","vertexCount","quantX","quantY","quantZ","v","capacity","mask","table","canonical","slot","existing","out","outLength","emit","i0","i1","grown","edgeSlots","pendingIndex0","pendingIndex1","pendingNormals","triCount","i2","c","e0x","e0y","e0z","e1x","e1y","e1z","nx","ny","nz","lengthSq","inverseLength","j","from","to","fromCanonical","toCanonical","reverseKey","reverseSlot","forwardKey","edgeExtractWorkerSource","EDGE_USERDATA_KIND","EDGES_SKIPPED_TRIANGLE_CAP","DEFAULT_EDGE_COLOR","DEFAULT_EDGE_WIDTH","DEFAULT_THRESHOLD_ANGLE","DEFAULT_DARKEN","DEFAULT_MAX_TRIANGLES","DEFAULT_MAX_SEGMENTS","INLINE_TRIANGLE_BUDGET","FADE_START_PX","FADE_END_PX","EDGE_OFFSET_FACTOR","EDGE_OFFSET_UNITS","resolveOptions","options","triangleCountOf","position","fastPathData","contentKey","data","thresholdAngle","hash","mix","word","words","head","indexLength","headIndex"],"mappings":"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","file":"/home/runner/work/selva/selva/packages/visualization/dist/render.cjs","sourcesContent":[null,"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 { computeContentBounds } from '../three-helpers.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\tconst labelContainer = canvas.parentElement ?? canvas;\n\tconst labelLayer: LabelLayer | null = config.measure.enabled\n\t\t? createLabelLayer(labelContainer, scene)\n\t\t: null;\n\tconst measureTool: MeasureTool | null =\n\t\tconfig.measure.enabled && labelLayer\n\t\t\t? createMeasureTool({\n\t\t\t\t\tcanvas,\n\t\t\t\t\tscene,\n\t\t\t\t\tgetActiveCamera,\n\t\t\t\t\tlabelLayer,\n\t\t\t\t\toptions: {\n\t\t\t\t\t\tsnapPixels: config.measure.snapPixels,\n\t\t\t\t\t\tcolor: config.measure.color,\n\t\t\t\t\t\tlabelClassName: config.measure.labelClassName,\n\t\t\t\t\t\tdisplayUnit: config.measure.displayUnit,\n\t\t\t\t\t\tformat: config.measure.format\n\t\t\t\t\t}\n\t\t\t\t})\n\t\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// 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 these see the click before bubble-phase selection; measurement claims it\n\t// first, then the gizmo, and stopImmediatePropagation keeps selection from also firing.\n\tconst handleToolClick = (event: MouseEvent) => {\n\t\tif (wasDrag(event)) return;\n\t\tif (measureTool?.handleClick(event)) {\n\t\t\tevent.stopImmediatePropagation();\n\t\t\treturn;\n\t\t}\n\t\tif (gizmo?.handleClick(event)) {\n\t\t\tevent.stopImmediatePropagation();\n\t\t}\n\t};\n\tif (gizmo || measureTool) {\n\t\tcanvas.addEventListener('mousedown', handlePointerDown, { capture: true });\n\t\tcanvas.addEventListener('click', handleToolClick, { capture: true });\n\t}\n\t// Passive: only previews the snap point, never consumes, so it can't interfere with orbit/pan.\n\tconst handleToolMove = (event: MouseEvent) => measureTool?.handleMove(event);\n\tif (measureTool) {\n\t\tcanvas.addEventListener('mousemove', handleToolMove, { passive: true });\n\t}\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) => {\n\t\tobject.userData.source = 'user';\n\t\tscene.add(object);\n\t};\n\n\tconst removeUserGeometry = (object: THREE.Object3D) => {\n\t\tobject.removeFromParent();\n\t\tdisposeObjectTree(object);\n\t};\n\n\tconst clearUserGeometry = () => {\n\t\t// Snapshot first: removeFromParent would mutate scene.children mid-iteration otherwise.\n\t\tconst userObjects = scene.children.filter((child) => child.userData.source === 'user');\n\t\tuserObjects.forEach((object) => {\n\t\t\tobject.removeFromParent();\n\t\t\tdisposeObjectTree(object);\n\t\t});\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\tif (gizmo || measureTool) {\n\t\t\tcanvas.removeEventListener('mousedown', handlePointerDown, { capture: true });\n\t\t\tcanvas.removeEventListener('click', handleToolClick, { capture: true });\n\t\t}\n\t\tif (measureTool) {\n\t\t\tcanvas.removeEventListener('mousemove', handleToolMove);\n\t\t}\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\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","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 { 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// User-drawn geometry (tagged source==='user' by the viewer's addUserGeometry) persists\n\t\t// across solves so it isn't lost when compute content is replaced.\n\t\tif (object.userData.source === 'user') 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 * 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 { 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","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","/**\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\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 { 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 * @internal exported for tests\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';\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"]}
|
package/dist/render.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import { L as Look, a as LookPreset, M as MaterialAppearanceOptions } from './types-
|
|
3
|
-
export { b as LOOKS } from './types-
|
|
2
|
+
import { L as Look, a as LookPreset, M as MaterialAppearanceOptions } from './types-DCuos3gI.cjs';
|
|
3
|
+
export { b as LOOKS } from './types-DCuos3gI.cjs';
|
|
4
4
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -95,10 +95,8 @@ type EnvironmentConfig = {
|
|
|
95
95
|
backgroundColor?: THREE.Color | string;
|
|
96
96
|
enableEnvironmentLighting?: boolean;
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* this (view presets, default camera, sun, grid, floor, hemisphere light), but overriding it
|
|
101
|
-
* reorients the viewer only — it does NOT rotate incoming geometry.
|
|
98
|
+
* Default `(0, 0, 1)` — Rhino's Z-up. Every orientation default derives from this (see `up-axis.ts`);
|
|
99
|
+
* overriding it reorients the viewer only, it does not rotate incoming geometry.
|
|
102
100
|
*/
|
|
103
101
|
sceneUp?: THREE.Vector3;
|
|
104
102
|
showEnvironment?: boolean;
|
|
@@ -142,26 +140,24 @@ type RenderConfig = {
|
|
|
142
140
|
onDemand?: boolean;
|
|
143
141
|
};
|
|
144
142
|
|
|
145
|
-
/** Crisp boundary/crease edge overlays on meshes.
|
|
143
|
+
/** Crisp boundary/crease edge overlays on meshes. Field rationale: see `EdgeOptions` in `edges/options.ts`. */
|
|
146
144
|
type EdgesConfig = {
|
|
147
145
|
/** Default false (opt-in). */
|
|
148
146
|
enabled?: boolean;
|
|
149
|
-
/** Omit (default) to derive each mesh's edge color from its own surface material, darkened by `darken`. */
|
|
150
147
|
color?: THREE.ColorRepresentation;
|
|
151
148
|
/** 0–1, default 0.75. Ignored when `color` is set. */
|
|
152
149
|
darken?: number;
|
|
153
150
|
/** CSS px. Default 1.5. */
|
|
154
151
|
width?: number;
|
|
155
|
-
/**
|
|
152
|
+
/** Degrees. Default 44. */
|
|
156
153
|
thresholdAngle?: number;
|
|
157
|
-
/**
|
|
154
|
+
/** Default true. */
|
|
158
155
|
distanceFade?: boolean;
|
|
159
|
-
/**
|
|
156
|
+
/** Default 4M. */
|
|
160
157
|
maxTriangles?: number;
|
|
161
|
-
/**
|
|
158
|
+
/** Default 2M. */
|
|
162
159
|
maxSegments?: number;
|
|
163
|
-
/**
|
|
164
|
-
* (constant cost regardless of triangle count). Default true. */
|
|
160
|
+
/** Fall back to the screen-space edge-detection pass for meshes skipped by `maxTriangles`. Default true. */
|
|
165
161
|
screenSpaceFallback?: boolean;
|
|
166
162
|
};
|
|
167
163
|
type ControlsConfig = {
|
|
@@ -232,9 +228,9 @@ type ThreeInitializerOptions = {
|
|
|
232
228
|
measure?: MeasureConfig;
|
|
233
229
|
events?: EventConfig;
|
|
234
230
|
/**
|
|
235
|
-
* Called once at init with the GPU's max anisotropy.
|
|
236
|
-
*
|
|
237
|
-
*
|
|
231
|
+
* Called once at init with the GPU's max anisotropy. Not needed for sharp textures — `parse/`'s
|
|
232
|
+
* texture cache subscribes to this value on its own (see README's render↔parse seam). Only for
|
|
233
|
+
* hosts doing their own texture work.
|
|
238
234
|
*/
|
|
239
235
|
onMaxAnisotropy?: (value: number) => void;
|
|
240
236
|
};
|
|
@@ -267,8 +263,8 @@ type EventConfig = {
|
|
|
267
263
|
* render loop renders, resize reshapes, and the raycaster picks with — {@link getActiveCamera} is
|
|
268
264
|
* the one source of truth for all four call sites.
|
|
269
265
|
*
|
|
270
|
-
* Orthographic
|
|
271
|
-
* distance
|
|
266
|
+
* Orthographic mirrors perspective's position/target with a frustum derived from perspective's FOV
|
|
267
|
+
* and distance, so switching projections doesn't visually jump.
|
|
272
268
|
*/
|
|
273
269
|
type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';
|
|
274
270
|
type CameraProjection = 'perspective' | 'orthographic';
|
|
@@ -311,9 +307,9 @@ interface Grid {
|
|
|
311
307
|
* Two-click distance measurement. Click a point, click a second, read the distance off a label on
|
|
312
308
|
* the connecting line; a third click starts fresh.
|
|
313
309
|
*
|
|
314
|
-
* Picking snaps to the nearest vertex
|
|
315
|
-
*
|
|
316
|
-
*
|
|
310
|
+
* Picking snaps to the nearest vertex within {@link MeasureOptions.snapPixels} so measurements
|
|
311
|
+
* land exactly on vertices rather than wherever the ray happened to hit — a cheap local snap
|
|
312
|
+
* against the struck primitive's own vertices, no spatial index.
|
|
317
313
|
*
|
|
318
314
|
* Dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks (caller
|
|
319
315
|
* forwards them and swallows the event when {@link MeasureTool.handleClick} returns true) so
|
|
@@ -331,16 +327,16 @@ interface MeasureTool {
|
|
|
331
327
|
}
|
|
332
328
|
|
|
333
329
|
/**
|
|
334
|
-
* Corner nav-cube/axis gizmo. Uses three's {@link ViewHelper} only as the rendered widget,
|
|
330
|
+
* Corner nav-cube/axis gizmo. Uses three's {@link ViewHelper} only as the rendered widget, not its
|
|
335
331
|
* click→animate behavior: ViewHelper's snap assumes Y-up and animates straight onto the up axis,
|
|
336
|
-
* which rolls the view and jitters the gizmo at the pole in
|
|
337
|
-
* axis sprites
|
|
338
|
-
* with a pole nudge so the orbit basis never degenerates.
|
|
332
|
+
* which rolls the view and jitters the gizmo at the pole in a Z-up scene. Instead this hit-tests
|
|
333
|
+
* the axis sprites directly and drives the viewer's up-aware camera controller, which snaps
|
|
334
|
+
* instantly with a pole nudge so the orbit basis never degenerates.
|
|
339
335
|
*
|
|
340
|
-
* A click frames the current orbit target (not the world origin)
|
|
341
|
-
*
|
|
336
|
+
* A click frames the current orbit target (not the world origin) and switches back to perspective
|
|
337
|
+
* first if orthographic — the cube is a 3D-orientation tool.
|
|
342
338
|
*
|
|
343
|
-
* Caller contract (mirrors ViewHelper's own): call {@link ViewGizmo.render}
|
|
339
|
+
* Caller contract (mirrors ViewHelper's own): call {@link ViewGizmo.render} after the main scene
|
|
344
340
|
* render each frame, and forward pointer clicks to {@link ViewGizmo.handleClick}.
|
|
345
341
|
*/
|
|
346
342
|
interface ViewGizmo {
|
|
@@ -360,17 +356,16 @@ interface ThreeViewer {
|
|
|
360
356
|
cameraController: CameraController;
|
|
361
357
|
grid: Grid | null;
|
|
362
358
|
gizmo: ViewGizmo | null;
|
|
363
|
-
/** Null unless `measure.enabled
|
|
359
|
+
/** Null unless `measure.enabled`. */
|
|
364
360
|
measureTool: MeasureTool | null;
|
|
365
361
|
/**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* `edges.maxTriangles` are skipped and (by default) covered by the screen-space edge fallback.
|
|
362
|
+
* No-op unless `edges.enabled`. Extraction runs off-thread for large meshes, so overlays can
|
|
363
|
+
* attach a beat later; meshes over `edges.maxTriangles` fall back to the screen-space edge shader.
|
|
369
364
|
*/
|
|
370
365
|
applyEdges: (root: THREE.Object3D) => void;
|
|
371
366
|
/**
|
|
372
|
-
* Prefer over
|
|
373
|
-
*
|
|
367
|
+
* Prefer over `removeEdges` directly — also cancels in-flight async attaches and stands down the
|
|
368
|
+
* screen-space edge fallback if active.
|
|
374
369
|
*/
|
|
375
370
|
clearEdges: (root: THREE.Object3D) => void;
|
|
376
371
|
/**
|
|
@@ -380,14 +375,14 @@ interface ThreeViewer {
|
|
|
380
375
|
invalidate: () => void;
|
|
381
376
|
setAmbientOcclusion: (enabled: boolean) => void;
|
|
382
377
|
/**
|
|
383
|
-
* Retunes lighting/material
|
|
384
|
-
* any granular lighting dials set earlier.
|
|
378
|
+
* Retunes lighting/material (tone mapping, fill, IBL, AO) only — never edges/grid. Overwrites
|
|
379
|
+
* any granular lighting dials set earlier with the preset's values.
|
|
385
380
|
*/
|
|
386
381
|
setLook: (look: 'studio' | 'technical' | 'showcase') => void;
|
|
387
382
|
/**
|
|
388
|
-
* Raising `hemisphereIntensity` is the most effective way to lift shadowed
|
|
389
|
-
*
|
|
390
|
-
*
|
|
383
|
+
* Raising `hemisphereIntensity` is the most effective way to lift shadowed surfaces a dark HDR
|
|
384
|
+
* leaves black. Lazily creates the hemisphere light if the viewer was built without one; `0` turns
|
|
385
|
+
* it back off.
|
|
391
386
|
*/
|
|
392
387
|
setFillLights: (opts: {
|
|
393
388
|
hemisphereIntensity?: number;
|
|
@@ -412,10 +407,7 @@ interface ThreeViewer {
|
|
|
412
407
|
dispose: () => void;
|
|
413
408
|
fitToView: () => void;
|
|
414
409
|
clearSelection: () => void;
|
|
415
|
-
/**
|
|
416
|
-
* Tagged `userData.source = 'user'` so it survives `updateScene` solves instead of being cleared
|
|
417
|
-
* with compute content, and counts as normal content for fit-to-view framing.
|
|
418
|
-
*/
|
|
410
|
+
/** Tagged `userData.source = 'user'` so it survives `updateScene` solves and counts for fit-to-view. */
|
|
419
411
|
addUserGeometry: (object: THREE.Object3D) => void;
|
|
420
412
|
removeUserGeometry: (object: THREE.Object3D) => void;
|
|
421
413
|
/** Removes and disposes everything added via `addUserGeometry`. */
|