three-cad-viewer 4.3.9 → 5.0.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.
@@ -314,7 +314,7 @@ The keymap serves two purposes:
314
314
  - **Modifier keys** (`shift`, `ctrl`, `meta`, `alt`) remap which physical modifier key is used for mouse interactions (e.g. shift-click to isolate, ctrl-rotate). Values are DOM event properties like `"shiftKey"`, `"ctrlKey"`, `"metaKey"`, `"altKey"`.
315
315
  - **Action shortcuts** map single keys (with or without Shift) to toolbar buttons, camera presets, tab switches, and animation control. Only plain keys are supported — Ctrl/Alt/Meta combinations are reserved for modifier-based mouse interactions.
316
316
 
317
- Click on the viewer to give it focus, then press shortcut keys to trigger actions. Button tooltips show `[key]` suffixes when shortcuts are configured.
317
+ Click on the viewer to give it focus, then press shortcut keys to trigger actions. Button and tab tooltips show a `› key` suffix when shortcuts are configured.
318
318
 
319
319
  The default keymap:
320
320
 
@@ -323,19 +323,19 @@ keymap: {
323
323
  // Modifier keys (remap physical keys for mouse interactions)
324
324
  shift: "shiftKey", ctrl: "ctrlKey", meta: "metaKey", alt: "altKey",
325
325
  // Toggle buttons
326
- axes: "a", axes0: "A", grid: "g", gridxy: "G",
326
+ axes: "A", axes0: "0", grid: "g", gridxy: "G",
327
327
  perspective: "p", transparent: "t", blackedges: "b",
328
328
  explode: "x", zscale: "L",
329
- distance: "D", properties: "P", select: "S",
329
+ distance: "D", properties: "P", select: "I",
330
330
  // Execute buttons
331
331
  reset: "R", resize: "r",
332
- iso: "0", front: "1", rear: "2", top: "3", bottom: "4", left: "5", right: "6",
332
+ iso: "5", front: "1", rear: "3", top: "8", bottom: "2", left: "4", right: "6",
333
333
  // Help
334
334
  help: "h",
335
335
  // Animation
336
336
  play: " ", stop: "Escape",
337
337
  // Tab selection
338
- tree: "T", clip: "C", material: "M", zebra: "Z",
338
+ tree: "T", clip: "C", material: "M", zebra: "Z", studio: "S",
339
339
  }
340
340
  ```
341
341
 
@@ -347,6 +347,8 @@ const displayOptions = {
347
347
  };
348
348
  ```
349
349
 
350
+ **Topo filter shortcuts.** When the shape filter is visible (B-rep models), the lowercase keys `a` (All), `v` (Vertex), `e` (Edge), `f` (Face), `s` (Solid) set the picking filter. These are fixed (not part of the configurable keymap) — which is why `axes` and `studio` default to the uppercase `A`/`S`.
351
+
350
352
  ## Examples
351
353
 
352
354
  To understand the data format, a look at the simple 1 unit sized box might be helpful:
