Develop

Chairs Etc. sample overview

Updated: May 10, 2026

Overview

This sample demonstrates physics-based furniture placement in mixed reality. It shows how to combine WebXR plane detection with a physics engine to create natural placement interactions, build a 3D UI catalog panel anchored to controllers, and use real-world surfaces as physics obstacles. Use this sample when building AR experiences that require intuitive object placement with realistic collision behavior.

What you will learn

  • Integrating WebXR plane detection with a physics engine (Rapier3D) to create wall and floor colliders from real-world surfaces
  • Building physics-driven placement using impulse-based movement and raycasting from controllers
  • Creating 3D UI panels with @pmndrs/uikit that follow controller position and respond to thumbstick input
  • Structuring WebXR apps with the elics Entity Component System for controller tracking, physics, and UI
  • Converting dynamic physics bodies to fixed obstacles after placement to enable multi-object interactions

Requirements

  • Meta Quest device with WebXR support
  • Development environment with Node.js and npm
For platform setup, see the Meta Quest development documentation.

Get started

Clone the webxr-showcases repository and navigate to the chairs-etc directory. Install dependencies with npm install, then run npm run serve to launch the webpack dev server on https://localhost:8081. Open the URL on your Quest browser and tap “ENTER AR” to begin the session. For detailed build instructions, see the sample README.

Explore the sample

File / SceneWhat it demonstratesKey concepts
App entry point, ECS world creation, animation loop
RATK initialization, WebXR session configuration, system registration
FurnitureSystem — physics world, GLB loading, placement logic
Rapier3D physics integration, plane-to-collider conversion, raycasting from controllers
PlayerSystem — controller and headset tracking
GamepadWrapper for input, controller pose tracking
UIPanelSystem — 3D catalog UI
@pmndrs/uikit flexbox panels, thumbstick navigation, controller-anchored UI
Three.js scene setup
Multiview stereo rendering, shadow mapping, PMREM environment lighting
Gradient cube outline marker
Visual feedback for placement target
Shared state object
Cross-system communication pattern

Runtime behavior

When you run the sample, you enter an AR session and the app begins detecting planes in your environment. If no planes appear within five seconds, the system prompts room capture. As vertical planes are detected, the app creates wall colliders; when a floor plane appears, it adjusts the ground collider height.
A 3D catalog panel appears above your left controller, displaying 11 chair thumbnails in a grid. You navigate with the left thumbstick and select a chair with the left trigger. The selected chair loads as a 3D model attached to a dynamic physics body, marked with a gradient cube outline.
Aiming your right controller at the floor causes the furniture to slide toward the raycasted target via physics impulses. You rotate the furniture with the right thumbstick and finalize placement with the right trigger, converting the dynamic body to a fixed obstacle that affects future placements.

Key concepts

Physics-driven placement

The sample uses Rapier3D to create natural placement behavior. Each furniture model attaches to a dynamic rigid body. Every frame during placement, a raycaster fires from the right controller to the floor, finding the target point. The system applies an impulse proportional to the body’s mass toward the target:
let velocity = dir.normalize().multiplyScalar(0.1);
let impulse = velocity.multiplyScalar(this.rigidBody.mass());
this.rigidBody.applyImpulse(impulse, true);
Wall colliders prevent furniture from passing through detected surfaces. See src/furniture.js for the complete implementation.

Plane detection feeding physics

RATK’s plane detection callbacks create physics colliders from real-world geometry. Vertical planes become wall cuboid colliders positioned and rotated to match the detected surface. Floor planes adjust the ground collider height to the detected floor level:
const wallColliderDesc = this.RAPIER.ColliderDesc.cuboid(
  plane.boundingRectangleWidth, 0, plane.boundingRectangleHeight)
  .setTranslation(...plane.position.toArray())
  .setRotation(plane.quaternion);
This bridges WebXR spatial understanding with physics simulation, enabling placed objects to respect real-world boundaries. See src/furniture.js for the onPlaneAdded callback implementation.

Furniture finalization

When the user presses the right trigger, the system detaches the furniture model from the dynamic mover body and places it in the scene with a new fixed rigid body and collider. Placed furniture becomes a physics obstacle for subsequent placements:
globals.scene.attach(furnitureModel);
furnitureModel.castShadow = true;
The dynamic mover resets to position (0, 3, 0) for the next item. See the finalizeFurniturePosition method in src/furniture.js.

3D UI panel system

The catalog UI uses @pmndrs/uikit to render a flexbox-based panel directly in the Three.js scene. The panel anchors to the left controller position with a slight Y-axis offset and rotates via lookAt() to always face the player’s head:
controller.targetRaySpace.getWorldPosition(this._panelAnchor.position);
this._panelAnchor.position.y += 0.1;
this._panelAnchor.lookAt(globals.playerHead.position);
This demonstrates controller-anchored UI that remains readable without requiring the user to reposition. See src/panel.js for the complete UI system.

Extend the sample

  • Replace the chair catalog: Update src/search.json and add GLB files to src/assets/. Models must include DRACO-compressed geometry and KTX2-compressed textures for optimal performance.
  • Add scale controls: Map a thumbstick axis to adjust the furniture model’s scale before finalization, enabling users to resize objects to fit their space.
  • Implement undo functionality: Store placed furniture references in an array and provide a button input that removes the most recent placement from the scene and physics world.