Object edges on selection

Object edges on selection

When a user picks an object, a clear way to confirm which object they hit is to outline it. Instead of a post-process outline shader, Twinfinity can extract the object's actual edges and draw them with its line renderer — crisp, screen-space lines that stay sharp at any zoom.

The pieces

There are three pieces to this:

  • loadProductEdges(product) pulls the object's edge segments. Each segment is a flat [x1, y1, z1, x2, y2, z2] in world space, the edges load asynchronously, and the result is cached per object.

  • The line API (api.lines) draws them. api.lines.add(slab) registers a set of segments and returns a LineHandle; a SegmentBuilder collects segments into the slab, and everything renders in a single instanced GPU draw.

  • You add one handle up front, then on each pick replace its content with setSlab. Reusing one handle means every new pick simply replaces the previous object's outline — no bookkeeping.

Picking and drawing the edges

Create the handle up front, then on a tap pick the object, load its edges, build the segments, and hand them to the handle with setSlab:

import { PointerEventTypes } from '@babylonjs/core'; import { PickResultType, loadProductEdges } from '@twinfinity/core'; import { SegmentBuilder } from '@twinfinity/core/lines'; const outline = api.lines.add(new SegmentBuilder(0).finish(), { id: 'selected-edges' }); api.onPointerObservable.add((eventData) => { // POINTERTAP fires only on a genuine tap, so a camera drag won't trigger a pick. if (eventData.type !== PointerEventTypes.POINTERTAP) return; const pick = eventData.twinfinity.pick(false); if (pick.type !== PickResultType.IfcProductMesh) return; void loadProductEdges(pick.ifcProductMesh.ifcProduct).then((edges) => { const builder = new SegmentBuilder(edges.length); for (const [x1, y1, z1, x2, y2, z2] of edges) { builder.add({ start: [x1, y1, z1], end: [x2, y2, z2], color: [1, 0, 0], width: 2 }); } outline.setSlab(builder.finish()); }); });

The line width is in screen-space pixels, so the outline reads the same whether you're zoomed out over a whole building or in close on a single fitting.

The handle is also how you take the outline away again — outline.visible = false hides it, outline.remove() disposes it for good.

Try it in the playground: Object edges on selection.

See also