Skip to content

Chapter 11: Scene Understanding

The IWSDK provides a comprehensive scene understanding system that enables AR/VR applications to detect and interact with real-world geometry. This chapter covers everything you need to know about implementing plane detection, mesh detection, and anchoring in your WebXR applications.

What You'll Build

By the end of this chapter, you'll be able to:

  • Set up scene understanding systems for plane and mesh detection
  • Automatically detect flat surfaces like floors, walls, and ceilings
  • Detect complex 3D geometry including furniture and room structure
  • Create stable anchor points that persist across tracking loss
  • Build semantic-aware interactions based on detected object types
  • Place virtual content accurately on real-world surfaces

Overview

The scene understanding system leverages WebXR's scene understanding capabilities to provide:

  • Plane Detection - Automatically detect flat surfaces like floors, walls, and ceilings
  • Mesh Detection - Detect complex 3D geometry including furniture and room structure
  • Anchoring - Create stable reference points that persist across tracking loss
  • Automatic Entity Management - Real-world geometry is automatically converted to ECS entities
  • Semantic Understanding - Meshes include semantic labels (table, chair, wall, etc.)

Key Components

  • SceneUnderstandingSystem - Core system that manages WebXR scene understanding
  • XRPlane - Component for detected flat surfaces
  • XRMesh - Component for detected 3D geometry
  • XRAnchor - Component for anchoring objects to stable positions

Quick Start

Here's a minimal example to enable scene understanding:

javascript
import {
  World,
  SceneUnderstandingSystem,
  XRPlane,
  XRMesh,
  XRAnchor,
  SessionMode,
} from '@iwsdk/core';

// React to detected planes
class PlaneProcessSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (entity) => {
      console.log('New plane detected!', entity.object3D?.position);
    });
  }

  update() {
    this.queries.planeEntities.entities.forEach((entity) => {
      console.log('Existing plane: ', entity.object3D?.position);
    });
  }
}

// Create world with AR session and required features
World.create(document.getElementById('scene-container'), {
  assets,
  xr: {
    sessionMode: SessionMode.ImmersiveAR,
    features: {
      planeDetection: true,
      meshDetection: true,
      anchors: true,
    },
  },
}).then((world) => {
  // Register the scene understanding system
  world
    .registerSystem(SceneUnderstandingSystem)
    .registerComponent(XRPlane)
    .registerComponent(XRMesh)
    .registerComponent(XRAnchor)
    .registerSystem(PlaneProcessSystem);
});

System Setup

Step 1: WebXR Session Configuration

Scene understanding requires specific WebXR features to be enabled:

javascript
World.create(container, {
  xr: {
    sessionMode: SessionMode.ImmersiveAR,
    features: {
      planeDetection: true, // Enable plane detection
      meshDetection: true, // Enable mesh detection
      anchors: true, // Enable anchor creation
    },
  },
});

Important: Not all WebXR implementations support all features. The system will log warnings for unsupported features.

Step 2: Register System and Components

javascript
world
  .registerSystem(SceneUnderstandingSystem)
  .registerComponent(XRPlane)
  .registerComponent(XRMesh)
  .registerComponent(XRAnchor);

Step 3: System Configuration

The system accepts configuration options:

javascript
world.registerSystem(SceneUnderstandingSystem, {
  configData: {
    showWireFrame: true, // Show/hide wireframe visualization for detected planes and meshes
  },
});

Understanding the Components

XRPlane

Represents detected flat surfaces in the real world. Do not manually create - these are automatically managed by the system.

Plane Properties

  • Entity Object3D - Visual representation of the plane (wireframe box by default)
  • _plane - Internal WebXR plane object (read-only)

XRMesh

Represents detected 3D geometry in the real world. Do not manually create - these are automatically managed by the system.

javascript
class MeshProcessSystem extends createSystem({
  meshEntities: { required: [XRMesh] },
}) {
  init() {
    // Query for detected meshes
    this.queries.meshEntities.subscribe('qualify', (entity) => {
      const isBounded = entity.getValue(XRMesh, 'isBounded3D');
      const semanticLabel = entity.getValue(XRMesh, 'semanticLabel');

      if (isBounded) {
        console.log('Object detected:', semanticLabel);
        const dimensions = entity.getValue(XRMesh, 'dimensions');
        const min = entity.getValue(XRMesh, 'min');
        const max = entity.getValue(XRMesh, 'max');

        console.log('Object size:', dimensions);
        console.log('Bounding box:', min, max);
      } else {
        console.log('Global mesh detected (room structure)');
      }
    });
  }
}

