Skip to main content

@realitycollective/iwsdk-environment

The Meta IWSDK adapter for the Reality Collective WebXR Environment Extensions. Re-exports the engine-free @realitycollective/webxr-environment core, so this is the only package you install.

npm install @realitycollective/iwsdk-environment

Use

Setup is one call, and it registers the system that ticks the directors:

import {
registerEnvironment,
STOCK_PRESETS,
VOID,
} from "@realitycollective/iwsdk-environment";

const env = registerEnvironment(world, {
presets: STOCK_PRESETS,
initial: VOID,
audio: {
cues: [{ id: "hum", src: "/audio/hum.mp3", bus: "ambience", loop: true }],
},
});

env.environment.transition("dusk", { durationMs: 8000 });

Because the tick is an IWSDK system, the environment stops advancing when the session loses focus, exactly like the rest of the app. IWSDK hands systems a delta in seconds and the directors take milliseconds; that conversion happens once, inside the system.

What the adapter does

It drives IWSDK's own machinery rather than reaching past it to three.js:

  • Sky - a DomeGradient on the level root. IWSDK's EnvironmentSystem already hides authored backgrounds in an AR session, which is behaviour worth inheriting rather than fighting. A gradient's equator is derived from the same ramp the three.js adapter uses, so the two engines agree at the horizon and not only at the poles.
  • Lights - AmbientLightComponent and DirectionalLightComponent on transform entities. The key entity is rotated so its local -Z runs along the direction the light travels.
  • Image-based lighting - IBLGradient and IBLTexture. kind: "room" is native here: IWSDK's own component takes the string "room" for its built-in probe, so the one kind three.js has to approximate is the one this host has for free.
  • Authored skies - DomeTexture, with blurriness and rotation. The gradient dome is removed rather than left underneath, so the two never fight over the background.
  • Depth occlusion - DepthSensingSystem for the system-level settings and DepthOccludable per entity. IWSDK opts entities in one at a time by patching their materials, so scope: "all" needs an occludables option naming which entities the real world may hide - the entities are content, and content is yours. Whatever the port adds it remembers, so turning occlusion off never strips a component you added yourself. Without that option it reports unavailable and says so.
  • Light estimation - none. IWSDK 0.5.3 has no WebXR light estimation at all, and the port reports unsupported with that sentence rather than going quiet. Filed upstream as item 6.1. An ibl: { kind: "estimated" } asked for here is refused the same way, and the environment map you set yourself is left where it was.
  • The room - hits carry no distance here, because IWSDK moves a target entity to the hit and keeps the ray to itself; guessing from the head would be a different number wearing the same name. IWSDKWorldSensingPort queries the entities IWSDK's SceneUnderstandingSystem already created rather than reaching past it to the session, so planes, meshes and anchors come with IWSDK's own measurements. A hit test becomes an entity carrying EnvironmentRaycastTarget, which is how IWSDK casts; creating an anchor means creating an entity with XRAnchor, which is how IWSDK anchors.
  • Audio - one entity per voice, carrying AudioSource with playbackMode pinned to Overlap, plus the cue's own reference distance, rolloff, maximum distance, DistanceModel and cone. IWSDK hands the entity transform to the same three.js positional audio the other adapter uses, so a facing turns the entity and the cone angles are written in degrees. The core has already applied the cue's retrigger policy, and letting IWSDK apply its own on top would make restart mean two different things on two engines.

Every one of those is an IWSDK platform component with no app-side equivalent. The adapter creates no geometry: a floor would be a mesh, and meshes are the app's.

Fog is the exception. IWSDK has no fog component, so it is set on world.scene directly, with Fog / FogExp2 imported from three, a declared peer. Importing them through @iwsdk/core's export * from 'three' looked equivalent, but a consumer that excludes three from Vite's dependency optimizer cannot resolve names that only exist behind that star.

Knowing when a sound finished

IWSDK reports whether a source is playing, never that it has just stopped. So the port polls in the tick it is already given, and reports the end once a voice has been seen playing and then is not. A voice that never starts at all - a missing file, a failed decode - is reaped after startTimeoutMs (10 s by default) with a warning, rather than being tracked forever.

