| name | thatopen-impl-highlighting |
| description | Use when implementing element selection, hover effects, outline visualization, or converting fragments to meshes in a ThatOpen viewer. Prevents missing Highlighter setup and style configuration errors. Covers Highlighter (click selection with styles), Hoverer (hover animation), Outliner (post-processing outlines), Mesher (fragment-to-mesh), FastModelPicker (GPU picking), multi-select, zoom-to-selection, autoToggle. Keywords: highlighter, selection, hover, outline, pick, highlight, multi-select, color, style, mesher, fast model picker, click to select, hover effect, selected element color.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires @thatopen/components-front 3.3.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
ThatOpen Highlighting & Selection
Overview
This skill covers element highlighting, selection, hover effects, outline
rendering, and fragment-to-mesh conversion in a ThatOpen BIM viewer. The
five components covered are:
| Component | Package | Purpose |
|---|
| Highlighter | @thatopen/components-front | Click-based element selection with color styles |
| Hoverer | @thatopen/components-front | Fade animation on hover |
| Outliner | @thatopen/components-front | Post-processing outline effects |
| Mesher | @thatopen/components-front | Convert fragment selections to THREE.Mesh |
| FastModelPicker | @thatopen/components | GPU-based element picking |
Version: @thatopen/components-front 3.3.x
Prerequisites: thatopen-impl-viewer (world setup, PostproductionRenderer)
MaterialDefinition Type
The core type for highlight styles:
type MaterialDefinition = {
color: THREE.Color;
opacity: number;
};
All Highlighter styles are either a MaterialDefinition object or null
(meaning transparent/no-color selection tracking only).
Highlighter
Setup
ALWAYS call setup() before using any Highlighter functionality. Without
setup(), the Highlighter has no world reference and all operations fail
silently or throw.
import * as OBCF from "@thatopen/components-front";
const highlighter = components.get(OBCF.Highlighter);
highlighter.setup({ world });
HighlighterConfig
| Property | Type | Default | Description |
|---|
world | World | required | World to pick elements from |
selectName | string | "select" | Default selection style name |
selectionColor | THREE.Color | #BCF124 | Default selection color |
autoHighlightOnClick | boolean | true | Auto-highlight clicked elements |
selectEnabled | boolean | true | Enable/disable selection globally |
autoUpdateFragments | boolean | true | Auto-update fragment visuals |
selectMaterialDefinition | MaterialDefinition | yellow | Default style material |
highlighter.setup({
world,
selectName: "select",
selectionColor: new THREE.Color("#BCF124"),
autoHighlightOnClick: true,
});
Styles
Styles define how highlighted elements appear. Each style has a name and a
MaterialDefinition (or null for invisible tracking).
highlighter.styles.set("warning", {
color: new THREE.Color("#FF0000"),
opacity: 0.5,
});
highlighter.styles.set("invisible", null);
- ALWAYS define styles AFTER calling
setup().
- The default
"select" style is created automatically by setup().
- Style names are arbitrary strings. Use descriptive names.
Highlight (Click Selection)
await highlighter.highlight("select");
await highlighter.highlight("select", false);
await highlighter.highlight("select", true, true);
const exclude: ModelIdMap = new Map();
await highlighter.highlight("select", true, false, exclude);
Method signature:
highlight(
name: string,
removePrevious?: boolean,
zoomToSelection?: boolean,
exclude?: ModelIdMap
): Promise<void>;
HighlightByID (Programmatic Selection)
Select specific elements by their model/element IDs without requiring a
mouse event:
const items: ModelIdMap = new Map();
items.set(modelId, new Set([elementId1, elementId2]));
await highlighter.highlightByID("select", items);
await highlighter.highlightByID("select", items, false);
await highlighter.highlightByID("select", items, true, true);
Method signature:
highlightByID(
name: string,
modelIdMap: ModelIdMap,
removePrevious?: boolean,
zoomToSelection?: boolean,
exclude?: ModelIdMap
): Promise<void>;
Reading the Selection
The selection property holds the current state for every style:
const selected: ModelIdMap = highlighter.selection["select"];
for (const [modelId, elementIds] of selected) {
console.log(`Model ${modelId}: ${elementIds.size} elements selected`);
}
Clear Selection
await highlighter.clear("select");
await highlighter.clear();
await highlighter.clear("select", filterModelIdMap);
Multi-Select
The multiple property controls multi-selection behavior:
highlighter.multiple = "none";
highlighter.multiple = "shiftKey";
highlighter.multiple = "ctrlKey";
- When
multiple is "shiftKey" or "ctrlKey", holding the specified
modifier key while clicking adds to the selection instead of replacing it.
- ALWAYS set
multiple AFTER calling setup().
Zoom to Selection
highlighter.zoomToSelection = true;
await highlighter.highlight("select", true, true);
When zoomToSelection is true, the camera frames the highlighted elements
after each highlight operation.
AutoToggle
Styles listed in autoToggle automatically deselect when the same element
is clicked again (toggle behavior):
highlighter.autoToggle.add("select");
- ALWAYS add style names to
autoToggle AFTER calling setup().
- AutoToggle works per-element, not per-click. Clicking a different element
still replaces the selection (unless
multiple is also set).
Selectable (Per-Style Filtering)
Restrict which elements can be highlighted for a given style:
const allowedElements: ModelIdMap = new Map();
allowedElements.set(modelId, new Set([101, 102, 103]));
highlighter.selectable["select"] = allowedElements;
- When
selectable is set for a style, ONLY elements in that ModelIdMap
can be highlighted with that style. All other elements are ignored.
- If
selectable is not set for a style, all elements are selectable.
Update Colors
After changing a style's MaterialDefinition, call updateColors() to apply
the visual change immediately:
highlighter.styles.set("select", {
color: new THREE.Color("#0000FF"),
opacity: 0.8,
});
await highlighter.updateColors();
Hoverer
Hoverer provides a fade animation effect when the mouse hovers over
elements. It is a separate component from Highlighter.
const hoverer = components.get(OBCF.Hoverer);
Properties
| Property | Type | Default | Description |
|---|
duration | number | — | Fade animation duration (ms) |
delay | number | — | Delay before hover triggers (ms) |
animation | string | — | Animation type |
Events
hoverer.onHoverStarted.add((data) => {
console.log("Hover started:", data);
});
hoverer.onHoverEnded.add(() => {
console.log("Hover ended");
});
- Hoverer works independently of Highlighter. You can use both
simultaneously: Hoverer for visual feedback, Highlighter for selection.
- NEVER rely on Hoverer for selection state. Use Highlighter for that.
Outliner
Outliner renders post-processing outlines around highlighted elements. It
requires PostproductionRenderer (not SimpleRenderer).
Setup
import * as OBCF from "@thatopen/components-front";
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBCF.PostproductionRenderer
>();
world.renderer.postproduction.enabled = true;
const outliner = components.get(OBCF.Outliner);
Styles
Outliner styles reference Highlighter style names. When elements are
highlighted with a style that the Outliner tracks, outlines appear.
outliner.styles.add("select");
Outline Appearance
| Property | Type | Default | Description |
|---|
color | THREE.Color | — | Outline stroke color |
thickness | number | — | Outline stroke width (px) |
fillColor | THREE.Color | — | Interior fill color |
fillOpacity | number | — | Interior fill opacity (0-1) |
outliner.color = new THREE.Color("#FF6600");
outliner.thickness = 2;
outliner.fillColor = new THREE.Color("#FF6600");
outliner.fillOpacity = 0.1;
Manual Item Management
const items: ModelIdMap = new Map();
items.set(modelId, new Set([elementId]));
outliner.addItems(items);
outliner.removeItems(items);
outliner.clean();
Mesher
Mesher converts fragment selections into standard THREE.Mesh objects.
This is useful for custom rendering, export, or manipulation outside the
fragment system.
const mesher = components.get(OBCF.Mesher);
const items: ModelIdMap = new Map();
items.set(modelId, new Set([elementId1, elementId2]));
const meshes: THREE.Mesh[] = mesher.get(items);
Method Signature
get(modelIdMap: ModelIdMap, config?: MesherConfig): THREE.Mesh[];
- Returns standard
THREE.Mesh objects with geometry and material.
- The returned meshes are NOT managed by the fragment system. You MUST
manually add them to a scene and dispose them when done.
- Useful for: custom shaders, physics engines, export to glTF, snapshots.
FastModelPicker
FastModelPicker provides GPU-based element picking as an alternative to
raycasting. It renders element IDs to an off-screen buffer and reads back
the pixel under the cursor. This is significantly faster than raycasting
for large models.
const picker = components.get(OBC.FastModelPicker);
- FastModelPicker is in
@thatopen/components (not components-front).
- It is new in v3 and replaces manual raycasting for performance-critical
selection scenarios.
- For most use cases, the built-in raycasting via Highlighter is sufficient.
Use FastModelPicker only when raycasting becomes a bottleneck with very
large models (100k+ elements).
Complete Click-to-Select Workflow
import * as THREE from "three";
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBCF.PostproductionRenderer
>();
world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.renderer = new OBCF.PostproductionRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
world.renderer.postproduction.enabled = true;
highlighter = components.(.);
highlighter.({ world });
highlighter. = ;
highlighter. = ;
highlighter..(, {
: .(),
: ,
});
outliner = components.(.);
outliner..();
outliner. = .();
outliner. = ;
hoverer = components.(.);
components.();
: = ();
items.(modelId, ([elementId]));
highlighter.(, items);
highlighter.();
Critical Rules
- ALWAYS call
highlighter.setup({ world }) before using any
Highlighter method. Without it, highlight operations fail.
- ALWAYS use
PostproductionRenderer when using Outliner. Outlines
are a post-processing effect and have no effect with SimpleRenderer.
- ALWAYS enable postproduction (
world.renderer.postproduction.enabled = true) before Outliner will render.
- ALWAYS dispose meshes returned by
Mesher.get() manually. They are
not tracked by the fragment system.
- NEVER set
multiple, autoToggle, or selectable before calling
setup(). The Highlighter is not initialized until setup() completes.
- NEVER assume Hoverer and Highlighter share state. They are
independent components with separate tracking.
- NEVER use FastModelPicker without measuring whether raycasting is
actually a bottleneck. The built-in raycasting is sufficient for most
models.
Reference Files
- references/methods.md — Highlighter, Hoverer,
Outliner, Mesher, FastModelPicker API signatures
- references/examples.md — Click select, hover,
outline, multi-select, custom styles
- references/anti-patterns.md — Missing
setup, style conflicts, performance issues
Source Verification
All API signatures verified against:
- GitHub:
ThatOpen/engine_components main branch
- npm:
@thatopen/components-front@3.3.3
- Research:
docs/research/vooronderzoek-thatopen.md (Sections 4.2-4.5)
- Skill:
thatopen-impl-viewer (PostproductionRenderer context)