Mesh Properties

  • isBounded3D - Whether this is a bounded object vs global scene mesh, read more about bounded 3D meshes and scene meshes
  • semanticLabel - Semantic classification ('table', 'chair', 'wall', etc.)
  • min - Minimum bounding box coordinates
  • max - Maximum bounding box coordinates
  • dimensions - Calculated dimensions [width, height, depth]
  • _mesh - Internal WebXR mesh object (read-only)

XRAnchor

Anchors objects to stable real-world positions. User creates this component to anchor entities.

javascript
// Create an anchored object
const hologram = world.createTransformEntity(hologramMesh);
hologram.addComponent(XRAnchor);

// The system will automatically:
// 1. Create a stable anchor at the current world position
// 2. Attach the object to the anchored reference frame
// 3. Maintain stable positioning across tracking loss

Anchor Properties

  • attached - Whether the entity has been attached to the anchor group (managed by system)

Plane Detection

Understanding Plane Detection

WebXR plane detection identifies flat surfaces in the real world:

  • Horizontal planes: Floors, tables, desks
  • Vertical planes: Walls, doors, windows
  • Arbitrary planes: Angled surfaces like ramps

Working with Detected Planes

javascript
// React to new planes
class PlaneProcessSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (entity) => {
      const planeObject = entity.object3D;
      const position = planeObject.position;
      const rotation = planeObject.quaternion;

      console.log('Plane detected at:', position);

      // Place content on the plane
      const marker = createMarkerMesh();
      marker.position.copy(position);
      marker.position.y += 0.1; // Slightly above the plane
      scene.add(marker);
    });

    this.queries.planeEntities.subscribe('disqualify', (entity) => {
      console.log('Plane lost:', entity.object3D?.position);
      // Clean up any content placed on this plane
    });
  }
}

Plane-based Content Placement

javascript
// Place objects on detected floor planes
class PlaneProcessSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (entity) => {
      const plane = entity.getValue(XRPlane, '_plane');

      // Check if it's a horizontal plane (floor-like)
      if (plane.orientation === 'horizontal') {
        const planePosition = entity.object3D.position;

        // Place a virtual object on the floor
        const virtualObject = createVirtualObject();
        virtualObject.position.copy(planePosition);
        virtualObject.position.y += 0.5; // Above the floor
        scene.add(virtualObject);
      }
    });
  }
}

Mesh Detection

Understanding Mesh Detection

WebXR mesh detection provides detailed 3D geometry:

  • Bounded meshes: Individual objects with semantic labels
  • Global mesh: Generic scene mesh objects
  • Semantic classification: The label of the detected mesh object if it's a bounded 3d mesh. Available sementic label values are listed here.

Working with Detected Meshes

javascript
class MeshProcessSystem extends createSystem({
  meshEntities: { required: [XRMesh] },
}) {
  init() {
    this.queries.meshEntities.subscribe('qualify', (entity) => {
      const isBounded = entity.getValue(XRMesh, 'isBounded3D');
      const semanticLabel = entity.getValue(XRMesh, 'semanticLabel');

      if (isBounded) {
        handleBoundedObject(entity, semanticLabel);
      } else {
        handleGlobalMesh(entity);
      }
    });
  }
}

function handleBoundedObject(entity, semanticLabel) {
  const dimensions = entity.getValue(XRMesh, 'dimensions');
  const position = entity.object3D.position;

  switch (semanticLabel) {
    case 'table':
      console.log(`Table detected: ${dimensions[0]}x${dimensions[2]}m`);
      break;

    case 'chair':
      console.log('Chair detected');
      // Maybe spawn a virtual cushion or highlight
      break;

    case 'wall':
      console.log('Wall detected');
      // Could place artwork or UI panels
      break;
  }
}

function handleGlobalMesh(entity) {
  console.log('Room structure detected');
  // Could use for occlusion, physics, or environmental effects
}

Semantic-based Interactions

javascript
// Example: Interactive table system
class TableInteractionSystem extends createSystem({
  tables: { required: [XRMesh], where: [eq(XRMesh, 'semanticLabel', 'table')] },
}) {
  update() {
    this.queries.tables.entities.forEach((entity) => {
      const dimensions = entity.getValue(XRMesh, 'dimensions');
      const position = entity.object3D.position;

      // Create interactive surface
      this.createTableInterface(position, dimensions);
    });
  }

  createTableInterface(position, dimensions) {
    // Create UI or interactive elements for the table
    const interface = createTableUI(dimensions);
    interface.position.copy(position);
    interface.position.y += 0.01; // Just above table surface
    this.scene.add(interface);
  }
}