Passthrough

Push it in; do not expect the package to find it:

// `adapter` is the service framework's RuntimeAdapter; the flag it reports is
// derived from the LIVE session, not from what was requested.
adapter.onCapabilitiesChange((c) => env.environment.setPassthrough(c.passthrough));

A session belongs to the platform layer, not to the environment. setPassthrough(true) suppresses the sky and fog on top of whatever the app asked for, and turning it off restores exactly what was there - nothing has to remember what to put back.

Peer dependencies

@iwsdk/core >= 0.5.0 < 0.6.0, developed and tested against 0.5.3.

three >= 0.170.0, which every IWSDK application already carries (IWSDK projects alias it to super-three). The adapter imports three.js classes from three directly rather than through @iwsdk/core's star re-export, so it prebundles even when an application excludes three from Vite's dependency optimizer to transform its source.

Licence

MIT.

Classes

ClassDescription
AudioDirector-
EnvironmentDirector-
EnvironmentSystemThe per-frame tick.
IWSDKAudioPortPlays sounds.
IWSDKEnvironmentPortApplies an environment to a scene.
IWSDKWorldSensingPortWhat an adapter implements. Everything except observe is optional, because every host has a different subset and a missing method is reported rather than thrown.
WorldSensingDirector-

Interfaces

InterfaceDescription
AmbientLightSpecUniform illumination from every direction.
AudioConeA directional sound, in this package's own terms.
AudioCueA sound the app knows how to make. Registered once, played by id.
AudioDirectorOptions-
AudioPortPlays sounds.
AudioSpatialHow a positional voice gets quieter with distance.
AudioVoiceA sounding voice. Opaque to the app apart from the fields shown.
AudioVoiceRequestA play request with every decision already made: which file, how loud in absolute terms, looping or not, and where from. An adapter implementing AudioPort has no mixing left to do.
EnvironmentDirectorOptions-
EnvironmentPortApplies an environment to a scene.
EnvironmentPortHostWhat a port is handed so it can talk back. Implemented by the director.
EnvironmentSpecA PARTIAL description of the environment.
EstimatedLightingLighting the host measured from the real world.
FogExponentialFog that thickens exponentially with distance.
FogLinearFog that ramps between two distances.
HitTestRequestA standing question: "where does this ray meet the room".
IblEstimatedThe reflections the HOST measured from the real room.
IblGradientImage-based lighting: what the world reflects and how it lights everything that is not lit by the key light.
IblRoomThe host's built-in neutral room probe. Neither authored nor measured.
IblTexture@realitycollective/webxr-environment - the engine-free core.
IWSDKAudioPortOptions-
IWSDKEnvironmentOptions-
IWSDKEnvironmentPortOptions-
IWSDKEnvironmentSetup-
IWSDKWorldSensingPortOptions-
KeyLightSpecThe one directional light most scenes need: a sun, a moon, a work lamp.
LightEstimationSpecWhich slots a live estimate may take over.
OcclusionSourceHow the app would like the depth sensor configured.
OcclusionSpec-
PlayOptionsPer-play overrides. Everything here beats the cue's own value.
ResolvedEnvironmentEvery slot decided. This is what the director holds and adapters receive.
ResolvedLightEstimationspec with every default filled in, which is what a port receives.
ResolvedWorldDetection-
SensingReport-
SkyGradientA vertical gradient sky. top sits at the zenith and bottom at the nadir; horizon is where they meet, as a fraction of the way up the sphere.
SkySolidA single flat colour behind everything. The cheapest sky there is.
SkyTextureAn authored sky: an equirectangular image wrapped around the world.
TransitionOptions-
WorldAnchorA point in the real world the runtime promises to keep track of.
WorldChangeWhat moved since the last flush, per feature. Ids only; read the lists.
WorldDetectionSpecWhich of them to turn on. Everything defaults OFF: each one costs.
WorldHitWhere a ray met the world.
WorldMeshScanned geometry: furniture, walls, whatever the room-scan produced.
WorldPlaneA flat surface the host found: a floor, a wall, a table top.
WorldPoseWhere something is, in whatever reference space the app is using.
WorldSensingDirectorOptions-
WorldSensingPortWhat an adapter implements. Everything except observe is optional, because every host has a different subset and a missing method is reported rather than thrown.
WorldSensingPortHostWhat a port is handed so it can report what the room looks like.

