A mesh of your own
A SceneMesh with its engine object, its controller (what the transform tool may do), an undoable add command, moved with the transform tool or from code.
import {
AddObjectCommand, type AddObjectCommandOptions, type ObjectTransformConfig, SceneMesh,
SceneMeshController, SetObjectTransformCommand, Viewer,
} from '@stereograph/teiaviewer';
import { GeometryBuffersConverter } from '@stereograph/teiaviewer-engine';
import {
Color, ConeGeometry, Matrix4, Vector3,
} from 'three';
/**
* A mesh of your own: a `SceneMesh` holds its engine object (its proxy), which the engine draws,
* bounds and picks. Its state (visibility, selection, colour…) goes through the viewer as for any
* loaded object: commands, undo, object state.
*/
export class Marker extends SceneMesh {
/** Tells a marker, without `instanceof`. */
readonly isMarker = true;
constructor(readonly label: string, uuid?: string) {
super(uuid);
}
}
/**
* The controller of the markers: their place in the scene (added, removed, disposed), and what
* the transform tool may do to them: moved on the floor only.
*/
export class MarkerController extends SceneMeshController<Marker> {
override getTransformConfig(_marker: Marker): ObjectTransformConfig {
return {
translate: {
x: true,
y: false,
z: true,
},
rotate: {
x: false,
y: false,
z: false,
},
scale: {
x: false,
y: false,
z: false,
},
};
}
}
// One geometry, converted once (with its sharp edges), shared by every marker.
const coneGeometry = new GeometryBuffersConverter().convertGeometry(new ConeGeometry(0.3, 1, 16));
/** Adds a marker at a position, undoable (the undo removes it, the redo adds it back). */
export class AddMarkerCommand extends AddObjectCommand<Marker> {
readonly name = 'AddMarkerCommand';
constructor(
viewer: Viewer,
controller: MarkerController,
private readonly _label: string,
private readonly _position: Vector3,
options: AddObjectCommandOptions = {},
) {
super(viewer, controller, options);
}
protected override async _createObject(): Promise<Marker> {
const marker = new Marker(this._label);
marker.name = this._label;
// Its engine object: one part, the cone, in its material.
const [proxy] = this.viewer.scene.engineScene.meshController.addMeshes([
{
uuid: marker.uuid,
name: marker.name,
parts: [
{
geometry: coneGeometry,
matrix: new Matrix4().elements,
material: {
color: new Color(0x2f80ed),
emissive: new Color(0x000000),
opacity: 1,
roughness: 0.6,
metalness: 0,
},
},
],
},
]);
marker.setProxy(proxy);
marker.setPosition(this._position);
return marker;
}
}
export async function addAndMoveMarker(viewer: Viewer) {
// The controller goes into the scene once (e.g. in a plugin's `onRegister`).
const markers = new MarkerController(viewer);
viewer.scene.addObjectController(markers);
// Added and selected: the default operator shows the transform tool on it.
const marker = await new AddMarkerCommand(viewer, markers, 'Entrance', new Vector3(2, 0, 3), {
selected: true,
}).execute();
// Or attached to the transform tool from code, in translation (the only mode it allows).
const { transformController } = viewer.viewport;
transformController.attachObjects([marker]);
transformController.setMode('translate');
// Moved from code, undoable as a move with the tool.
const moved = new Matrix4().makeTranslation(4, 0, 3);
await new SetObjectTransformCommand(viewer, marker.uuid, moved).execute();
await viewer.commandHistory.undoLastCommand();
// Gone for good: out of the scene, its engine object freed.
transformController.detachObjects();
markers.removeObject(marker);
markers.disposeObject(marker);
}