Anchoring

Understanding Anchors

Anchors provide stable positioning that persists it's world location that won't be changed with recentering the view.

Creating Anchored Content

javascript
// Anchor existing object
const existingObject = world.createTransformEntity(mesh);
existingObject.object3D.position.set(0, 1, -2);
existingObject.addComponent(XRAnchor);

Visual Feedback

Wireframe Visualization

The system provides optional wireframe visualization for detected geometry:

javascript
// Configure during registration
world.registerSystem(SceneUnderstandingSystem, {
  configData: { showWireFrame: false },
});

// Or enable/disable wireframes
world.getSystem(SceneUnderstandingSystem).config.showWireFrame.value = true;

Common Patterns

AR Furniture Placement

javascript
// Place virtual furniture on detected floor planes
class FurnitureSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (entity) => {
      const plane = entity.getValue(XRPlane, '_plane');

      if (plane.orientation === 'horizontal') {
        const planePosition = entity.object3D.position;

        // Create anchored furniture
        const chairMesh = // Create your chair mesh here
        const chair = world.createTransformEntity(chairMesh);
        chair.object3D.position.copy(planePosition);
        chair.object3D.position.y += 0.4; // Chair height
        chair.addComponent(XRAnchor);
      }
    });
  }
}

Interactive Surface Detection

javascript
// Create interactive UI on detected tables
class SurfaceUISystem extends createSystem({
  meshes: { required: [XRMesh] },
}) {
  update() {
    this.queries.meshes.entities.forEach((entity) => {
      const semanticLabel = entity.getValue(XRMesh, 'semanticLabel');
      const isBounded = entity.getValue(XRMesh, 'isBounded3D');

      if (isBounded && semanticLabel === 'table') {
        this.createTableUI(entity);
      }
    });
  }

  createTableUI(tableEntity) {
    const dimensions = tableEntity.getValue(XRMesh, 'dimensions');
    const position = tableEntity.object3D.position;

    // Create UI panel
  }
}

Scene-Aware Physics

javascript
// Use detected planes as physics collision surfaces
class PlanePhysicsSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (planeEntity) => {
      // Add physics collision to detected planes
      const planeMesh = planeEntity.object3D;

      planeEntity.addComponent(PhysicsShape, {
        shape: PhysicsShapeType.Box,
        dimensions: [
          planeMesh.geometry.parameters.width,
          planeMesh.geometry.parameters.height,
          planeMesh.geometry.parameters.depth,
        ],
      });

      planeEntity.addComponent(PhysicsBody, {
        state: PhysicsState.Static,
      });
    });
  }
}

Troubleshooting

Common Issues

Scene understanding not working:

  • Verify AR session with related features are properly requested
  • If you are running on a Meta Horizon OS device, ensure that the room scaning (environment setup) is finished.

Planes/meshes not detected:

  • Ensure the environment has clear, well-lit surfaces
  • Try manual room setup if available on the device

Anchors not staying stable:

  • Verify 'anchor' feature is enabled in WebXR session
  • Ensure adequate visual features for tracking
  • Some devices have limitations on anchor count

Performance issues:

  • Limit the number of entities with scene understanding components
  • Consider disabling wireframes for better performance

Debug Tips

Log detection events:

javascript
class PlaneSystem extends createSystem({
  planeEntities: { required: [XRPlane] },
}) {
  init() {
    this.queries.planeEntities.subscribe('qualify', (entity) => {
      console.log('Plane detected:', entity.getValue(XRPlane, '_plane'));
    });
    this.queries.planeEntities.subscribe('disqualify', (entity) => {
      console.log('Plane lost');
    });
  }
}

Visualize bounding boxes:

javascript
class MeshSystem extends createSystem({
  meshEntities: { required: [XRMesh] },
}) {
  init() {
    this.queries.meshEntities.subscribe('qualify', (entity) => {
      const isBounded = entity.getValue(XRMesh, 'isBounded3D');
      if (isBounded) {
        const min = entity.getValue(XRMesh, 'min');
        const max = entity.getValue(XRMesh, 'max');

        // Create debug bounding box visualization
        const box = createBoundingBoxHelper(min, max);
      }
    });
  }
}