thatopen-impl-highlighting
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).
// Add a custom highlight style
highlighter.styles.set("warning", {
color: new THREE.Color("#FF0000"),
opacity: 0.5,
});
// Null style — tracks selection without visual change
highlighter.styles.set("invisible", null);
- ALWAYS define styles AFTER calling
setup(). - The default
"select"style is created automatically bysetup(). - Style names are arbitrary strings. Use descriptive names.
Highlight (Click Selection)
// Highlight whatever the mouse is pointing at with the "select" style
await highlighter.highlight("select");
// Highlight without removing previous selection
await highlighter.highlight("select", false);
// Highlight and zoom to selection
await highlighter.highlight("select", true, true);
// Highlight with exclusion list
const exclude: ModelIdMap = new Map();
await highlighter.highlight("select", true, false, exclude);
Method signature:
highlight(
name: string,
removePrevious?: boolean, // default: true
zoomToSelection?: boolean, // default: false
exclude?: ModelIdMap // elements to skip
): 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);
// Keep previous selection
await highlighter.highlightByID("select", items, false);
// Highlight and zoom
await highlighter.highlightByID("select", items, true, true);
Method signature:
highlightByID(
name: string,
modelIdMap: ModelIdMap,
removePrevious?: boolean, // default: true
zoomToSelection?: boolean, // default: false
exclude?: ModelIdMap
): Promise<void>;
Reading the Selection
The selection property holds the current state for every style:
// Get current "select" selection
const selected: ModelIdMap = highlighter.selection["select"];
// Iterate selected elements
for (const [modelId, elementIds] of selected) {
console.log(`Model ${modelId}: ${elementIds.size} elements selected`);
}
Clear Selection
// Clear a specific style
await highlighter.clear("select");
// Clear all styles
await highlighter.clear();
// Clear with a filter (keep specific elements)
await highlighter.clear("select", filterModelIdMap);
Multi-Select
The multiple property controls multi-selection behavior:
// No multi-select (default) — each click replaces previous selection
highlighter.multiple = "none";
// Multi-select with Shift key
highlighter.multiple = "shiftKey";
// Multi-select with Ctrl key
highlighter.multiple = "ctrlKey";
- When
multipleis"shiftKey"or"ctrlKey", holding the specified modifier key while clicking adds to the selection instead of replacing it. - ALWAYS set
multipleAFTER callingsetup().
Zoom to Selection
// Enable zoom-to-selection globally
highlighter.zoomToSelection = true;
// Or per-call
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");
// First click on element A → selects A
// Second click on element A → deselects A
- ALWAYS add style names to
autoToggleAFTER callingsetup(). - AutoToggle works per-element, not per-click. Clicking a different element
still replaces the selection (unless
multipleis 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
selectableis set for a style, ONLY elements in that ModelIdMap can be highlighted with that style. All other elements are ignored. - If
selectableis 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";
// MUST use PostproductionRenderer — Outliner has no effect with SimpleRenderer
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBCF.PostproductionRenderer
>();
// ... world setup ...
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 tracks Highlighter style names
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(); // Remove all outlined items
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.Meshobjects 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";
// 1. Viewer setup (see thatopen-impl-viewer)
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;
// 2. Setup Highlighter — MUST call setup() before any other operation
const highlighter = components.get(OBCF.Highlighter);
highlighter.setup({ world });
// 3. Configure multi-select
highlighter.multiple = "shiftKey";
// 4. Enable zoom-to-selection
highlighter.zoomToSelection = true;
// 5. Add custom styles
highlighter.styles.set("warning", {
color: new THREE.Color("#FF0000"),
opacity: 0.5,
});
// 6. Setup Outliner for post-processing outlines
const outliner = components.get(OBCF.Outliner);
outliner.styles.add("select");
outliner.color = new THREE.Color("#BCF124");
outliner.thickness = 2;
// 7. Setup Hoverer for hover feedback
const hoverer = components.get(OBCF.Hoverer);
// 8. Start render loop
components.init();
// 9. Load a model (see thatopen-syntax-ifc-loading)
// ... model loading code ...
// 10. Programmatic selection
const items: ModelIdMap = new Map();
items.set(modelId, new Set([elementId]));
await highlighter.highlightByID("select", items);
// 11. Clear selection
await highlighter.clear("select");
Critical Rules
- ALWAYS call
highlighter.setup({ world })before using any Highlighter method. Without it, highlight operations fail. - ALWAYS use
PostproductionRendererwhen 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, orselectablebefore callingsetup(). The Highlighter is not initialized untilsetup()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_componentsmain 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)