Use this task to place your own interface on a captured space. Pick a point on the model geometry; a screen click is not automatically a valid 3D location.
Run the example
Open SDK examples from the starter. Enable picking, select a visible model surface, and inspect the coordinate result. Place a label at that point, then turn the camera to check that the overlay follows it.
Pick a point on the model
Subscribe only while the placement tool is active. The callback prevents the same tap from also triggering the SDK's normal viewpoint navigation.
import type { Five, Intersection } from "@realsee/five";
/** Opt in while a placement tool is active; the returned cleanup restores it. */
export function watchPickedPoint(
five: Five,
onPick: (hit: Intersection | null) => void
): () => void {
return five.on("gesture.tap", (event) => {
// Prevent this tool's tap from also triggering default point navigation.
event.preventDefault();
const hits = five.model.intersectRaycaster(event.center.raycaster);
onPick(hits[0] ?? null);
});
}null means there is no model intersection at that point. Sky, transparent areas, missing geometry and scene loading can produce no hit. Clear the placement state instead of inventing a coordinate.
Project the label into the viewport
The overlay must cover the same rectangle as the canvas. Use a stable positioned parent, an absolutely positioned overlay, and pointer-events: none for a passive label.
import type { Five } from "@realsee/five";
import type { Vector3 } from "three";
/** Put overlay over the canvas, with the same dimensions and position. */
export function placeLabel(
five: Five,
overlay: HTMLElement,
position: Vector3,
text: string
): () => void {
const label = document.createElement("span");
label.textContent = text;
label.style.cssText =
"position:absolute;pointer-events:none;transform:translate(-50%,-100%);padding:6px 10px;border-radius:6px;background:#fff;color:#102f50";
overlay.append(label);
const update = () => {
const point = five.project2d(position, true);
label.hidden = point === null;
if (point) {
label.style.left = `${point.x}px`;
label.style.top = `${point.y}px`;
}
};
update();
const stop = five.on("render", update);
return () => {
stop();
label.remove();
};
}Use textContent for untrusted text. Remove the label and its render listener when the task ends. The true argument enables the SDK's model-occlusion test; it adds intersection work for each label. This remains a 2D interface element, not a persistent annotation stored on your server.
For editable panorama tags and richer spatial UI, continue with Dnalogel panorama tags.
Check the result
- An actual surface produces coordinates; a miss stays empty.
- Placement mode does not also navigate to another panorama.
- Turning or resizing the scene keeps the label aligned.
- Closing the tool unsubscribes its listener and removes its label.
Continue with Capture an image.
