Develop

Project Flowerbed sample overview

Updated: May 10, 2026

Overview

This sample demonstrates production VR development patterns through an immersive WebXR gardening experience built with THREE.js and ECSY. You plant over 30 flower varieties, interact with wildlife, take in-VR photographs, and navigate using teleportation, snap turn, and joystick locomotion. The codebase uses ECS architecture, WebXR session management with desktop fallback, curved raycasting for plant placement, and a comprehensive asset pipeline with KTX2 compression.

What you will learn

  • Structure a complex WebXR application using ECS architecture
  • Build desktop fallback that runs the same code on VR and non-VR platforms
  • Optimize rendering performance for mobile VR headsets
  • Build curved raycasting for teleportation and surface interaction
  • Create an asset pipeline that compresses GLTF models for efficient loading

Requirements

  • Meta Quest headset for VR mode, or desktop browser for emulated mode
  • Node.js and Yarn for building the project
For detailed setup and build prerequisites, see the Project Flowerbed README.

Get started

Clone the repository from github.com/meta-quest/ProjectFlowerbed. Install dependencies using the vendored Yarn 1.23.0 package manager, then run the webpack development server. For detailed build instructions, configuration options, and deployment guidance, see the sample’s README.

Explore the sample

The project separates ECS systems by functional domain, library utilities for reusable rendering and collision logic, and a standalone asset pipeline for GLTF compression:
File / DirectoryDemonstratesKey concepts
src/index.js
Application entry point, THREE.js initialization, ECSY world creation
WebXR session creation, iwer device emulation fallback, BVH raycasting attachment, rendering loop setup
src/js/ECSYConfig.js
Three-phase ECS system registration
Startup systems, after-load systems, after-ready systems for progressive feature initialization
src/js/systems/core/
Bootstrap, scene creation, rendering, input, player physics
WebXR lifecycle management, capsule-based player collision, gravity simulation
src/js/systems/hands/
Controller detection, hand model loading, bone-based animation
Context-aware hand poses per interaction mode, per-bone quaternion slerp
src/js/systems/plants/
Planting, growth, watering, picking
Curved raycasting to surfaces, seed projectile animation, PD spring dynamics for growth
src/js/systems/fauna/
Wildlife spawning, movement, animation
Bounded random movement, distance-based culling, skeleton and morph target animation
src/js/systems/locomotion/
Teleportation, snap turn, joystick movement, comfort vignette
Curved ray validation with obstacle detection, dynamic vignette for motion comfort
src/js/systems/audio/
Spatial audio with Howler.js
3D positional audio, dynamics compressor, ambient sound creation
src/js/systems/performance/
Runtime performance optimization toggles
Shadow map control, render distance adjustment, framebuffer scaling
src/js/systems/mesh/
Auto-instancing and dynamic mesh optimization
Naming convention-based instancing (Name__001), per-plant dynamic instancing
src/js/systems/xplat/
Desktop keyboard and mouse control emulation
WASD and mouse look mapped to emulated WebXR controller inputs
src/js/lib/
Reusable rendering, collision, and audio utilities
Custom shaders, KD-tree spatial partitioning, curved raycaster, LOD with hysteresis
src/js/components/
Component schemas for ECSY entities
Game state machine, controller interface, planted item serialization
asset_pipeline/
GLTF model and texture compression pipeline
KTX2 basis compression, meshopt encoding, vertex quantization, incremental builds

Runtime behavior

When you run this sample, you see a 3D garden environment with a central pond, bridges, and surrounding terrain. You select a seed type from the floating seedbox UI panel, aim a curved ray at valid planting surfaces, and release the seed. Planted flowers grow using spring dynamics, and you can water them, pick them up, or take in-VR photographs using the camera tool.
Wildlife including fish, ducks, seagulls, squirrels, rabbits, and butterflies populate the environment with autonomous movement patterns.
You navigate using teleportation (curved ray with landing zone visualization), snap turn (45-degree increments), or joystick locomotion with a comfort vignette. In desktop mode, WASD controls movement and mouse controls view direction, with all VR systems functioning identically.

Key concepts

ECS architecture

The sample uses ECSY v0.4.2 to organize logic into 75+ systems and 70+ component classes. Systems use static queries to track entity changes, and singletons like THREEGlobalComponent bridge THREE.js objects into the ECS world. Three-phase registration in src/js/ECSYConfig.js ensures core systems initialize first, gameplay systems load after assets are ready, and audio systems activate last.

WebXR session and desktop fallback

The sample requests an immersive-vr session with local-floor and bounded-floor reference spaces in src/js/lib/VRButton.js. When navigator.xr does not support VR, src/index.js creates an XRDevice from the iwer library that emulates a Meta Quest 3, or Quest 3S at the WebXR API level. The XPlatControlSystem in src/js/systems/xplat/xplat.js maps keyboard and mouse inputs to the emulated controller state, so all ECS systems run identically on VR and desktop without branching code paths.

Controller input abstraction

ControllerInterface in src/js/lib/ControllerInterface.js wraps WebXR gamepad input into a clean API with edge detection (buttonJustPressed(), triggerJustReleased()), joystick angle interpretation, and haptic feedback. The VrInputSystem polls controller state each frame, and other systems query entities with VrControllerComponent to read normalized input.

Plant interaction with curved raycasting

The CurvedRaycaster in src/js/lib/objects/CurvedRaycaster.js projects a parabolic curve from the controller and tests intersection with scene meshes using three-mesh-bvh acceleration. The PlantingSystem validates that the intersection point lies on a planting surface and is not blocked by obstacles, then visualizes the landing zone with a green ring indicator. See src/js/systems/plants/PlantingSystem.js for the full validation logic.

Performance optimizations for mobile VR

The sample targets 72fps on Meta Quest headsets using multiple optimization layers. ModelOptimizeSystem automatically instances meshes that follow the naming convention Name__001, while LODWithHysteresis switches detail levels with a 5-unit threshold to prevent popping. The PerformanceOptionsSystem exposes runtime toggles for shadow updates, render distance, and framebuffer scale. See src/js/systems/performance/ and src/js/systems/mesh/ for the complete optimization implementations.

Asset pipeline

The standalone Node.js pipeline in asset_pipeline/asset_pipeline.js uses @gltf-transform to compress GLTF models through texture compression (KTX2 basis), geometry optimization (tangent generation, quantization), and GPU cache reordering (meshopt). The pipeline supports per-directory overrides and incremental builds that skip files where output is newer than source.

Extend the sample

  • Add new plant types: Create GLTF models, add entries to the PLANT_TYPES enum in src/js/Constants.js, and register growth parameters in the PlantGrowingSystem.
  • Implement new fauna behaviors: Extend the MovableFaunaSystem in src/js/systems/fauna/MovableFaunaSystem.js with custom movement patterns or interaction triggers.
  • Create additional interaction modes: Add states to the GameStateComponent state machine in src/js/components/GameStateComponent.js and implement corresponding systems.