Develop

RealMeasure sample overview

Updated: May 10, 2026

Overview

This sample demonstrates how to build a mixed reality measurement tool using WebXR, three.js, and an Entity-Component-System architecture. It showcases two measurement modes, spatial anchor persistence, controller input handling with haptic feedback, and a floating settings panel — all structured around the lightweight elics ECS framework.

What you will learn

  • Structure a WebXR mixed reality application using an Entity-Component-System pattern with elics
  • Configure WebXR sessions for mixed reality on Meta Quest with spatial anchors and passthrough rendering
  • Handle controller input with GamepadWrapper, including button states, axis navigation, and haptic feedback patterns
  • Implement spatial persistence using XR anchors so measurements remain fixed in physical space
  • Render dynamic 3D measurements with distance calculations, unit conversion, and billboard text labels

Requirements

  • Meta Quest device
  • Development environment with Node.js and npm
For complete setup instructions, see the RealMeasure README.

Get started

Clone the webxr-showcases repository and navigate to the realmeasure directory. Install dependencies with npm install, then run npm run serve to launch the development server at https://localhost:8081. Open the URL in Meta Quest Browser and tap Launch to enter the mixed reality experience. For detailed build instructions, see the RealMeasure README.

Explore the sample

FileWhat it demonstratesKey concepts
Application bootstrap and WebXR session setup
ECS world creation, RATK integration, ARButton configuration with optional features (local-floor, layers, anchors, unbounded), framebuffer scaling, foveation control
Three.js rendering pipeline
multiviewStereo rendering for Quest, passthrough-ready alpha rendering, environment-based lighting
Shared state container
Service locator pattern for lightweight ECS architectures
Controller lifecycle and head tracking
WebXR controller connected/disconnected events, GamepadWrapper integration, XRViewerPose-based head tracking
Pointer visualization and calibration
Attaching objects to controller ray space, per-hand offsets, runtime recalibration
Measurement data model and rendering
ECS component/system pattern, spatial anchor creation with RATK, troika-three-text for SDF text rendering, unit conversion (metric/imperial), haptic feedback on distance change, billboard text with lookAt()
Single-hand tape measure mode
Trigger-based measurement start/finalize, squeeze-based axis snapping, entity lifecycle management
Dual-hand caliper mode
Two-controller coordinate tracking, simultaneous endpoint updates
Floating settings panel
Thumbstick navigation with deadzone, gaze-following UI with lerp interpolation, haptic feedback on UI interactions, per-hand control tip texture overlays
Clear all measurements
Long-press safety pattern with ring progress indicator, selective entity destruction

Runtime behavior

When you run this sample, you see a floating settings panel positioned one meter in front of you. Two controller pointers appear at your controller tips. Pull the trigger on either controller to place the first measurement point, move your controller to stretch the tape, then pull the trigger again to finalize. A white line connects the two points with a floating distance label showing the measurement in centimeters or inches. Squeeze either controller to activate axis snapping, which constrains the measurement to horizontal or vertical and turns the line green. Click the thumbstick to toggle the settings panel, then tilt the thumbstick to navigate between unit (Metric/Imperial), mode (Tape/Clamp), and tips (On/Off). Hold BUTTON_1 for three seconds to clear all finalized measurements, with a circular progress ring filling as you hold.

Key concepts

ECS architecture with elics

The sample uses the elics Entity-Component-System framework to separate data from logic. The World owns all entities, components, and systems. A single component type, MeasurementComponent, stores marker positions, line geometry, text labels, and material references. Seven systems process logic each frame: PlayerSystem updates controller state, SettingsSystem manages the floating UI, PointerSystem renders controller tips, TapeSystem and ClampSystem create and update measurements, PurgeSystem handles deletion, and MeasurementSystem renders all measurements. For the complete registration order, see src/index.js.

WebXR session configuration

The sample requests an immersive-ar session with four optional features: local-floor for floor-level tracking, layers for composition control, anchors for spatial persistence, and unbounded for large-scale experiences. On session start, the renderer disables foveation (sets to 0) for uniform rendering precision across the entire field of view, critical for accurate measurement visualization. The framebuffer scale factor is set to 2 for higher resolution.

Spatial anchors for measurement persistence

The sample creates a single XR anchor at world origin when the XR session starts, using RATK’s createAnchor() method. All measurement objects attach to this anchor’s Object3D, making them persist relative to the physical world even as you move. The sample initializes the anchor lazily on the first frame after session start, guarded by a _justEnteredXR flag.

Controller input with GamepadWrapper

GamepadWrapper abstracts the WebXR Gamepad API with edge-triggered and level-triggered button helpers. The sample uses getButtonClick() for single actions like placing measurements and getButton() for continuous actions like axis snapping. The get2DInputAngle() method detects thumbstick direction for settings navigation with a 0.7 magnitude deadzone. Each controller’s GamepadWrapper instance requires an update() call every frame to track button state transitions. See src/player.js, src/settings.js, and src/tape.js.

Axis snapping

Both Tape and Clamp modes use identical snapping logic: compute the horizontal distance (sqrt(x*x + z*z)) and compare to the absolute vertical distance. If horizontal exceeds vertical, snap to horizontal by constraining Y to the reference point’s Y. Otherwise, snap to vertical by constraining X and Z. The reference point differs by mode: in Tape mode it is the fixed start marker, while in Clamp mode it is the opposite hand’s pointer position. See src/tape.js and src/clamp.js.

Haptic feedback patterns

The sample uses distinct haptic intensities and durations for different interactions: measurement distance changes trigger a subtle 10ms pulse at 0.1 intensity when crossing centimeter boundaries, settings navigation uses 0.2 intensity for 100ms, option changes use 0.3 intensity for 50ms, and purge completion delivers a strong 0.8 intensity pulse for 100ms. Haptics are accessed via gamepad._gamepad.hapticActuators[0]?.pulse(intensity, duration). See src/measurement.js, src/settings.js, and src/purge.js.

Extend the sample

  • Add area measurement: Track multiple points to form a polygon, then calculate and display the enclosed area. Extend MeasurementComponent with a points array and render a filled polygon mesh.
  • Export measurements: Add a UI button that serializes all finalized measurements (positions and distances) to JSON or CSV format for documentation or analysis.
  • Custom marker colors: Let users assign colors to measurements, or auto-color by measurement type (horizontal in blue, vertical in red) using the existing snap detection logic.