DEV Community

Cover image for Part 7: Anchors (WebXR with Babylon.js)
Bryan for Taikonauten

Posted on • Edited on • Originally published at Medium

Part 7: Anchors (WebXR with Babylon.js)

๐Ÿ‘€ Stumbled here on accident? Start with the first part!


Welcome back to the 7th article in this WebXR/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

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;
  ...
}
Enter fullscreen mode Exit fullscreen mode

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;
    });
}
Enter fullscreen mode Exit fullscreen mode

This is where we add a new anchor at 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 parent to it. Therefore the box keeps itโ€™s 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();
            }
        });
    })
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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



Conclusion

In conclusion, this seventh installment of our WebXR/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 eighth part we dive into loading models and assets.

Top comments (0)