@selvajs/visualization 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,430 @@
1
+ import * as THREE from 'three';
2
+ import { L as Look, a as LookPreset, M as MaterialAppearanceOptions } from './types-CdF9R3qA.js';
3
+ export { b as LOOKS } from './types-CdF9R3qA.js';
4
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
5
+
6
+ /**
7
+ * Errors for the visualization package. Replaces `@selvajs/compute`'s `RhinoComputeError`, which
8
+ * mis-named failures on paths (e.g. the plugin WebSocket) that never touch Rhino.Compute. `code`
9
+ * values match compute's so existing catch-sites keep working.
10
+ */
11
+ declare const ErrorCodes: {
12
+ /** Structural check failed: bad magic bytes, out-of-window index, malformed metadata. */
13
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
14
+ readonly INVALID_STATE: "INVALID_STATE";
15
+ /** No `DecompressionStream`, no WebGL context, etc. */
16
+ readonly ENVIRONMENT_ERROR: "ENVIRONMENT_ERROR";
17
+ readonly INVALID_CONFIG: "INVALID_CONFIG";
18
+ /** Base64 input could not be decoded. */
19
+ readonly ENCODING_ERROR: "ENCODING_ERROR";
20
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
21
+ };
22
+ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
23
+ declare class VisualizationError extends Error {
24
+ readonly code: ErrorCode;
25
+ readonly context?: Record<string, unknown>;
26
+ readonly originalError?: Error;
27
+ constructor(message: string, code?: ErrorCode, options?: {
28
+ context?: Record<string, unknown>;
29
+ originalError?: Error;
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Logging facility for the visualization package. Deliberately local rather than imported from
35
+ * `@selvajs/compute` (logging isn't a compute concern). Mirrors compute's logger shape so a host
36
+ * wanting one sink for both can call `setLogger(computeLogger.getLogger())`.
37
+ */
38
+ interface Logger {
39
+ debug(message: string, ...args: unknown[]): void;
40
+ info(message: string, ...args: unknown[]): void;
41
+ warn(message: string, ...args: unknown[]): void;
42
+ error(message: string, ...args: unknown[]): void;
43
+ }
44
+ declare function getLogger(): Logger;
45
+ declare function setLogger(logger: Logger | Console | null): void;
46
+ declare function enableDebugLogging(): void;
47
+
48
+ /** The look applied when the caller passes no `look` option. */
49
+ declare const DEFAULT_LOOK: Look;
50
+ /**
51
+ * Single source of truth for both `applyDefaults` (construction) and `setLook` (runtime), so the two
52
+ * can't drift.
53
+ *
54
+ * `ambientOcclusion: false` on every look: GTAO is a heavy full-screen pass, so it stays opt-in
55
+ * (`render.ambientOcclusion` or `setAmbientOcclusion(true)`) rather than costing every viewer 60fps.
56
+ */
57
+ declare const LOOK_PRESETS: Record<Look, LookPreset>;
58
+ /** Baked at parse time (not toggleable at runtime). */
59
+ declare function materialAppearanceForLook(look: Look): MaterialAppearanceOptions;
60
+
61
+ type CameraConfig = {
62
+ position?: THREE.Vector3;
63
+ fov?: number;
64
+ near?: number;
65
+ far?: number;
66
+ target?: THREE.Vector3;
67
+ /**
68
+ * Refit the near plane to the camera↔content gap every frame (default true) — recovers
69
+ * depth-buffer precision when zoomed out, preventing distant z-fighting. `near` is only ever
70
+ * raised, never lowered below the configured value.
71
+ */
72
+ dynamicNear?: boolean;
73
+ };
74
+ type LightingConfig = {
75
+ enableSunlight?: boolean;
76
+ sunlightIntensity?: number;
77
+ sunlightPosition?: THREE.Vector3;
78
+ ambientLightColor?: THREE.Color;
79
+ ambientLightIntensity?: number;
80
+ sunlightColor?: THREE.Color | number;
81
+ /**
82
+ * Direction-aware fill (sky color above, ground color below) so surfaces facing away from the
83
+ * sun don't collapse to black under a dark HDR. Default false — enabling it shifts the look.
84
+ */
85
+ enableHemisphereLight?: boolean;
86
+ /** Default white. */
87
+ hemisphereSkyColor?: THREE.Color | number;
88
+ /** Default a mid grey. */
89
+ hemisphereGroundColor?: THREE.Color | number;
90
+ /** Default 0.6. Only applies when {@link LightingConfig.enableHemisphereLight}. */
91
+ hemisphereIntensity?: number;
92
+ };
93
+ type EnvironmentConfig = {
94
+ hdrPath?: string;
95
+ backgroundColor?: THREE.Color | string;
96
+ enableEnvironmentLighting?: boolean;
97
+ /**
98
+ * Defaults to `(0, 0, 1)` — Rhino's Z-up, not Three's native Y-up — because geometry arrives in
99
+ * Rhino's frame and is never rotated on ingress. Everything orientation-dependent derives from
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.
102
+ */
103
+ sceneUp?: THREE.Vector3;
104
+ showEnvironment?: boolean;
105
+ /**
106
+ * Multiplier on the HDR's image-based lighting contribution — normalizes brightness across HDRs
107
+ * of differing exposure. Default 1 (unchanged look).
108
+ */
109
+ environmentIntensity?: number;
110
+ };
111
+ type FloorConfig = {
112
+ enabled?: boolean;
113
+ size?: number;
114
+ color?: THREE.Color | string;
115
+ roughness?: number;
116
+ metalness?: number;
117
+ receiveShadow?: boolean;
118
+ };
119
+ type RenderConfig = {
120
+ enableShadows?: boolean;
121
+ shadowMapSize?: number;
122
+ antialias?: boolean;
123
+ pixelRatio?: number;
124
+ toneMapping?: THREE.ToneMapping;
125
+ toneMappingExposure?: number;
126
+ preserveDrawingBuffer?: boolean;
127
+ /** Default false — switches rendering from `renderer.render` to an EffectComposer, which costs more. */
128
+ ambientOcclusion?: boolean;
129
+ /** AO strength 0–1 when {@link RenderConfig.ambientOcclusion} is on. Default 1. */
130
+ aoIntensity?: number;
131
+ /**
132
+ * DPR cap for AO buffers — AO is low-frequency, so sampling below display DPR is nearly invisible
133
+ * but much cheaper (a DPR-2 display would otherwise push 4× the pixels through GTAO's per-pixel
134
+ * sample loop). Default 1; only relevant when AO is enabled.
135
+ */
136
+ aoPixelRatio?: number;
137
+ /**
138
+ * Render only on change (camera motion, invalidate(), pointer input, resize) plus a ~500ms safety
139
+ * repaint, instead of every frame. Default true — cuts idle GPU/battery use. Set false to restore
140
+ * a continuous loop.
141
+ */
142
+ onDemand?: boolean;
143
+ };
144
+
145
+ /** Crisp boundary/crease edge overlays on meshes. See `addEdges`. */
146
+ type EdgesConfig = {
147
+ /** Default false (opt-in). */
148
+ enabled?: boolean;
149
+ /** Omit (default) to derive each mesh's edge color from its own surface material, darkened by `darken`. */
150
+ color?: THREE.ColorRepresentation;
151
+ /** 0–1, default 0.75. Ignored when `color` is set. */
152
+ darken?: number;
153
+ /** CSS px. Default 1.5. */
154
+ width?: number;
155
+ /** Crease angle in degrees: keep edges where faces differ by more than this. Default 44. */
156
+ thresholdAngle?: number;
157
+ /** Fade an overlay out as its mesh shrinks on screen. Default true. */
158
+ distanceFade?: boolean;
159
+ /** Skip overlay extraction for meshes above this triangle count. Default 4M. */
160
+ maxTriangles?: number;
161
+ /** Overlays above this segment count render opaque (no distance fade). Default 2M. */
162
+ maxSegments?: number;
163
+ /** Meshes skipped for exceeding `maxTriangles` fall back to the screen-space edge-detection pass
164
+ * (constant cost regardless of triangle count). Default true. */
165
+ screenSpaceFallback?: boolean;
166
+ };
167
+ type ControlsConfig = {
168
+ enableDamping?: boolean;
169
+ dampingFactor?: number;
170
+ autoRotate?: boolean;
171
+ autoRotateSpeed?: number;
172
+ enableZoom?: boolean;
173
+ enablePan?: boolean;
174
+ minDistance?: number;
175
+ maxDistance?: number;
176
+ };
177
+ /** Infinite distance-fading reference grid. See `createGrid`. */
178
+ type GridConfig = {
179
+ /** Default false (opt-in). */
180
+ enabled?: boolean;
181
+ /** World units (meters). Default 1. */
182
+ cellSize?: number;
183
+ /** Minor cells per major line. Default 10. */
184
+ majorEvery?: number;
185
+ cellColor?: THREE.ColorRepresentation;
186
+ majorColor?: THREE.ColorRepresentation;
187
+ /** World radius at which the grid fully fades. Default 100. */
188
+ fadeDistance?: number;
189
+ /**
190
+ * Axis the grid lies perpendicular to. Defaults to whichever axis `sceneUp` points along
191
+ * (`'z'` unless `sceneUp` is overridden); set explicitly to force an orientation that ignores it.
192
+ */
193
+ plane?: 'x' | 'y' | 'z';
194
+ };
195
+ /** Corner nav-cube/axis gizmo that snaps to preset views. See `createViewGizmo`. */
196
+ type GizmoConfig = {
197
+ /** Default false (opt-in). */
198
+ enabled?: boolean;
199
+ };
200
+ /** Two-click distance measurement tool. See `createMeasureTool`. */
201
+ type MeasureConfig = {
202
+ /** Default false. Only builds the tool; start measuring via `measureTool.setEnabled(true)` on the init result. */
203
+ enabled?: boolean;
204
+ /** Snap to a vertex within this many screen px. Default 12. */
205
+ snapPixels?: number;
206
+ /** Default yellow. */
207
+ color?: THREE.ColorRepresentation;
208
+ /** CSS class for the distance label. */
209
+ labelClassName?: string;
210
+ /** Scene is in meters; pass the response's `modelunits` to convert the label (e.g. "25.0 mm"). Default meters. Ignored if `format` is set. */
211
+ displayUnit?: string;
212
+ /** Receives the straight-line `distance` and per-axis `delta`. Default renders the total plus a Δx/Δy/Δz breakdown. */
213
+ format?: (distance: number, delta: THREE.Vector3) => string;
214
+ };
215
+ type ThreeInitializerOptions = {
216
+ sceneScale?: 'mm' | 'cm' | 'm' | 'inches' | 'feet';
217
+ /**
218
+ * Seeds lighting/material defaults (tone mapping, AO, IBL strength, hemisphere fill); explicit
219
+ * `lighting`/`environment`/`render` options still win. Does NOT touch edges/grid. Default
220
+ * 'technical'. Re-apply later via the init result's `setLook`.
221
+ */
222
+ look?: Look;
223
+ camera?: CameraConfig;
224
+ lighting?: LightingConfig;
225
+ environment?: EnvironmentConfig;
226
+ floor?: FloorConfig;
227
+ render?: RenderConfig;
228
+ controls?: ControlsConfig;
229
+ grid?: GridConfig;
230
+ gizmo?: GizmoConfig;
231
+ edges?: EdgesConfig;
232
+ measure?: MeasureConfig;
233
+ events?: EventConfig;
234
+ /**
235
+ * Called once at init with the GPU's max anisotropy. **Not needed for sharp textures** — the
236
+ * parse layer's texture cache subscribes to this value itself via a shared sink. This hook is
237
+ * only for hosts doing their own texture work on top.
238
+ */
239
+ onMaxAnisotropy?: (value: number) => void;
240
+ };
241
+ type EventConfig = {
242
+ onBackgroundClicked?: (event: {
243
+ x: number;
244
+ y: number;
245
+ }) => void;
246
+ onObjectSelected?: (object: THREE.Object3D) => void;
247
+ /** Receives the clicked mesh's `userData`; only fires for meshes with non-empty `userData`. */
248
+ onMeshMetadataClicked?: (metadata: Record<string, unknown>) => void;
249
+ onMeshDoubleClicked?: (object: THREE.Object3D) => void;
250
+ /** Default red (#ff0000). */
251
+ selectionColor?: THREE.Color | string;
252
+ /** Enable all event handlers (click/selection/metadata). Default true. */
253
+ enableEventHandlers?: boolean;
254
+ enableKeyboardControls?: boolean;
255
+ enableClickToFocus?: boolean;
256
+ /** Default true. */
257
+ enableDoubleClickZoom?: boolean;
258
+ onReady?: () => void;
259
+ /** Fires every animation frame, after controls update and before render. */
260
+ onFrame?: (delta: number) => void;
261
+ };
262
+
263
+ /**
264
+ * Runtime camera control: preset views, perspective⇄orthographic toggle, rotate lock.
265
+ *
266
+ * Centralized because projection switching swaps the camera object that OrbitControls drives, the
267
+ * render loop renders, resize reshapes, and the raycaster picks with — {@link getActiveCamera} is
268
+ * the one source of truth for all four call sites.
269
+ *
270
+ * Orthographic shadows perspective (same position/target, frustum derived from perspective FOV +
271
+ * distance) so switching doesn't visually jump.
272
+ */
273
+ type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';
274
+ type CameraProjection = 'perspective' | 'orthographic';
275
+ interface CameraController {
276
+ /** Swaps identity on {@link setProjection}. */
277
+ getActiveCamera(): THREE.Camera;
278
+ getProjection(): CameraProjection;
279
+ setProjection(projection: CameraProjection): void;
280
+ toggleProjection(): CameraProjection;
281
+ setView(preset: ViewPreset, animate?: boolean): void;
282
+ /**
283
+ * Frame current content from an explicit world-space direction (target → camera) instead of a
284
+ * named preset — used by the nav-cube, whose clicked axis is a world axis.
285
+ */
286
+ setViewDirection(direction: THREE.Vector3, animate?: boolean): void;
287
+ /** Frame a world-space box from the current view direction. No-op on an empty box. */
288
+ frameBounds(box: THREE.Box3, animate?: boolean): void;
289
+ setRotateEnabled(enabled: boolean): void;
290
+ isRotateEnabled(): boolean;
291
+ updateAspect(width: number, height: number): void;
292
+ /** Cancel any in-flight camera tween. Call on viewer teardown so ticks can't touch disposed controls. */
293
+ dispose(): void;
294
+ }
295
+
296
+ interface Grid {
297
+ /** Tagged `userData.id = 'grid'` so pick/fit code skips it. */
298
+ readonly object: THREE.Mesh;
299
+ /** Re-centers the fade on the camera so the grid feels infinite as you move. Call per frame. */
300
+ update(cameraPosition: THREE.Vector3): void;
301
+ /**
302
+ * Rescales cell spacing and fade radius to the content's extent, so a 3-unit or 3000-unit part
303
+ * both get sensible cells. No-op for empty/degenerate bounds.
304
+ */
305
+ fitToContent(bounds: THREE.Box3): void;
306
+ setVisible(visible: boolean): void;
307
+ dispose(): void;
308
+ }
309
+
310
+ /**
311
+ * Two-click distance measurement. Click a point, click a second, read the distance off a label on
312
+ * the connecting line; a third click starts fresh.
313
+ *
314
+ * Picking snaps to the nearest vertex of the struck triangle within {@link MeasureOptions.snapPixels}
315
+ * so measurements land exactly on vertices rather than wherever the ray happened to hit — a cheap
316
+ * local snap (three candidate vertices, no spatial index).
317
+ *
318
+ * Dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks (caller
319
+ * forwards them and swallows the event when {@link MeasureTool.handleClick} returns true) so
320
+ * measuring doesn't also select objects.
321
+ */
322
+ interface MeasureTool {
323
+ setEnabled(enabled: boolean): void;
324
+ isEnabled(): boolean;
325
+ /** Returns true if the tool consumed the click (caller should not also select). */
326
+ handleClick(event: MouseEvent): boolean;
327
+ /** Preview the next snap point via a ghost marker. No-op when disabled; never consumes the event. */
328
+ handleMove(event: MouseEvent): void;
329
+ clear(): void;
330
+ dispose(): void;
331
+ }
332
+
333
+ /**
334
+ * Corner nav-cube/axis gizmo. Uses three's {@link ViewHelper} only as the rendered widget, NOT its
335
+ * 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 our Z-up scene. Instead we hit-test the
337
+ * axis sprites ourselves and drive the viewer's up-aware camera controller, which snaps instantly
338
+ * with a pole nudge so the orbit basis never degenerates.
339
+ *
340
+ * A click frames the current orbit target (not the world origin), and flips the viewer back to
341
+ * perspective first if it's in orthographic mode (the cube is inherently a 3D-orientation tool).
342
+ *
343
+ * Caller contract (mirrors ViewHelper's own): call {@link ViewGizmo.render} *after* the main scene
344
+ * render each frame, and forward pointer clicks to {@link ViewGizmo.handleClick}.
345
+ */
346
+ interface ViewGizmo {
347
+ render(renderer: THREE.WebGLRenderer): void;
348
+ /** Returns true if it hit the gizmo (and a view change started). */
349
+ handleClick(event: MouseEvent): boolean;
350
+ setVisible(visible: boolean): void;
351
+ isVisible(): boolean;
352
+ dispose(): void;
353
+ }
354
+
355
+ interface ThreeViewer {
356
+ scene: THREE.Scene;
357
+ camera: THREE.PerspectiveCamera;
358
+ controls: OrbitControls;
359
+ renderer: THREE.WebGLRenderer;
360
+ cameraController: CameraController;
361
+ grid: Grid | null;
362
+ gizmo: ViewGizmo | null;
363
+ /** Null unless `measure.enabled`; `setEnabled(true)` to use. */
364
+ measureTool: MeasureTool | null;
365
+ /**
366
+ * Attach edge overlays to meshes under `root` (no-op unless `edges.enabled`). Large-mesh
367
+ * extraction runs off-thread, so overlays may attach a beat later; meshes over
368
+ * `edges.maxTriangles` are skipped and (by default) covered by the screen-space edge fallback.
369
+ */
370
+ applyEdges: (root: THREE.Object3D) => void;
371
+ /**
372
+ * Prefer over calling `removeEdges` directly — also cancels in-flight async attaches and stands
373
+ * down the screen-space fallback if active.
374
+ */
375
+ clearEdges: (root: THREE.Object3D) => void;
376
+ /**
377
+ * Request a repaint from the on-demand render loop. Built-in setters and input invalidate
378
+ * automatically; call this after mutating the scene externally. No-op when `render.onDemand` is false.
379
+ */
380
+ invalidate: () => void;
381
+ setAmbientOcclusion: (enabled: boolean) => void;
382
+ /**
383
+ * Retunes lighting/material only (tone mapping, fill, IBL, AO) — never edges/grid. Overwrites
384
+ * any granular lighting dials set earlier.
385
+ */
386
+ setLook: (look: 'studio' | 'technical' | 'showcase') => void;
387
+ /**
388
+ * Raising `hemisphereIntensity` is the most effective way to lift shadowed/under-facing surfaces
389
+ * a dark HDR leaves black; a positive value lazily creates the hemisphere light if the viewer was
390
+ * built without one, `0` switches it off.
391
+ */
392
+ setFillLights: (opts: {
393
+ hemisphereIntensity?: number;
394
+ hemisphereSkyColor?: THREE.Color | number;
395
+ hemisphereGroundColor?: THREE.Color | number;
396
+ ambientIntensity?: number;
397
+ }) => void;
398
+ /**
399
+ * Normalizes IBL brightness across HDRs of differing exposure. Applies even before the HDR
400
+ * finishes decoding.
401
+ */
402
+ setEnvironmentIntensity: (intensity: number) => void;
403
+ setToneMappingExposure: (exposure: number) => void;
404
+ /** GTAO strength (0-1). No-op when ambient occlusion isn't active. */
405
+ setAoIntensity: (intensity: number) => void;
406
+ /** Feed into the batch parser's `material` option so freshly-loaded meshes match the active look. */
407
+ getMaterialAppearance: () => MaterialAppearanceOptions;
408
+ /** Call after loading or replacing geometry. No-op when sunlight/shadows are off. */
409
+ updateShadowBounds: () => void;
410
+ /** Call after loading or replacing geometry. No-op when the grid is off or empty. */
411
+ updateGridScale: () => void;
412
+ dispose: () => void;
413
+ fitToView: () => void;
414
+ 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
+ */
419
+ addUserGeometry: (object: THREE.Object3D) => void;
420
+ removeUserGeometry: (object: THREE.Object3D) => void;
421
+ /** Removes and disposes everything added via `addUserGeometry`. */
422
+ clearUserGeometry: () => void;
423
+ }
424
+
425
+ declare const initThree: (canvas: HTMLCanvasElement, options?: ThreeInitializerOptions) => ThreeViewer;
426
+
427
+ /** Replaces scene content with `meshes`, rescales the camera frustum to fit, and (first call only) positions the camera/controls. */
428
+ declare function updateScene(scene: THREE.Scene, meshes: THREE.Object3D[], camera: THREE.PerspectiveCamera, controls: OrbitControls, initialPositionSet: boolean): void;
429
+
430
+ export { type CameraConfig, type CameraController, type CameraProjection, type ControlsConfig, DEFAULT_LOOK, type EdgesConfig, type EnvironmentConfig, type ErrorCode, ErrorCodes, type EventConfig, type FloorConfig, type GizmoConfig, type Grid, type GridConfig, LOOK_PRESETS, type LightingConfig, type Logger, Look, LookPreset, MaterialAppearanceOptions, type MeasureConfig, type MeasureTool, type RenderConfig, type ThreeInitializerOptions, type ThreeViewer, type ViewGizmo, type ViewPreset, VisualizationError, enableDebugLogging, getLogger, initThree, materialAppearanceForLook, setLogger, updateScene };
package/dist/render.js ADDED
@@ -0,0 +1,110 @@
1
+ import{a as Kt,b as Yt,c as me,d as Zt,e as $t,g as Qt,h as Le,i as Ee,j as Oe,m as Ge,p as Fe,q as Ue,s as Be,t as We,v as se}from"./chunk-EXAI6IC5.js";import{a as X}from"./chunk-5XGN7UAV.js";import*as Ut from"three";import*as ne from"three";import*as he from"three";import*as K from"three";function fe(e){let n=e.clone().normalize(),t=new K.Vector3(0,0,1),r=new K.Vector3(0,1,0),i=Math.abs(n.dot(t))>.9?r:t,o=new K.Vector3().crossVectors(i,n).normalize(),a=new K.Vector3().crossVectors(n,o).normalize();return{up:n,forward:a,right:o}}function ye(e,n){let{forward:t,right:r,up:i}=fe(e);return t.clone().multiplyScalar(-1).add(r.clone().multiplyScalar(-1)).add(i).normalize().multiplyScalar(n)}function qe(e,n,t){let{forward:r,right:i,up:o}=fe(e);return i.clone().multiplyScalar(n).add(r.clone().multiplyScalar(n)).add(o.clone().multiplyScalar(t))}function Xe(e){let n=e.clone().normalize(),t=new K.Vector3(0,1,0);if(n.dot(t)>.9999)return new K.Euler;if(n.dot(t)<-.9999)return new K.Euler(Math.PI,0,0);let r=new K.Quaternion().setFromUnitVectors(t,n);return new K.Euler().setFromQuaternion(r)}function xe(e){let n=Math.abs(e.x),t=Math.abs(e.y),r=Math.abs(e.z);return n>=t&&n>=r?"x":t>=r?"y":"z"}var $={HUGE_THRESHOLD:1e4,LARGE_THRESHOLD:1e3,SCALE_RATIO_THRESHOLD:100,NEAR_PLANE_FACTOR:{TINY:1e-4,SMALL:.001,NORMAL:.01},FAR_PLANE_FACTOR:{HUGE:100,LARGE:50,NORMAL:20},INITIAL_DISTANCE_MULTIPLIER:4};function Jt(e,n,t,r,i){if(nr(e),n.length===0)return;n.forEach(f=>{e.add(f)});let o=Ge(n),a=o.getCenter(new he.Vector3),s=o.getSize(new he.Vector3),l=Math.max(s.x,s.y,s.z);if(l/Math.min(s.x||1,s.y||1,s.z||1)>$.SCALE_RATIO_THRESHOLD||l>$.HUGE_THRESHOLD?(t.near=l*$.NEAR_PLANE_FACTOR.TINY,t.far=l*$.FAR_PLANE_FACTOR.HUGE):l>$.LARGE_THRESHOLD?(t.near=l*$.NEAR_PLANE_FACTOR.SMALL,t.far=l*$.FAR_PLANE_FACTOR.LARGE):(t.near=Math.max(.01,l*$.NEAR_PLANE_FACTOR.NORMAL),t.far=Math.max(2e3,l*$.FAR_PLANE_FACTOR.NORMAL)),t.updateProjectionMatrix(),!i){let f=l*$.INITIAL_DISTANCE_MULTIPLIER;t.position.copy(a).add(ye(t.up,f)),r.target.copy(a),r.update()}}var er=new Set(["grid","floor","label-layer","measure"]);function tr(e){let n=e;for(;n;){if(typeof n.userData.id=="string"&&er.has(n.userData.id))return!0;n=n.parent}return!1}function Q(e){e.updateMatrixWorld(!0);let n=new he.Box3;return e.traverse(t=>{let r=t;t.visible&&!tr(t)&&r.geometry&&n.expandByObject(t)}),n}var rr=new Set(["floor","grid","label-layer"]);function nr(e){[...e.children].forEach(t=>{rr.has(t.userData.id)||t.userData.source!=="user"&&(se(t),t.removeFromParent())})}function or(e){let{up:n,forward:t,right:r}=fe(e),i=t.clone().negate(),o=r.clone();return{top:n.clone(),bottom:n.clone().negate(),front:i.clone(),back:i.clone().negate(),right:o.clone(),left:o.clone().negate(),iso:i.clone().multiplyScalar(1.2).add(o.clone()).add(n.clone()).normalize()}}function Ke(e){let{scene:n,perspective:t,controls:r,onActiveCameraChange:i}=e,o=(e.up??t.up).clone().normalize(),a=or(o),s=new ne.OrthographicCamera(-1,1,1,-1,t.near,t.far);s.up.copy(o);let l="perspective",m=t.aspect,f=()=>l==="perspective"?t:s,E=null,g=()=>{E?.cancel(),E=null},u=()=>{let R=(l==="orthographic"?s:t).position.distanceTo(r.target)*Math.tan(t.fov*Math.PI/360),T=R*m;s.left=-T,s.right=T,s.top=R,s.bottom=-R,s.near=t.near,s.far=t.far,s.updateProjectionMatrix()},p=d=>{if(d!==l){if(g(),d==="orthographic")s.position.copy(t.position),s.up.copy(t.up),s.lookAt(r.target),s.zoom=1,u();else{let R=(s.top-s.bottom)/(2*s.zoom)/Math.tan(t.fov*Math.PI/360),T=s.position.clone().sub(r.target);T.lengthSq()<1e-12&&T.copy(o),T.normalize(),t.position.copy(r.target).add(T.multiplyScalar(R))}l=d,r.object=f(),r.update(),i(f())}},h=(d,c,R,T)=>{let x=t.fov*(Math.PI/180),M=c/(2*Math.tan(x/2))*1.5,P=ir(R,o),L=d.clone().add(P.clone().multiplyScalar(M)),Y=f();l==="orthographic"&&(s.zoom=1),g(),T?E=sr(Y,r,L,d,()=>{l==="orthographic"&&u()}):(Y.position.copy(L),r.target.copy(d),l==="orthographic"&&u(),r.update())},C=(d,c=!0)=>{let R=Q(n),T=R.isEmpty()?r.target.clone():R.getCenter(new ne.Vector3),x=R.isEmpty()?new ne.Vector3(1,1,1):R.getSize(new ne.Vector3),M=Math.max(x.x,x.y,x.z)||1;h(T,M,d,c)};return{getActiveCamera:f,getProjection:()=>l,setProjection:p,toggleProjection:()=>(p(l==="perspective"?"orthographic":"perspective"),l),setView:(d,c=!0)=>{C(a[d],c)},setViewDirection:C,frameBounds:(d,c=!0)=>{if(d.isEmpty())return;let R=d.getCenter(new ne.Vector3),T=d.getSize(new ne.Vector3),x=Math.max(T.x,T.y,T.z)||1,M=f().position.clone().sub(r.target);M.lengthSq()<1e-12&&M.copy(a.iso),h(R,x,M.normalize(),c)},setRotateEnabled:d=>{r.enableRotate=d},isRotateEnabled:()=>r.enableRotate,updateAspect:(d,c)=>{m=c===0?m:d/c,l==="orthographic"&&u()},dispose:g}}function ir(e,n){let{up:t,forward:r}=fe(n),i=e.clone().normalize();if(Math.abs(i.dot(t))<.9999)return e;let o=r.clone().negate(),a=.5*Math.PI/180;return i.multiplyScalar(Math.cos(a)).add(o.multiplyScalar(Math.sin(a))).normalize()}var ar=e=>1-Math.pow(1-e,3);function sr(e,n,t,r,i,o=250){let a=e.position.clone(),s=n.target.clone(),l=performance.now(),m=null,f=()=>{m=null;let E=ar(Math.min((performance.now()-l)/o,1));e.position.lerpVectors(a,t,E),n.target.lerpVectors(s,r,E),i(),n.update(),E<1&&(m=requestAnimationFrame(f))};return m=requestAnimationFrame(f),{cancel:()=>{m!==null&&(cancelAnimationFrame(m),m=null)}}}import*as mt from"three";import{LineSegments2 as Sr}from"three/addons/lines/LineSegments2.js";import{LineSegmentsGeometry as lr}from"three/addons/lines/LineSegmentsGeometry.js";var cr=.15,dr=4096;function ur(e){let n=Math.floor(e.length/6);if(n===0)return 1/0;let t=Math.max(1,Math.ceil(n/dr)),r=[];for(let i=0;i<n;i+=t){let o=i*6,a=Math.hypot(e[o+3]-e[o],e[o+4]-e[o+1],e[o+5]-e[o+2]);a>0&&r.push(a)}return r.length===0?1/0:(r.sort((i,o)=>i-o),r[Math.min(r.length-1,Math.floor(r.length*cr))])}function Ye(e){let n=new lr;return n.setPositions(e),{geometry:n,segmentCount:e.length/6,edgeSpacing:ur(e)}}import*as nt from"three";function He(e,n,t){let o=Math.cos(Math.PI/180*t),a=e.length/3;if(a>=67108864)throw new Error(`extractEdgeSegments: ${a} vertices exceeds 2^26 limit`);let s=new Float64Array(a),l=new Float64Array(a),m=new Float64Array(a);for(let c=0;c<a;c++)s[c]=Math.round(e[3*c]*1e4),l[c]=Math.round(e[3*c+1]*1e4),m[c]=Math.round(e[3*c+2]*1e4);let f=16;for(;f<a*2;)f<<=1;let E=f-1,g=new Int32Array(f).fill(-1),u=new Int32Array(a);for(let c=0;c<a;c++){let R=(Math.imul(s[c]|0,73856093)^Math.imul(l[c]|0,19349663)^Math.imul(m[c]|0,83492791))&E;for(;;){let T=g[R];if(T===-1){g[R]=c,u[c]=c;break}if(s[T]===s[c]&&l[T]===l[c]&&m[T]===m[c]){u[c]=T;break}R=R+1&E}}let p=new Float32Array(4096),h=0,C=(c,R)=>{if(h+6>p.length){let T=new Float32Array(p.length*2);T.set(p),p=T}p[h++]=e[3*c],p[h++]=e[3*c+1],p[h++]=e[3*c+2],p[h++]=e[3*R],p[h++]=e[3*R+1],p[h++]=e[3*R+2]},b=new Map,A=[],S=[],I=[],d=(n?n.length:a)/3;for(let c=0;c<d;c++){let R=n?n[3*c]:3*c,T=n?n[3*c+1]:3*c+1,x=n?n[3*c+2]:3*c+2,M=u[R],P=u[T],L=u[x];if(M===P||P===L||L===M)continue;let Y=e[3*x]-e[3*T],w=e[3*x+1]-e[3*T+1],k=e[3*x+2]-e[3*T+2],v=e[3*R]-e[3*T],y=e[3*R+1]-e[3*T+1],z=e[3*R+2]-e[3*T+2],j=w*z-k*y,V=k*v-Y*z,G=Y*y-w*v,be=j*j+V*V+G*G;if(be>0){let Z=1/Math.sqrt(be);j*=Z,V*=Z,G*=Z}else j=0,V=0,G=0;for(let Z=0;Z<3;Z++){let ee,re,U,q;Z===0?(ee=R,re=T,U=M,q=P):Z===1?(ee=T,re=x,U=P,q=L):(ee=x,re=R,U=L,q=M);let ve=q*67108864+U,ie=b.get(ve);if(ie!==void 0&&ie!==-1)j*I[3*ie]+V*I[3*ie+1]+G*I[3*ie+2]<=o&&C(ee,re),b.set(ve,-1);else{let ue=U*67108864+q;if(!b.has(ue)){let Pe=A.length;b.set(ue,Pe),A.push(ee),S.push(re),I.push(j,V,G)}}}}for(let c of b.values())c!==-1&&C(A[c],S[c]);return p.slice(0,h)}function Ze(){return[`const extract = ${He.toString()};`,"self.onmessage = (event) => {"," const { id, positions, index, thresholdAngle } = event.data;"," try {"," const segments = extract(positions, index, thresholdAngle);"," self.postMessage({ id, segments }, [segments.buffer]);"," } catch (error) {"," self.postMessage({ id, error: String((error && error.message) || error) });"," }","};"].join(`
2
+ `)}import*as Ce from"three";var le="edge-overlay",we="triangle-cap",$e=2236962,mr=1.5,pr=44,Er=.75,fr=4e6,hr=2e6,Qe=25e3,Ie=128*1024*1024,Je=4,ke=1,et=0,tt=-1;function rt(e){return{forcedColor:e.color!=null?new Ce.Color(e.color):null,darken:Ce.MathUtils.clamp(e.darken??Er,0,1),width:e.width??mr,thresholdAngle:e.thresholdAngle??pr,distanceFade:e.distanceFade??!0,maxTriangles:e.maxTriangles??fr,maxSegments:e.maxSegments??hr}}function Me(e){let n=e.getAttribute("position");return n?(e.index?e.index.count:n.count)/3:0}function ot(e){let n=e.getAttribute("position");if(!n||n.isInterleavedBufferAttribute||n.itemSize!==3||!(n.array instanceof Float32Array)||n.count>=67108864)return null;let t=e.index;return t&&!(t.array instanceof Uint32Array)&&!(t.array instanceof Uint16Array)?null:{positions:n.array,index:t?t.array:null}}function it(e,n){let r=2166136261,i=l=>{r^=l,r=Math.imul(r,16777619)},o=new Uint32Array(e.positions.buffer,e.positions.byteOffset,e.positions.length),a=Math.min(4096,o.length);for(let l=0;l<a;l++)i(o[l]);for(let l=Math.max(a,o.length-4096);l<o.length;l++)i(o[l]);let s=0;if(e.index){s=e.index.length;let l=Math.min(4096,s);for(let m=0;m<l;m++)i(e.index[m]);for(let m=Math.max(l,s-4096);m<s;m++)i(e.index[m])}return`${n}:${e.positions.length}:${s}:${r>>>0}`}var J=new Map,ge=0;Ue(()=>{J.clear(),ge=0});function at(e){let n=J.get(e);return n&&(J.delete(e),J.set(e,n)),n}function st(e,n){if(n.byteLength>Ie)return;let t=J.get(e);for(t&&(ge-=t.byteLength,J.delete(e)),J.set(e,n),ge+=n.byteLength;ge>Ie;){let r=J.keys().next().value;ge-=J.get(r).byteLength,J.delete(r)}}function Rr(e,n){let t=new nt.EdgesGeometry(e,n),r=t.attributes.position?t.attributes.position.array:new Float32Array(0);return t.dispose(),r}function _e(e,n){let t=ot(e);if(!t)return Rr(e,n);let r=it(t,n),i=at(r);if(i)return i;let o=He(t.positions,t.index,n);return st(r,o),o}var ce,Re=new Map,Tr=1;function br(){if(ce!==void 0)return ce;if(typeof Worker>"u"||typeof Blob>"u"||typeof URL>"u"||typeof URL.createObjectURL!="function")return ce=null,null;try{let e=URL.createObjectURL(new Blob([Ze()],{type:"text/javascript"})),n=new Worker(e);n.onmessage=t=>{let{id:r,segments:i,error:o}=t.data,a=Re.get(r);a&&(Re.delete(r),i?a.resolve(i):a.reject(new Error(o??"edge extraction failed in worker")))},n.onerror=()=>{for(let t of Re.values())t.reject(new Error("edge extraction worker crashed"));Re.clear(),n.terminate(),ce=null},ce=n}catch{ce=null}return ce}function vr(e,n,t){return new Promise((r,i)=>{let o=Tr++;Re.set(o,{resolve:r,reject:i});let a=n.positions.slice(),s=n.index?n.index.slice():null,l=[a.buffer];s&&l.push(s.buffer),e.postMessage({id:o,positions:a,index:s,thresholdAngle:t},l)})}var ze=new Map;function lt(e,n){let t=ot(e);if(!t||Me(e)<Qe)return Promise.resolve(_e(e,n));let r=it(t,n),i=at(r);if(i)return Promise.resolve(i);let o=ze.get(r);if(o)return o;let a=br();if(!a)return Promise.resolve(_e(e,n));let s=vr(a,t,n).catch(()=>He(t.positions,t.index,n)).then(l=>(st(r,l),l)).finally(()=>{ze.delete(r)});return ze.set(r,s),s}import*as oe from"three";import{LineMaterial as yr}from"three/addons/lines/LineMaterial.js";import{LineSegments2 as dt}from"three/addons/lines/LineSegments2.js";function xr(e,n){let r=(Array.isArray(e.material)?e.material[0]:e.material)?.color;return r?r.clone().multiplyScalar(1-n):new oe.Color($e)}var Se=class{constructor(n){X(this,"options",n);X(this,"byKey",new Map)}for(n,t){let r=this.options.forcedColor??xr(n,this.options.darken),i=r.getHex()*2+(t?1:0),o=this.byKey.get(i);return o||(o=Hr(r,this.options.width,t),this.byKey.set(i,o)),o}disposeUnused(n){let t=new Set(n.map(r=>r.material));for(let r of this.byKey.values())t.has(r)||r.dispose()}};function Hr(e,n,t){let r=new yr({color:e});return r.linewidth=n,r.polygonOffset=!0,r.polygonOffsetFactor=et,r.polygonOffsetUnits=tt,t&&(r.transparent=!0),r}function ut(e,n,t){let r=new dt(e.geometry,n);return r.userData.kind=le,r.raycast=()=>{},t&&Mr(r,e.edgeSpacing),r}var ct=new oe.Vector3,Cr=new oe.Vector3;function wr(e,n,t){e.geometry.boundingSphere||e.geometry.computeBoundingSphere();let r=e.geometry.boundingSphere;if(!r)return 1/0;if(n.isPerspectiveCamera){let i=n;ct.copy(r.center).applyMatrix4(e.matrixWorld);let o=Cr.setFromMatrixPosition(n.matrixWorld).distanceTo(ct),a=r.radius*e.matrixWorld.getMaxScaleOnAxis();if(o<=a)return 1/0;let s=Math.tan(oe.MathUtils.degToRad(i.fov)*.5),l=2*o*s;return l>0?t/l:1/0}if(n.isOrthographicCamera){let i=n,o=(i.top-i.bottom)/i.zoom;return o>0?t/o:1/0}return 1/0}function Mr(e,n){e.onBeforeRender=(t,r,i)=>{dt.prototype.onBeforeRender.call(e,t);let o=e.material,a=wr(e,i,o.resolution.y),s=n*a;o.opacity=oe.MathUtils.clamp((s-ke)/(Je-ke),0,1)}}function Dr(e){return e.userData?.kind===le}function Ar(e,n){let t=[];return e.traverse(r=>{if(r instanceof mt.Mesh&&!(r.userData.id==="floor"||r.userData.id==="grid")&&r.userData.kind!==le&&!r.children.some(i=>i.userData?.kind===le)&&r.geometry){if(Me(r.geometry)>n){r.userData.edgesSkipped=we,console.debug(`[edges] skipping mesh over triangle cap (${Me(r.geometry)} > ${n})`);return}delete r.userData.edgesSkipped,t.push(r)}}),t}function Pr(e,n,t,r){let i=r.distanceFade&&n.segmentCount<=r.maxSegments,o=ut(n,t.for(e,i),i);return e.add(o),o}var pt=new WeakMap;function Ve(e){return pt.get(e)??0}function Lr(e,n){for(let t=e;t;t=t.parent)if(t===n)return!0;return!1}async function Et(e,n={}){let t=rt(n),r=new Se(t),i=Ve(e),o=[],a=Ar(e,t.maxTriangles).map(async s=>{let l=await lt(s.geometry,t.thresholdAngle);Ve(e)===i&&Lr(s,e)&&(s.children.some(m=>m.userData?.kind===le)||o.push(Pr(s,Ye(l),r,t)))});return await Promise.all(a),r.disposeUnused(o),o}function ft(e){pt.set(e,Ve(e)+1);let n=[];e.traverse(r=>{r instanceof Sr&&Dr(r)&&n.push(r)});let t=new Set;for(let r of n)r.geometry.dispose(),t.add(r.material),r.removeFromParent();return t.forEach(r=>r.dispose()),n.length}import*as _ from"three";function Or(e){if(!(e>0)||!Number.isFinite(e))return 1;let n=Math.floor(Math.log10(e)),t=Math.pow(10,n),r=e/t;return(r>=5?5:r>=2?2:1)*t}var Fr=`
3
+ varying vec3 vWorldPos;
4
+ void main() {
5
+ vec4 world = modelMatrix * vec4(position, 1.0);
6
+ vWorldPos = world.xyz;
7
+ gl_Position = projectionMatrix * viewMatrix * world;
8
+ }
9
+ `,Ir=`
10
+ precision highp float;
11
+ varying vec3 vWorldPos;
12
+
13
+ uniform vec2 uAxes; // indices (0=x,1=y,2=z) of the two in-plane world axes
14
+ uniform float uCell;
15
+ uniform float uMajor;
16
+ uniform vec3 uCellColor;
17
+ uniform vec3 uMajorColor;
18
+ uniform vec3 uCenter; // fade center (camera position projected onto the plane)
19
+ uniform float uFade;
20
+
21
+ // Antialiased grid line intensity for a given spacing, using screen-space derivatives so lines
22
+ // stay ~1px regardless of zoom (the standard "pristine grid" technique).
23
+ float gridLine(vec2 coord, float spacing) {
24
+ vec2 c = coord / spacing;
25
+ vec2 d = fwidth(c);
26
+ vec2 g = abs(fract(c - 0.5) - 0.5) / max(d, 1e-6);
27
+ float line = min(g.x, g.y);
28
+ return 1.0 - clamp(line, 0.0, 1.0);
29
+ }
30
+
31
+ // Index a vec3 by a float axis id (0/1/2) without dynamic indexing (WebGL1-safe).
32
+ float axis(vec3 v, float i) {
33
+ return i < 0.5 ? v.x : (i < 1.5 ? v.y : v.z);
34
+ }
35
+
36
+ void main() {
37
+ // Pick the two in-plane world coordinates.
38
+ vec2 coord = vec2(axis(vWorldPos, uAxes.x), axis(vWorldPos, uAxes.y));
39
+
40
+ float minor = gridLine(coord, uCell);
41
+ float major = gridLine(coord, uCell * uMajor);
42
+
43
+ vec3 color = mix(uCellColor, uMajorColor, major);
44
+ float alpha = max(minor, major);
45
+
46
+ // Radial fade from the camera-projected center.
47
+ float dist = distance(vWorldPos, uCenter);
48
+ float fade = 1.0 - clamp(dist / uFade, 0.0, 1.0);
49
+ alpha *= fade * fade;
50
+
51
+ if (alpha < 0.001) discard;
52
+ gl_FragColor = vec4(color, alpha);
53
+ }
54
+ `;function ht(e={}){let{cellSize:n=1,majorEvery:t=10,cellColor:r=8947848,majorColor:i=4473924,fadeDistance:o=100,plane:a="y"}=e,s=a==="y"?new _.Vector2(0,2):a==="z"?new _.Vector2(0,1):new _.Vector2(1,2),l=2.5,m=new _.PlaneGeometry(1,1);a==="y"?m.rotateX(-Math.PI/2):a==="x"&&m.rotateY(Math.PI/2);let f=new _.ShaderMaterial({vertexShader:Fr,fragmentShader:Ir,transparent:!0,depthWrite:!1,side:_.DoubleSide,uniforms:{uAxes:{value:s},uCell:{value:n},uMajor:{value:t},uCellColor:{value:new _.Color(r)},uMajorColor:{value:new _.Color(i)},uCenter:{value:new _.Vector3},uFade:{value:o}}}),E=new _.Mesh(m,f);E.name="grid",E.userData.id="grid",E.renderOrder=-1;let g=o,u=o*l,p=new _.Vector3;return{object:E,update:h=>{a==="y"?(E.position.set(h.x,0,h.z),p.set(h.x,0,h.z)):a==="z"?(E.position.set(h.x,h.y,0),p.set(h.x,h.y,0)):(E.position.set(0,h.y,h.z),p.set(0,h.y,h.z)),f.uniforms.uCenter.value.copy(p),E.scale.setScalar(u)},fitToContent:h=>{if(h.isEmpty())return;let C=h.getSize(new _.Vector3),b=(I,d)=>d===0?I.x:d===1?I.y:I.z,A=Math.max(b(C,s.x),b(C,s.y));if(!(A>0)||!Number.isFinite(A))return;let S=20;f.uniforms.uCell.value=Or(A/S),g=A*2,f.uniforms.uFade.value=g,u=g*l},setVisible:h=>{E.visible=h},dispose:()=>{E.removeFromParent(),m.dispose(),f.dispose()}}}import*as gt from"three";import{CSS2DRenderer as kr,CSS2DObject as zr}from"three/addons/renderers/CSS2DRenderer.js";function Rt(e,n){let t=new kr,r=t.domElement;r.style.position="absolute",r.style.top="0",r.style.left="0",r.style.overflow="hidden",r.style.pointerEvents="none",r.style.zIndex="30",getComputedStyle(e).position==="static"&&(e.style.position="relative"),e.appendChild(r);let i={width:e.clientWidth||1,height:e.clientHeight||1};t.setSize(i.width,i.height);let o=new gt.Group;o.name="label-layer",o.userData.id="label-layer",n.add(o);let a=new Set;return{addLabel:(l,m,f)=>{let E=document.createElement("div");E.textContent=l,f?E.className=f:Object.assign(E.style,{padding:"2px 6px",borderRadius:"4px",background:"rgba(20, 20, 20, 0.78)",color:"#fff",font:"12px/1.3 system-ui, sans-serif",whiteSpace:"pre",textAlign:"center",userSelect:"none"}),E.style.pointerEvents="none";let g=new zr(E);return g.position.copy(m),o.add(g),a.add(g),{object:g,setPosition:u=>g.position.copy(u),setText:u=>{E.textContent=u},remove:()=>{g.removeFromParent(),E.remove(),a.delete(g)}}},render:(l,m)=>t.render(l,m),setSize:(l,m)=>t.setSize(l,m),dispose:()=>{a.forEach(l=>{l.removeFromParent(),l.element.remove()}),a.clear(),o.removeFromParent(),r.remove()}}}import*as D from"three";import{Line2 as _r}from"three/addons/lines/Line2.js";import{LineGeometry as Vr}from"three/addons/lines/LineGeometry.js";import{LineMaterial as jr}from"three/addons/lines/LineMaterial.js";var Nr=12,Gr=16763904,Tt=.015,bt={Millimeters:{metersPerUnit:1/1e3,suffix:"mm"},Centimeters:{metersPerUnit:1/100,suffix:"cm"},Meters:{metersPerUnit:1,suffix:"m"},Inches:{metersPerUnit:1/39.37,suffix:"in"},Feet:{metersPerUnit:1/3.28084,suffix:"ft"}};function Ur(e){let n=e&&bt[e]||bt.Meters;return t=>`${(t/n.metersPerUnit).toPrecision(3)} ${n.suffix}`}function Br(e,n){if(e.isOrthographicCamera){let r=e;return Math.abs(r.top-r.bottom)/(r.zoom||1)*Tt}return((n?e.position.distanceTo(n):e.position.length())||1)*Tt}function Wr(e){let n=e.object;if(n instanceof D.Mesh)return e.face?[e.face.a,e.face.b,e.face.c]:null;if(n instanceof D.Points)return e.index!=null?[e.index]:null;if(n instanceof D.Line){if(e.index==null)return null;let t=n.geometry.index;return t?e.index+1>=t.count?null:[t.getX(e.index),t.getX(e.index+1)]:[e.index,e.index+1]}return null}function qr(e,n,t,r){let i=e.point.clone(),o=e.object,a=Wr(e);if(!a||!o.geometry)return i;let s=o.geometry.attributes.position;if(!s)return i;let l=g=>{let u=g.clone().project(n);return new D.Vector2((u.x+1)/2*t.width,(1-u.y)/2*t.height)},m=l(i),f=i,E=r;for(let g of a){if(g>=s.count)continue;let p=new D.Vector3().fromBufferAttribute(s,g).applyMatrix4(o.matrixWorld),h=l(p).distanceTo(m);h<E&&(E=h,f=p)}return f}function vt(e){let{canvas:n,scene:t,getActiveCamera:r,getViewTarget:i,labelLayer:o,options:a={}}=e,s=a.snapPixels??Nr,l=new D.Color(a.color??Gr),m=Ur(a.displayUnit),f=(v,y)=>`${m(v)}
55
+ \u0394x ${m(y.x)} \u0394y ${m(y.y)} \u0394z ${m(y.z)}`,E=a.format??f,g=new D.Raycaster,u=new D.Vector2,p=!1,h=[],C=[],b=null,A=null,S=new D.PointsMaterial({color:l,size:8,sizeAttenuation:!1,depthTest:!1}),I=new D.PointsMaterial({color:l,size:11,sizeAttenuation:!1,depthTest:!1,transparent:!0,opacity:.5}),d=null,c=v=>{if(!v){d&&(d.visible=!1);return}if(!d){let y=new D.BufferGeometry;y.setAttribute("position",new D.Float32BufferAttribute([0,0,0],3)),d=new D.Points(y,I),d.renderOrder=1e3,d.userData.id="measure",d.raycast=()=>{},t.add(d)}d.position.copy(v),d.visible=!0},R=v=>{let y=new D.BufferGeometry;y.setAttribute("position",new D.Float32BufferAttribute([v.x,v.y,v.z],3));let z=new D.Points(y,S);return z.renderOrder=999,z.userData.id="measure",z.raycast=()=>{},t.add(z),z},T=()=>{h.length=0,C.forEach(v=>{v.geometry.dispose(),v.removeFromParent()}),C.length=0,b&&(b.geometry.dispose(),b.material.dispose(),b.removeFromParent(),b=null),A?.remove(),A=null},x=()=>{if(h.length!==2)return;let[v,y]=h,z=new Vr;z.setPositions([v.x,v.y,v.z,y.x,y.y,y.z]);let j=new jr({color:l});j.linewidth=2,j.depthTest=!1,b=new _r(z,j),b.renderOrder=998,b.userData.id="measure",b.raycast=()=>{},t.add(b);let V=v.clone().add(y).multiplyScalar(.5),G=new D.Vector3(Math.abs(y.x-v.x),Math.abs(y.y-v.y),Math.abs(y.z-v.z));A=o.addLabel(E(v.distanceTo(y),G),V,a.labelClassName)},M=v=>{let y=n.getBoundingClientRect();u.x=(v.clientX-y.left)/y.width*2-1,u.y=-((v.clientY-y.top)/y.height)*2+1;let z=r();g.setFromCamera(u,z);let j=Br(z,i?.());g.params.Line.threshold=j,g.params.Points.threshold=j;let V=g.intersectObjects(t.children,!0).filter(G=>G.object.userData.id!=="measure"&&G.object.userData.id!=="grid");return V.length===0?null:qr(V[0],z,{width:y.width,height:y.height},s)},P=null,L=0,Y=()=>{L&&(cancelAnimationFrame(L),L=0),P=null};return{setEnabled:v=>{p=v,v||(Y(),T(),c(null))},isEnabled:()=>p,handleClick:v=>{if(!p)return!1;h.length===2&&T();let y=M(v);return y===null||(h.push(y),C.push(R(y)),h.length===2&&x()),!0},handleMove:v=>{p&&(P=v,!L&&(L=requestAnimationFrame(()=>{L=0;let y=P;P=null,!(!p||!y)&&c(M(y))})))},clear:T,dispose:()=>{Y(),T(),d&&(d.geometry.dispose(),d.removeFromParent(),d=null),S.dispose(),I.dispose()}}}import*as Te from"three";var Xr=.5,Kr=.01,Yr=.05,Zr=[];function yt({camera:e,scene:n,groundNormals:t=()=>Zr}){let r=e.near,i=e.near,o=new Te.Vector3,a=new Te.Vector3;return{update:()=>{e.near!==i&&(r=e.near);let l=Q(n),m=r;if(!l.isEmpty()){let f=l.getSize(a).length()*.5,E=e.position.distanceTo(l.getCenter(o))-f;for(let g of t())E=Math.min(E,Math.abs(e.position.dot(g)));m=Te.MathUtils.clamp(E*Xr,r,e.far*Kr)}Math.abs(m-i)>i*Yr&&(e.near=m,e.updateProjectionMatrix(),i=m)}}}import*as B from"three";import{ViewHelper as $r}from"three/addons/helpers/ViewHelper.js";function xt(e){let{camera:n,domElement:t,controller:r}=e,i=new $r(n,t);i.setLabels("X","Y","Z");let o=!0,a=128,s=new B.Raycaster,l=new B.OrthographicCamera(-2,2,2,-2,0,4);l.position.set(0,0,2),l.updateMatrixWorld();let m={posX:new B.Vector3(1,0,0),negX:new B.Vector3(-1,0,0),posY:new B.Vector3(0,1,0),negY:new B.Vector3(0,-1,0),posZ:new B.Vector3(0,0,1),negZ:new B.Vector3(0,0,-1)},f=g=>{let u=t.getBoundingClientRect(),p=u.left+t.offsetWidth-a-i.location.right,h=u.top+t.offsetHeight-a-i.location.bottom,C=new B.Vector2((g.clientX-p)/a*2-1,-((g.clientY-h)/a)*2+1);if(Math.abs(C.x)>1||Math.abs(C.y)>1)return null;i.quaternion.copy(n.quaternion).invert(),i.updateMatrixWorld(),s.setFromCamera(C,l);let b=s.intersectObjects(i.children,!1);for(let A of b){let S=A.object.userData?.type;if(typeof S=="string"&&S in m)return S}return null};return{render:g=>{if(!o)return;let u=g.autoClear;g.autoClear=!1,i.render(g),g.autoClear=u},handleClick:g=>{if(!o)return!1;let u=f(g);return u?(r.getProjection()==="orthographic"&&r.setProjection("perspective"),r.setViewDirection(m[u],!1),!0):!1},setVisible:g=>{o=g},isVisible:()=>o,dispose:()=>i.dispose()}}import*as je from"three";function Ht(e,n,t,r,i,o,a,s,l,m,f,E,g,u,p=!0){let h=null,C=performance.now(),b=!0,A=0,S=500,I=new je.Matrix4,d=new je.Matrix4,c=null,R=()=>{b=!0},T=w=>{w.updateMatrixWorld();let k=c!==w||!I.equals(w.matrixWorld)||!d.equals(w.projectionMatrix);return k&&(c=w,I.copy(w.matrixWorld),d.copy(w.projectionMatrix)),k},x=e.domElement,M=["pointerdown","pointerup","wheel"];if(p)for(let w of M)x.addEventListener(w,R,{passive:!0});let P=()=>{let{width:w,height:k}=a();if(w===0||k===0)return;let v=Math.floor(w*s),y=Math.floor(k*s);(e.domElement.width!==v||e.domElement.height!==y)&&(e.setPixelRatio(s),e.setSize(w,k,!1),t.aspect=w/k,t.updateProjectionMatrix(),i.updateAspect(w,k),E?.()?.setSize(w,k,s),g?.setSize(w,k),R())},L=function(){h=requestAnimationFrame(L);let w=performance.now(),k=(w-C)/1e3;C=w,P(),(o.enableDamping||o.autoRotate)&&o.update(),m&&m.update(r().position),u&&u.update(),l?.(k);let v=r();if(p){if(!(b||T(v)||w-A>=S))return;b=!1,A=w}let y=E?.();y?(y.setCamera(v),y.render(k)):e.render(n,v),g&&g.render(n,v),f&&f.render(e)};return{animate:L,dispose:()=>{if(h!==null&&(cancelAnimationFrame(h),h=null),p)for(let w of M)x.removeEventListener(w,R)},invalidate:R}}import*as wt from"three";import*as de from"three";var W=new de.Vector3(0,0,1);function Ct(e){let n=e.sceneScale||"m",r={mm:{cameraDistance:20,near:.1,far:2e3,floorSize:100,lightDistance:10,lightHeight:20,minDistance:.1,shadowSize:100,scaleFactor:1e3},cm:{cameraDistance:20,near:.1,far:2e3,floorSize:100,lightDistance:25,lightHeight:50,minDistance:.1,shadowSize:100,scaleFactor:100},m:{cameraDistance:10,near:.01,far:2e3,floorSize:50,lightDistance:25,lightHeight:50,minDistance:.001,shadowSize:100,scaleFactor:1},inches:{cameraDistance:15,near:.1,far:2e3,floorSize:80,lightDistance:20,lightHeight:40,minDistance:.1,shadowSize:80,scaleFactor:39.37},feet:{cameraDistance:8,near:.1,far:2e3,floorSize:40,lightDistance:15,lightHeight:30,minDistance:.1,shadowSize:60,scaleFactor:3.28084}}[n],i=e.look??Le,o=Ee[i];return{sceneScale:n,look:i,camera:{position:e.camera?.position||ye(e.environment?.sceneUp??W,r.cameraDistance*Math.sqrt(3)),fov:e.camera?.fov||20,near:e.camera?.near||r.near,far:e.camera?.far||r.far,target:e.camera?.target||new de.Vector3(0,0,0),dynamicNear:e.camera?.dynamicNear??!0},lighting:{enableSunlight:e.lighting?.enableSunlight??!0,sunlightIntensity:e.lighting?.sunlightIntensity??1,sunlightPosition:e.lighting?.sunlightPosition||qe(e.environment?.sceneUp??W,r.lightDistance,r.lightHeight),ambientLightColor:e.lighting?.ambientLightColor||new de.Color(4210752),ambientLightIntensity:e.lighting?.ambientLightIntensity??o.ambientIntensity,sunlightColor:e.lighting?.sunlightColor||16777215,enableHemisphereLight:e.lighting?.enableHemisphereLight??o.hemisphereIntensity>0,hemisphereSkyColor:e.lighting?.hemisphereSkyColor??14673663,hemisphereGroundColor:e.lighting?.hemisphereGroundColor??7036754,hemisphereIntensity:e.lighting?.hemisphereIntensity??o.hemisphereIntensity},environment:{hdrPath:e.environment?.hdrPath||"/baseHDR.hdr",backgroundColor:e.environment?.backgroundColor||new de.Color(15790320),enableEnvironmentLighting:e.environment?.enableEnvironmentLighting??!0,sceneUp:e.environment?.sceneUp||W,showEnvironment:e.environment?.showEnvironment??!1,environmentIntensity:e.environment?.environmentIntensity??o.environmentIntensity},floor:{enabled:e.floor?.enabled??!1,size:e.floor?.size||r.floorSize,color:e.floor?.color||new de.Color(8421504),roughness:e.floor?.roughness??.7,metalness:e.floor?.metalness??0,receiveShadow:e.floor?.receiveShadow??!0},render:{enableShadows:e.render?.enableShadows??!0,shadowMapSize:e.render?.shadowMapSize||2048,antialias:e.render?.antialias??!0,pixelRatio:e.render?.pixelRatio||Math.min(window.devicePixelRatio,2),toneMapping:e.render?.toneMapping??o.toneMapping,toneMappingExposure:e.render?.toneMappingExposure??o.toneMappingExposure,preserveDrawingBuffer:e.render?.preserveDrawingBuffer??!1,ambientOcclusion:e.render?.ambientOcclusion??o.ambientOcclusion,aoIntensity:e.render?.aoIntensity??1,aoPixelRatio:e.render?.aoPixelRatio??1,onDemand:e.render?.onDemand??!0},controls:{enableDamping:e.controls?.enableDamping??!1,dampingFactor:e.controls?.dampingFactor||.05,autoRotate:e.controls?.autoRotate??!1,autoRotateSpeed:e.controls?.autoRotateSpeed||.5,enableZoom:e.controls?.enableZoom??!0,enablePan:e.controls?.enablePan??!0,minDistance:e.controls?.minDistance||r.minDistance,maxDistance:e.controls?.maxDistance||1/0},grid:{enabled:e.grid?.enabled??!1,cellSize:e.grid?.cellSize??1,majorEvery:e.grid?.majorEvery??10,cellColor:e.grid?.cellColor??8947848,majorColor:e.grid?.majorColor??4473924,fadeDistance:e.grid?.fadeDistance??100,plane:e.grid?.plane??xe(e.environment?.sceneUp??W)},gizmo:{enabled:e.gizmo?.enabled??!1},edges:{enabled:e.edges?.enabled??!1,color:e.edges?.color,darken:e.edges?.darken,width:e.edges?.width??1.5,thresholdAngle:e.edges?.thresholdAngle??44,distanceFade:e.edges?.distanceFade??!0,maxTriangles:e.edges?.maxTriangles,maxSegments:e.edges?.maxSegments,screenSpaceFallback:e.edges?.screenSpaceFallback},measure:{enabled:e.measure?.enabled??!1,snapPixels:e.measure?.snapPixels,color:e.measure?.color,labelClassName:e.measure?.labelClassName,displayUnit:e.measure?.displayUnit,format:e.measure?.format},events:{onBackgroundClicked:e.events?.onBackgroundClicked,onObjectSelected:e.events?.onObjectSelected,onMeshMetadataClicked:e.events?.onMeshMetadataClicked,onMeshDoubleClicked:e.events?.onMeshDoubleClicked,selectionColor:e.events?.selectionColor||"#ff0000",enableEventHandlers:e.events?.enableEventHandlers??!0,enableKeyboardControls:e.events?.enableKeyboardControls??!0,enableClickToFocus:e.events?.enableClickToFocus??!0,enableDoubleClickZoom:e.events?.enableDoubleClickZoom??!0,onReady:e.events?.onReady,onFrame:e.events?.onFrame},onMaxAnisotropy:e.onMaxAnisotropy}}function Mt(e){let{scene:n,renderer:t,lights:r,config:i,pipeline:o,requestRender:a}=e,s=i.look,l=u=>{u.ambientIntensity!==void 0&&(r.ambient.intensity=u.ambientIntensity),u.hemisphereIntensity!==void 0&&!r.hemisphere&&u.hemisphereIntensity>0&&(r.hemisphere=new wt.HemisphereLight(u.hemisphereSkyColor??i.lighting.hemisphereSkyColor,u.hemisphereGroundColor??i.lighting.hemisphereGroundColor,u.hemisphereIntensity),r.hemisphere.position.copy(i.environment.sceneUp??W),n.add(r.hemisphere)),r.hemisphere&&(u.hemisphereIntensity!==void 0&&(r.hemisphere.intensity=u.hemisphereIntensity),u.hemisphereSkyColor!==void 0&&r.hemisphere.color.set(u.hemisphereSkyColor),u.hemisphereGroundColor!==void 0&&r.hemisphere.groundColor.set(u.hemisphereGroundColor)),a()},m=u=>{i.environment.environmentIntensity=u,n.environmentIntensity=u,a()};return{setFillLights:l,setEnvironmentIntensity:m,setToneMappingExposure:u=>{i.render.toneMappingExposure=u,t.toneMappingExposure=u,o.get()&&o.rebuild()},setAoIntensity:u=>{i.render.aoIntensity=u,o.get()&&o.rebuild()},setLook:u=>{let p=Ee[u];s=u,t.toneMapping=p.toneMapping,t.toneMappingExposure=p.toneMappingExposure,i.render.toneMapping=p.toneMapping,i.render.toneMappingExposure=p.toneMappingExposure,l({hemisphereIntensity:p.hemisphereIntensity,ambientIntensity:p.ambientIntensity}),m(p.environmentIntensity);let h=o.get()!==null;o.setAmbientOcclusion(p.ambientOcclusion),h&&o.rebuild(),n.traverse(C=>{if(C.userData.source!=="compute")return;let b=C,A=Array.isArray(b.material)?b.material:b.material?[b.material]:[];for(let S of A)"envMapIntensity"in S&&(S.envMapIntensity=p.envMapIntensity)}),a()},getMaterialAppearance:()=>Oe(s)}}import*as St from"three";function Dt(e,n){let t=n.parentElement,r=t?t.clientWidth:window.innerWidth,i=t?t.clientHeight:window.innerHeight,o=new St.PerspectiveCamera(e.camera.fov,r/i,e.camera.near,e.camera.far),a=e.camera.position;return a&&o.position.set(a.x,a.y,a.z),o}import*as De from"three";function At(e){let n=new De.Scene,t=typeof e.environment.backgroundColor=="string"?new De.Color(e.environment.backgroundColor):e.environment.backgroundColor;return n.background=t||null,n}import*as Pt from"three";function Lt(e,n){se(e,n),Fe(e.environment??void 0)&&e.environment?.dispose(),e.background instanceof Pt.Texture&&Fe(e.background)&&e.background.dispose()}import*as Ft from"three";import{EffectComposer as en}from"three/addons/postprocessing/EffectComposer.js";import{RenderPass as tn}from"three/addons/postprocessing/RenderPass.js";import{GTAOPass as rn}from"three/addons/postprocessing/GTAOPass.js";import{SMAAPass as nn}from"three/addons/postprocessing/SMAAPass.js";import{OutputPass as on}from"three/addons/postprocessing/OutputPass.js";import*as O from"three";import{Pass as Qr,FullScreenQuad as Jr}from"three/addons/postprocessing/Pass.js";var Ne={uniforms:{tDiffuse:{value:null},tNormal:{value:null},tDepth:{value:null},uResolution:{value:new O.Vector2(1,1)},uColor:{value:new O.Color(2236962)},uOpacity:{value:1},uNormalThreshold:{value:.4},uDepthThreshold:{value:.02},uThickness:{value:1},uNear:{value:.1},uFar:{value:1e3},uPerspective:{value:1}},vertexShader:`
56
+ varying vec2 vUv;
57
+ void main() {
58
+ vUv = uv;
59
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
60
+ }
61
+ `,fragmentShader:`
62
+ uniform sampler2D tDiffuse;
63
+ uniform sampler2D tNormal;
64
+ uniform sampler2D tDepth;
65
+ uniform vec2 uResolution;
66
+ uniform vec3 uColor;
67
+ uniform float uOpacity;
68
+ uniform float uNormalThreshold;
69
+ uniform float uDepthThreshold;
70
+ uniform float uThickness;
71
+ uniform float uNear;
72
+ uniform float uFar;
73
+ uniform float uPerspective;
74
+ varying vec2 vUv;
75
+
76
+ float viewZOf(const in float depth) {
77
+ float perspective = (uNear * uFar) / ((uFar - uNear) * depth - uFar);
78
+ float orthographic = -(depth * (uFar - uNear) + uNear);
79
+ return mix(orthographic, perspective, uPerspective);
80
+ }
81
+
82
+ void main() {
83
+ vec4 color = texture2D(tDiffuse, vUv);
84
+ vec2 texel = uThickness / uResolution;
85
+
86
+ vec2 offsetA = vec2(texel.x, texel.y);
87
+ vec2 offsetB = vec2(texel.x, -texel.y);
88
+
89
+ float z0 = viewZOf(texture2D(tDepth, vUv + offsetA).x);
90
+ float z1 = viewZOf(texture2D(tDepth, vUv - offsetA).x);
91
+ float z2 = viewZOf(texture2D(tDepth, vUv + offsetB).x);
92
+ float z3 = viewZOf(texture2D(tDepth, vUv - offsetB).x);
93
+ float zCenter = viewZOf(texture2D(tDepth, vUv).x);
94
+ // Normalized by center depth, not absolute Z: keeps the response scale-invariant across
95
+ // the viewer's mm-to-m scenes (an absolute threshold would be noise far away, blind up close).
96
+ float depthDelta = (abs(z0 - z1) + abs(z2 - z3)) / max(abs(zCenter), 1e-6);
97
+ float depthEdge = step(uDepthThreshold, depthDelta);
98
+
99
+ vec3 n0 = texture2D(tNormal, vUv + offsetA).rgb * 2.0 - 1.0;
100
+ vec3 n1 = texture2D(tNormal, vUv - offsetA).rgb * 2.0 - 1.0;
101
+ vec3 n2 = texture2D(tNormal, vUv + offsetB).rgb * 2.0 - 1.0;
102
+ vec3 n3 = texture2D(tNormal, vUv - offsetB).rgb * 2.0 - 1.0;
103
+ float normalDelta = (1.0 - dot(n0, n1)) + (1.0 - dot(n2, n3));
104
+ float normalEdge = step(uNormalThreshold, normalDelta);
105
+
106
+ float edge = max(depthEdge, normalEdge) * uOpacity;
107
+ gl_FragColor = vec4(mix(color.rgb, uColor, edge), color.a);
108
+ }
109
+ `},Ae=class extends Qr{constructor(t,r,i,o,a={}){super();X(this,"camera");X(this,"scene");X(this,"normalMaterial");X(this,"edgeMaterial");X(this,"fsQuad");X(this,"normalTarget",null);X(this,"width");X(this,"height");this.scene=t,this.camera=r,this.width=Math.max(1,i),this.height=Math.max(1,o),this.normalMaterial=new O.MeshNormalMaterial,this.normalMaterial.blending=O.NoBlending,this.edgeMaterial=new O.ShaderMaterial({uniforms:O.UniformsUtils.clone(Ne.uniforms),vertexShader:Ne.vertexShader,fragmentShader:Ne.fragmentShader});let s=this.edgeMaterial.uniforms;s.uColor.value=new O.Color(a.color??2236962),s.uOpacity.value=a.opacity??1,s.uNormalThreshold.value=a.normalThreshold??.4,s.uDepthThreshold.value=a.depthThreshold??.02,s.uThickness.value=a.thickness??1,this.fsQuad=new Jr(this.edgeMaterial),this.needsSwap=!0}acquireNormalTarget(){if(!this.normalTarget){let t=new O.DepthTexture(this.width,this.height);this.normalTarget=new O.WebGLRenderTarget(this.width,this.height,{minFilter:O.NearestFilter,magFilter:O.NearestFilter,depthTexture:t})}return this.normalTarget}setSize(t,r){this.width=Math.max(1,t),this.height=Math.max(1,r),this.normalTarget?.setSize(this.width,this.height)}render(t,r,i){let o=this.acquireNormalTarget(),a=t.getRenderTarget(),s=t.autoClear,l=t.getClearColor(new O.Color),m=t.getClearAlpha(),f=this.scene.overrideMaterial;t.setRenderTarget(o),t.setClearColor(7829503,1),t.autoClear=!0,this.scene.overrideMaterial=this.normalMaterial,t.render(this.scene,this.camera),this.scene.overrideMaterial=f,t.setClearColor(l,m),t.autoClear=s;let E=this.edgeMaterial.uniforms;E.tDiffuse.value=i.texture,E.tNormal.value=o.texture,E.tDepth.value=o.depthTexture,E.uResolution.value.set(this.width,this.height);let g=this.camera;E.uPerspective.value=g.isPerspectiveCamera?1:0,E.uNear.value=this.camera.near??.1,E.uFar.value=this.camera.far??1e3,t.setRenderTarget(this.renderToScreen?null:r),this.fsQuad.render(t),t.setRenderTarget(a)}dispose(){this.normalTarget?.dispose(),this.normalMaterial.dispose(),this.edgeMaterial.dispose(),this.fsQuad.dispose()}};function Ot(e,n,t,r,i,o){let a=new en(e),s=new tn(n,t);a.addPass(s);let l=null;(o.ambientOcclusion??!0)&&(l=new rn(n,t,r,i),l.blendIntensity=o.aoIntensity??1,l.updateGtaoMaterial({screenSpaceRadius:!0}),a.addPass(l));let m=typeof o.edgeDetection=="object"?o.edgeDetection:{},f=new Ae(n,t,r,i,m);f.enabled=!!o.edgeDetection,a.addPass(f);let E=new nn;a.addPass(E);let g=new on;a.addPass(g),e.toneMapping=o.toneMapping,e.toneMappingExposure=o.toneMappingExposure;let u=o.aoPixelRatio??1;return a.setSize(r,i),{render:p=>a.render(p),setSize:(p,h,C)=>{a.setPixelRatio(Math.min(C,u)),a.setSize(p,h)},setCamera:p=>{if(s.camera=p,f.camera=p,!l)return;l.camera=p;let h=p.isPerspectiveCamera?1:0;l.gtaoMaterial.defines.PERSPECTIVE_CAMERA!==h&&(l.gtaoMaterial.defines.PERSPECTIVE_CAMERA=h,l.gtaoMaterial.needsUpdate=!0)},setEdgeDetection:p=>{f.enabled=p},edgeDetectionEnabled:()=>f.enabled,dispose:()=>{a.dispose(),l?.dispose(),f.dispose(),E.dispose(),g.dispose()}}}function It(e){let{renderer:n,scene:t,getActiveCamera:r,getCanvasSize:i,pixelRatio:o,config:a,requestRender:s}=e,l=null,m=!!a.render.ambientOcclusion,f=!1,E=!1,g=p=>{let{width:h,height:C}=i(),b=Ot(n,t,r(),Math.max(1,h),Math.max(1,C),{toneMapping:a.render.toneMapping??Ft.NeutralToneMapping,toneMappingExposure:a.render.toneMappingExposure??1,ambientOcclusion:p,aoIntensity:a.render.aoIntensity,aoPixelRatio:a.render.aoPixelRatio,edgeDetection:!1});return b.setSize(Math.max(1,h),Math.max(1,C),o),b},u=()=>{if(!(m||f)){l?.dispose(),l=null,s();return}(!l||E!==m)&&(l?.dispose(),l=g(m),E=m),l.setEdgeDetection(f),s()};return{get:()=>l,sync:u,rebuild:()=>{l?.dispose(),l=null,u()},setAmbientOcclusion:p=>{m=p,u()},setEdgeFallback:p=>{p!==f&&(f=p,u())},isEdgeFallbackActive:()=>f,dispose:()=>{l?.dispose(),l=null}}}import{OrbitControls as an}from"three/addons/controls/OrbitControls.js";function kt(e,n,t){let r=new an(e,n),i=t.camera.target;return i&&r.target.set(i.x,i.y,i.z),r.enableDamping=t.controls.enableDamping||!1,r.dampingFactor=t.controls.dampingFactor||.05,r.autoRotate=t.controls.autoRotate||!1,r.autoRotateSpeed=t.controls.autoRotateSpeed||.5,r.enableZoom=t.controls.enableZoom??!0,r.enablePan=t.controls.enablePan??!0,r.minDistance=t.controls.minDistance||.001,r.maxDistance=t.controls.maxDistance||1/0,r.screenSpacePanning=!1,r.maxPolarAngle=Math.PI,r.update(),r}import*as N from"three";import{HDRLoader as sn}from"three/addons/loaders/HDRLoader.js";function zt(e,n,t,r){t.environment.enableEnvironmentLighting?new sn().load(t.environment.hdrPath||"/baseHDR.hdr",function(i){if(r()){i.dispose();return}if(!i?.image){me().warn("HDR loaded without image data; skipping environment map."),i?.dispose(),t.events.onReady?.();return}i.mapping=N.EquirectangularReflectionMapping;let o=new N.PMREMGenerator(n);o.compileEquirectangularShader();let a=o.fromEquirectangular(i).texture;o.dispose(),e.environment=a,e.environmentIntensity=t.environment.environmentIntensity??1;let s=Xe(t.environment.sceneUp??W);e.environmentRotation.copy(s),t.environment.showEnvironment?(e.background=i,e.backgroundRotation.copy(s)):i.dispose(),t.events.onReady?.()},void 0,function(i){r()||(me().warn("HDR texture could not be loaded, falling back to basic lighting:",i),t.events.onReady?.())}):t.events.onReady?.()}function _t(e,n){let t=n.floor.size,r=new N.PlaneGeometry(t,t),i=typeof n.floor.color=="string"?new N.Color(n.floor.color):n.floor.color,o=new N.MeshStandardMaterial({color:i,roughness:n.floor.roughness,metalness:n.floor.metalness,side:N.DoubleSide}),a=new N.Mesh(r,o);a.userData.id="floor",a.name="floor";let s=(n.environment?.sceneUp||W).clone().normalize();a.quaternion.setFromUnitVectors(new N.Vector3(0,0,1),s),a.position.set(0,0,0),n.floor.receiveShadow&&n.render.enableShadows&&(a.receiveShadow=!0),e.add(a)}import*as F from"three";function Vt(e,n,t,r){let i=new Set,o=new Map,a=new F.Raycaster,s=new F.Vector2,l=new F.Vector2,m=()=>t.getActiveCamera(),f=d=>{let c=d;for(;c;){if(!c.visible)return!1;c=c.parent}return!0},E=()=>{let d=Q(n);if(d.isEmpty()){me().warn("No objects to fit to view");return}t.frameBounds(d,!1)},g=typeof r.events.selectionColor=="string"?new F.Color(r.events.selectionColor):r.events.selectionColor instanceof F.Color?r.events.selectionColor:new F.Color("#ff0000"),u=()=>{i.forEach(d=>{let c=d;if(o.has(d)){let R=o.get(d),T=c.material;T instanceof F.Material?T.dispose():Array.isArray(T)&&T.forEach(M=>M.dispose()),c.material=R,o.delete(d);let x=d;for(;x.parent;)x=x.parent;x!==n&&(R instanceof F.Material?R.dispose():R.forEach(M=>M.dispose()))}}),i.clear()},p=d=>{let c=d;if(!(c.material instanceof F.Material))return!1;o.set(d,c.material);let R=c.material.clone();return d instanceof F.Mesh&&"emissive"in R?R.emissive=g.clone():"color"in R&&(R.color=g.clone()),c.material=R,!0},h=()=>{let d=Q(n),c=d.isEmpty()?1:d.getSize(new F.Vector3).length();a.params.Points.threshold=c*.01},C=d=>{l.set(d.clientX,d.clientY)},b=d=>{let c=new F.Vector2(d.clientX,d.clientY);if(l.distanceTo(c)>5)return;let R=e.getBoundingClientRect();s.x=(d.clientX-R.left)/R.width*2-1,s.y=-((d.clientY-R.top)/R.height)*2+1,h(),a.setFromCamera(s,m());let T=a.intersectObjects(n.children,!0).filter(x=>f(x.object));if(T.length>0){let x=T[0].object;i.has(x)||(u(),i.add(x),p(x),r.events?.onObjectSelected?.(x),x instanceof F.Mesh&&Object.keys(x.userData).length>0&&r.events?.onMeshMetadataClicked?.(x.userData))}else u(),r.events?.onBackgroundClicked?.({x:s.x,y:s.y})},A=d=>{let c=e.getBoundingClientRect();s.x=(d.clientX-c.left)/c.width*2-1,s.y=-((d.clientY-c.top)/c.height)*2+1,h(),a.setFromCamera(s,m());let R=a.intersectObjects(n.children,!0).filter(M=>f(M.object));if(R.length===0)return;let T=R[0].object;if(r.events?.onMeshDoubleClicked?.(T),!r.events?.enableDoubleClickZoom)return;let x=new F.Box3().setFromObject(T);x.isEmpty()||t.frameBounds(x,!0)},S=d=>{if(r.events?.enableKeyboardControls)switch(d.key.toLowerCase()){case"f":d.preventDefault(),E();break;case"escape":d.preventDefault(),u();break;case" ":d.preventDefault(),E();break}};return r.events?.enableClickToFocus&&(e.addEventListener("mousedown",C),e.addEventListener("click",b),e.addEventListener("dblclick",A)),r.events?.enableKeyboardControls&&(e.setAttribute("tabindex","0"),e.addEventListener("keydown",S)),{dispose:()=>{e.removeEventListener("mousedown",C),e.removeEventListener("click",b),e.removeEventListener("dblclick",A),e.removeEventListener("keydown",S),u()},fitToView:E,clearSelection:u}}import*as te from"three";function jt(e,n){let t=new te.AmbientLight(n.lighting.ambientLightColor,n.lighting.ambientLightIntensity);e.add(t);let r=null;if(n.lighting.enableHemisphereLight){r=new te.HemisphereLight(n.lighting.hemisphereSkyColor,n.lighting.hemisphereGroundColor,n.lighting.hemisphereIntensity);let a=n.environment.sceneUp??W;r.position.copy(a),e.add(r)}if(!n.lighting.enableSunlight)return{ambient:t,hemisphere:r,sun:null};let i=new te.DirectionalLight(n.lighting.sunlightColor??16777215,n.lighting.sunlightIntensity),o=n.lighting.sunlightPosition;return o&&i.position.set(o.x,o.y,o.z),n.render.enableShadows?(i.castShadow=!0,i.shadow.mapSize.width=n.render.shadowMapSize||2048,i.shadow.mapSize.height=n.render.shadowMapSize||2048,i.shadow.bias=-1e-4,i.shadow.normalBias=.02,i.shadow.radius=4,e.add(i),e.add(i.target),{ambient:t,hemisphere:r,sun:i}):(e.add(i),{ambient:t,hemisphere:r,sun:null})}function Nt(e,n){if(n.isEmpty())return;let t=n.getCenter(new te.Vector3),r=n.getSize(new te.Vector3).length()*.5*1.2,i=e.shadow.camera;i.left=-r,i.right=r,i.top=r,i.bottom=-r,e.target.position.copy(t),e.target.updateMatrixWorld();let o=e.position.distanceTo(t);i.near=Math.max(r*.01,o-r),i.far=o+r,i.updateProjectionMatrix()}import*as pe from"three";function Gt(e,n,t){let r=new pe.WebGLRenderer({antialias:n.render.antialias,canvas:e,alpha:!0,powerPreference:"high-performance",preserveDrawingBuffer:n.render.preserveDrawingBuffer,logarithmicDepthBuffer:!1}),i=e.parentElement,o=i?i.clientWidth:window.innerWidth,a=i?i.clientHeight:window.innerHeight;return i&&(e.style.width="100%",e.style.height="100%",e.style.display="block"),r.setSize(o,a,!1),r.setPixelRatio(t),n.render.enableShadows&&(r.shadowMap.enabled=!0,r.shadowMap.type=pe.VSMShadowMap),r.toneMapping=n.render.toneMapping,r.toneMappingExposure=n.render.toneMappingExposure??1,r.outputColorSpace=pe.SRGBColorSpace,r.sortObjects=!0,r}var ln=function(e,n){let t=Ct(n||{}),r=t.environment?.sceneUp||W,i=t.render.pixelRatio??Math.min(window.devicePixelRatio,2),o=At(t),a=Dt(t,e);a.up.copy(r);let s=Gt(e,t,i);We(s.capabilities.getMaxAnisotropy()),n?.onMaxAnisotropy?.(s.capabilities.getMaxAnisotropy());let l=Be(),m=kt(a,e,t),f=Ke({scene:o,perspective:a,controls:m,onActiveCameraChange:()=>{},up:r}),E=()=>f.getActiveCamera(),g=!1;zt(o,s,t,()=>g);let u=jt(o,t),p=u.sun,h=()=>{p&&Nt(p,Q(o))};t.floor?.enabled&&_t(o,t);let C=t.floor?.enabled?o.children.find(H=>H.userData.id==="floor")??null:null,b=t.grid.enabled?ht({cellSize:t.grid.cellSize,majorEvery:t.grid.majorEvery,cellColor:t.grid.cellColor,majorColor:t.grid.majorColor,fadeDistance:t.grid.fadeDistance,plane:t.grid.plane}):null;b&&o.add(b.object);let A=()=>{b&&b.fitToContent(Q(o))},S=t.gizmo.enabled?xt({camera:a,domElement:e,controller:f}):null,I=t.grid.plane??xe(r),d=new Ut.Vector3(I==="x"?1:0,I==="y"?1:0,I==="z"?1:0),c=r.clone().normalize(),R=()=>{let H=[];return b?.object.visible&&H.push(d),t.floor.enabled&&C?.visible&&H.push(c),H},T=t.camera.dynamicNear?yt({camera:a,scene:o,groundNormals:R}):null,x=e.parentElement??e,M=t.measure.enabled?Rt(x,o):null,P=t.measure.enabled&&M?vt({canvas:e,scene:o,getActiveCamera:E,labelLayer:M,options:{snapPixels:t.measure.snapPixels,color:t.measure.color,labelClassName:t.measure.labelClassName,displayUnit:t.measure.displayUnit,format:t.measure.format}}):null,L=t.events.enableEventHandlers!==!1?Vt(e,o,f,t):{dispose:()=>{},fitToView:()=>{},clearSelection:()=>{}},Y=5,w=0,k=0,v=H=>{w=H.clientX,k=H.clientY},y=H=>Math.hypot(H.clientX-w,H.clientY-k)>Y,z=H=>{if(!y(H)){if(P?.handleClick(H)){H.stopImmediatePropagation();return}S?.handleClick(H)&&H.stopImmediatePropagation()}};(S||P)&&(e.addEventListener("mousedown",v,{capture:!0}),e.addEventListener("click",z,{capture:!0}));let j=H=>P?.handleMove(H);P&&e.addEventListener("mousemove",j,{passive:!0});let V=()=>{},G=H=>{Et(H,{color:t.edges.color,darken:t.edges.darken,width:t.edges.width,thresholdAngle:t.edges.thresholdAngle,distanceFade:t.edges.distanceFade,maxTriangles:t.edges.maxTriangles,maxSegments:t.edges.maxSegments}).then(()=>{be(H),V()})},be=H=>{if(t.edges.screenSpaceFallback===!1)return;let ae=!1;H.traverse(Xt=>{Xt.userData?.edgesSkipped===we&&(ae=!0)}),U.setEdgeFallback(ae)},Z=H=>{ft(H),U.setEdgeFallback(!1),V()},ee=e.parentElement,re=()=>ee?{width:ee.clientWidth,height:ee.clientHeight}:{width:window.innerWidth,height:window.innerHeight},U=It({renderer:s,scene:o,getActiveCamera:E,getCanvasSize:re,pixelRatio:i,config:t,requestRender:()=>V()});U.sync();let q=Mt({scene:o,renderer:s,lights:u,config:t,pipeline:U,requestRender:()=>V()}),{animate:ve,dispose:ie,invalidate:ue}=Ht(s,o,a,E,f,m,re,i,t.events.onFrame,b,S,()=>U.get(),M,T,t.render.onDemand??!0);V=ue,ve(),o.up.set(r.x,r.y,r.z),h(),A();let Pe=H=>{H.userData.source="user",o.add(H)},Bt=H=>{H.removeFromParent(),se(H)},Wt=()=>{o.children.filter(ae=>ae.userData.source==="user").forEach(ae=>{ae.removeFromParent(),se(ae)})},qt=()=>{g||(g=!0,ie(),L.dispose(),(S||P)&&(e.removeEventListener("mousedown",v,{capture:!0}),e.removeEventListener("click",z,{capture:!0})),P&&e.removeEventListener("mousemove",j),P?.dispose(),M?.dispose(),S?.dispose(),b?.dispose(),U.dispose(),f.dispose(),m.dispose(),s.dispose(),s.forceContextLoss(),Lt(o),l())};return{scene:o,camera:a,controls:m,renderer:s,cameraController:f,grid:b,gizmo:S,measureTool:P,applyEdges:G,clearEdges:Z,invalidate:ue,setAmbientOcclusion:U.setAmbientOcclusion,setLook:q.setLook,setFillLights:q.setFillLights,setEnvironmentIntensity:q.setEnvironmentIntensity,setToneMappingExposure:q.setToneMappingExposure,setAoIntensity:q.setAoIntensity,getMaterialAppearance:q.getMaterialAppearance,updateShadowBounds:h,updateGridScale:A,dispose:qt,fitToView:L.fitToView,clearSelection:L.clearSelection,addUserGeometry:Pe,removeUserGeometry:Bt,clearUserGeometry:Wt}};export{Le as DEFAULT_LOOK,Kt as ErrorCodes,Qt as LOOKS,Ee as LOOK_PRESETS,Yt as VisualizationError,$t as enableDebugLogging,me as getLogger,ln as initThree,Oe as materialAppearanceForLook,Zt as setLogger,Jt as updateScene};
110
+ //# sourceMappingURL=render.js.map