Skip to main content

@realitycollective/webxr-interactions

The core of the Reality Collective Interaction Extensions. It holds all the interaction logic for WebXR and contains no 3D engine code at all.

Two words are used throughout. An interactable is an object that can be interacted with, such as a button. An interactor is the thing doing the interacting: a hand, controller, ray or mouse.

npm install @realitycollective/webxr-interactions

You usually do not install this directly. Install an adapter for your engine instead. Each adapter re-exports everything here, so your app depends on one package: threejs-interactions · iwsdk-interactions · xrblocks-interactions

What it provides

AreaDetail
BehavioursReady-made interaction types: press (with an optional latching mode that stays down until pressed again), pulse, hinge, dial, slide, grab, and tossScore for throw-and-catch scoring
GazeOptional look-to-activate. Require the user to be looking before an object responds, or let a sustained look trigger the press by itself
TargetingPicks one target per hand, trying the platform's own answer first, then a close-range touch, then a pointing ray. A short delay stops the target flickering between two objects on a boundary
Capability checksIf the headset cannot do what a behaviour needs, the behaviour switches itself off and reports behaviourDisabled, rather than silently doing nothing
EventsThe core never calls into your code. It emits events and you subscribe
FeedbackThe core asks for a haptic pulse or a sound. Playing it is your app's job. routeHapticsToProvider is an opt-in helper that sends those requests straight to the controller

All input arrives through the shared @realitycollective/webxr-input types, so the same core runs on any runtime that has an adapter.

Design rules

  • No engine imports. No three, no @iwsdk/*, no xrblocks. The adapter supplies the code that works out what a ray hits, and the code that moves an object.
  • Events out, never callbacks in. The core never reaches into your objects.
  • Fail loudly, not silently. A behaviour the hardware cannot support reports behaviourDisabled instead of quietly doing nothing.

Live demo

The interaction playground - the full station set, mouse-capable on desktop, VR button for headsets: webxr-interactions.pages.dev

Documentation

Full architecture notes, the behaviour catalogue and the playground demo live in the repository README.

License

MIT - see LICENSE.

Classes

ClassDescription
DialBehaviour-
DwellState-
EmitterMinimal synchronous emitter (insertion-ordered, unsubscribe-safe).
GrabBehaviour-
HingeBehaviour-
InteractionRuntime-
PressBehaviour-
PulseBehaviour-
SlideBehaviour-
TossScoreBehaviour-
VelocityTracker-

Interfaces

InterfaceDescription
Behaviour-
BehaviourContextEverything a behaviour can reach during a tick or a routed moment.
BehaviourFactoryContext-
DialConfig-
DwellConfigGaze support - first-class, per the design decision that gaze serves two roles:
DwellTick-
FeedbackAudioSinkWhatever the client uses to play a sound. Deliberately tiny: an id the client resolved to a buffer/sample of its own, and a gain. The core never loads, decodes or owns audio.
FeedbackIntent-
GazeConfig-
GrabConfig-
HingeConfig-
HitTesterResolves which interactable a ray or a proximity probe touches. Implemented by the adapter (three.js Raycaster, engine BVH, …). A provider that pre-resolves targeting (IWSDK) may make this redundant - provider hints always take precedence over hit-tester results.
InteractableDescriptor-
InteractableHit-
InteractableState-
InteractionDescriptor-
InteractionEvent-
InteractionRuntimeOptions-
InteractorInfoThe slice of a live interactor a behaviour may consume.
PointerBridge-
PressConfig-
PulseConfig-
RegisterPorts-
SlideConfig-
TossScoreConfig-
TransformPortThe transform/effect surface of ONE interactable's scene object. Behaviours write through this; the adapter owns how writes land (Object3D fields, ECS components, …).
VelocityTrackerOptions-

Type Aliases

Type AliasDescription
BehaviourConfig-
FeedbackCueA named moment a client may want to sonify/hapticise.
FeedbackListener-
GrabFulfilment-
InteractionEventListener-
InteractionEventType-
TransformOwnershipWho owns the interactable's transform while this behaviour is active.

Variables

VariableDescription
DWELL_DEFAULTS-
HEAD_GAZE_INTERACTOR_IDInteractor id used when gaze is synthesized from the head pose.

Functions

FunctionDescription
centeredUnitMap a centered hinge angle in `[-
clamp01Clamp to the unit interval.
clampDeadzoneZero out a signal below threshold, leaving larger values untouched.
clampSymClamp a value to the symmetric range `[-
createBehaviour-
createPointerBridgeCreate a pointer stream from the runtime's sampled sources. match picks which source drives it (default: any source with a ray - first pressed wins and holds until release).
decayExponential decay of value toward 0 over dt at ratePerSec.
easeOutBouncePenner's ease-out-bounce on t ∈ [0,1].
hingeAngleToHandHinge angle for a position-driven lever: given the hand's offset from the pivot resolved into the hinge plane - alongRest (toward the arm's rest direction) and alongSwing (the in-plane perpendicular) - the angle the arm should take to point at the hand, clamped to `±
holderPointHand position a hand-driven behaviour should track, best available.
normalize01Normalize a measured displacement/angle against a signed range to 0..1. Sign-agnostic: a range of -π/2 maps -π/2 → 1.
passedThroughHoopDid a tossed object pass through a hoop this frame? True when it crossed the hoop plane in the scoring direction (downward - prev above, curr at/below) while inside the ring (radial <= radius).
quatConjugate-
quatFromAxisAngleQuaternion from an axis (normalised) and an angle in radians.
quatMultiply-
rayAngleToAngular offset (radians) between a ray direction and the direction to a point.
rayPointDistanceClosest-approach distance from a ray to a point, and the along-ray t.
registerBehaviourKind-
registerDescriptorBuild a runtime's interactables from a portable descriptor.
resolveDwellConfig-
routeAudioToSinkClient opt-in: play a sound for the feedback intents named in cueMap. Cues absent from the map are ignored, so an app sonifies only the moments it has sounds for. Gain is the intent's intensity scaled by gainScale, capped at 1. Returns the unsubscribe.
routeHapticsToProviderClient opt-in: forward the haptic half of feedback intents to the input provider's pulse (which no-ops on sources without actuators - hands). Returns the unsubscribe. Audio remains entirely the client's concern.
springStepOne step of a damped harmonic oscillator (semi-implicit Euler). out[0] = next value, out[1] = next velocity. An under-damped spring (damping ratio ≈ 0.3-0.5) is what gives a pressed button its bounce. dt is clamped to 1/30 s so a long frame can't blow the integrator up.
vAdd-
vApplyQuatRotate a vector by a quaternion.
vCross-
vDistance-
vDot-
vLength-
vNormalize-
vScale-
vSub-
worldToLocalTransform a world-space point into the local frame of pose.