Type Aliases

Type AliasDescription
AudioBusA mix group. Any string works; the conventional set is below.
CuePolicyWhat a second play request does while a cue is already sounding.
EasingFunctiont in 0..1 -> eased t in 0..1.
EasingNameThe named easing curves. A transition may also carry its own function.
EnvironmentBlendModeHow the host composites what is rendered over the real world.
EnvironmentListenerFired whenever what the port holds, or what the app asked for, changes.
EnvironmentSlot@realitycollective/webxr-environment - the engine-free core.
FogSpec@realitycollective/webxr-environment - the engine-free core.
IblSpec@realitycollective/webxr-environment - the engine-free core.
OcclusionModeHow hard the edge between real and virtual is drawn.
OcclusionScopeWhich content the real world is allowed to hide.
QuatA rotation as [x, y, z, w]. Normalised; the adapters guarantee that.
RgbAn sRGB colour, one channel per component, each in 0..1.
SensingFeature-
SensingListener-
SensingStateHow a feature is doing, in the order things usually go wrong:
SkySpec@realitycollective/webxr-environment - the engine-free core.
Vec3A direction or position in world space, metres. Need not be normalised.
WorldChangeListener-
WorldFeature-
WorldPlaneOrientationRoughly which way a surface faces.

Variables

VariableDescription
ALL_WORLD_DETECTIONEverything on: what setDetection(true) means.
DAWN-
DEFAULT_BUSThe bus a cue lands on when it does not name one.
DEFAULT_BUSESThe buses a director starts with, each at unity gain. Any other name works too - a bus springs into existence at unity the first time it is named. master is NOT in here: it is a separate scalar over all buses, so that "duck everything" and "turn the music down" never fight over one number.
DEFAULT_LIGHT_ESTIMATION-
DEFAULT_OCCLUSIONSoft edges over everything, from a GPU texture.
DUSK-
EMPTY_ENVIRONMENTAn environment with nothing in it: a black void with no light.
ENVIRONMENT_SLOTSThe slot names, in the order the director pushes them to the port.
NIGHT-
NOON-
OVERCAST-
SENSING_FEATURESThe sensor-backed features an adapter can report on.
STOCK_PRESETSAll of the above, ready to hand to EnvironmentDirector's presets.
VOIDNothing at all: black, unlit, empty. The starting point of a void scene.
WORLD_FEATURESThe world features an app can ask for. They are named separately from the environment's because a host can have any subset: Quest has planes, meshes and anchors; a phone AR runtime may have only planes and hit test.

Functions

FunctionDescription
clampClamp to an arbitrary range.
clamp01Clamp to the unit interval.
clearedFogThe same fog, pushed far enough away to be invisible.
deepEqualsStructural equality for the plain-data specs.
environmentForThe setup registered for a world, if registerEnvironment has run.
interpolateEnvironmentBlend two resolved environments. t is the EASED progress in 0..1; the director has already applied the curve, so this stays linear per channel.
lerpLinear interpolation. t is NOT clamped - the callers already clamp it.
lerpRgbComponent-wise colour interpolation.
registerEnvironmentOne call to wire the environment (and optionally the audio) into an IWSDK world, and register the system that ticks them.
resolveEasingResolve an easing name or function to a function. Unknown names are linear.
resolveLightEstimation-
resolveWorldDetection-
rgbEqualsTrue when two colours are identical channel for channel.
rgbFromHex0xff8800 -> [1, 0.533…, 0]. Convenience for authoring presets.
rgbToHex[1, 0.533…, 0] -> 0xff8800. The inverse, for adapters that want a hex.
unsupportedThe report a feature has before any adapter has said otherwise.
worldEntryChangedHas this entry changed since the last one with the same id?