@@ -0,0 +1,125 @@
1
+ import * as THREE from "three";
2
+ import { type IdPicker } from "../rendering/id-picking.js";
3
+ import { type PickedComponent } from "../rendering/picked.js";
4
+ import type { Shapes } from "./types.js";
5
+ import type { RenderedState } from "./viewer.js";
6
+ import type { Display } from "../ui/display.js";
7
+ import type { Tools } from "../tools/cad_tools/tools.js";
8
+ /**
9
+ * The narrow surface {@link PickingController} needs from its host (the Viewer).
10
+ * Keeps the controller decoupled from the rest of the Viewer god-object and
11
+ * unit-testable. `rendered` is a lazy getter that throws before render() — the
12
+ * controller must always read through it (never cache `nestedGroup`/`highlight`,
13
+ * which are rebuilt every render()).
14
+ */
15
+ export interface PickHost {
16
+ readonly idPicker: IdPicker | null;
17
+ readonly ready: boolean;
18
+ readonly hasAnimationLoop: boolean;
19
+ /** True while the Studio (presentation) tab owns the render. */
20
+ readonly studioActive: boolean;
21
+ readonly shapes: Shapes | null;
22
+ readonly renderer: THREE.WebGLRenderer;
23
+ readonly rendered: RenderedState;
24
+ readonly display: Display;
25
+ readonly cadTools: Tools;
26
+ update(updateMarker: boolean, notify: boolean): void;
27
+ /** Restore the most recently meta-double-click-hidden leaf (hide-undo stack). */
28
+ showLastHidden(): void;
29
+ handlePick(path: string, name: string, meta: boolean, shift: boolean, alt: boolean, point: THREE.Vector3 | null, nodeType?: string | null, tree?: boolean): void;
30
+ }
31
+ /**
32
+ * Owns all pointer-driven picking on the compact graph: hover preselection
33
+ * (highlight + status line), left-click selection commit, right-click/key removal,
34
+ * and double-click pick. Hover proposes the component under the cursor
35
+ * ({@link lastObject}); a left-click commits it ({@link lastSelection}) — one state
36
+ * machine, so both fields live here.
37
+ *
38
+ * Listener lifecycle: hover (pointermove/leave) is always-on and added at
39
+ * construction; selection (mousedown/mouseup + document keydown) is tool-scoped via
40
+ * {@link setSelectionInput}; double-click via {@link setPickHandler}. {@link dispose}
41
+ * tears them all down.
42
+ */
43
+ export declare class PickingController {
44
+ private readonly host;
45
+ /** Component under the cursor (hover); committed on left-click. */
46
+ lastObject: PickedComponent | null;
47
+ /** Last committed selection. */
48
+ lastSelection: PickedComponent | null;
49
+ private idHoverClientX;
50
+ private idHoverClientY;
51
+ private idHoverInside;
52
+ private idHoverRenderQueued;
53
+ /** Per-component hover status text (fixed per mesh; cleared on reload / z-scale). */
54
+ private hoverStatusCache;
55
+ private selectionInputActive;
56
+ private selectDownPosition;
57
+ private pickHandlerActive;
58
+ constructor(host: PickHost);
59
+ /** Remove every listener this controller owns. */
60
+ dispose(): void;
61
+ /** Record the cursor position over the canvas. */
62
+ private onIdHoverMove;
63
+ /** Cursor left the canvas → clear any hover highlight. */
64
+ private onIdHoverLeave;
65
+ /**
66
+ * Whether hover preselection (highlight + status line) is active. Disabled for GDS:
67
+ * dense, stacked, instance-unrolled layout data where per-pixel hover flickers
68
+ * endlessly and the B-rep readout ("area ≈ …") is meaningless, so GDS is
69
+ * double-click-identify only. Also disabled in Studio (presentation) mode: hover
70
+ * tint/status is a CAD/analysis affordance, not wanted in Studio, and skipping it
71
+ * means the id buffer is never re-rendered for a bare mouse-move there.
72
+ */
73
+ private hoverPreselectActive;
74
+ /**
75
+ * Called once per render: drive hover preselection unless a model is GDS or the
76
+ * camera is being dragged.
77
+ */
78
+ handleHover(): void;
79
+ /**
80
+ * Hover via the GPU id picker on the compact graph: resolve the component under the
81
+ * cursor and drive the shader `HighlightController`. Sets {@link lastObject}
82
+ * (committed on left-click by {@link commitSelection} when a tool is active).
83
+ */
84
+ private handleIdHover;
85
+ /** Drop the cached hover texts (e.g. on z-scale change — world lengths change). */
86
+ invalidateHoverCache(): void;
87
+ /**
88
+ * Drop any lingering hover highlight + status line (keeping the committed selection).
89
+ * Called when entering Studio mode: hover preselection is disabled there, so the
90
+ * per-render {@link handleHover} no longer runs its own release and a stale tint/status
91
+ * from CAD mode would otherwise persist.
92
+ */
93
+ clearHover(): void;
94
+ /**
95
+ * Whether the component a pick resolved to is currently visible — used to drop picks
96
+ * of hidden geometry. Visibility is the owning group's material `visible` flag
97
+ * (faces for solids/standalone faces; edge/vertex materials for standalone leaves).
98
+ */
99
+ private pickVisible;
100
+ clearSelection: () => void;
101
+ private releaseLastSelected;
102
+ removeLastSelected(): void;
103
+ /** Drop selection state + hover cache + status line (on model reload). */
104
+ reset(): void;
105
+ /**
106
+ * Add/remove the canvas mousedown+mouseup and document keydown listeners
107
+ * (idempotent, guarded on `selectionInputActive`). Hover maintains
108
+ * {@link lastObject}; these handlers commit it on left-click and handle the key +
109
+ * right-click actions.
110
+ */
111
+ setSelectionInput(flag: boolean): void;
112
+ private onSelectMouseDown;
113
+ private onSelectMouseUp;
114
+ private onSelectKeyDown;
115
+ private commitSelection;
116
+ setPickHandler(flag: boolean): void;
117
+ /**
118
+ * Double-click pick via the GPU id picker: `idPicker.pickAt` resolves the component
119
+ * under the cursor, the registry gives its owning tree-leaf path ({@link leafPath}),
120
+ * and the readback world-space `point` feeds `handlePick` (the `shift && meta` camera
121
+ * target, with a bbox-center fallback when `point` is null). No topo filter: a
122
+ * double-click selects whatever is under the cursor and resolves it to its leaf.
123
+ */
124
+ private onDoubleClick;
125
+ }
@@ -185,8 +185,12 @@ export interface DisplayOptions {
185
185
  zebraTool?: boolean;
186
186
  /** Show studio tool (default: true) */
187
187
  studioTool?: boolean;
188
- /** Enable measurement debug mode (default: false) */
189
- measurementDebug?: boolean;
188
+ /**
189
+ * Use an external (Python/`ocp_vscode`) measurement backend (default: false).
190
+ * When false, the built-in TypeScript mesh-based measurement backend answers
191
+ * measurements locally — so measure works without a backend connection.
192
+ */
193
+ externalMeasurementBackend?: boolean;
190
194
  /** External canvas element to use for the WebGL renderer, enabling shared WebGL context scenarios (default: undefined — renderer creates its own canvas) */
191
195
  canvas?: HTMLCanvasElement;
192
196
  /** External WebGL context to use for the renderer. When provided together with `canvas`, the renderer will use this context instead of creating a new one. Useful for sharing a context with other renderers like PixiJS. (default: undefined) */
@@ -338,7 +342,7 @@ export interface ViewerStateShape {
338
342
  zscaleTool: boolean;
339
343
  zebraTool: boolean;
340
344
  studioTool: boolean;
341
- measurementDebug: boolean;
345
+ externalMeasurementBackend: boolean;
342
346
  ambientIntensity: number;
343
347
  directIntensity: number;
344
348
  metalness: number;
@@ -501,9 +505,12 @@ export interface MaterialAppearance {
501
505
  *
502
506
  * This format is produced by the threejs-materials Python library, which catalogs
503
507
  * PBR materials from ambientCG, GPUOpen, PolyHaven, and PhysicallyBased.
504
- * `values` contains scalar properties (e.g., color as linear RGB array,
505
- * roughness as float). `textures` contains texture references (inline data URIs
506
- * or file paths) keyed by property name.
508
+ * `values` contains scalar properties (e.g., color as an sRGB RGB array,
509
+ * roughness as float). Note the per-key color-space convention: `color` is
510
+ * sRGB-stored, while `emissive`/`specularColor`/`sheenColor`/`attenuationColor`
511
+ * are linear-stored (see MaterialFactory.createStudioMaterialFromMaterialX).
512
+ * `textures` contains texture references (inline data URIs or file paths)
513
+ * keyed by property name.
507
514
  *
508
515
  * Detected by the presence of the `values` key.
509
516
  * Extra keys from threejs-materials (id, name, source, url, license) pass through
@@ -20,7 +20,7 @@ interface DisplayDefaults {
20
20
  zscaleTool: boolean;
21
21
  zebraTool: boolean;
22
22
  studioTool: boolean;
23
- measurementDebug: boolean;
23
+ externalMeasurementBackend: boolean;
24
24
  }
25
25
  /**
26
26
  * Render configuration defaults
@@ -19,7 +19,10 @@ import { Controls } from "../camera/controls.js";
19
19
  import { Camera, type CameraDirection } from "../camera/camera.js";
20
20
  import { BoundingBox, BoxHelper } from "../scene/bbox.js";
21
21
  import { Tools } from "../tools/cad_tools/tools.js";
22
- import { PickedObject, Raycaster } from "../rendering/raycast.js";
22
+ import { MeshMeasureBackend } from "../tools/cad_tools/mesh-measure.js";
23
+ import { IdPicker } from "../rendering/id-picking.js";
24
+ import { type PickedComponent } from "../rendering/picked.js";
25
+ import { PickingController } from "./picking-controller.js";
23
26
  import { ViewerState } from "./viewer-state.js";
24
27
  import type { Display } from "../ui/display.js";
25
28
  import type { Vector3Tuple, QuaternionTuple } from "three";
@@ -70,14 +73,6 @@ interface ImageResult {
70
73
  task: string;
71
74
  dataUrl: string | ArrayBuffer | null;
72
75
  }
73
- /**
74
- * Raycast event from keyboard or mouse.
75
- */
76
- interface RaycastEvent {
77
- key?: string;
78
- mouse?: "left" | "right";
79
- shift?: boolean;
80
- }
81
76
  /**
82
77
  * Backend response structure.
83
78
  */
@@ -90,7 +85,7 @@ interface BackendResponse {
90
85
  */
91
86
  interface DisplayOptionsInternal {
92
87
  measureTools?: boolean;
93
- measurementDebug?: boolean;
88
+ externalMeasurementBackend?: boolean;
94
89
  selectTool?: boolean;
95
90
  explodeTool?: boolean;
96
91
  zscaleTool?: boolean;
@@ -106,7 +101,7 @@ interface DisplayOptionsInternal {
106
101
  * State that exists only after render() and before clear().
107
102
  * Groups all resources that are created together during rendering.
108
103
  */
109
- interface RenderedState {
104
+ export interface RenderedState {
110
105
  scene: THREE.Scene;
111
106
  ambientLight: THREE.AmbientLight;
112
107
  directLight: THREE.DirectionalLight;
@@ -167,6 +162,12 @@ declare class Viewer {
167
162
  onAfterRender: (() => void) | null;
168
163
  mouse: THREE.Vector2;
169
164
  cadTools: Tools;
165
+ /**
166
+ * Internal mesh-based measurement backend, used when
167
+ * `externalMeasurementBackend === false` (the default). Resolves component paths
168
+ * against the compact group's {@link MeshGeometrySource}.
169
+ */
170
+ meshBackend: MeshMeasureBackend;
170
171
  animation: Animation;
171
172
  clipNormals: [THREE.Vector3, THREE.Vector3, THREE.Vector3];
172
173
  private _rendered;
@@ -183,7 +184,6 @@ declare class Viewer {
183
184
  private _pendingDisposal;
184
185
  shapes: Shapes | null;
185
186
  gridSize: number;
186
- private _previousGridSize;
187
187
  hasAnimationLoop: boolean;
188
188
  mixer: THREE.AnimationMixer | null;
189
189
  continueAnimation: boolean;
@@ -194,19 +194,24 @@ declare class Viewer {
194
194
  renderOptions: RenderOptions | null;
195
195
  lastNotification: Record<string, unknown>;
196
196
  lastBbox: LastBboxInfo | null;
197
- lastObject: PickedObject | null;
198
- lastSelection: PickedObject | null;
197
+ private _hiddenUndo;
199
198
  lastPosition: THREE.Vector3 | null;
200
199
  bboxNeedsUpdate: boolean;
201
200
  keepHighlight: boolean;
202
- expandedTree: ShapeTreeData | null;
203
201
  compactTree: ShapeTreeData | null;
204
- expandedNestedGroup: NestedGroup | null;
205
202
  compactNestedGroup: NestedGroup | null;
206
- raycaster: Raycaster | null;
203
+ private _lastPickCam;
204
+ private _lastPickProj;
205
+ private _lastClipSig;
206
+ private _suppressUpdate;
207
+ private _zebraSettingsApplied;
208
+ idPicker: IdPicker | null;
209
+ pickingController: PickingController;
207
210
  private _studioManager;
208
211
  /** Environment manager — proxied from StudioManager for display.ts access. */
209
212
  get envManager(): import("../index.js").EnvironmentManager;
213
+ /** True while the Studio (presentation) tab owns the render. PickHost member. */
214
+ get studioActive(): boolean;
210
215
  zScale: number;
211
216
  clipNormal0: Vector3Tuple | null;
212
217
  clipNormal1: Vector3Tuple | null;
@@ -251,11 +256,10 @@ declare class Viewer {
251
256
  private getShapeRenderer;
252
257
  /**
253
258
  * Render the shapes of the CAD object.
254
- * @param exploded - Whether to render the compact or exploded version
255
259
  * @param shapes - The Shapes object.
256
260
  * @returns A nested THREE.Group object and navigation tree.
257
261
  */
258
- renderTessellatedShapes(exploded: boolean, shapes: Shapes): RenderResult;
262
+ renderTessellatedShapes(shapes: Shapes): RenderResult;
259
263
  /**
260
264
  * Add a position animation track (full 3D translation).
261
265
  * @param selector - path/id of group to be animated.
@@ -342,7 +346,7 @@ declare class Viewer {
342
346
  * - WebGL renderer and context
343
347
  * - All Three.js objects (geometries, materials, textures)
344
348
  * - Event listeners
345
- * - CAD tools and raycaster
349
+ * - CAD tools and id picker
346
350
  *
347
351
  * After calling dispose(), the viewer instance should not be used.
348
352
  *
@@ -358,15 +362,6 @@ declare class Viewer {
358
362
  * @public
359
363
  */
360
364
  clear(): void;
361
- /**
362
- * Synchronizes the states of two tree structures recursively.
363
- *
364
- * @param compactTree - The compact tree structure.
365
- * @param expandedTree - The expanded tree structure.
366
- * @param exploded - Whether rendering in exploded mode.
367
- * @param path - The current path in the tree structure.
368
- */
369
- syncTreeStates: (compactTree: ShapeTreeData | VisibilityState, expandedTree: ShapeTreeData | VisibilityState, exploded: boolean, path: string) => void;
370
365
  /**
371
366
  * Get the color of a node from its path
372
367
  * @param path - path of the CAD object
@@ -375,16 +370,9 @@ declare class Viewer {
375
370
  /**
376
371
  * Build nestedGroup and treeview for initial render.
377
372
  * @param scene - The scene to add the group to
378
- * @param expanded - whether to render the exploded or compact version
379
373
  * @returns The nestedGroup and treeview
380
374
  */
381
375
  private buildInitialGroup;
382
- /**
383
- * Toggle the two version of the NestedGroup.
384
- * Must only be called after render() has completed.
385
- * @param expanded - whether to render the exploded or compact version
386
- */
387
- toggleGroup(expanded: boolean): void;
388
376
  /**
389
377
  * Set the active sidebar tab.
390
378
  * @param tabName - Tab name: "tree", "clip", "material", "zebra", or "studio"
@@ -534,22 +522,30 @@ declare class Viewer {
534
522
  * @param tree - whether from tree
535
523
  */
536
524
  handlePick: (path: string, name: string, meta: boolean, shift: boolean, alt: boolean, point: THREE.Vector3 | null, nodeType?: string | null, tree?: boolean) => void;
537
- setPickHandler(flag: boolean): void;
538
525
  /**
539
- * Find the shape that was double clicked and send notification
540
- * @param e - a DOM PointerEvent or MouseEvent
526
+ * Record a leaf about to be hidden via meta-double-click onto the hide-undo stack,
527
+ * capturing its current (pre-hide) visibility state for a faithful restore. Dedups:
528
+ * an existing entry for the same id is dropped so the id moves to the top.
541
529
  */
542
- pick: (e: PointerEvent | MouseEvent) => void;
543
- clearSelection: () => void;
544
- _releaseLastSelected: () => void;
545
- _removeLastSelected: () => void;
530
+ private _recordHidden;
546
531
  /**
547
- * Set raycast mode
548
- * @param flag - turn raycast mode on or off
532
+ * Restore the most recently meta-double-click-hidden leaf (LIFO), skipping entries
533
+ * that no longer apply — removed objects, or ones already shown again via the tree.
534
+ * In Studio the restored leaf keeps edges off (presentation); in CAD its pre-hide
535
+ * edge state is restored. Bound to a meta-double-click on empty space, so a hidden
536
+ * object can be brought back without the tree (notably in Studio). @public
549
537
  */
550
- setRaycastMode(flag: boolean): void;
551
- handleRaycast: () => void;
552
- handleRaycastEvent: (event: RaycastEvent) => void;
538
+ showLastHidden: () => void;
539
+ /** Component under the cursor (hover); committed on left-click. */
540
+ get lastObject(): PickedComponent | null;
541
+ /** Last committed selection. */
542
+ get lastSelection(): PickedComponent | null;
543
+ /** Enable/disable the double-click pick handler (on when no tool is active). */
544
+ setPickHandler(flag: boolean): void;
545
+ /** Enable/disable click+key selection input (on while a select/measure tool is active). */
546
+ setSelectionInput(flag: boolean): void;
547
+ /** Clear the current selection (and reset the select tool). */
548
+ clearSelection: () => void;
553
549
  /**
554
550
  * Handle a backend response sent by the backend
555
551
  * The response is a JSON object sent by the Python backend through VSCode
@@ -0,0 +1,209 @@
1
+ import * as THREE from "three";
2
+ import type { ComponentRegistry } from "./id-picking.js";
3
+ /**
4
+ * Shader-based component highlight (compact graph).
5
+ *
6
+ * Design:
7
+ * - Per-component highlight state lives in ONE `R8UI` data texture indexed by
8
+ * `componentId` (the attribute already on the compact geometry). The texture is
9
+ * shared by every compact visual material via `onBeforeCompile`, so a state
10
+ * write is reflected by all materials with no recompile.
11
+ * - State is BIT FLAGS ({@link HighlightFlag}); SELECTED wins over HOVER.
12
+ * - `selectSolid` sets the flag for every registry component sharing a `solidPath`.
13
+ *
14
+ * Driven by the live event loop via `IdPicker.pickAt → registry → controller`.
15
+ */
16
+ /** Highlight color for a selected component (was `ObjectGroup.HIGHLIGHT_COLOR_SELECTED`). */
17
+ export declare const HIGHLIGHT_COLOR_SELECTED = 5480675;
18
+ /** Highlight color for a hovered, not-selected component (was `HIGHLIGHT_COLOR_HOVER`). */
19
+ export declare const HIGHLIGHT_COLOR_HOVER = 9026019;
20
+ /**
21
+ * Per-topo FOCUS BASE sizes (was `ObjectGroup.vertexFocusSize` / `edgeFocusWidth`).
22
+ * These are injected PER-MATERIAL by the `patch*Material` methods (a `#define` /
23
+ * material-local uniform), NOT via the shared {@link HighlightUniforms} — edges
24
+ * (5) and vertices (8) need different values, and the hover-vs-selected `−2` delta
25
+ * (`objectgroup.ts:285-298 widen()`) is resolved in-shader from the {@link
26
+ * HighlightFlag} bits: `HOVER → base`, `SELECTED && !HOVER → base − 2`, else the
27
+ * material's authored size.
28
+ */
29
+ export declare const VERTEX_FOCUS_SIZE = 6;
30
+ export declare const EDGE_FOCUS_WIDTH = 5;
31
+ /**
32
+ * Per-component highlight state, stored as bit flags in one texel of the state
33
+ * texture. SELECTED takes precedence over HOVER when both are set, so hovering an
34
+ * already-selected component keeps the selected color (matches the old
35
+ * `_getHighlightColor` / `unhighlight(true)`).
36
+ */
37
+ export declare const HighlightFlag: {
38
+ readonly NONE: 0;
39
+ readonly SELECTED: number;
40
+ readonly HOVER: number;
41
+ };
42
+ export type HighlightFlagValue = (typeof HighlightFlag)[keyof typeof HighlightFlag];
43
+ /**
44
+ * Width of the highlight-state data texture in texels. Height grows with the
45
+ * component count: `height = ceil((maxId + 1) / WIDTH)`. A component's texel is at
46
+ * `(id % WIDTH, floor(id / WIDTH))` — the same mapping the shader recomputes.
47
+ */
48
+ export declare const HIGHLIGHT_STATE_TEXTURE_WIDTH = 2048;
49
+ /** Uniform: the shared `usampler2D` highlight-state texture (R8UI). */
50
+ export declare const U_HIGHLIGHT_STATE = "uHighlightState";
51
+ /** Uniform: texture width, for `id -> ivec2` texel coordinates. */
52
+ export declare const U_HIGHLIGHT_TEX_WIDTH = "uHighlightTexWidth";
53
+ /** Uniform: selected color (linear RGB vec3). */
54
+ export declare const U_HIGHLIGHT_SELECTED_COLOR = "uHighlightSelectedColor";
55
+ /** Uniform: hover color (linear RGB vec3). */
56
+ export declare const U_HIGHLIGHT_HOVER_COLOR = "uHighlightHoverColor";
57
+ /**
58
+ * The shared `THREE.IUniform` set bound into every patched compact material. One
59
+ * instance per {@link HighlightController}; the same object is referenced by all
60
+ * materials so a single write updates them together. Property names MUST match the
61
+ * `U_HIGHLIGHT_*` GLSL identifier constants above.
62
+ *
63
+ * Intentionally TOPO-AGNOSTIC: focus sizes are per-topo and the original (authored)
64
+ * size is per-material, so those are injected by `patch*Material` per material —
65
+ * NOT here. See {@link VERTEX_FOCUS_SIZE} / {@link EDGE_FOCUS_WIDTH}.
66
+ */
67
+ export interface HighlightUniforms {
68
+ uHighlightState: {
69
+ value: THREE.DataTexture | null;
70
+ };
71
+ uHighlightTexWidth: {
72
+ value: number;
73
+ };
74
+ uHighlightSelectedColor: {
75
+ value: THREE.Color;
76
+ };
77
+ uHighlightHoverColor: {
78
+ value: THREE.Color;
79
+ };
80
+ }
81
+ /**
82
+ * Minimal material surface the patch methods need. Declared structurally because
83
+ * three's examples-jsm `LineMaterial` has a `vertexColors: string | boolean` field
84
+ * that is not assignable to the nominal `THREE.Material` type.
85
+ */
86
+ type PatchableMaterial = Pick<THREE.Material, "onBeforeCompile" | "customProgramCacheKey" | "needsUpdate" | "userData">;
87
+ /**
88
+ * Owns the per-component highlight state texture and patches compact visual
89
+ * materials to read it. Created by the compact `NestedGroup` alongside its
90
+ * `ComponentRegistry`.
91
+ *
92
+ * Lifecycle: construct → `patch*Material` on each compact face/edge/vertex visual
93
+ * material as it is built → `resize(registry.maxId)` once all components are
94
+ * registered → `setHover` / `setSelected` / `selectSolid` / `clear` to drive
95
+ * highlight → `dispose`.
96
+ */
97
+ export declare class HighlightController {
98
+ /** The registry whose ids index the state texture. */
99
+ readonly registry: ComponentRegistry;
100
+ /** Shared uniforms bound into every patched material. */
101
+ readonly uniforms: HighlightUniforms;
102
+ /** Backing R8UI state texture (one byte = {@link HighlightFlag} bits per id). */
103
+ private texture;
104
+ /** CPU-side mirror of the texture data (length = `capacity`). */
105
+ private data;
106
+ /** Number of texels currently allocated (= width * height ≥ maxId + 1). */
107
+ private capacity;
108
+ /** Ids currently carrying the HOVER flag (one component, or a whole solid). */
109
+ private hoverIds;
110
+ /** Cheap identity of the current hover target so a repeat is a no-op. */
111
+ private hoverKey;
112
+ /**
113
+ * @param registry - the compact group's component registry; sizes the texture
114
+ * and resolves `solidPath` for {@link selectSolid}.
115
+ */
116
+ constructor(registry: ComponentRegistry);
117
+ /**
118
+ * Allocate an R8UI data texture (+ CPU mirror) holding at least `texelCount`
119
+ * texels. `NearestFilter`, no mips — the shader reads exact integer flags via
120
+ * `texelFetch`. This is the FROZEN texture format.
121
+ */
122
+ private _allocate;
123
+ /** The shared state texture (re-created by {@link resize}). */
124
+ get stateTexture(): THREE.DataTexture;
125
+ /**
126
+ * Set or clear `bit` on a component's texel and flag the texture for re-upload
127
+ * on change. The linear data index equals the id (row-major, width-W texels), so
128
+ * texel `(id % W, floor(id / W))` is `data[id]`. Ignores background (0) and ids
129
+ * past the current capacity (caller must {@link resize} first).
130
+ */
131
+ private _setBit;
132
+ /**
133
+ * Move the HOVER flag onto exactly the given `ids` (clearing it from the
134
+ * previously hovered set). `key` is a cheap identity so a repeat target is a
135
+ * no-op — without it, re-hovering the same target every mouse-move would toggle
136
+ * the bits off→on and re-upload the texture each frame. Does not touch SELECTED.
137
+ */
138
+ private _applyHover;
139
+ /**
140
+ * Move the HOVER flag onto a single component `id` (clearing the previous hover),
141
+ * or clear hover entirely when `id` is `null`/background.
142
+ */
143
+ setHover(id: number | null): void;
144
+ /**
145
+ * HOVER a whole solid (its FACES only — see {@link selectSolid}), or clear hover
146
+ * when `null`. Mirrors {@link selectSolid} for the transient hover state.
147
+ */
148
+ setHoverSolid(solidPath: string | null): void;
149
+ /** Set or clear the SELECTED flag for a single component id. */
150
+ setSelected(id: number, flag: boolean): void;
151
+ /** Whether a component currently carries the SELECTED flag. */
152
+ isSelected(id: number): boolean;
153
+ /**
154
+ * Whether a solid is selected — every one of its faces carries SELECTED (false if it
155
+ * has no faces). Used for solid toggle, so a solid that is only partially selected
156
+ * (e.g. one face previously single-selected) is treated as not-selected and a click
157
+ * selects the whole solid rather than clearing it.
158
+ */
159
+ isSolidSelected(solidPath: string): boolean;
160
+ /**
161
+ * Set or clear SELECTED for a whole solid. Flags only the solid's **faces**
162
+ * (`topo === "face"`) — the body tints while edges keep their colour and corners
163
+ * stay hidden. Iterates {@link ComponentRegistry.entries}.
164
+ */
165
+ selectSolid(solidPath: string, flag: boolean): void;
166
+ /** Clear all highlight state (hover + selection) for every component. */
167
+ clear(): void;
168
+ /**
169
+ * Grow the state texture to hold at least `maxId + 1` texels, preserving existing
170
+ * state, and re-bind the new texture object into {@link uniforms}. No-op when the
171
+ * current capacity already suffices.
172
+ */
173
+ resize(maxId: number): void;
174
+ /**
175
+ * Shared `onBeforeCompile` installer: binds the shared uniforms, prepends the
176
+ * common vertex/fragment headers, forwards the component id, then runs the
177
+ * topo-specific `customize` (color override / widening). Idempotent per material.
178
+ */
179
+ private _install;
180
+ /**
181
+ * Install `onBeforeCompile` on a face (`MeshStandardMaterial`) visual material.
182
+ * Overwrites `diffuseColor.rgb` with the highlight color BEFORE lighting (after
183
+ * the base color/map is applied) so a highlighted face is lit exactly as the old
184
+ * `material.color` swap was — selected wins over hover.
185
+ */
186
+ patchFaceMaterial(material: PatchableMaterial): void;
187
+ /**
188
+ * Install `onBeforeCompile` on an edge (`LineMaterial`) visual material (Option A).
189
+ * Widens the screen-space half-width for flagged segments by patching the stock
190
+ * LineMaterial expansion (`offset *= linewidth;`, screen-space branch) and recolors
191
+ * via the shared fragment override. `vertexColors` axes/trihedron carry no
192
+ * registry id (state 0) so they stay inert.
193
+ */
194
+ patchEdgeMaterial(material: PatchableMaterial): void;
195
+ /**
196
+ * Install `onBeforeCompile` on a vertex (`PointsMaterial`) visual material.
197
+ * Flagged points widen to the focus size and recolor. When `cullUnhighlighted`
198
+ * (the solid highlight-Points cloud, invisible until selected), non-flagged points
199
+ * are culled — `gl_PointSize = 0` AND a fragment `discard` (the discard is the
200
+ * real guard; size-0 rasterization is driver-defined). Standalone visible vertices
201
+ * (default) keep their authored `size` and color when unflagged.
202
+ */
203
+ patchVertexMaterial(material: PatchableMaterial, options?: {
204
+ cullUnhighlighted?: boolean;
205
+ }): void;
206
+ /** Dispose the state texture and release tracking. */
207
+ dispose(): void;
208
+ }
209
+ export {};