Create Anchors in WebXR with Babylon.js

Taikonauten
Taikonauten  Magazine
4 min readFeb 13, 2024

👀 Stumbled here on accident? Start with the introduction!

Welcome back to the 7th article of our WebXR with Babylon.js series. This part is about Anchors in WebXR.

ℹ️ Remember — you can always run the code associated with this article and follow along using npm start --part=7

attaching a mesh to an anchor
attaching a mesh to an anchor

What are Anchors?

Anchors are used to create a stable reference point in the physical space, which can be tracked by the device’s sensors and cameras. When an anchor is set, the virtual object tied to it appears to be part of the real world, staying in the same place as if it were a physical object.

Anchoring is crucial for maintaining consistency in the virtual environment as it relates to the real world.

Adding anchors to the scene in Babylon.js

To add an Anchor to the scene, we go back into our handleControllerSelection, take the raycastHit value and call addAnchorAtPositionAndRotationAsync to add an anchor at the ray cast hit position.

Interacting with the controller

handleControllerSelection() {
...
if (raycastHit.pickedMesh === this._box) {
const mat = this._box!.material as StandardMaterial;
mat.diffuseColor = Color3.Random();
this._box!.material = mat;
} else {
this.addAnchorAtPosition(raycastHit);
}
this._box!.isVisible = true;
...
}

We first check if the mesh we picked is the box and if so, we just give it a randomly different color.

If we don’t pick the box but any other position in the 3D space we will call a new function addAnchorAtPosition with the raycastHit as a parameter.

Adding the Anchor

addAnchorAtPosition(raycastHit: PickingInfo) {
this._xrAnchors!.addAnchorAtPositionAndRotationAsync(raycastHit.pickedPoint!).then((anchor) => {

const boxTransformNode = new TransformNode('boxTransformNode');
boxTransformNode.position = raycastHit.pickedPoint!;

this._box!.parent = boxTransformNode;
this._box!.position = Vector3.Zero();
this._box!.isVisible = true;
anchor.attachedNode = boxTransformNode;
});
}

This is where we add a new anchor to our raycastHit.pickedPointposition.

Since anchors have a fixed position in space and don’t support animations, we first have to help ourselves by creating a so-called TransformNode for the rotating box. A TransformNode is a versatile and flexible helper that, in our case, acts as a helper to the box by acting as a parent to it. Therefore the box keeps its rotation animation.

The position of this transform node is set to the point where the ray cast hit, making it align with the XR anchor.

The box is then parented to boxTransformNode. This means any transformation applied to boxTransformNode will also affect the box. The box's position is set to Vector3.Zero(), which means it's placed at the origin relative to its parent (i.e., the boxTransformNode). Since the transform node is already positioned at the ray cast hit point, this effectively places the box there.

this._box!.isVisible = true; makes the box visible in the scene.

Finally, the transform node is attached to the XR anchor. This ensures that the transform node (and therefore the box) will maintain its position relative to the real world, even as the user moves around in the XR space.

Cleaning up

observeAnchors() {
if (this._xrAnchors === null) {
return;
}
this._xrAnchors.onAnchorAddedObservable.add((addedAnchor) => {
this._xrAnchors!.anchors.forEach((anchor: IWebXRAnchor) => {
if (anchor !== addedAnchor) {
anchor.remove();
}
});
})
}

this._xrAnchors.onAnchorAddedObservable.add(() => { ... }) sets up an observer that will execute the provided callback function every time a new anchor is added to the XR environment.

Within the callback, there’s a loop this._xrAnchors!.anchors.forEach((anchor: IWebXRAnchor) => { ... }) that iterates over all the anchors currently in this._xrAnchors.

Inside the loop, anchor.remove() is called on each anchor except the one we just added. This means that every time a new anchor is added to the system, all existing anchors are removed.

The primary intent of this function is to maintain a single active anchor in the XR environment. When a new anchor is added, it triggers the removal of all existing anchors. This is useful in scenarios where only the latest anchor is relevant, and previous anchors should not persist.

Adding observeAnchors to the scene

async createScene(): Promise<Scene> {
...
this.observeAnchors();

return this._scene;
}

To make use of the function we have to add it to createScene.

the final scene in this chapter

Conclusion

In conclusion, this seventh installment of our WebXR with Babylon.js series has taken us through the pivotal concept of Anchors in WebXR. Anchors are more than just a technical component in the realm of extended reality; they serve as the vital bridge connecting the virtual and real worlds. By creating these stable reference points in physical space, which remain consistent regardless of the viewer’s movement or perspective, we enhance the immersion and believability of our virtual environments.

The practical demonstrations and code examples provided, from handling controller selections to dynamically adding and managing anchors, have illustrated how these concepts are not just theoretical but highly applicable in real-world scenarios. The use of TransformNode as a flexible tool to maintain object animations while anchoring, and the strategic management of anchors to keep the XR environment clean and relevant, show the depth and versatility of Babylon.js in creating compelling XR experiences.

In the seventh part, we dive into loading models and assets.

--

--

Taikonauten
Taikonauten  Magazine

We Create Digital Products & Services Users Love. Strategy, Concept, Design & Engineering