import * as THREE from "three"; import type { ComponentRegistry } from "./id-picking.js"; /** * Shader-based component highlight (compact graph). * * Design: * - Per-component highlight state lives in ONE `R8UI` data texture indexed by * `componentId` (the attribute already on the compact geometry). The texture is * shared by every compact visual material via `onBeforeCompile`, so a state * write is reflected by all materials with no recompile. * - State is BIT FLAGS ({@link HighlightFlag}); SELECTED wins over HOVER. * - `selectSolid` sets the flag for every registry component sharing a `solidPath`. * * Driven by the live event loop via `IdPicker.pickAt → registry → controller`. */ /** Highlight color for a selected component (was `ObjectGroup.HIGHLIGHT_COLOR_SELECTED`). */ export declare const HIGHLIGHT_COLOR_SELECTED = 5480675; /** Highlight color for a hovered, not-selected component (was `HIGHLIGHT_COLOR_HOVER`). */ export declare const HIGHLIGHT_COLOR_HOVER = 9026019; /** * Per-topo FOCUS BASE sizes (was `ObjectGroup.vertexFocusSize` / `edgeFocusWidth`). * These are injected PER-MATERIAL by the `patch*Material` methods (a `#define` / * material-local uniform), NOT via the shared {@link HighlightUniforms} — edges * (5) and vertices (8) need different values, and the hover-vs-selected `−2` delta * (`objectgroup.ts:285-298 widen()`) is resolved in-shader from the {@link * HighlightFlag} bits: `HOVER → base`, `SELECTED && !HOVER → base − 2`, else the * material's authored size. */ export declare const VERTEX_FOCUS_SIZE = 6; export declare const EDGE_FOCUS_WIDTH = 5; /** * Per-component highlight state, stored as bit flags in one texel of the state * texture. SELECTED takes precedence over HOVER when both are set, so hovering an * already-selected component keeps the selected color (matches the old * `_getHighlightColor` / `unhighlight(true)`). */ export declare const HighlightFlag: { readonly NONE: 0; readonly SELECTED: number; readonly HOVER: number; }; export type HighlightFlagValue = (typeof HighlightFlag)[keyof typeof HighlightFlag]; /** * Width of the highlight-state data texture in texels. Height grows with the * component count: `height = ceil((maxId + 1) / WIDTH)`. A component's texel is at * `(id % WIDTH, floor(id / WIDTH))` — the same mapping the shader recomputes. */ export declare const HIGHLIGHT_STATE_TEXTURE_WIDTH = 2048; /** Uniform: the shared `usampler2D` highlight-state texture (R8UI). */ export declare const U_HIGHLIGHT_STATE = "uHighlightState"; /** Uniform: texture width, for `id -> ivec2` texel coordinates. */ export declare const U_HIGHLIGHT_TEX_WIDTH = "uHighlightTexWidth"; /** Uniform: selected color (linear RGB vec3). */ export declare const U_HIGHLIGHT_SELECTED_COLOR = "uHighlightSelectedColor"; /** Uniform: hover color (linear RGB vec3). */ export declare const U_HIGHLIGHT_HOVER_COLOR = "uHighlightHoverColor"; /** * The shared `THREE.IUniform` set bound into every patched compact material. One * instance per {@link HighlightController}; the same object is referenced by all * materials so a single write updates them together. Property names MUST match the * `U_HIGHLIGHT_*` GLSL identifier constants above. * * Intentionally TOPO-AGNOSTIC: focus sizes are per-topo and the original (authored) * size is per-material, so those are injected by `patch*Material` per material — * NOT here. See {@link VERTEX_FOCUS_SIZE} / {@link EDGE_FOCUS_WIDTH}. */ export interface HighlightUniforms { uHighlightState: { value: THREE.DataTexture | null; }; uHighlightTexWidth: { value: number; }; uHighlightSelectedColor: { value: THREE.Color; }; uHighlightHoverColor: { value: THREE.Color; }; } /** * Minimal material surface the patch methods need. Declared structurally because * three's examples-jsm `LineMaterial` has a `vertexColors: string | boolean` field * that is not assignable to the nominal `THREE.Material` type. */ type PatchableMaterial = Pick; /** * Owns the per-component highlight state texture and patches compact visual * materials to read it. Created by the compact `NestedGroup` alongside its * `ComponentRegistry`. * * Lifecycle: construct → `patch*Material` on each compact face/edge/vertex visual * material as it is built → `resize(registry.maxId)` once all components are * registered → `setHover` / `setSelected` / `selectSolid` / `clear` to drive * highlight → `dispose`. */ export declare class HighlightController { /** The registry whose ids index the state texture. */ readonly registry: ComponentRegistry; /** Shared uniforms bound into every patched material. */ readonly uniforms: HighlightUniforms; /** Backing R8UI state texture (one byte = {@link HighlightFlag} bits per id). */ private texture; /** CPU-side mirror of the texture data (length = `capacity`). */ private data; /** Number of texels currently allocated (= width * height ≥ maxId + 1). */ private capacity; /** Ids currently carrying the HOVER flag (one component, or a whole solid). */ private hoverIds; /** Cheap identity of the current hover target so a repeat is a no-op. */ private hoverKey; /** * @param registry - the compact group's component registry; sizes the texture * and resolves `solidPath` for {@link selectSolid}. */ constructor(registry: ComponentRegistry); /** * Allocate an R8UI data texture (+ CPU mirror) holding at least `texelCount` * texels. `NearestFilter`, no mips — the shader reads exact integer flags via * `texelFetch`. This is the FROZEN texture format. */ private _allocate; /** The shared state texture (re-created by {@link resize}). */ get stateTexture(): THREE.DataTexture; /** * Set or clear `bit` on a component's texel and flag the texture for re-upload * on change. The linear data index equals the id (row-major, width-W texels), so * texel `(id % W, floor(id / W))` is `data[id]`. Ignores background (0) and ids * past the current capacity (caller must {@link resize} first). */ private _setBit; /** * Move the HOVER flag onto exactly the given `ids` (clearing it from the * previously hovered set). `key` is a cheap identity so a repeat target is a * no-op — without it, re-hovering the same target every mouse-move would toggle * the bits off→on and re-upload the texture each frame. Does not touch SELECTED. */ private _applyHover; /** * Move the HOVER flag onto a single component `id` (clearing the previous hover), * or clear hover entirely when `id` is `null`/background. */ setHover(id: number | null): void; /** * HOVER a whole solid (its FACES only — see {@link selectSolid}), or clear hover * when `null`. Mirrors {@link selectSolid} for the transient hover state. */ setHoverSolid(solidPath: string | null): void; /** * HOVER the components at or below the tree path `prefix` (a leaf or a whole * group), or clear hover when `null`. Drives the tree-row hover highlight. Each * leaf shows its highest topology: the faces of solids and face shapes (like the * canvas solid hover), the edges of edge-only shapes, the vertices of vertex-only * shapes — so every tree row highlights something. */ setHoverPath(prefix: string | null): void; /** Set or clear the SELECTED flag for a single component id. */ setSelected(id: number, flag: boolean): void; /** Whether a component currently carries the SELECTED flag. */ isSelected(id: number): boolean; /** * Whether a solid is selected — every one of its faces carries SELECTED (false if it * has no faces). Used for solid toggle, so a solid that is only partially selected * (e.g. one face previously single-selected) is treated as not-selected and a click * selects the whole solid rather than clearing it. */ isSolidSelected(solidPath: string): boolean; /** * Set or clear SELECTED for a whole solid. Flags only the solid's **faces** * (`topo === "face"`) — the body tints while edges keep their colour and corners * stay hidden. Iterates {@link ComponentRegistry.entries}. */ selectSolid(solidPath: string, flag: boolean): void; /** Clear all highlight state (hover + selection) for every component. */ clear(): void; /** * Grow the state texture to hold at least `maxId + 1` texels, preserving existing * state, and re-bind the new texture object into {@link uniforms}. No-op when the * current capacity already suffices. */ resize(maxId: number): void; /** * Shared `onBeforeCompile` installer: binds the shared uniforms, prepends the * common vertex/fragment headers, forwards the component id, then runs the * topo-specific `customize` (color override / widening). Idempotent per material. */ private _install; /** * Install `onBeforeCompile` on a face (`MeshStandardMaterial`) visual material. * Overwrites `diffuseColor.rgb` with the highlight color BEFORE lighting (after * the base color/map is applied) so a highlighted face is lit exactly as the old * `material.color` swap was — selected wins over hover. */ patchFaceMaterial(material: PatchableMaterial): void; /** * Install `onBeforeCompile` on an edge (`LineMaterial`) visual material (Option A). * Widens the screen-space half-width for flagged segments by patching the stock * LineMaterial expansion (`offset *= linewidth;`, screen-space branch) and recolors * via the shared fragment override. `vertexColors` axes/trihedron carry no * registry id (state 0) so they stay inert. */ patchEdgeMaterial(material: PatchableMaterial): void; /** * Install `onBeforeCompile` on a vertex (`PointsMaterial`) visual material. * Flagged points widen to the focus size and recolor. When `cullUnhighlighted` * (the solid highlight-Points cloud, invisible until selected), non-flagged points * are culled — `gl_PointSize = 0` AND a fragment `discard` (the discard is the * real guard; size-0 rasterization is driver-defined). Standalone visible vertices * (default) keep their authored `size` and color when unflagged. */ patchVertexMaterial(material: PatchableMaterial, options?: { cullUnhighlighted?: boolean; }): void; /** Dispose the state texture and release tracking. */ dispose(): void; } export {};