@realitycollective/iwsdk-environment 0.1.0-preview.0 → 0.1.0-preview.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- Change log for the Reality Collective WebXR Environment Extensions packages. All three packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
3
+ Change log for the Reality Collective WebXR Environment Extensions packages. All four packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
6
6
 
@@ -8,15 +8,43 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
8
8
 
9
9
  ### Added
10
10
 
11
+ - `verify:pack` now lints the shape of what ships: publint over every package directory, with warnings counted as errors, and attw (Are The Types Wrong) over every packed tarball, resolving the published types under node10, node16 and bundler resolution. `cjs-resolves-to-esm` is ignored by design, because every package is ESM-only and a require() caller is expected to use a dynamic import. Both run offline on the tarballs the script already builds; `publint` and `@arethetypeswrong/cli` are dev dependencies. The script stays identical across the Reality Collective repositories.
11
12
  - `@realitycollective/webxr-environment` - the engine-free core. `EnvironmentDirector` holds a sky (gradient or solid), fog (linear or exponential), an ambient light and a key light as plain data; interpolates between named presets over a host-supplied `update(deltaMs)`; pushes only the slots that actually changed to an `EnvironmentPort`; and applies passthrough as a suppression over what the app asked for, so nothing has to remember what to restore. A partial `EnvironmentSpec` inherits the slots it omits and turns off the ones it sets to `null`, which is what makes presets composable. Six example presets (`void`, `dawn`, `noon`, `dusk`, `night`, `overcast`) to copy, and `clearedFog`, the helper that makes fog easeable in.
12
13
  - `@realitycollective/webxr-environment` - `AudioDirector`: a cue registry, a bus and master mix with mute kept separate from gain, per-cue retrigger policy (`overlap` / `restart` / `ignore`), a per-cue minimum retrigger interval, and voice tracking. The `AudioPort` is handed an absolute resolved gain and has no mixing left to do. A silent one-shot is never started; a silent loop is, so that unmuting mid-flight finds the ambience bed already running.
13
14
  - `@realitycollective/threejs-environment` - the three.js adapter. Gradient skies become an equirectangular `DataTexture` generated by a pure function and regenerated in place across a transition; `Fog` / `FogExp2` are mutated rather than rebuilt while the kind holds; the key light is positioned from the direction light travels. Audio runs over `Audio` / `PositionalAudio`, and a play that arrives before its buffer has decoded is held rather than dropped, so the first press of a session is not silent.
14
15
  - `@realitycollective/iwsdk-environment` - the Meta IWSDK adapter, driving IWSDK's own `DomeGradient`, light components, transform entities and `AudioSource` machinery rather than reaching past them to three.js. One-call `registerEnvironment(world)` setup that also registers the ECS system ticking both directors, converting IWSDK's seconds to the directors' milliseconds once. Fog is the one exception and is set on `world.scene`, since IWSDK has no fog component. Playback end is inferred by polling, because IWSDK reports only whether a source is playing; a voice that never starts is reaped with a warning rather than tracked forever.
16
+ - `@realitycollective/webxr-environment` - an **image-based lighting slot**. `IblSpec` is a gradient, an authored image or the host's own room probe, with intensity and a Y rotation, interpolated like every other slot. Without it a physically-based material had nothing to reflect: every host keeps the sky and the environment map as two separate facilities.
17
+ - `@realitycollective/webxr-environment` - **authored skies** (`SkySpec` kind `"texture"`, with intensity, rotation and blur) and an optional **horizon stop** on a gradient sky. The horizon colour exists because IWSDK's dome has always been a three-stop ramp and the adapter was deriving the middle colour; an app that puts a bright band at eye level can now say so and have both engines agree.
18
+ - `@realitycollective/webxr-environment` - **the sensing seam**. `EnvironmentPort.observe(host)` is the one inbound path: a port reports `unsupported` / `unavailable` / `pending` / `active` per feature with a sentence saying why, and reports measured lighting. `EnvironmentDirector.getSensing()` and `onSensing()` read it. A report never changes what the app asked for; it changes what the app can be told - which matters because a sensor that is doing nothing looks exactly like a sensor that is working.
19
+ - `@realitycollective/webxr-environment` - **depth occlusion**. `setOcclusion(spec)` reaches the port only while passthrough is on, and is remembered across passthrough changes. `OcclusionSpec` carries the union of what the hosts actually expose: three shader modes, a scope, softness, the WebXR session preferences (usage, data format, depth type, `matchDepthView`) and an update-rate cap. While occlusion is ACTIVE the sky is suppressed as well, because three.js stops drawing `scene.background` itself when its depth sensing runs.
20
+ - `@realitycollective/webxr-environment` - **light estimation**. `setLightEstimation(true | spec)` lets the host's measurement of the real room take over the ambient, key and ibl slots while it is measuring, and hands them straight back when it stops. The estimate arrives as the same spec types the app writes by hand, so it can be read, blended or ignored.
21
+ - `@realitycollective/webxr-environment` - **blend-mode-aware passthrough**. `setPassthrough` now takes `"opaque" | "alpha-blend" | "additive"` as well as a boolean, and `passthroughSuppresses` can differ per mode. On an additive display the rendered image is ADDED to the world, so a dark fog is invisible rather than dimming; an app can now do the opposite thing on each rather than choosing one compromise.
22
+ - `@realitycollective/webxr-environment` - **per-cue spatial attenuation** (`AudioCue.spatial`: reference distance, rolloff, maximum distance, curve) and a `ui` bus in the default set. All three hosts expose those four per source; the contract previously flattened them to one number chosen by the adapter.
23
+ - `@realitycollective/webxr-environment` - `EnvironmentPort.update(deltaMs)`, optional, called from the director's own `update`. Only sensor-backed ports need it: three.js reports depth through a renderer flag that flips some frames after the session starts, and a light estimate has to be read out of the current `XRFrame` or not at all.
24
+ - `@realitycollective/xrblocks-environment` - **a Google XR Blocks adapter**, EXPERIMENTAL, built on the three.js adapter the way `xrblocks-interactions` is built on `threejs-interactions`. Occlusion registers as a client of XR Blocks' `Depth` manager and picks its blur; light estimation reads the `Lighting` manager, which already owns the WebXR half. XR Blocks is never imported - its managers are described structurally, as in the other XR Blocks adapters in this estate - and anything configured before this adapter could ask is named in the report rather than dropped, including the warning that XR Blocks may be lighting the scene itself.
25
+ - `@realitycollective/webxr-environment` - **world sensing**, as a separate component on the same seam. `WorldSensingDirector` asks for planes, meshes and anchors, keeps one copy of each registry, and emits `added` / `updated` / `removed` by id so an app can spawn content on a new surface and drop it when the surface goes. Standing hit-test requests, anchor creation, and the host's own semantic labels passed through rather than mapped to a closed set. It reports detected geometry and creates none: a mesh's vertices are the runtime's buffers by reference, and what to draw on a table is the app's.
26
+ - `@realitycollective/threejs-environment`, `@realitycollective/iwsdk-environment`, `@realitycollective/xrblocks-environment` - world-sensing ports for all three hosts. three.js reads the `XRFrame` directly and measures a mesh once per `lastChangedTime`; IWSDK queries the entities its `SceneUnderstandingSystem` already made and casts through `EnvironmentRaycastTarget`; XR Blocks reads its `PlaneDetector` and `MeshDetector`, and reports anchors and hit testing as unsupported because it places objects for you rather than answering where a ray lands.
27
+ - `@realitycollective/webxr-environment` - `IblSpec` gained `{ kind: "estimated" }`: the reflections the host measured from the real room. It is a MARKER rather than data, because a reflection cube map is a live texture on a GPU and cannot be a `src` string without ceasing to be what it is. The adapter that measured it applies it; the others report that they cannot. Arrives on its own through `setLightEstimation({ ibl: true })`.
28
+ - `@realitycollective/webxr-environment` - **directional sound**. `AudioSpatial.cone` says how narrowly a cue points (inner and outer widths in RADIANS, like every other angle here, plus the gain outside), and `PlayOptions.facing` says which way this particular one is turned - the direction the sound travels, the same convention `KeyLightSpec.direction` uses. Both hosts want degrees and both point a source along +Z, because both are wrapping the same Web Audio panner, and each adapter converts. Without it a television, a PA horn and a person talking all played as glowing orbs of sound on every engine.
29
+ - `@realitycollective/webxr-environment` - `WorldSensingDirector.meshesLabelled`, the counterpart of `planesLabelled`. A wall arrives as a detected mesh as often as a plane, and having the helper on one side only also hid that the matching is case-insensitive.
15
30
  - `demos/playground` - a standalone three.js/WebXR playground: preset transitions, a passthrough toggle that shows suppression restoring the environment exactly, and live bus/master sliders moving voices that are already sounding. It builds its own floor and objects, and binds a DOM event to `audio.play`, because both are the app's job.
16
31
 
32
+ ### Fixed
33
+
34
+ - `@realitycollective/iwsdk-environment` imported three.js's `Vector3`, `Quaternion`, `Fog` and `FogExp2` through `@iwsdk/core`, which only provides them by `export * from 'three'`. That resolves in node and in every demo, and fails a consumer whose Vite config excludes `three` from dependency optimization, which any app that transforms three's source does: esbuild cannot enumerate a star re-export it is not bundling, and the dev server stops with `No matching export in "@iwsdk/core" for import "Vector3"`, a message that names neither the package nor the cause. The audio, environment and world-sensing ports now import those classes from `three`, declared as a peer dependency (`>=0.170.0`, the range the three.js adapter carries), which every IWSDK application already has. `@types/three` is pinned to `0.185.4` across the workspace (root `overrides`), the typings for the super-three 0.185 the workspace runs, so the tree holds one copy of the three typings rather than the two that made the adapter's tests import `Scene` through `@iwsdk/core`. The Anatomy Atlas XR client reported the failure against the UI Extensions adapter; the estate sweep found the same import here.
35
+ - Two gates, shared with every Reality Collective repository. `packages/webxr-environment/test/import-surface.test.ts` runs `scripts/import-surface.mjs` over every published package and fails an import of a name that a dependency only re-exports from another package, and any bare import of a package the manifest does not declare. `packages/iwsdk-environment/test/prebundle.test.ts` runs the dependency optimizer of every bundler `scripts/release.config.json` names (`scripts/prebundle-check.mjs`) over the adapter next to `@iwsdk/core`, once with defaults and once with every package `@iwsdk/core` re-exports wholesale excluded, so the consumer path is exercised on every `npm test`. It runs on **Vite 7 and Vite 8**, because this fault belongs to a bundler rather than to bundling: Vite 7 optimizes with esbuild, which requires every named import to be statically enumerable and so rejects a name behind an external `export *`, while Vite 8 optimizes with rolldown, which resolves the same import lazily through a namespace object and accepts it. Testing only one of them would say nothing about consumers on the other. The versions under test are ordinary devDependencies installed under npm aliases, `vite-7` and `vite-8`, the same mechanism the workspace already uses for `three`; add an entry to `prebundle.bundlers` to support another. Each run also feeds the optimizer a **deliberately broken copy** of the adapter, carrying one extra module that imports a name the host only re-exports wholesale, and asserts the bundler's verdict matches that bundler's `detectsStarHops` flag. The probed name is discovered from the host's own surface rather than hard-coded, so it stays valid as that surface changes. This is what stops the check becoming decoration: on a permissive bundler a gate that can only pass reports safety it never verified, and the suite now fails if a bundler's strictness moves in either direction, or if no supported bundler can detect the fault at all. Transpiling uses the TypeScript compiler the repository already builds with, so no bundler is a hidden requirement of the harness itself. Both were red before this fix.
36
+ - **A hand ray is now cast from the hand.** The three.js world-sensing port used to bind every hit test to the viewer's reference space and mention in a detail string that hands were not really supported, which meant the most common AR gesture - point at the floor, place something - silently aimed from the head. It now looks the input source up by handedness, binds to its `targetRaySpace`, waits visibly while there is no such source, and unbinds when the hand goes away.
37
+ - **`HitTestRequest.offsetRay` is no longer dropped.** It becomes an `XRRay` (injectable, defaulting to the browser's) and is passed to the runtime. When no `XRRay` is available the request still works and the report says the offset was ignored, rather than quietly aiming somewhere else.
38
+ - **`WorldHit.distance` is filled where a host can fill it.** The three.js port poses the ray's own space once per frame and measures from there. IWSDK keeps its ray to itself, so it reports `null`, and the contract now says exactly what `null` means and what it does not.
39
+ - **Creating an anchor turns anchor detection on.** `createAnchor` used to hand back an id that nothing would ever update unless the app had separately asked to detect anchors - content pinned to it would never move again. The director now enables it and reports that it did.
40
+
17
41
  ### Notes
18
42
 
19
43
  - The core has **no runtime dependencies at all**, and its architecture test asserts it: no engine, no service framework, no input contracts. No sibling package is imported, named in a type, or asserted against in a test either. Passthrough arrives through `setPassthrough(boolean)` rather than by reading a session, and a sound is played because the APP subscribed something to `play(cueId)` - if a source is present the app binds it, and if it is not, nothing here notices. Those two boundaries are what keep this package a leaf.
44
+ - The three.js adapter uses three.js's **own** depth-sensing occlusion, which has been in the renderer since r158 and draws its occlusion mesh before everything else. There is no hand-rolled depth-priming pass here: writing one would duplicate the renderer in the one place this estate cannot afford drift, GL state that three.js caches. What the adapter does instead is decide whether the request can be served - a session, the `depth-sensing` feature, gpu-optimized depth, a scope three.js can honour - and report the reason when it cannot.
45
+ - The IWSDK adapter takes an `occludables` option for `scope: "all"`, because IWSDK opts entities into occlusion one at a time and the entities are content, which is the app's. It never removes a `DepthOccludable` the app added itself. IWSDK 0.5.3 has no light estimation at all, and that is reported rather than left silent; both gaps are filed in [`docs/UPSTREAM_ENHANCEMENTS.md`](docs/UPSTREAM_ENHANCEMENTS.md) as items 6.1 and 6.2.
46
+ - World sensing is a **second director**, not more slots. Sky, fog and lighting are things the app describes and the host draws; planes and meshes are things the host measures and the app reads, and their consumers are placement, locomotion, physics and interaction rather than the eye. An app that wants only a sky pays for none of it.
47
+ - **A body solver was considered and declined.** No host exposes a body, so it fails the boundary test this repository is built on; see [`docs/BOUNDARY.md`](docs/BOUNDARY.md). Planes, meshes, anchors and hit test are real platform sensing but feed placement and interaction rather than the environment, and are recorded there as a separate component rather than as slots of `EnvironmentSpec`.
20
48
  - Every slot the environment describes is a PLATFORM facility each host exposes differently - `scene.background` and `Fog` on three.js, `DomeGradient` and `AmbientLightComponent` on IWSDK. CONTENT is the app's: no geometry is created by either adapter, and the stock presets are examples to copy rather than art direction. See [`docs/BOUNDARY.md`](docs/BOUNDARY.md) for the boundary in full, and [`docs/UPSTREAM_ENHANCEMENTS.md`](docs/UPSTREAM_ENHANCEMENTS.md) for the gaps that belong to the other packages rather than this one.
21
49
 
22
50
  [0.1.0]: https://github.com/realitycollective/WebXR-Environment/commits/main
package/README.md CHANGED
@@ -36,11 +36,16 @@ It drives IWSDK's own machinery rather than reaching past it to three.js:
36
36
 
37
37
  - **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.
38
38
  - **Lights** - `AmbientLightComponent` and `DirectionalLightComponent` on transform entities. The key entity is rotated so its local `-Z` runs along the direction the light travels.
39
- - **Audio** - one entity per voice, carrying `AudioSource` with `playbackMode` pinned to `Overlap`. 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.
39
+ - **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.
40
+ - **Authored skies** - `DomeTexture`, with blurriness and rotation. The gradient dome is removed rather than left underneath, so the two never fight over the background.
41
+ - **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.
42
+ - **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.
43
+ - **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.
44
+ - **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.
40
45
 
41
46
  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.
42
47
 
43
- **Fog is the exception.** IWSDK has no fog component, so it is set on `world.scene` directly, with `Fog` / `FogExp2` imported from `@iwsdk/core` (one three.js instance, reached through the framework's re-export).
48
+ **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.
44
49
 
45
50
  ## Knowing when a sound finished
46
51
 
@@ -58,10 +63,12 @@ adapter.onCapabilitiesChange((c) => env.environment.setPassthrough(c.passthrough
58
63
 
59
64
  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.
60
65
 
61
- ## Peer dependency
66
+ ## Peer dependencies
62
67
 
63
68
  `@iwsdk/core >= 0.5.0 < 0.6.0`, developed and tested against 0.5.3.
64
69
 
70
+ `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.
71
+
65
72
  ## Licence
66
73
 
67
74
  MIT.
@@ -21,7 +21,8 @@
21
21
  * A voice that never starts at all (a missing file, a decode failure) would
22
22
  * otherwise be tracked forever, so it is reaped after `startTimeoutMs`.
23
23
  */
24
- import { AudioSource, AudioUtils, PlaybackMode, Transform } from "@iwsdk/core";
24
+ import { AudioSource, AudioUtils, DistanceModel, PlaybackMode, Transform, } from "@iwsdk/core";
25
+ import { Quaternion, Vector3 } from "three";
25
26
  export class IWSDKAudioPort {
26
27
  #world;
27
28
  #parent;
@@ -46,6 +47,22 @@ export class IWSDKAudioPort {
46
47
  view[1] = request.at[1];
47
48
  view[2] = request.at[2];
48
49
  }
50
+ // IWSDK's audio system hands the entity's transform to the same three.js
51
+ // `PositionalAudio` the other adapter uses, so forward is +Z here too and
52
+ // the entity is turned to point that axis the way the sound travels.
53
+ const towards = request.facing ?? null;
54
+ if (towards !== null) {
55
+ const facing = TEMP_FACING.set(towards[0], towards[1], towards[2]);
56
+ if (facing.lengthSq() > 0) {
57
+ TEMP_ROTATION.setFromUnitVectors(AUDIO_FORWARD, facing.normalize());
58
+ const view = entity.getVectorView(Transform, "orientation");
59
+ view[0] = TEMP_ROTATION.x;
60
+ view[1] = TEMP_ROTATION.y;
61
+ view[2] = TEMP_ROTATION.z;
62
+ view[3] = TEMP_ROTATION.w;
63
+ }
64
+ }
65
+ const attenuation = request.spatial;
49
66
  entity.addComponent(AudioSource, {
50
67
  src: request.cue.src,
51
68
  volume: request.gain,
@@ -54,6 +71,25 @@ export class IWSDKAudioPort {
54
71
  autoplay: false,
55
72
  // The core already applied the cue's policy before we got here.
56
73
  playbackMode: PlaybackMode.Overlap,
74
+ // Anything the cue did not describe is left to IWSDK's own defaults,
75
+ // which is why these are spread in rather than defaulted here: this port
76
+ // has no opinion about how far a sound carries, and inventing one would
77
+ // make the same cue sound different on each engine.
78
+ ...(attenuation?.refDistance === undefined ? {} : { refDistance: attenuation.refDistance }),
79
+ ...(attenuation?.rolloffFactor === undefined
80
+ ? {}
81
+ : { rolloffFactor: attenuation.rolloffFactor }),
82
+ ...(attenuation?.maxDistance === undefined ? {} : { maxDistance: attenuation.maxDistance }),
83
+ ...(attenuation?.model === undefined ? {} : { distanceModel: distanceModel(attenuation.model) }),
84
+ // Radians in the contract, degrees on the component - IWSDK passes these
85
+ // straight to the same Web Audio panner three.js uses.
86
+ ...(attenuation?.cone === undefined
87
+ ? {}
88
+ : {
89
+ coneInnerAngle: toDegrees(attenuation.cone.inner),
90
+ coneOuterAngle: toDegrees(attenuation.cone.outer),
91
+ coneOuterGain: attenuation.cone.outsideGain,
92
+ }),
57
93
  });
58
94
  this.#voices.set(request.voiceId, { entity, request, started: false, waitedMs: 0 });
59
95
  AudioUtils.play(entity);
@@ -108,4 +144,20 @@ export class IWSDKAudioPort {
108
144
  voice.entity.destroy();
109
145
  }
110
146
  }
147
+ /** Our distance model names onto IWSDK's enum, which holds the same three. */
148
+ function distanceModel(model) {
149
+ if (model === "linear")
150
+ return DistanceModel.Linear;
151
+ if (model === "exponential")
152
+ return DistanceModel.Exponential;
153
+ return DistanceModel.Inverse;
154
+ }
155
+ /** Web Audio points a source along +Z, and IWSDK's audio system follows it. */
156
+ const AUDIO_FORWARD = new Vector3(0, 0, 1);
157
+ const TEMP_FACING = new Vector3();
158
+ const TEMP_ROTATION = new Quaternion();
159
+ /** Radians in the contract; degrees on the component. */
160
+ function toDegrees(radians) {
161
+ return (radians * 180) / Math.PI;
162
+ }
111
163
  //# sourceMappingURL=audio-port.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio-port.js","sourceRoot":"","sources":["../src/audio-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `AudioPort` for Meta IWSDK, over `AudioSource` + `AudioUtils`.\n *\n * ---------------------------------------------------------------------------\n * ONE ENTITY PER VOICE\n * ---------------------------------------------------------------------------\n * IWSDK's audio is a component on an entity, with its own pooling and its own\n * retrigger policy per source. Reusing one entity per CUE would hand those\n * decisions back to IWSDK and make `restart` and `ignore` mean something\n * different here than on three.js, which is the divergence this stack exists\n * to remove. So a voice is an entity: the director's policy wins, every voice\n * is individually stoppable, and IWSDK's own `playbackMode` is pinned to\n * `Overlap` so it never second-guesses the core.\n *\n * ---------------------------------------------------------------------------\n * KNOWING WHEN A SOUND FINISHED\n * ---------------------------------------------------------------------------\n * IWSDK reports whether a source IS playing, never that it has just stopped.\n * So this port polls in `update()` - which the director already calls - and\n * reports the end once a voice has been observed playing and then is not.\n * A voice that never starts at all (a missing file, a decode failure) would\n * otherwise be tracked forever, so it is reaped after `startTimeoutMs`.\n */\nimport { AudioSource, AudioUtils, PlaybackMode, Transform, type Entity, type World } from \"@iwsdk/core\";\nimport type { AudioPort, AudioVoiceRequest } from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKAudioPortOptions {\n /** Parent for the voice entities. Defaults to the world's default parent. */\n readonly parent?: Entity;\n /**\n * How long to wait for a voice to start before giving up on it,\n * milliseconds. Default 10 000 - generous, because a cold asset fetch on a\n * headset over hotel wifi is slower than anyone's patience.\n */\n readonly startTimeoutMs?: number;\n}\n\ninterface Voice {\n readonly entity: Entity;\n readonly request: AudioVoiceRequest;\n started: boolean;\n waitedMs: number;\n}\n\nexport class IWSDKAudioPort implements AudioPort {\n readonly #world: World;\n readonly #parent: Entity | undefined;\n readonly #startTimeoutMs: number;\n readonly #voices = new Map<number, Voice>();\n\n #disposed = false;\n\n constructor(world: World, options: IWSDKAudioPortOptions = {}) {\n this.#world = world;\n this.#parent = options.parent;\n this.#startTimeoutMs = options.startTimeoutMs ?? 10_000;\n }\n\n start(request: AudioVoiceRequest): void {\n if (this.#disposed) {\n request.ended();\n return;\n }\n const entity = this.#world.createTransformEntity(\n undefined,\n this.#parent === undefined ? undefined : { parent: this.#parent },\n );\n const positional = request.at !== null || (request.cue.positional ?? false);\n if (request.at !== null) {\n const view = entity.getVectorView(Transform, \"position\");\n view[0] = request.at[0];\n view[1] = request.at[1];\n view[2] = request.at[2];\n }\n entity.addComponent(AudioSource, {\n src: request.cue.src,\n volume: request.gain,\n loop: request.loop,\n positional,\n autoplay: false,\n // The core already applied the cue's policy before we got here.\n playbackMode: PlaybackMode.Overlap,\n });\n this.#voices.set(request.voiceId, { entity, request, started: false, waitedMs: 0 });\n AudioUtils.play(entity);\n }\n\n stop(voiceId: number): void {\n const voice = this.#voices.get(voiceId);\n if (voice === undefined) return;\n this.#voices.delete(voiceId);\n this.#retire(voice);\n }\n\n setGain(voiceId: number, gain: number): void {\n const voice = this.#voices.get(voiceId);\n if (voice === undefined) return;\n AudioUtils.setVolume(voice.entity, gain);\n }\n\n update(deltaMs: number): void {\n if (this.#disposed || this.#voices.size === 0) return;\n for (const [id, voice] of [...this.#voices]) {\n // A looping voice ends when someone stops it, never on its own.\n if (voice.request.loop) continue;\n const playing = AudioUtils.isPlaying(voice.entity);\n if (playing) {\n voice.started = true;\n continue;\n }\n if (!voice.started) {\n voice.waitedMs += deltaMs;\n if (voice.waitedMs < this.#startTimeoutMs) continue;\n console.warn(\n `[iwsdk-environment] cue \"${voice.request.cue.id}\" never started (${voice.request.cue.src})`,\n );\n }\n this.#voices.delete(id);\n this.#retire(voice);\n voice.request.ended();\n }\n }\n\n dispose(): void {\n if (this.#disposed) return;\n this.#disposed = true;\n for (const [id, voice] of [...this.#voices]) {\n this.#voices.delete(id);\n this.#retire(voice);\n }\n }\n\n #retire(voice: Voice): void {\n AudioUtils.stop(voice.entity);\n voice.entity.destroy();\n }\n}\n"]}
1
+ {"version":3,"file":"audio-port.js","sourceRoot":"","sources":["../src/audio-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `AudioPort` for Meta IWSDK, over `AudioSource` + `AudioUtils`.\n *\n * ---------------------------------------------------------------------------\n * ONE ENTITY PER VOICE\n * ---------------------------------------------------------------------------\n * IWSDK's audio is a component on an entity, with its own pooling and its own\n * retrigger policy per source. Reusing one entity per CUE would hand those\n * decisions back to IWSDK and make `restart` and `ignore` mean something\n * different here than on three.js, which is the divergence this stack exists\n * to remove. So a voice is an entity: the director's policy wins, every voice\n * is individually stoppable, and IWSDK's own `playbackMode` is pinned to\n * `Overlap` so it never second-guesses the core.\n *\n * ---------------------------------------------------------------------------\n * KNOWING WHEN A SOUND FINISHED\n * ---------------------------------------------------------------------------\n * IWSDK reports whether a source IS playing, never that it has just stopped.\n * So this port polls in `update()` - which the director already calls - and\n * reports the end once a voice has been observed playing and then is not.\n * A voice that never starts at all (a missing file, a decode failure) would\n * otherwise be tracked forever, so it is reaped after `startTimeoutMs`.\n */\nimport {\n AudioSource,\n AudioUtils,\n DistanceModel,\n PlaybackMode,\n Transform,\n type Entity,\n type World,\n} from \"@iwsdk/core\";\nimport { Quaternion, Vector3 } from \"three\";\nimport type { AudioPort, AudioVoiceRequest } from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKAudioPortOptions {\n /** Parent for the voice entities. Defaults to the world's default parent. */\n readonly parent?: Entity;\n /**\n * How long to wait for a voice to start before giving up on it,\n * milliseconds. Default 10 000 - generous, because a cold asset fetch on a\n * headset over hotel wifi is slower than anyone's patience.\n */\n readonly startTimeoutMs?: number;\n}\n\ninterface Voice {\n readonly entity: Entity;\n readonly request: AudioVoiceRequest;\n started: boolean;\n waitedMs: number;\n}\n\nexport class IWSDKAudioPort implements AudioPort {\n readonly #world: World;\n readonly #parent: Entity | undefined;\n readonly #startTimeoutMs: number;\n readonly #voices = new Map<number, Voice>();\n\n #disposed = false;\n\n constructor(world: World, options: IWSDKAudioPortOptions = {}) {\n this.#world = world;\n this.#parent = options.parent;\n this.#startTimeoutMs = options.startTimeoutMs ?? 10_000;\n }\n\n start(request: AudioVoiceRequest): void {\n if (this.#disposed) {\n request.ended();\n return;\n }\n const entity = this.#world.createTransformEntity(\n undefined,\n this.#parent === undefined ? undefined : { parent: this.#parent },\n );\n const positional = request.at !== null || (request.cue.positional ?? false);\n if (request.at !== null) {\n const view = entity.getVectorView(Transform, \"position\");\n view[0] = request.at[0];\n view[1] = request.at[1];\n view[2] = request.at[2];\n }\n // IWSDK's audio system hands the entity's transform to the same three.js\n // `PositionalAudio` the other adapter uses, so forward is +Z here too and\n // the entity is turned to point that axis the way the sound travels.\n const towards = request.facing ?? null;\n if (towards !== null) {\n const facing = TEMP_FACING.set(towards[0], towards[1], towards[2]);\n if (facing.lengthSq() > 0) {\n TEMP_ROTATION.setFromUnitVectors(AUDIO_FORWARD, facing.normalize());\n const view = entity.getVectorView(Transform, \"orientation\");\n view[0] = TEMP_ROTATION.x;\n view[1] = TEMP_ROTATION.y;\n view[2] = TEMP_ROTATION.z;\n view[3] = TEMP_ROTATION.w;\n }\n }\n const attenuation = request.spatial;\n entity.addComponent(AudioSource, {\n src: request.cue.src,\n volume: request.gain,\n loop: request.loop,\n positional,\n autoplay: false,\n // The core already applied the cue's policy before we got here.\n playbackMode: PlaybackMode.Overlap,\n // Anything the cue did not describe is left to IWSDK's own defaults,\n // which is why these are spread in rather than defaulted here: this port\n // has no opinion about how far a sound carries, and inventing one would\n // make the same cue sound different on each engine.\n ...(attenuation?.refDistance === undefined ? {} : { refDistance: attenuation.refDistance }),\n ...(attenuation?.rolloffFactor === undefined\n ? {}\n : { rolloffFactor: attenuation.rolloffFactor }),\n ...(attenuation?.maxDistance === undefined ? {} : { maxDistance: attenuation.maxDistance }),\n ...(attenuation?.model === undefined ? {} : { distanceModel: distanceModel(attenuation.model) }),\n // Radians in the contract, degrees on the component - IWSDK passes these\n // straight to the same Web Audio panner three.js uses.\n ...(attenuation?.cone === undefined\n ? {}\n : {\n coneInnerAngle: toDegrees(attenuation.cone.inner),\n coneOuterAngle: toDegrees(attenuation.cone.outer),\n coneOuterGain: attenuation.cone.outsideGain,\n }),\n });\n this.#voices.set(request.voiceId, { entity, request, started: false, waitedMs: 0 });\n AudioUtils.play(entity);\n }\n\n stop(voiceId: number): void {\n const voice = this.#voices.get(voiceId);\n if (voice === undefined) return;\n this.#voices.delete(voiceId);\n this.#retire(voice);\n }\n\n setGain(voiceId: number, gain: number): void {\n const voice = this.#voices.get(voiceId);\n if (voice === undefined) return;\n AudioUtils.setVolume(voice.entity, gain);\n }\n\n update(deltaMs: number): void {\n if (this.#disposed || this.#voices.size === 0) return;\n for (const [id, voice] of [...this.#voices]) {\n // A looping voice ends when someone stops it, never on its own.\n if (voice.request.loop) continue;\n const playing = AudioUtils.isPlaying(voice.entity);\n if (playing) {\n voice.started = true;\n continue;\n }\n if (!voice.started) {\n voice.waitedMs += deltaMs;\n if (voice.waitedMs < this.#startTimeoutMs) continue;\n console.warn(\n `[iwsdk-environment] cue \"${voice.request.cue.id}\" never started (${voice.request.cue.src})`,\n );\n }\n this.#voices.delete(id);\n this.#retire(voice);\n voice.request.ended();\n }\n }\n\n dispose(): void {\n if (this.#disposed) return;\n this.#disposed = true;\n for (const [id, voice] of [...this.#voices]) {\n this.#voices.delete(id);\n this.#retire(voice);\n }\n }\n\n #retire(voice: Voice): void {\n AudioUtils.stop(voice.entity);\n voice.entity.destroy();\n }\n}\n\n/** Our distance model names onto IWSDK's enum, which holds the same three. */\nfunction distanceModel(model: \"linear\" | \"inverse\" | \"exponential\"): string {\n if (model === \"linear\") return DistanceModel.Linear;\n if (model === \"exponential\") return DistanceModel.Exponential;\n return DistanceModel.Inverse;\n}\n\n/** Web Audio points a source along +Z, and IWSDK's audio system follows it. */\nconst AUDIO_FORWARD = new Vector3(0, 0, 1);\nconst TEMP_FACING = new Vector3();\nconst TEMP_ROTATION = new Quaternion();\n\n/** Radians in the contract; degrees on the component. */\nfunction toDegrees(radians: number): number {\n return (radians * 180) / Math.PI;\n}\n"]}
@@ -29,7 +29,7 @@
29
29
  * through `writeColour` for that reason - it is not a style choice.
30
30
  */
31
31
  import { type Entity, type World } from "@iwsdk/core";
32
- import type { AmbientLightSpec, EnvironmentPort, FogSpec, KeyLightSpec, SkySpec } from "@realitycollective/webxr-environment";
32
+ import type { AmbientLightSpec, EnvironmentPort, EnvironmentPortHost, FogSpec, IblSpec, KeyLightSpec, OcclusionSpec, ResolvedLightEstimation, SkySpec } from "@realitycollective/webxr-environment";
33
33
  export interface IWSDKEnvironmentPortOptions {
34
34
  /**
35
35
  * Where the light entities are parented. Defaults to the active level root,
@@ -37,13 +37,54 @@ export interface IWSDKEnvironmentPortOptions {
37
37
  * with the level.
38
38
  */
39
39
  readonly parent?: Entity;
40
- /** Dome brightness multiplier. Default 1. */
40
+ /** Dome brightness multiplier, used when a sky does not name its own. Default 1. */
41
41
  readonly skyIntensity?: number;
42
+ /**
43
+ * Which entities the real world is allowed to hide, for `scope: "all"`.
44
+ *
45
+ * IWSDK opts entities INTO occlusion one at a time, by patching the
46
+ * materials under each one, so somebody has to say which entities those
47
+ * are. It cannot be this port: the entities are content, content is the
48
+ * app's, and walking a scene graph looking for meshes would be this package
49
+ * guessing at the app's structure.
50
+ *
51
+ * With no list, `scope: "all"` reports `unavailable` and says why. An app
52
+ * that would rather tag its own entities uses `scope: "tagged"` and adds
53
+ * `DepthOccludable` itself; then this port only configures the system.
54
+ */
55
+ readonly occludables?: () => Iterable<Entity>;
42
56
  }
43
57
  export declare class IWSDKEnvironmentPort implements EnvironmentPort {
44
58
  #private;
45
59
  constructor(world: World, options?: IWSDKEnvironmentPortOptions);
60
+ observe(host: EnvironmentPortHost): () => void;
46
61
  applySky(sky: SkySpec | null): void;
62
+ /**
63
+ * The environment map, as IWSDK's own image-based lighting components.
64
+ *
65
+ * `room` is not an approximation here: IWSDK's `IBLTexture` takes the string
66
+ * `"room"` for its built-in probe, so the one kind three.js has to fake is
67
+ * the one kind this host has natively.
68
+ */
69
+ applyIbl(ibl: IblSpec | null): void;
70
+ /**
71
+ * Depth occlusion, through IWSDK's `DepthSensingSystem` and the per-entity
72
+ * `DepthOccludable` component.
73
+ *
74
+ * IWSDK patches the materials under each opted-in entity, which is why the
75
+ * scope matters more here than on three.js: there is no global mode to turn
76
+ * on, and an entity nobody opted in is an entity the real world will not
77
+ * hide. Whatever this port adds, it remembers, so turning occlusion off
78
+ * never strips a component the app put there itself.
79
+ */
80
+ applyOcclusion(spec: OcclusionSpec | null): void;
81
+ /**
82
+ * IWSDK 0.5.3 has no light estimation at all - no probe, no session hook,
83
+ * nothing in its typings. This says so instead of leaving the app to wonder
84
+ * why the room never lights anything, and the request is recorded in
85
+ * docs/UPSTREAM_ENHANCEMENTS.md.
86
+ */
87
+ applyLightEstimation(estimation: ResolvedLightEstimation | null): void;
47
88
  applyFog(fog: FogSpec | null): void;
48
89
  applyAmbient(light: AmbientLightSpec | null): void;
49
90
  applyKeyLight(light: KeyLightSpec | null): void;
@@ -28,8 +28,11 @@
28
28
  * `getVectorView`; `setValue` on one throws. Every colour write here goes
29
29
  * through `writeColour` for that reason - it is not a style choice.
30
30
  */
31
- import { AmbientLightComponent, DirectionalLightComponent, DomeGradient, Fog, FogExp2, Quaternion, Transform, Vector3, } from "@iwsdk/core";
31
+ import { AmbientLightComponent, DepthOccludable, DepthSensingSystem, DirectionalLightComponent, DomeGradient, DomeTexture, IBLGradient, IBLTexture, OcclusionShadersMode, Transform, } from "@iwsdk/core";
32
+ import { Fog, FogExp2, Quaternion, Vector3 } from "three";
32
33
  import { clamp01, lerpRgb } from "@realitycollective/webxr-environment";
34
+ /** 0..1 softness onto IWSDK's blur radius in pixels, whose default is 20. */
35
+ const MAX_BLUR_RADIUS = 40;
33
36
  /** Local -Z is where an IWSDK directional light points. */
34
37
  const LIGHT_FORWARD = new Vector3(0, 0, -1);
35
38
  const TEMP_DIRECTION = new Vector3();
@@ -38,14 +41,26 @@ export class IWSDKEnvironmentPort {
38
41
  #world;
39
42
  #parent;
40
43
  #skyIntensity;
44
+ #occludables;
41
45
  #domeEntity = null;
46
+ #iblEntity = null;
42
47
  #ambientEntity = null;
43
48
  #keyEntity = null;
44
49
  #fogKind = null;
50
+ #host = null;
51
+ /** Only what THIS port added, so it never removes the app's own component. */
52
+ #occluding = [];
45
53
  constructor(world, options = {}) {
46
54
  this.#world = world;
47
55
  this.#parent = options.parent;
48
56
  this.#skyIntensity = options.skyIntensity ?? 1;
57
+ this.#occludables = options.occludables;
58
+ }
59
+ observe(host) {
60
+ this.#host = host;
61
+ return () => {
62
+ this.#host = null;
63
+ };
49
64
  }
50
65
  applySky(sky) {
51
66
  const root = this.#levelRoot();
@@ -58,29 +73,191 @@ export class IWSDKEnvironmentPort {
58
73
  this.#domeEntity = null;
59
74
  return;
60
75
  }
76
+ if (sky.kind === "texture") {
77
+ // An authored sky is a different IWSDK component, so the dome one goes
78
+ // rather than sitting underneath and fighting over the same background.
79
+ if (root.hasComponent(DomeGradient))
80
+ root.removeComponent(DomeGradient);
81
+ if (!root.hasComponent(DomeTexture))
82
+ root.addComponent(DomeTexture);
83
+ this.#domeEntity = root;
84
+ root.setValue(DomeTexture, "src", sky.src);
85
+ root.setValue(DomeTexture, "intensity", sky.intensity ?? this.#skyIntensity);
86
+ root.setValue(DomeTexture, "blurriness", sky.blur ?? 0);
87
+ writeVec3(root.getVectorView(DomeTexture, "rotation"), 0, sky.rotationY ?? 0, 0);
88
+ root.setValue(DomeTexture, "_needsUpdate", true);
89
+ return;
90
+ }
91
+ if (root.hasComponent(DomeTexture))
92
+ root.removeComponent(DomeTexture);
61
93
  if (this.#domeEntity !== root || !root.hasComponent(DomeGradient)) {
62
94
  if (!root.hasComponent(DomeGradient))
63
95
  root.addComponent(DomeGradient);
64
96
  this.#domeEntity = root;
65
97
  }
66
98
  // IWSDK's dome is a Unity-style sky / equator / ground triple. A solid sky
67
- // is all three the same; a gradient reads its equator from the same ramp
68
- // the three.js adapter uses, so the two engines agree at the horizon
99
+ // is all three the same. A gradient uses the horizon colour the app gave,
100
+ // and derives one from the ramp when it did not - which is the same colour
101
+ // the three.js adapter puts at the horizon, so the two engines agree there
69
102
  // rather than only at the poles.
70
103
  if (sky.kind === "solid") {
71
104
  writeRgb(root.getVectorView(DomeGradient, "sky"), sky.colour);
72
105
  writeRgb(root.getVectorView(DomeGradient, "equator"), sky.colour);
73
106
  writeRgb(root.getVectorView(DomeGradient, "ground"), sky.colour);
107
+ root.setValue(DomeGradient, "intensity", this.#skyIntensity);
74
108
  }
75
109
  else {
76
- const equator = lerpRgb(sky.bottom, sky.top, Math.pow(0.5, sky.exponent ?? 1));
77
110
  writeRgb(root.getVectorView(DomeGradient, "sky"), sky.top);
78
- writeRgb(root.getVectorView(DomeGradient, "equator"), equator);
111
+ writeRgb(root.getVectorView(DomeGradient, "equator"), equatorOf(sky));
79
112
  writeRgb(root.getVectorView(DomeGradient, "ground"), sky.bottom);
113
+ root.setValue(DomeGradient, "intensity", sky.intensity ?? this.#skyIntensity);
80
114
  }
81
- root.setValue(DomeGradient, "intensity", this.#skyIntensity);
82
115
  root.setValue(DomeGradient, "_needsUpdate", true);
83
116
  }
117
+ /**
118
+ * The environment map, as IWSDK's own image-based lighting components.
119
+ *
120
+ * `room` is not an approximation here: IWSDK's `IBLTexture` takes the string
121
+ * `"room"` for its built-in probe, so the one kind three.js has to fake is
122
+ * the one kind this host has natively.
123
+ */
124
+ applyIbl(ibl) {
125
+ const root = this.#levelRoot();
126
+ if (root === null)
127
+ return;
128
+ if (ibl === null) {
129
+ if (this.#iblEntity !== null) {
130
+ if (this.#iblEntity.hasComponent(IBLGradient))
131
+ this.#iblEntity.removeComponent(IBLGradient);
132
+ if (this.#iblEntity.hasComponent(IBLTexture))
133
+ this.#iblEntity.removeComponent(IBLTexture);
134
+ }
135
+ this.#iblEntity = null;
136
+ return;
137
+ }
138
+ this.#iblEntity = root;
139
+ if (ibl.kind === "estimated") {
140
+ // IWSDK measures no reflections (UPSTREAM_ENHANCEMENTS 6.1), so the slot
141
+ // keeps whatever it had rather than being cleared: an app that asked for
142
+ // measured reflections and got none should not also lose its own map.
143
+ this.#report({
144
+ feature: "lightEstimation",
145
+ state: "unsupported",
146
+ detail: "IWSDK measures no reflections, so an estimated environment map cannot be applied",
147
+ });
148
+ return;
149
+ }
150
+ if (ibl.kind === "gradient") {
151
+ if (root.hasComponent(IBLTexture))
152
+ root.removeComponent(IBLTexture);
153
+ if (!root.hasComponent(IBLGradient))
154
+ root.addComponent(IBLGradient);
155
+ writeRgb(root.getVectorView(IBLGradient, "sky"), ibl.top);
156
+ writeRgb(root.getVectorView(IBLGradient, "equator"), equatorOf(ibl));
157
+ writeRgb(root.getVectorView(IBLGradient, "ground"), ibl.bottom);
158
+ root.setValue(IBLGradient, "intensity", ibl.intensity ?? 1);
159
+ root.setValue(IBLGradient, "_needsUpdate", true);
160
+ return;
161
+ }
162
+ if (root.hasComponent(IBLGradient))
163
+ root.removeComponent(IBLGradient);
164
+ if (!root.hasComponent(IBLTexture))
165
+ root.addComponent(IBLTexture);
166
+ root.setValue(IBLTexture, "src", ibl.kind === "room" ? "room" : ibl.src);
167
+ root.setValue(IBLTexture, "intensity", ibl.intensity ?? 1);
168
+ writeVec3(root.getVectorView(IBLTexture, "rotation"), 0, ibl.rotationY ?? 0, 0);
169
+ root.setValue(IBLTexture, "_needsUpdate", true);
170
+ }
171
+ /**
172
+ * Depth occlusion, through IWSDK's `DepthSensingSystem` and the per-entity
173
+ * `DepthOccludable` component.
174
+ *
175
+ * IWSDK patches the materials under each opted-in entity, which is why the
176
+ * scope matters more here than on three.js: there is no global mode to turn
177
+ * on, and an entity nobody opted in is an entity the real world will not
178
+ * hide. Whatever this port adds, it remembers, so turning occlusion off
179
+ * never strips a component the app put there itself.
180
+ */
181
+ applyOcclusion(spec) {
182
+ if (spec === null) {
183
+ this.#releaseOccludables();
184
+ const running = this.#system();
185
+ if (running !== undefined)
186
+ running.config.enableOcclusion.value = false;
187
+ this.#report({ feature: "occlusion", state: "unavailable", detail: "occlusion is off" });
188
+ return;
189
+ }
190
+ const blurRadius = spec.softness === undefined ? undefined : clamp01(spec.softness) * MAX_BLUR_RADIUS;
191
+ const system = this.#system();
192
+ if (system === undefined) {
193
+ this.#world.registerSystem(DepthSensingSystem, {
194
+ configData: {
195
+ enableOcclusion: true,
196
+ enableDepthTexture: true,
197
+ useFloat32: spec.source?.format !== "luminance-alpha",
198
+ ...(blurRadius === undefined ? {} : { blurRadius }),
199
+ },
200
+ });
201
+ }
202
+ else {
203
+ system.config.enableOcclusion.value = true;
204
+ system.config.useFloat32.value = spec.source?.format !== "luminance-alpha";
205
+ if (blurRadius !== undefined)
206
+ system.config.blurRadius.value = blurRadius;
207
+ }
208
+ const mode = occlusionMode(spec.mode);
209
+ this.#releaseOccludables();
210
+ if (spec.scope === "tagged") {
211
+ this.#report({
212
+ feature: "occlusion",
213
+ state: "active",
214
+ detail: "occluding the entities the app marked with DepthOccludable",
215
+ });
216
+ return;
217
+ }
218
+ if (this.#occludables === undefined) {
219
+ this.#report({
220
+ feature: "occlusion",
221
+ state: "unavailable",
222
+ detail: "IWSDK occludes per entity; pass an occludables option, or use scope: tagged and add DepthOccludable yourself",
223
+ });
224
+ return;
225
+ }
226
+ let count = 0;
227
+ for (const entity of this.#occludables()) {
228
+ if (entity.hasComponent(DepthOccludable))
229
+ continue;
230
+ entity.addComponent(DepthOccludable, { mode });
231
+ this.#occluding.push(entity);
232
+ count += 1;
233
+ }
234
+ this.#report({
235
+ feature: "occlusion",
236
+ state: "active",
237
+ detail: "occluding " + String(count) + " entities",
238
+ });
239
+ }
240
+ /**
241
+ * IWSDK 0.5.3 has no light estimation at all - no probe, no session hook,
242
+ * nothing in its typings. This says so instead of leaving the app to wonder
243
+ * why the room never lights anything, and the request is recorded in
244
+ * docs/UPSTREAM_ENHANCEMENTS.md.
245
+ */
246
+ applyLightEstimation(estimation) {
247
+ if (estimation === null) {
248
+ this.#report({
249
+ feature: "lightEstimation",
250
+ state: "unavailable",
251
+ detail: "light estimation is off",
252
+ });
253
+ return;
254
+ }
255
+ this.#report({
256
+ feature: "lightEstimation",
257
+ state: "unsupported",
258
+ detail: "IWSDK 0.5.3 does not expose WebXR light estimation",
259
+ });
260
+ }
84
261
  applyFog(fog) {
85
262
  const scene = this.#world.scene;
86
263
  if (fog === null) {
@@ -147,6 +324,22 @@ export class IWSDKEnvironmentPort {
147
324
  this.applyFog(null);
148
325
  this.applyAmbient(null);
149
326
  this.applyKeyLight(null);
327
+ this.applyIbl(null);
328
+ this.#releaseOccludables();
329
+ this.#host = null;
330
+ }
331
+ #system() {
332
+ return this.#world.getSystem(DepthSensingSystem);
333
+ }
334
+ #releaseOccludables() {
335
+ for (const entity of this.#occluding) {
336
+ if (entity.hasComponent(DepthOccludable))
337
+ entity.removeComponent(DepthOccludable);
338
+ }
339
+ this.#occluding = [];
340
+ }
341
+ #report(report) {
342
+ this.#host?.report(report);
150
343
  }
151
344
  #levelRoot() {
152
345
  return this.#parent ?? this.#world.activeLevel?.value ?? null;
@@ -173,6 +366,34 @@ export class IWSDKEnvironmentPort {
173
366
  this.#keyEntity = null;
174
367
  }
175
368
  }
369
+ /**
370
+ * The colour at the horizon: the app's, or the one the ramp implies.
371
+ *
372
+ * IWSDK's dome and IBL components are both three-stop, so this port always has
373
+ * to supply a middle colour. Deriving it from the same ramp the three.js
374
+ * adapter draws is what stops the two engines disagreeing about a sky the app
375
+ * described once.
376
+ */
377
+ function equatorOf(gradient) {
378
+ if (gradient.equator !== undefined)
379
+ return gradient.equator;
380
+ const exponent = "exponent" in gradient ? (gradient.exponent ?? 1) : 1;
381
+ return lerpRgb(gradient.bottom, gradient.top, Math.pow(0.5, exponent));
382
+ }
383
+ /** Our mode names onto IWSDK's shader modes. */
384
+ function occlusionMode(mode) {
385
+ if (mode === "hard")
386
+ return OcclusionShadersMode.HardOcclusion;
387
+ if (mode === "minmax-soft")
388
+ return OcclusionShadersMode.MinMaxSoftOcclusion;
389
+ return OcclusionShadersMode.SoftOcclusion;
390
+ }
391
+ /** As `writeRgb`, for a `Types.Vec3` that is not a colour. */
392
+ function writeVec3(view, x, y, z) {
393
+ view[0] = x;
394
+ view[1] = y;
395
+ view[2] = z;
396
+ }
176
397
  /** `[r, g, b]` in 0..1 -> a packed 0xRRGGBB, for the three.js-side setters. */
177
398
  function rgbToHexNumber(rgb) {
178
399
  const channel = (value) => Math.round(clamp01(value) * 255);
@@ -1 +1 @@
1
- {"version":3,"file":"environment-port.js","sourceRoot":"","sources":["../src/environment-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `EnvironmentPort` for Meta IWSDK.\n *\n * ---------------------------------------------------------------------------\n * IT USES IWSDK'S OWN MACHINERY, NOT THREE.JS BEHIND ITS BACK\n * ---------------------------------------------------------------------------\n * The sky is a `DomeGradient` on the level root and the lights are\n * `AmbientLightComponent` and `DirectionalLightComponent` on entities.\n * Reaching past IWSDK to write `scene.background` or to `scene.add()` a light\n * would work right up until IWSDK's own `EnvironmentSystem` or `LightSystem`\n * disagreed - and one of the things IWSDK's environment system already does\n * correctly is hide authored backgrounds in an AR session, which is behaviour\n * worth inheriting rather than fighting.\n *\n * No geometry is created here. Every component this port touches is an IWSDK\n * platform facility with no app-side equivalent; a floor would be a mesh, and\n * meshes are the app's.\n *\n * Fog is the exception. IWSDK has no fog component, so it is set on\n * `world.scene` directly, with three.js's own `Fog` / `FogExp2` imported from\n * `@iwsdk/core` (the framework's rule - one three.js instance, reached through\n * the framework's re-export).\n *\n * ---------------------------------------------------------------------------\n * WRITING COMPONENT COLOURS\n * ---------------------------------------------------------------------------\n * A `Types.Color` or `Types.Vec3` field must be written through\n * `getVectorView`; `setValue` on one throws. Every colour write here goes\n * through `writeColour` for that reason - it is not a style choice.\n */\nimport {\n AmbientLightComponent,\n DirectionalLightComponent,\n DomeGradient,\n Fog,\n FogExp2,\n Quaternion,\n Transform,\n Vector3,\n type Entity,\n type World,\n} from \"@iwsdk/core\";\nimport type {\n AmbientLightSpec,\n EnvironmentPort,\n FogSpec,\n KeyLightSpec,\n Rgb,\n SkySpec,\n} from \"@realitycollective/webxr-environment\";\nimport { clamp01, lerpRgb } from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKEnvironmentPortOptions {\n /**\n * Where the light entities are parented. Defaults to the active level root,\n * which is also where the dome lives, so the whole environment is torn down\n * with the level.\n */\n readonly parent?: Entity;\n /** Dome brightness multiplier. Default 1. */\n readonly skyIntensity?: number;\n}\n\n/**\n * The narrowest thing a vector view is: elics types it as a union of every\n * typed array, and all these helpers need is indexed writes and a length.\n */\ntype NumericView = { [index: number]: number; readonly length: number };\n\n/** Local -Z is where an IWSDK directional light points. */\nconst LIGHT_FORWARD = new Vector3(0, 0, -1);\nconst TEMP_DIRECTION = new Vector3();\nconst TEMP_QUATERNION = new Quaternion();\n\nexport class IWSDKEnvironmentPort implements EnvironmentPort {\n readonly #world: World;\n readonly #parent: Entity | undefined;\n readonly #skyIntensity: number;\n\n #domeEntity: Entity | null = null;\n #ambientEntity: Entity | null = null;\n #keyEntity: Entity | null = null;\n #fogKind: FogSpec[\"kind\"] | null = null;\n\n constructor(world: World, options: IWSDKEnvironmentPortOptions = {}) {\n this.#world = world;\n this.#parent = options.parent;\n this.#skyIntensity = options.skyIntensity ?? 1;\n }\n\n applySky(sky: SkySpec | null): void {\n const root = this.#levelRoot();\n if (root === null) return;\n\n if (sky === null) {\n if (this.#domeEntity !== null && this.#domeEntity.hasComponent(DomeGradient)) {\n this.#domeEntity.removeComponent(DomeGradient);\n }\n this.#domeEntity = null;\n return;\n }\n\n if (this.#domeEntity !== root || !root.hasComponent(DomeGradient)) {\n if (!root.hasComponent(DomeGradient)) root.addComponent(DomeGradient);\n this.#domeEntity = root;\n }\n\n // IWSDK's dome is a Unity-style sky / equator / ground triple. A solid sky\n // is all three the same; a gradient reads its equator from the same ramp\n // the three.js adapter uses, so the two engines agree at the horizon\n // rather than only at the poles.\n if (sky.kind === \"solid\") {\n writeRgb(root.getVectorView(DomeGradient, \"sky\"), sky.colour);\n writeRgb(root.getVectorView(DomeGradient, \"equator\"), sky.colour);\n writeRgb(root.getVectorView(DomeGradient, \"ground\"), sky.colour);\n } else {\n const equator = lerpRgb(sky.bottom, sky.top, Math.pow(0.5, sky.exponent ?? 1));\n writeRgb(root.getVectorView(DomeGradient, \"sky\"), sky.top);\n writeRgb(root.getVectorView(DomeGradient, \"equator\"), equator);\n writeRgb(root.getVectorView(DomeGradient, \"ground\"), sky.bottom);\n }\n root.setValue(DomeGradient, \"intensity\", this.#skyIntensity);\n root.setValue(DomeGradient, \"_needsUpdate\", true);\n }\n\n applyFog(fog: FogSpec | null): void {\n const scene = this.#world.scene;\n if (fog === null) {\n scene.fog = null;\n this.#fogKind = null;\n return;\n }\n if (this.#fogKind !== fog.kind || scene.fog === null) {\n scene.fog =\n fog.kind === \"linear\"\n ? new Fog(rgbToHexNumber(fog.colour), fog.near, fog.far)\n : new FogExp2(rgbToHexNumber(fog.colour), fog.density);\n this.#fogKind = fog.kind;\n return;\n }\n const existing = scene.fog;\n existing.color.setHex(rgbToHexNumber(fog.colour));\n if (fog.kind === \"linear\" && existing instanceof Fog) {\n existing.near = fog.near;\n existing.far = fog.far;\n } else if (fog.kind === \"exponential\" && existing instanceof FogExp2) {\n existing.density = fog.density;\n }\n }\n\n applyAmbient(light: AmbientLightSpec | null): void {\n if (light === null) {\n this.#destroy(\"ambient\");\n return;\n }\n const entity = this.#ensure(\"ambient\");\n if (!entity.hasComponent(AmbientLightComponent)) {\n entity.addComponent(AmbientLightComponent);\n }\n writeRgb(entity.getVectorView(AmbientLightComponent, \"color\"), light.colour);\n entity.setValue(AmbientLightComponent, \"intensity\", light.intensity);\n }\n\n applyKeyLight(light: KeyLightSpec | null): void {\n if (light === null) {\n this.#destroy(\"key\");\n return;\n }\n const entity = this.#ensure(\"key\");\n if (!entity.hasComponent(DirectionalLightComponent)) {\n entity.addComponent(DirectionalLightComponent);\n }\n writeRgb(entity.getVectorView(DirectionalLightComponent, \"color\"), light.colour);\n entity.setValue(DirectionalLightComponent, \"intensity\", light.intensity);\n entity.setValue(DirectionalLightComponent, \"castShadow\", light.castShadow ?? false);\n\n // The spec names the direction light TRAVELS; the component emits along\n // the entity's local -Z, so the entity is rotated to face that way. A\n // zero-length direction would produce a NaN quaternion, so it falls back\n // to straight down.\n const [x, y, z] = light.direction;\n if (Math.hypot(x, y, z) === 0) TEMP_DIRECTION.set(0, -1, 0);\n else TEMP_DIRECTION.set(x, y, z).normalize();\n TEMP_QUATERNION.setFromUnitVectors(LIGHT_FORWARD, TEMP_DIRECTION);\n writeQuaternion(entity.getVectorView(Transform, \"orientation\"), TEMP_QUATERNION);\n }\n\n dispose(): void {\n this.applySky(null);\n this.applyFog(null);\n this.applyAmbient(null);\n this.applyKeyLight(null);\n }\n\n #levelRoot(): Entity | null {\n return this.#parent ?? this.#world.activeLevel?.value ?? null;\n }\n\n /** The light entities exist independently of the level, so this cannot fail. */\n #ensure(which: \"ambient\" | \"key\"): Entity {\n const existing = which === \"ambient\" ? this.#ambientEntity : this.#keyEntity;\n if (existing !== null) return existing;\n const parent = this.#levelRoot();\n const entity = this.#world.createTransformEntity(\n undefined,\n parent === null ? undefined : { parent },\n );\n if (which === \"ambient\") this.#ambientEntity = entity;\n else this.#keyEntity = entity;\n return entity;\n }\n\n #destroy(which: \"ambient\" | \"key\"): void {\n const entity = which === \"ambient\" ? this.#ambientEntity : this.#keyEntity;\n entity?.destroy();\n if (which === \"ambient\") this.#ambientEntity = null;\n else this.#keyEntity = null;\n }\n}\n\n/** `[r, g, b]` in 0..1 -> a packed 0xRRGGBB, for the three.js-side setters. */\nfunction rgbToHexNumber(rgb: Rgb): number {\n const channel = (value: number) => Math.round(clamp01(value) * 255);\n return (channel(rgb[0]) << 16) | (channel(rgb[1]) << 8) | channel(rgb[2]);\n}\n\n/**\n * Write an RGBA colour into a component's vector view.\n *\n * The VIEW is passed in rather than the component and field name, so every\n * call site keeps elics's own `getVectorView` typing - which checks that the\n * field exists AND that it is an array type. A helper that took the component\n * would have to erase those generics, and the first misspelled field name\n * would then reach a headset instead of the compiler.\n *\n * Alpha is always 1: the specs describe opaque colours. Writing index 3 of a\n * three-component view is a no-op in JavaScript rather than an error, so no\n * length check is needed for a component whose colour is a `Types.Vec3`.\n */\nfunction writeRgb(view: NumericView, rgb: Rgb): void {\n view[0] = rgb[0];\n view[1] = rgb[1];\n view[2] = rgb[2];\n view[3] = 1;\n}\n\n/** As above, for a `Types.Vec4` orientation. */\nfunction writeQuaternion(view: NumericView, quaternion: Quaternion): void {\n view[0] = quaternion.x;\n view[1] = quaternion.y;\n view[2] = quaternion.z;\n view[3] = quaternion.w;\n}\n"]}
1
+ {"version":3,"file":"environment-port.js","sourceRoot":"","sources":["../src/environment-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,SAAS,GAGV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAe1D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC;AA2BxE,6EAA6E;AAC7E,MAAM,eAAe,GAAG,EAAE,CAAC;AAQ3B,2DAA2D;AAC3D,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5C,MAAM,cAAc,GAAG,IAAI,OAAO,EAAE,CAAC;AACrC,MAAM,eAAe,GAAG,IAAI,UAAU,EAAE,CAAC;AAEzC,MAAM,OAAO,oBAAoB;IACtB,MAAM,CAAQ;IACd,OAAO,CAAqB;IAC5B,aAAa,CAAS;IAEtB,YAAY,CAAuC;IAE5D,WAAW,GAAkB,IAAI,CAAC;IAClC,UAAU,GAAkB,IAAI,CAAC;IACjC,cAAc,GAAkB,IAAI,CAAC;IACrC,UAAU,GAAkB,IAAI,CAAC;IACjC,QAAQ,GAA2B,IAAI,CAAC;IACxC,KAAK,GAA+B,IAAI,CAAC;IACzC,8EAA8E;IAC9E,UAAU,GAAa,EAAE,CAAC;IAE1B,YAAY,KAAY,EAAE,UAAuC,EAAE;QACjE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC;QAC/C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,IAAyB;QAC/B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,GAAmB;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QAE1B,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7E,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;YACxE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YACpE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC;YAC7E,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;YACxD,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;YAAE,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;YACtE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;QAED,2EAA2E;QAC3E,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,iCAAiC;QACjC,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAC9D,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAClE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;YACtE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,GAAmB;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QAE1B,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;gBAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC;oBAAE,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;gBAC5F,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU,CAAC;oBAAE,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;YAC5F,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC7B,yEAAyE;YACzE,yEAAyE;YACzE,sEAAsE;YACtE,IAAI,CAAC,OAAO,CAAC;gBACX,OAAO,EAAE,iBAAiB;gBAC1B,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,kFAAkF;aAC3F,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;gBAAE,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;YACpE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1D,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;YACrE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;YAAE,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QAC3D,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;;OASG;IACH,cAAc,CAAC,IAA0B;QACvC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,GAAG,KAAK,CAAC;YACxE,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GACd,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC;QACrF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,kBAAkB,EAAE;gBAC7C,UAAU,EAAE;oBACV,eAAe,EAAE,IAAI;oBACrB,kBAAkB,EAAE,IAAI;oBACxB,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,iBAAiB;oBACrD,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;iBACpD;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,GAAG,IAAI,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,iBAAiB,CAAC;YAC3E,IAAI,UAAU,KAAK,SAAS;gBAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,UAAU,CAAC;QAC5E,CAAC;QAED,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC;gBACX,OAAO,EAAE,WAAW;gBACpB,KAAK,EAAE,QAAQ;gBACf,MAAM,EAAE,4DAA4D;aACrE,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACpC,IAAI,CAAC,OAAO,CAAC;gBACX,OAAO,EAAE,WAAW;gBACpB,KAAK,EAAE,aAAa;gBACpB,MAAM,EACJ,8GAA8G;aACjH,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACzC,IAAI,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC;gBAAE,SAAS;YACnD,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7B,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;QACD,IAAI,CAAC,OAAO,CAAC;YACX,OAAO,EAAE,WAAW;YACpB,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW;SACnD,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,UAA0C;QAC7D,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC;gBACX,OAAO,EAAE,iBAAiB;gBAC1B,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,yBAAyB;aAClC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,CAAC;YACX,OAAO,EAAE,iBAAiB;YAC1B,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,oDAAoD;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,GAAmB;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAChC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;YACrD,KAAK,CAAC,GAAG;gBACP,GAAG,CAAC,IAAI,KAAK,QAAQ;oBACnB,CAAC,CAAC,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC;oBACxD,CAAC,CAAC,IAAI,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3D,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC;YACzB,OAAO;QACT,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,YAAY,GAAG,EAAE,CAAC;YACrD,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;YACzB,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;QACzB,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,IAAI,QAAQ,YAAY,OAAO,EAAE,CAAC;YACrE,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QACjC,CAAC;IACH,CAAC;IAED,YAAY,CAAC,KAA8B;QACzC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC,CAAC;QAC7C,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,qBAAqB,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,CAAC,QAAQ,CAAC,qBAAqB,EAAE,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvE,CAAC;IAED,aAAa,CAAC,KAA0B;QACtC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,EAAE,CAAC;YACpD,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,CAAC;QACjD,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,yBAAyB,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACjF,MAAM,CAAC,QAAQ,CAAC,yBAAyB,EAAE,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACzE,MAAM,CAAC,QAAQ,CAAC,yBAAyB,EAAE,YAAY,EAAE,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC;QAEpF,wEAAwE;QACxE,sEAAsE;QACtE,yEAAyE;QACzE,oBAAoB;QACpB,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAClC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;;YACvD,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QAC7C,eAAe,CAAC,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAClE,eAAe,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,aAAa,CAAC,EAAE,eAAe,CAAC,CAAC;IACnF,CAAC;IAED,OAAO;QACL,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACnD,CAAC;IAED,mBAAmB;QACjB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrC,IAAI,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC;gBAAE,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACvB,CAAC;IAED,OAAO,CAAC,MAAqB;QAC3B,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,IAAI,IAAI,CAAC;IAChE,CAAC;IAED,gFAAgF;IAChF,OAAO,CAAC,KAAwB;QAC9B,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAC7E,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAC9C,SAAS,EACT,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CACzC,CAAC;QACF,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC;;YACjD,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,QAAQ,CAAC,KAAwB;QAC/B,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAC3E,MAAM,EAAE,OAAO,EAAE,CAAC;QAClB,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;;YAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,QAAgE;IACjF,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC5D,MAAM,QAAQ,GAAG,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,gDAAgD;AAChD,SAAS,aAAa,CAAC,IAA2B;IAChD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,oBAAoB,CAAC,aAAa,CAAC;IAC/D,IAAI,IAAI,KAAK,aAAa;QAAE,OAAO,oBAAoB,CAAC,mBAAmB,CAAC;IAC5E,OAAO,oBAAoB,CAAC,aAAa,CAAC;AAC5C,CAAC;AAED,8DAA8D;AAC9D,SAAS,SAAS,CAAC,IAAiB,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;IACnE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACZ,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACZ,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,GAAQ;IAC9B,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IACpE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,QAAQ,CAAC,IAAiB,EAAE,GAAQ;IAC3C,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,gDAAgD;AAChD,SAAS,eAAe,CAAC,IAAiB,EAAE,UAAsB;IAChE,IAAI,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACzB,CAAC","sourcesContent":["/**\n * `EnvironmentPort` for Meta IWSDK.\n *\n * ---------------------------------------------------------------------------\n * IT USES IWSDK'S OWN MACHINERY, NOT THREE.JS BEHIND ITS BACK\n * ---------------------------------------------------------------------------\n * The sky is a `DomeGradient` on the level root and the lights are\n * `AmbientLightComponent` and `DirectionalLightComponent` on entities.\n * Reaching past IWSDK to write `scene.background` or to `scene.add()` a light\n * would work right up until IWSDK's own `EnvironmentSystem` or `LightSystem`\n * disagreed - and one of the things IWSDK's environment system already does\n * correctly is hide authored backgrounds in an AR session, which is behaviour\n * worth inheriting rather than fighting.\n *\n * No geometry is created here. Every component this port touches is an IWSDK\n * platform facility with no app-side equivalent; a floor would be a mesh, and\n * meshes are the app's.\n *\n * Fog is the exception. IWSDK has no fog component, so it is set on\n * `world.scene` directly, with three.js's own `Fog` / `FogExp2` imported from\n * `@iwsdk/core` (the framework's rule - one three.js instance, reached through\n * the framework's re-export).\n *\n * ---------------------------------------------------------------------------\n * WRITING COMPONENT COLOURS\n * ---------------------------------------------------------------------------\n * A `Types.Color` or `Types.Vec3` field must be written through\n * `getVectorView`; `setValue` on one throws. Every colour write here goes\n * through `writeColour` for that reason - it is not a style choice.\n */\nimport {\n AmbientLightComponent,\n DepthOccludable,\n DepthSensingSystem,\n DirectionalLightComponent,\n DomeGradient,\n DomeTexture,\n IBLGradient,\n IBLTexture,\n OcclusionShadersMode,\n Transform,\n type Entity,\n type World,\n} from \"@iwsdk/core\";\nimport { Fog, FogExp2, Quaternion, Vector3 } from \"three\";\nimport type {\n AmbientLightSpec,\n EnvironmentPort,\n EnvironmentPortHost,\n FogSpec,\n IblSpec,\n KeyLightSpec,\n OcclusionSpec,\n ResolvedLightEstimation,\n Rgb,\n SensingReport,\n SkyGradient,\n SkySpec,\n} from \"@realitycollective/webxr-environment\";\nimport { clamp01, lerpRgb } from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKEnvironmentPortOptions {\n /**\n * Where the light entities are parented. Defaults to the active level root,\n * which is also where the dome lives, so the whole environment is torn down\n * with the level.\n */\n readonly parent?: Entity;\n /** Dome brightness multiplier, used when a sky does not name its own. Default 1. */\n readonly skyIntensity?: number;\n /**\n * Which entities the real world is allowed to hide, for `scope: \"all\"`.\n *\n * IWSDK opts entities INTO occlusion one at a time, by patching the\n * materials under each one, so somebody has to say which entities those\n * are. It cannot be this port: the entities are content, content is the\n * app's, and walking a scene graph looking for meshes would be this package\n * guessing at the app's structure.\n *\n * With no list, `scope: \"all\"` reports `unavailable` and says why. An app\n * that would rather tag its own entities uses `scope: \"tagged\"` and adds\n * `DepthOccludable` itself; then this port only configures the system.\n */\n readonly occludables?: () => Iterable<Entity>;\n}\n\n/** 0..1 softness onto IWSDK's blur radius in pixels, whose default is 20. */\nconst MAX_BLUR_RADIUS = 40;\n\n/**\n * The narrowest thing a vector view is: elics types it as a union of every\n * typed array, and all these helpers need is indexed writes and a length.\n */\ntype NumericView = { [index: number]: number; readonly length: number };\n\n/** Local -Z is where an IWSDK directional light points. */\nconst LIGHT_FORWARD = new Vector3(0, 0, -1);\nconst TEMP_DIRECTION = new Vector3();\nconst TEMP_QUATERNION = new Quaternion();\n\nexport class IWSDKEnvironmentPort implements EnvironmentPort {\n readonly #world: World;\n readonly #parent: Entity | undefined;\n readonly #skyIntensity: number;\n\n readonly #occludables: (() => Iterable<Entity>) | undefined;\n\n #domeEntity: Entity | null = null;\n #iblEntity: Entity | null = null;\n #ambientEntity: Entity | null = null;\n #keyEntity: Entity | null = null;\n #fogKind: FogSpec[\"kind\"] | null = null;\n #host: EnvironmentPortHost | null = null;\n /** Only what THIS port added, so it never removes the app's own component. */\n #occluding: Entity[] = [];\n\n constructor(world: World, options: IWSDKEnvironmentPortOptions = {}) {\n this.#world = world;\n this.#parent = options.parent;\n this.#skyIntensity = options.skyIntensity ?? 1;\n this.#occludables = options.occludables;\n }\n\n observe(host: EnvironmentPortHost): () => void {\n this.#host = host;\n return () => {\n this.#host = null;\n };\n }\n\n applySky(sky: SkySpec | null): void {\n const root = this.#levelRoot();\n if (root === null) return;\n\n if (sky === null) {\n if (this.#domeEntity !== null && this.#domeEntity.hasComponent(DomeGradient)) {\n this.#domeEntity.removeComponent(DomeGradient);\n }\n this.#domeEntity = null;\n return;\n }\n\n if (sky.kind === \"texture\") {\n // An authored sky is a different IWSDK component, so the dome one goes\n // rather than sitting underneath and fighting over the same background.\n if (root.hasComponent(DomeGradient)) root.removeComponent(DomeGradient);\n if (!root.hasComponent(DomeTexture)) root.addComponent(DomeTexture);\n this.#domeEntity = root;\n root.setValue(DomeTexture, \"src\", sky.src);\n root.setValue(DomeTexture, \"intensity\", sky.intensity ?? this.#skyIntensity);\n root.setValue(DomeTexture, \"blurriness\", sky.blur ?? 0);\n writeVec3(root.getVectorView(DomeTexture, \"rotation\"), 0, sky.rotationY ?? 0, 0);\n root.setValue(DomeTexture, \"_needsUpdate\", true);\n return;\n }\n\n if (root.hasComponent(DomeTexture)) root.removeComponent(DomeTexture);\n if (this.#domeEntity !== root || !root.hasComponent(DomeGradient)) {\n if (!root.hasComponent(DomeGradient)) root.addComponent(DomeGradient);\n this.#domeEntity = root;\n }\n\n // IWSDK's dome is a Unity-style sky / equator / ground triple. A solid sky\n // is all three the same. A gradient uses the horizon colour the app gave,\n // and derives one from the ramp when it did not - which is the same colour\n // the three.js adapter puts at the horizon, so the two engines agree there\n // rather than only at the poles.\n if (sky.kind === \"solid\") {\n writeRgb(root.getVectorView(DomeGradient, \"sky\"), sky.colour);\n writeRgb(root.getVectorView(DomeGradient, \"equator\"), sky.colour);\n writeRgb(root.getVectorView(DomeGradient, \"ground\"), sky.colour);\n root.setValue(DomeGradient, \"intensity\", this.#skyIntensity);\n } else {\n writeRgb(root.getVectorView(DomeGradient, \"sky\"), sky.top);\n writeRgb(root.getVectorView(DomeGradient, \"equator\"), equatorOf(sky));\n writeRgb(root.getVectorView(DomeGradient, \"ground\"), sky.bottom);\n root.setValue(DomeGradient, \"intensity\", sky.intensity ?? this.#skyIntensity);\n }\n root.setValue(DomeGradient, \"_needsUpdate\", true);\n }\n\n /**\n * The environment map, as IWSDK's own image-based lighting components.\n *\n * `room` is not an approximation here: IWSDK's `IBLTexture` takes the string\n * `\"room\"` for its built-in probe, so the one kind three.js has to fake is\n * the one kind this host has natively.\n */\n applyIbl(ibl: IblSpec | null): void {\n const root = this.#levelRoot();\n if (root === null) return;\n\n if (ibl === null) {\n if (this.#iblEntity !== null) {\n if (this.#iblEntity.hasComponent(IBLGradient)) this.#iblEntity.removeComponent(IBLGradient);\n if (this.#iblEntity.hasComponent(IBLTexture)) this.#iblEntity.removeComponent(IBLTexture);\n }\n this.#iblEntity = null;\n return;\n }\n this.#iblEntity = root;\n\n if (ibl.kind === \"estimated\") {\n // IWSDK measures no reflections (UPSTREAM_ENHANCEMENTS 6.1), so the slot\n // keeps whatever it had rather than being cleared: an app that asked for\n // measured reflections and got none should not also lose its own map.\n this.#report({\n feature: \"lightEstimation\",\n state: \"unsupported\",\n detail: \"IWSDK measures no reflections, so an estimated environment map cannot be applied\",\n });\n return;\n }\n\n if (ibl.kind === \"gradient\") {\n if (root.hasComponent(IBLTexture)) root.removeComponent(IBLTexture);\n if (!root.hasComponent(IBLGradient)) root.addComponent(IBLGradient);\n writeRgb(root.getVectorView(IBLGradient, \"sky\"), ibl.top);\n writeRgb(root.getVectorView(IBLGradient, \"equator\"), equatorOf(ibl));\n writeRgb(root.getVectorView(IBLGradient, \"ground\"), ibl.bottom);\n root.setValue(IBLGradient, \"intensity\", ibl.intensity ?? 1);\n root.setValue(IBLGradient, \"_needsUpdate\", true);\n return;\n }\n\n if (root.hasComponent(IBLGradient)) root.removeComponent(IBLGradient);\n if (!root.hasComponent(IBLTexture)) root.addComponent(IBLTexture);\n root.setValue(IBLTexture, \"src\", ibl.kind === \"room\" ? \"room\" : ibl.src);\n root.setValue(IBLTexture, \"intensity\", ibl.intensity ?? 1);\n writeVec3(root.getVectorView(IBLTexture, \"rotation\"), 0, ibl.rotationY ?? 0, 0);\n root.setValue(IBLTexture, \"_needsUpdate\", true);\n }\n\n /**\n * Depth occlusion, through IWSDK's `DepthSensingSystem` and the per-entity\n * `DepthOccludable` component.\n *\n * IWSDK patches the materials under each opted-in entity, which is why the\n * scope matters more here than on three.js: there is no global mode to turn\n * on, and an entity nobody opted in is an entity the real world will not\n * hide. Whatever this port adds, it remembers, so turning occlusion off\n * never strips a component the app put there itself.\n */\n applyOcclusion(spec: OcclusionSpec | null): void {\n if (spec === null) {\n this.#releaseOccludables();\n const running = this.#system();\n if (running !== undefined) running.config.enableOcclusion.value = false;\n this.#report({ feature: \"occlusion\", state: \"unavailable\", detail: \"occlusion is off\" });\n return;\n }\n\n const blurRadius =\n spec.softness === undefined ? undefined : clamp01(spec.softness) * MAX_BLUR_RADIUS;\n const system = this.#system();\n if (system === undefined) {\n this.#world.registerSystem(DepthSensingSystem, {\n configData: {\n enableOcclusion: true,\n enableDepthTexture: true,\n useFloat32: spec.source?.format !== \"luminance-alpha\",\n ...(blurRadius === undefined ? {} : { blurRadius }),\n },\n });\n } else {\n system.config.enableOcclusion.value = true;\n system.config.useFloat32.value = spec.source?.format !== \"luminance-alpha\";\n if (blurRadius !== undefined) system.config.blurRadius.value = blurRadius;\n }\n\n const mode = occlusionMode(spec.mode);\n this.#releaseOccludables();\n if (spec.scope === \"tagged\") {\n this.#report({\n feature: \"occlusion\",\n state: \"active\",\n detail: \"occluding the entities the app marked with DepthOccludable\",\n });\n return;\n }\n if (this.#occludables === undefined) {\n this.#report({\n feature: \"occlusion\",\n state: \"unavailable\",\n detail:\n \"IWSDK occludes per entity; pass an occludables option, or use scope: tagged and add DepthOccludable yourself\",\n });\n return;\n }\n let count = 0;\n for (const entity of this.#occludables()) {\n if (entity.hasComponent(DepthOccludable)) continue;\n entity.addComponent(DepthOccludable, { mode });\n this.#occluding.push(entity);\n count += 1;\n }\n this.#report({\n feature: \"occlusion\",\n state: \"active\",\n detail: \"occluding \" + String(count) + \" entities\",\n });\n }\n\n /**\n * IWSDK 0.5.3 has no light estimation at all - no probe, no session hook,\n * nothing in its typings. This says so instead of leaving the app to wonder\n * why the room never lights anything, and the request is recorded in\n * docs/UPSTREAM_ENHANCEMENTS.md.\n */\n applyLightEstimation(estimation: ResolvedLightEstimation | null): void {\n if (estimation === null) {\n this.#report({\n feature: \"lightEstimation\",\n state: \"unavailable\",\n detail: \"light estimation is off\",\n });\n return;\n }\n this.#report({\n feature: \"lightEstimation\",\n state: \"unsupported\",\n detail: \"IWSDK 0.5.3 does not expose WebXR light estimation\",\n });\n }\n\n applyFog(fog: FogSpec | null): void {\n const scene = this.#world.scene;\n if (fog === null) {\n scene.fog = null;\n this.#fogKind = null;\n return;\n }\n if (this.#fogKind !== fog.kind || scene.fog === null) {\n scene.fog =\n fog.kind === \"linear\"\n ? new Fog(rgbToHexNumber(fog.colour), fog.near, fog.far)\n : new FogExp2(rgbToHexNumber(fog.colour), fog.density);\n this.#fogKind = fog.kind;\n return;\n }\n const existing = scene.fog;\n existing.color.setHex(rgbToHexNumber(fog.colour));\n if (fog.kind === \"linear\" && existing instanceof Fog) {\n existing.near = fog.near;\n existing.far = fog.far;\n } else if (fog.kind === \"exponential\" && existing instanceof FogExp2) {\n existing.density = fog.density;\n }\n }\n\n applyAmbient(light: AmbientLightSpec | null): void {\n if (light === null) {\n this.#destroy(\"ambient\");\n return;\n }\n const entity = this.#ensure(\"ambient\");\n if (!entity.hasComponent(AmbientLightComponent)) {\n entity.addComponent(AmbientLightComponent);\n }\n writeRgb(entity.getVectorView(AmbientLightComponent, \"color\"), light.colour);\n entity.setValue(AmbientLightComponent, \"intensity\", light.intensity);\n }\n\n applyKeyLight(light: KeyLightSpec | null): void {\n if (light === null) {\n this.#destroy(\"key\");\n return;\n }\n const entity = this.#ensure(\"key\");\n if (!entity.hasComponent(DirectionalLightComponent)) {\n entity.addComponent(DirectionalLightComponent);\n }\n writeRgb(entity.getVectorView(DirectionalLightComponent, \"color\"), light.colour);\n entity.setValue(DirectionalLightComponent, \"intensity\", light.intensity);\n entity.setValue(DirectionalLightComponent, \"castShadow\", light.castShadow ?? false);\n\n // The spec names the direction light TRAVELS; the component emits along\n // the entity's local -Z, so the entity is rotated to face that way. A\n // zero-length direction would produce a NaN quaternion, so it falls back\n // to straight down.\n const [x, y, z] = light.direction;\n if (Math.hypot(x, y, z) === 0) TEMP_DIRECTION.set(0, -1, 0);\n else TEMP_DIRECTION.set(x, y, z).normalize();\n TEMP_QUATERNION.setFromUnitVectors(LIGHT_FORWARD, TEMP_DIRECTION);\n writeQuaternion(entity.getVectorView(Transform, \"orientation\"), TEMP_QUATERNION);\n }\n\n dispose(): void {\n this.applySky(null);\n this.applyFog(null);\n this.applyAmbient(null);\n this.applyKeyLight(null);\n this.applyIbl(null);\n this.#releaseOccludables();\n this.#host = null;\n }\n\n #system() {\n return this.#world.getSystem(DepthSensingSystem);\n }\n\n #releaseOccludables(): void {\n for (const entity of this.#occluding) {\n if (entity.hasComponent(DepthOccludable)) entity.removeComponent(DepthOccludable);\n }\n this.#occluding = [];\n }\n\n #report(report: SensingReport): void {\n this.#host?.report(report);\n }\n\n #levelRoot(): Entity | null {\n return this.#parent ?? this.#world.activeLevel?.value ?? null;\n }\n\n /** The light entities exist independently of the level, so this cannot fail. */\n #ensure(which: \"ambient\" | \"key\"): Entity {\n const existing = which === \"ambient\" ? this.#ambientEntity : this.#keyEntity;\n if (existing !== null) return existing;\n const parent = this.#levelRoot();\n const entity = this.#world.createTransformEntity(\n undefined,\n parent === null ? undefined : { parent },\n );\n if (which === \"ambient\") this.#ambientEntity = entity;\n else this.#keyEntity = entity;\n return entity;\n }\n\n #destroy(which: \"ambient\" | \"key\"): void {\n const entity = which === \"ambient\" ? this.#ambientEntity : this.#keyEntity;\n entity?.destroy();\n if (which === \"ambient\") this.#ambientEntity = null;\n else this.#keyEntity = null;\n }\n}\n\n/**\n * The colour at the horizon: the app's, or the one the ramp implies.\n *\n * IWSDK's dome and IBL components are both three-stop, so this port always has\n * to supply a middle colour. Deriving it from the same ramp the three.js\n * adapter draws is what stops the two engines disagreeing about a sky the app\n * described once.\n */\nfunction equatorOf(gradient: SkyGradient | { top: Rgb; bottom: Rgb; equator?: Rgb }): Rgb {\n if (gradient.equator !== undefined) return gradient.equator;\n const exponent = \"exponent\" in gradient ? (gradient.exponent ?? 1) : 1;\n return lerpRgb(gradient.bottom, gradient.top, Math.pow(0.5, exponent));\n}\n\n/** Our mode names onto IWSDK's shader modes. */\nfunction occlusionMode(mode: OcclusionSpec[\"mode\"]): string {\n if (mode === \"hard\") return OcclusionShadersMode.HardOcclusion;\n if (mode === \"minmax-soft\") return OcclusionShadersMode.MinMaxSoftOcclusion;\n return OcclusionShadersMode.SoftOcclusion;\n}\n\n/** As `writeRgb`, for a `Types.Vec3` that is not a colour. */\nfunction writeVec3(view: NumericView, x: number, y: number, z: number): void {\n view[0] = x;\n view[1] = y;\n view[2] = z;\n}\n\n/** `[r, g, b]` in 0..1 -> a packed 0xRRGGBB, for the three.js-side setters. */\nfunction rgbToHexNumber(rgb: Rgb): number {\n const channel = (value: number) => Math.round(clamp01(value) * 255);\n return (channel(rgb[0]) << 16) | (channel(rgb[1]) << 8) | channel(rgb[2]);\n}\n\n/**\n * Write an RGBA colour into a component's vector view.\n *\n * The VIEW is passed in rather than the component and field name, so every\n * call site keeps elics's own `getVectorView` typing - which checks that the\n * field exists AND that it is an array type. A helper that took the component\n * would have to erase those generics, and the first misspelled field name\n * would then reach a headset instead of the compiler.\n *\n * Alpha is always 1: the specs describe opaque colours. Writing index 3 of a\n * three-component view is a no-op in JavaScript rather than an error, so no\n * length check is needed for a component whose colour is a `Types.Vec3`.\n */\nfunction writeRgb(view: NumericView, rgb: Rgb): void {\n view[0] = rgb[0];\n view[1] = rgb[1];\n view[2] = rgb[2];\n view[3] = 1;\n}\n\n/** As above, for a `Types.Vec4` orientation. */\nfunction writeQuaternion(view: NumericView, quaternion: Quaternion): void {\n view[0] = quaternion.x;\n view[1] = quaternion.y;\n view[2] = quaternion.z;\n view[3] = quaternion.w;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ export type { IWSDKEnvironmentPortOptions } from "./environment-port.js";
15
15
  export { IWSDKEnvironmentPort } from "./environment-port.js";
16
16
  export type { IWSDKAudioPortOptions } from "./audio-port.js";
17
17
  export { IWSDKAudioPort } from "./audio-port.js";
18
+ export type { IWSDKWorldSensingPortOptions } from "./world-sensing-port.js";
19
+ export { IWSDKWorldSensingPort } from "./world-sensing-port.js";
18
20
  export * from "@realitycollective/webxr-environment";
19
21
  export interface IWSDKEnvironmentOptions extends EnvironmentDirectorOptions, IWSDKEnvironmentPortOptions {
20
22
  /** Audio director + port options. Omit to set up the environment only. */
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { IWSDKEnvironmentPort } from "./environment-port.js";
10
10
  import { IWSDKAudioPort } from "./audio-port.js";
11
11
  export { IWSDKEnvironmentPort } from "./environment-port.js";
12
12
  export { IWSDKAudioPort } from "./audio-port.js";
13
+ export { IWSDKWorldSensingPort } from "./world-sensing-port.js";
13
14
  export * from "@realitycollective/webxr-environment";
14
15
  const hosts = new WeakMap();
15
16
  /** The setup registered for a world, if `registerEnvironment` has run. */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAA2B,MAAM,aAAa,CAAC;AAKpE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,cAAc,sCAAsC,CAAC;AAkBrD,MAAM,KAAK,GAAG,IAAI,OAAO,EAAgC,CAAC;AAE1D,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,KAAY;IACzC,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,iBAAkB,SAAQ,YAAY,CAAC,EAAE,CAAC;IAC5C,MAAM,CAAC,KAAa;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAyB,CAAC,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,MAAM,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC;QAC7B,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAY,EACZ,UAAmC,EAAE;IAErC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAE5C,MAAM,eAAe,GAAG,IAAI,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAEtE,IAAI,KAAK,GAAyB,IAAI,CAAC;IACvC,IAAI,SAAS,GAA0B,IAAI,CAAC;IAC5C,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,SAAS,GAAG,IAAI,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACrD,KAAK,GAAG,IAAI,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,KAAK,GAA0B;QACnC,WAAW;QACX,eAAe;QACf,KAAK;QACL,SAAS;QACT,OAAO;YACL,KAAK,EAAE,OAAO,EAAE,CAAC;YACjB,WAAW,CAAC,OAAO,EAAE,CAAC;YACtB,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;IACF,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACxB,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * `@realitycollective/iwsdk-environment` - the Meta IWSDK adapter.\n *\n * Re-exports the whole engine-free core, so an app installs this package and\n * nothing else.\n */\nimport { createSystem, type Entity, type World } from \"@iwsdk/core\";\nimport type {\n AudioDirectorOptions,\n EnvironmentDirectorOptions,\n} from \"@realitycollective/webxr-environment\";\nimport { AudioDirector, EnvironmentDirector } from \"@realitycollective/webxr-environment\";\nimport type { IWSDKEnvironmentPortOptions } from \"./environment-port.js\";\nimport { IWSDKEnvironmentPort } from \"./environment-port.js\";\nimport type { IWSDKAudioPortOptions } from \"./audio-port.js\";\nimport { IWSDKAudioPort } from \"./audio-port.js\";\n\nexport type { IWSDKEnvironmentPortOptions } from \"./environment-port.js\";\nexport { IWSDKEnvironmentPort } from \"./environment-port.js\";\nexport type { IWSDKAudioPortOptions } from \"./audio-port.js\";\nexport { IWSDKAudioPort } from \"./audio-port.js\";\n\nexport * from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKEnvironmentOptions\n extends EnvironmentDirectorOptions,\n IWSDKEnvironmentPortOptions {\n /** Audio director + port options. Omit to set up the environment only. */\n readonly audio?: AudioDirectorOptions & IWSDKAudioPortOptions;\n}\n\nexport interface IWSDKEnvironmentSetup {\n readonly environment: EnvironmentDirector;\n readonly environmentPort: IWSDKEnvironmentPort;\n readonly audio: AudioDirector | null;\n readonly audioPort: IWSDKAudioPort | null;\n /** Tear both directors down and remove everything they created. */\n dispose(): void;\n}\n\nconst hosts = new WeakMap<World, IWSDKEnvironmentSetup>();\n\n/** The setup registered for a world, if `registerEnvironment` has run. */\nexport function environmentFor(world: World): IWSDKEnvironmentSetup | undefined {\n return hosts.get(world);\n}\n\n/**\n * The per-frame tick.\n *\n * Neither director owns a loop, so something has to drive them, and on IWSDK\n * that something is a system - which means the environment stops advancing\n * when the session loses focus, exactly like the rest of the app.\n * `registerEnvironment` registers this for you; it is exported so an app that\n * builds its own system schedule can place it deliberately.\n *\n * IWSDK hands a system its delta in SECONDS and the directors take\n * milliseconds. That conversion happens here, once.\n */\nexport class EnvironmentSystem extends createSystem({}) {\n override update(delta: number): void {\n const setup = hosts.get(this.world as unknown as World);\n if (setup === undefined) return;\n const deltaMs = delta * 1000;\n setup.environment.update(deltaMs);\n setup.audio?.update(deltaMs);\n }\n}\n\n/**\n * One call to wire the environment (and optionally the audio) into an IWSDK\n * world, and register the system that ticks them.\n *\n * ```ts\n * const env = registerEnvironment(world, {\n * presets: STOCK_PRESETS,\n * initial: VOID,\n * audio: { cues: [{ id: \"hum\", src: \"/audio/hum.mp3\", bus: \"ambience\", loop: true }] },\n * });\n * env.environment.transition(\"dusk\", { durationMs: 8000 });\n * ```\n *\n * Passthrough is NOT read from the session here - push it in from whatever\n * already tracks capabilities (`env.environment.setPassthrough(...)`). The\n * session belongs to the platform layer, not to the environment.\n */\nexport function registerEnvironment(\n world: World,\n options: IWSDKEnvironmentOptions = {},\n): IWSDKEnvironmentSetup {\n const existing = hosts.get(world);\n if (existing !== undefined) return existing;\n\n const environmentPort = new IWSDKEnvironmentPort(world, options);\n const environment = new EnvironmentDirector(environmentPort, options);\n\n let audio: AudioDirector | null = null;\n let audioPort: IWSDKAudioPort | null = null;\n if (options.audio !== undefined) {\n audioPort = new IWSDKAudioPort(world, options.audio);\n audio = new AudioDirector(audioPort, options.audio);\n }\n\n const setup: IWSDKEnvironmentSetup = {\n environment,\n environmentPort,\n audio,\n audioPort,\n dispose() {\n audio?.dispose();\n environment.dispose();\n hosts.delete(world);\n },\n };\n hosts.set(world, setup);\n world.registerSystem(EnvironmentSystem);\n return setup;\n}\n\nexport type { Entity as IWSDKEntity };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAA2B,MAAM,aAAa,CAAC;AAKpE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,cAAc,sCAAsC,CAAC;AAkBrD,MAAM,KAAK,GAAG,IAAI,OAAO,EAAgC,CAAC;AAE1D,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,KAAY;IACzC,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,iBAAkB,SAAQ,YAAY,CAAC,EAAE,CAAC;IAC5C,MAAM,CAAC,KAAa;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAyB,CAAC,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,MAAM,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC;QAC7B,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAY,EACZ,UAAmC,EAAE;IAErC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAE5C,MAAM,eAAe,GAAG,IAAI,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAEtE,IAAI,KAAK,GAAyB,IAAI,CAAC;IACvC,IAAI,SAAS,GAA0B,IAAI,CAAC;IAC5C,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,SAAS,GAAG,IAAI,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACrD,KAAK,GAAG,IAAI,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,KAAK,GAA0B;QACnC,WAAW;QACX,eAAe;QACf,KAAK;QACL,SAAS;QACT,OAAO;YACL,KAAK,EAAE,OAAO,EAAE,CAAC;YACjB,WAAW,CAAC,OAAO,EAAE,CAAC;YACtB,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;IACF,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACxB,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * `@realitycollective/iwsdk-environment` - the Meta IWSDK adapter.\n *\n * Re-exports the whole engine-free core, so an app installs this package and\n * nothing else.\n */\nimport { createSystem, type Entity, type World } from \"@iwsdk/core\";\nimport type {\n AudioDirectorOptions,\n EnvironmentDirectorOptions,\n} from \"@realitycollective/webxr-environment\";\nimport { AudioDirector, EnvironmentDirector } from \"@realitycollective/webxr-environment\";\nimport type { IWSDKEnvironmentPortOptions } from \"./environment-port.js\";\nimport { IWSDKEnvironmentPort } from \"./environment-port.js\";\nimport type { IWSDKAudioPortOptions } from \"./audio-port.js\";\nimport { IWSDKAudioPort } from \"./audio-port.js\";\n\nexport type { IWSDKEnvironmentPortOptions } from \"./environment-port.js\";\nexport { IWSDKEnvironmentPort } from \"./environment-port.js\";\nexport type { IWSDKAudioPortOptions } from \"./audio-port.js\";\nexport { IWSDKAudioPort } from \"./audio-port.js\";\nexport type { IWSDKWorldSensingPortOptions } from \"./world-sensing-port.js\";\nexport { IWSDKWorldSensingPort } from \"./world-sensing-port.js\";\n\nexport * from \"@realitycollective/webxr-environment\";\n\nexport interface IWSDKEnvironmentOptions\n extends EnvironmentDirectorOptions,\n IWSDKEnvironmentPortOptions {\n /** Audio director + port options. Omit to set up the environment only. */\n readonly audio?: AudioDirectorOptions & IWSDKAudioPortOptions;\n}\n\nexport interface IWSDKEnvironmentSetup {\n readonly environment: EnvironmentDirector;\n readonly environmentPort: IWSDKEnvironmentPort;\n readonly audio: AudioDirector | null;\n readonly audioPort: IWSDKAudioPort | null;\n /** Tear both directors down and remove everything they created. */\n dispose(): void;\n}\n\nconst hosts = new WeakMap<World, IWSDKEnvironmentSetup>();\n\n/** The setup registered for a world, if `registerEnvironment` has run. */\nexport function environmentFor(world: World): IWSDKEnvironmentSetup | undefined {\n return hosts.get(world);\n}\n\n/**\n * The per-frame tick.\n *\n * Neither director owns a loop, so something has to drive them, and on IWSDK\n * that something is a system - which means the environment stops advancing\n * when the session loses focus, exactly like the rest of the app.\n * `registerEnvironment` registers this for you; it is exported so an app that\n * builds its own system schedule can place it deliberately.\n *\n * IWSDK hands a system its delta in SECONDS and the directors take\n * milliseconds. That conversion happens here, once.\n */\nexport class EnvironmentSystem extends createSystem({}) {\n override update(delta: number): void {\n const setup = hosts.get(this.world as unknown as World);\n if (setup === undefined) return;\n const deltaMs = delta * 1000;\n setup.environment.update(deltaMs);\n setup.audio?.update(deltaMs);\n }\n}\n\n/**\n * One call to wire the environment (and optionally the audio) into an IWSDK\n * world, and register the system that ticks them.\n *\n * ```ts\n * const env = registerEnvironment(world, {\n * presets: STOCK_PRESETS,\n * initial: VOID,\n * audio: { cues: [{ id: \"hum\", src: \"/audio/hum.mp3\", bus: \"ambience\", loop: true }] },\n * });\n * env.environment.transition(\"dusk\", { durationMs: 8000 });\n * ```\n *\n * Passthrough is NOT read from the session here - push it in from whatever\n * already tracks capabilities (`env.environment.setPassthrough(...)`). The\n * session belongs to the platform layer, not to the environment.\n */\nexport function registerEnvironment(\n world: World,\n options: IWSDKEnvironmentOptions = {},\n): IWSDKEnvironmentSetup {\n const existing = hosts.get(world);\n if (existing !== undefined) return existing;\n\n const environmentPort = new IWSDKEnvironmentPort(world, options);\n const environment = new EnvironmentDirector(environmentPort, options);\n\n let audio: AudioDirector | null = null;\n let audioPort: IWSDKAudioPort | null = null;\n if (options.audio !== undefined) {\n audioPort = new IWSDKAudioPort(world, options.audio);\n audio = new AudioDirector(audioPort, options.audio);\n }\n\n const setup: IWSDKEnvironmentSetup = {\n environment,\n environmentPort,\n audio,\n audioPort,\n dispose() {\n audio?.dispose();\n environment.dispose();\n hosts.delete(world);\n },\n };\n hosts.set(world, setup);\n world.registerSystem(EnvironmentSystem);\n return setup;\n}\n\nexport type { Entity as IWSDKEntity };\n"]}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `WorldSensingPort` for Meta IWSDK.
3
+ *
4
+ * ---------------------------------------------------------------------------
5
+ * IWSDK HAS ALREADY DONE HALF OF THIS
6
+ * ---------------------------------------------------------------------------
7
+ * Where the three.js adapter reads an `XRFrame` directly, IWSDK's own
8
+ * `SceneUnderstandingSystem` has already turned planes, meshes and anchors
9
+ * into ENTITIES with components on them, positioned in the scene. So this port
10
+ * queries for those entities and translates them, rather than reaching past
11
+ * IWSDK to the session - which would fight the system that owns them.
12
+ *
13
+ * The same goes for hit testing: IWSDK's `EnvironmentRaycastSystem` casts from
14
+ * a hand or the viewer and moves a target entity to the hit, so a request here
15
+ * becomes an entity with an `EnvironmentRaycastTarget` on it and the answer is
16
+ * read off that entity's transform.
17
+ *
18
+ * ---------------------------------------------------------------------------
19
+ * WHAT IT CANNOT DO
20
+ * ---------------------------------------------------------------------------
21
+ * Ask for the features. `plane-detection`, `mesh-detection` and `anchors` are
22
+ * session features, IWSDK requests them from its own world configuration, and
23
+ * the session belongs to the platform layer either way. If the app did not ask
24
+ * for them, the queries stay empty forever - so this port says `pending` and
25
+ * keeps saying it, rather than pretending to have turned something on.
26
+ */
27
+ import { type Entity, type World } from "@iwsdk/core";
28
+ import type { HitTestRequest, ResolvedWorldDetection, WorldPose, WorldSensingPort, WorldSensingPortHost } from "@realitycollective/webxr-environment";
29
+ export interface IWSDKWorldSensingPortOptions {
30
+ /** Where hit-test target entities are parented. Default: no parent. */
31
+ readonly parent?: Entity;
32
+ }
33
+ export declare class IWSDKWorldSensingPort implements WorldSensingPort {
34
+ #private;
35
+ constructor(world: World, options?: IWSDKWorldSensingPortOptions);
36
+ observe(host: WorldSensingPortHost): () => void;
37
+ setDetection(detection: ResolvedWorldDetection | null): void;
38
+ startHitTest(request: HitTestRequest): void;
39
+ stopHitTest(id: string): void;
40
+ /**
41
+ * IWSDK anchors an ENTITY rather than handing back an anchor object, so this
42
+ * makes one at the requested pose and lets `SceneUnderstandingSystem` attach
43
+ * it. The id that comes back is the same id the anchor will report with.
44
+ */
45
+ createAnchor(pose: WorldPose): Promise<string | null>;
46
+ removeAnchor(id: string): void;
47
+ update(): void;
48
+ dispose(): void;
49
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * `WorldSensingPort` for Meta IWSDK.
3
+ *
4
+ * ---------------------------------------------------------------------------
5
+ * IWSDK HAS ALREADY DONE HALF OF THIS
6
+ * ---------------------------------------------------------------------------
7
+ * Where the three.js adapter reads an `XRFrame` directly, IWSDK's own
8
+ * `SceneUnderstandingSystem` has already turned planes, meshes and anchors
9
+ * into ENTITIES with components on them, positioned in the scene. So this port
10
+ * queries for those entities and translates them, rather than reaching past
11
+ * IWSDK to the session - which would fight the system that owns them.
12
+ *
13
+ * The same goes for hit testing: IWSDK's `EnvironmentRaycastSystem` casts from
14
+ * a hand or the viewer and moves a target entity to the hit, so a request here
15
+ * becomes an entity with an `EnvironmentRaycastTarget` on it and the answer is
16
+ * read off that entity's transform.
17
+ *
18
+ * ---------------------------------------------------------------------------
19
+ * WHAT IT CANNOT DO
20
+ * ---------------------------------------------------------------------------
21
+ * Ask for the features. `plane-detection`, `mesh-detection` and `anchors` are
22
+ * session features, IWSDK requests them from its own world configuration, and
23
+ * the session belongs to the platform layer either way. If the app did not ask
24
+ * for them, the queries stay empty forever - so this port says `pending` and
25
+ * keeps saying it, rather than pretending to have turned something on.
26
+ */
27
+ import { EnvironmentRaycastSystem, EnvironmentRaycastTarget, RaycastSpace, XRAnchor, XRMesh, XRPlane, } from "@iwsdk/core";
28
+ import { Quaternion, Vector3 } from "three";
29
+ const TEMP_POSITION = new Vector3();
30
+ const TEMP_QUATERNION = new Quaternion();
31
+ export class IWSDKWorldSensingPort {
32
+ #world;
33
+ #parent;
34
+ #ids = new WeakMap();
35
+ #hitEntities = new Map();
36
+ #anchorEntities = new Map();
37
+ #states = new Map();
38
+ /**
39
+ * The three queries, registered once at construction.
40
+ *
41
+ * elics keeps one query per component mask and updates it as entities gain
42
+ * and lose components, so registering up front costs one mask walk each and
43
+ * removes any question of whether a set exists yet. An app that never turns
44
+ * detection on simply never reads them.
45
+ */
46
+ #planeQuery;
47
+ #meshQuery;
48
+ #anchorQuery;
49
+ #host = null;
50
+ #detection = null;
51
+ #nextId = 1;
52
+ constructor(world, options = {}) {
53
+ this.#world = world;
54
+ this.#parent = options.parent;
55
+ const manager = world.queryManager;
56
+ this.#planeQuery = manager.registerQuery({ required: [XRPlane] });
57
+ this.#meshQuery = manager.registerQuery({ required: [XRMesh] });
58
+ this.#anchorQuery = manager.registerQuery({ required: [XRAnchor] });
59
+ }
60
+ observe(host) {
61
+ this.#host = host;
62
+ return () => {
63
+ this.#host = null;
64
+ };
65
+ }
66
+ setDetection(detection) {
67
+ this.#detection = detection;
68
+ if (detection === null) {
69
+ this.#host?.planes([]);
70
+ this.#host?.meshes([]);
71
+ this.#host?.anchors([]);
72
+ for (const feature of ["planes", "meshes", "anchors"]) {
73
+ this.#report({ feature, state: "unavailable", detail: "detection is off" });
74
+ }
75
+ return;
76
+ }
77
+ for (const feature of ["planes", "meshes", "anchors"]) {
78
+ this.#report(detection[feature]
79
+ ? {
80
+ feature,
81
+ state: "pending",
82
+ detail: "waiting for IWSDK's scene understanding to find something",
83
+ }
84
+ : { feature, state: "unavailable", detail: "detection is off" });
85
+ }
86
+ }
87
+ startHitTest(request) {
88
+ this.#world.registerSystem(EnvironmentRaycastSystem);
89
+ const entity = this.#world.createTransformEntity(undefined, this.#parent === undefined ? undefined : { parent: this.#parent });
90
+ entity.addComponent(EnvironmentRaycastTarget, { space: raycastSpace(request.space) });
91
+ this.#hitEntities.set(request.id, entity);
92
+ this.#report({
93
+ feature: "hitTest",
94
+ state: "pending",
95
+ detail: "waiting for a hit from the " + request.space + " ray",
96
+ });
97
+ }
98
+ stopHitTest(id) {
99
+ const entity = this.#hitEntities.get(id);
100
+ if (entity === undefined)
101
+ return;
102
+ this.#hitEntities.delete(id);
103
+ entity.destroy();
104
+ }
105
+ /**
106
+ * IWSDK anchors an ENTITY rather than handing back an anchor object, so this
107
+ * makes one at the requested pose and lets `SceneUnderstandingSystem` attach
108
+ * it. The id that comes back is the same id the anchor will report with.
109
+ */
110
+ async createAnchor(pose) {
111
+ const entity = this.#world.createTransformEntity(undefined, this.#parent === undefined ? undefined : { parent: this.#parent });
112
+ const object3D = entity.object3D;
113
+ if (object3D === undefined) {
114
+ entity.destroy();
115
+ this.#report({
116
+ feature: "anchors",
117
+ state: "unavailable",
118
+ detail: "the anchor entity has no transform to place",
119
+ });
120
+ return null;
121
+ }
122
+ object3D.position.set(pose.position[0], pose.position[1], pose.position[2]);
123
+ object3D.quaternion.set(pose.orientation[0], pose.orientation[1], pose.orientation[2], pose.orientation[3]);
124
+ object3D.updateMatrixWorld();
125
+ entity.addComponent(XRAnchor);
126
+ const id = this.#idFor(entity);
127
+ this.#anchorEntities.set(id, entity);
128
+ return id;
129
+ }
130
+ removeAnchor(id) {
131
+ const entity = this.#anchorEntities.get(id);
132
+ if (entity === undefined)
133
+ return;
134
+ this.#anchorEntities.delete(id);
135
+ entity.destroy();
136
+ }
137
+ update() {
138
+ const detection = this.#detection;
139
+ if (detection === null || this.#host === null)
140
+ return;
141
+ if (detection.planes)
142
+ this.#readPlanes();
143
+ if (detection.meshes)
144
+ this.#readMeshes();
145
+ if (detection.anchors)
146
+ this.#readAnchors();
147
+ this.#readHits();
148
+ }
149
+ dispose() {
150
+ for (const entity of this.#hitEntities.values())
151
+ entity.destroy();
152
+ this.#hitEntities.clear();
153
+ this.#anchorEntities.clear();
154
+ this.#host = null;
155
+ }
156
+ #readPlanes() {
157
+ const planes = [];
158
+ for (const entity of this.#planeQuery.entities) {
159
+ const pose = poseOf(entity);
160
+ if (pose === null)
161
+ continue;
162
+ const raw = entity.getValue(XRPlane, "_plane");
163
+ const polygon = (raw?.polygon === undefined ? [] : Array.from(raw.polygon)).map((point) => [point.x, point.z]);
164
+ planes.push({
165
+ id: this.#idFor(entity),
166
+ pose,
167
+ extents: extentsOf(polygon),
168
+ orientation: orientationOf(raw?.orientation),
169
+ label: raw?.semanticLabel?.toLowerCase() ?? null,
170
+ ...(polygon.length === 0 ? {} : { polygon }),
171
+ ...(raw?.lastChangedTime === undefined ? {} : { changedAt: raw.lastChangedTime }),
172
+ });
173
+ }
174
+ this.#host?.planes(planes);
175
+ this.#report({ feature: "planes", state: "active", detail: planes.length + " surfaces" });
176
+ }
177
+ #readMeshes() {
178
+ const meshes = [];
179
+ for (const entity of this.#meshQuery.entities) {
180
+ const pose = poseOf(entity);
181
+ if (pose === null)
182
+ continue;
183
+ // IWSDK measures the mesh for us and puts the size on the component, so
184
+ // unlike the raw WebXR path there are no vertices to walk here.
185
+ const dimensions = entity.getVectorView(XRMesh, "dimensions");
186
+ const label = entity.getValue(XRMesh, "semanticLabel");
187
+ meshes.push({
188
+ id: this.#idFor(entity),
189
+ pose,
190
+ // A `Types.Vec3` view always has three components; the assertions are
191
+ // there because `noUncheckedIndexedAccess` cannot know that, and a
192
+ // fallback would be a branch no input can reach.
193
+ extents: [dimensions[0], dimensions[1], dimensions[2]],
194
+ label: typeof label === "string" && label.length > 0 ? label.toLowerCase() : null,
195
+ });
196
+ }
197
+ this.#host?.meshes(meshes);
198
+ this.#report({ feature: "meshes", state: "active", detail: meshes.length + " meshes" });
199
+ }
200
+ #readAnchors() {
201
+ const anchors = [];
202
+ for (const entity of this.#anchorQuery.entities) {
203
+ const pose = poseOf(entity);
204
+ if (pose === null)
205
+ continue;
206
+ anchors.push({
207
+ id: this.#idFor(entity),
208
+ pose,
209
+ // IWSDK's own flag: false until the system has attached the entity to
210
+ // a real anchor, which is exactly what "not tracked yet" means here.
211
+ tracked: entity.getValue(XRAnchor, "attached") === true,
212
+ });
213
+ }
214
+ this.#host?.anchors(anchors);
215
+ this.#report({ feature: "anchors", state: "active", detail: anchors.length + " anchors" });
216
+ }
217
+ #readHits() {
218
+ for (const [id, entity] of this.#hitEntities) {
219
+ const result = entity.getValue(EnvironmentRaycastTarget, "xrHitTestResult");
220
+ const pose = poseOf(entity);
221
+ const hits = result === undefined || result === null || pose === null
222
+ ? []
223
+ : [{ sourceId: id, pose, distance: null }];
224
+ this.#host?.hits(id, hits);
225
+ if (hits.length > 0) {
226
+ this.#report({ feature: "hitTest", state: "active", detail: "hitting the room" });
227
+ }
228
+ }
229
+ }
230
+ #idFor(object) {
231
+ const existing = this.#ids.get(object);
232
+ if (existing !== undefined)
233
+ return existing;
234
+ const id = "world-" + String(this.#nextId);
235
+ this.#nextId += 1;
236
+ this.#ids.set(object, id);
237
+ return id;
238
+ }
239
+ #report(report) {
240
+ const stamp = report.state + "|" + report.detail;
241
+ if (this.#states.get(report.feature) === stamp)
242
+ return;
243
+ this.#states.set(report.feature, stamp);
244
+ this.#host?.report(report);
245
+ }
246
+ }
247
+ /** The world transform of the entity IWSDK positioned for us. */
248
+ function poseOf(entity) {
249
+ const object3D = entity.object3D;
250
+ if (object3D === undefined)
251
+ return null;
252
+ object3D.getWorldPosition(TEMP_POSITION);
253
+ object3D.getWorldQuaternion(TEMP_QUATERNION);
254
+ return {
255
+ position: [TEMP_POSITION.x, TEMP_POSITION.y, TEMP_POSITION.z],
256
+ orientation: [TEMP_QUATERNION.x, TEMP_QUATERNION.y, TEMP_QUATERNION.z, TEMP_QUATERNION.w],
257
+ };
258
+ }
259
+ function orientationOf(value) {
260
+ const lowered = value?.toLowerCase();
261
+ if (lowered === "horizontal" || lowered === "vertical")
262
+ return lowered;
263
+ return "unknown";
264
+ }
265
+ function extentsOf(polygon) {
266
+ if (polygon.length === 0)
267
+ return [0, 0];
268
+ let minX = Infinity;
269
+ let maxX = -Infinity;
270
+ let minZ = Infinity;
271
+ let maxZ = -Infinity;
272
+ for (const [x, z] of polygon) {
273
+ minX = Math.min(minX, x);
274
+ maxX = Math.max(maxX, x);
275
+ minZ = Math.min(minZ, z);
276
+ maxZ = Math.max(maxZ, z);
277
+ }
278
+ return [maxX - minX, maxZ - minZ];
279
+ }
280
+ /** Our three ray sources onto IWSDK's four. */
281
+ function raycastSpace(space) {
282
+ if (space === "left")
283
+ return RaycastSpace.Left;
284
+ if (space === "right")
285
+ return RaycastSpace.Right;
286
+ return RaycastSpace.Viewer;
287
+ }
288
+ //# sourceMappingURL=world-sensing-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-sensing-port.js","sourceRoot":"","sources":["../src/world-sensing-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `WorldSensingPort` for Meta IWSDK.\n *\n * ---------------------------------------------------------------------------\n * IWSDK HAS ALREADY DONE HALF OF THIS\n * ---------------------------------------------------------------------------\n * Where the three.js adapter reads an `XRFrame` directly, IWSDK's own\n * `SceneUnderstandingSystem` has already turned planes, meshes and anchors\n * into ENTITIES with components on them, positioned in the scene. So this port\n * queries for those entities and translates them, rather than reaching past\n * IWSDK to the session - which would fight the system that owns them.\n *\n * The same goes for hit testing: IWSDK's `EnvironmentRaycastSystem` casts from\n * a hand or the viewer and moves a target entity to the hit, so a request here\n * becomes an entity with an `EnvironmentRaycastTarget` on it and the answer is\n * read off that entity's transform.\n *\n * ---------------------------------------------------------------------------\n * WHAT IT CANNOT DO\n * ---------------------------------------------------------------------------\n * Ask for the features. `plane-detection`, `mesh-detection` and `anchors` are\n * session features, IWSDK requests them from its own world configuration, and\n * the session belongs to the platform layer either way. If the app did not ask\n * for them, the queries stay empty forever - so this port says `pending` and\n * keeps saying it, rather than pretending to have turned something on.\n */\nimport {\n EnvironmentRaycastSystem,\n EnvironmentRaycastTarget,\n RaycastSpace,\n XRAnchor,\n XRMesh,\n XRPlane,\n type Entity,\n type World,\n} from \"@iwsdk/core\";\nimport { Quaternion, Vector3 } from \"three\";\nimport type {\n HitTestRequest,\n ResolvedWorldDetection,\n SensingFeature,\n SensingReport,\n WorldAnchor,\n WorldHit,\n WorldMesh,\n WorldPlane,\n WorldPlaneOrientation,\n WorldPose,\n WorldSensingPort,\n WorldSensingPortHost,\n} from \"@realitycollective/webxr-environment\";\n\n/** The raw `XRPlane` IWSDK keeps on the component, as far as we read it. */\ninterface RawPlane {\n readonly polygon?: ArrayLike<{ readonly x: number; readonly z: number }>;\n readonly orientation?: string;\n readonly semanticLabel?: string;\n readonly lastChangedTime?: number;\n}\n\n/** One elics query, of which this port uses only the entity set. */\ninterface QueryLike {\n readonly entities: Iterable<Entity>;\n}\n\ninterface QueryManagerLike {\n registerQuery(config: { required: unknown[] }): QueryLike;\n}\n\nexport interface IWSDKWorldSensingPortOptions {\n /** Where hit-test target entities are parented. Default: no parent. */\n readonly parent?: Entity;\n}\n\nconst TEMP_POSITION = new Vector3();\nconst TEMP_QUATERNION = new Quaternion();\n\nexport class IWSDKWorldSensingPort implements WorldSensingPort {\n readonly #world: World;\n readonly #parent: Entity | undefined;\n readonly #ids = new WeakMap<object, string>();\n readonly #hitEntities = new Map<string, Entity>();\n readonly #anchorEntities = new Map<string, Entity>();\n readonly #states = new Map<SensingFeature, string>();\n\n /**\n * The three queries, registered once at construction.\n *\n * elics keeps one query per component mask and updates it as entities gain\n * and lose components, so registering up front costs one mask walk each and\n * removes any question of whether a set exists yet. An app that never turns\n * detection on simply never reads them.\n */\n readonly #planeQuery: QueryLike;\n readonly #meshQuery: QueryLike;\n readonly #anchorQuery: QueryLike;\n\n #host: WorldSensingPortHost | null = null;\n #detection: ResolvedWorldDetection | null = null;\n #nextId = 1;\n\n constructor(world: World, options: IWSDKWorldSensingPortOptions = {}) {\n this.#world = world;\n this.#parent = options.parent;\n const manager = (world as unknown as { queryManager: QueryManagerLike }).queryManager;\n this.#planeQuery = manager.registerQuery({ required: [XRPlane] });\n this.#meshQuery = manager.registerQuery({ required: [XRMesh] });\n this.#anchorQuery = manager.registerQuery({ required: [XRAnchor] });\n }\n\n observe(host: WorldSensingPortHost): () => void {\n this.#host = host;\n return () => {\n this.#host = null;\n };\n }\n\n setDetection(detection: ResolvedWorldDetection | null): void {\n this.#detection = detection;\n if (detection === null) {\n this.#host?.planes([]);\n this.#host?.meshes([]);\n this.#host?.anchors([]);\n for (const feature of [\"planes\", \"meshes\", \"anchors\"] as const) {\n this.#report({ feature, state: \"unavailable\", detail: \"detection is off\" });\n }\n return;\n }\n for (const feature of [\"planes\", \"meshes\", \"anchors\"] as const) {\n this.#report(\n detection[feature]\n ? {\n feature,\n state: \"pending\",\n detail: \"waiting for IWSDK's scene understanding to find something\",\n }\n : { feature, state: \"unavailable\", detail: \"detection is off\" },\n );\n }\n }\n\n startHitTest(request: HitTestRequest): void {\n this.#world.registerSystem(EnvironmentRaycastSystem);\n const entity = this.#world.createTransformEntity(\n undefined,\n this.#parent === undefined ? undefined : { parent: this.#parent },\n );\n entity.addComponent(EnvironmentRaycastTarget, { space: raycastSpace(request.space) });\n this.#hitEntities.set(request.id, entity);\n this.#report({\n feature: \"hitTest\",\n state: \"pending\",\n detail: \"waiting for a hit from the \" + request.space + \" ray\",\n });\n }\n\n stopHitTest(id: string): void {\n const entity = this.#hitEntities.get(id);\n if (entity === undefined) return;\n this.#hitEntities.delete(id);\n entity.destroy();\n }\n\n /**\n * IWSDK anchors an ENTITY rather than handing back an anchor object, so this\n * makes one at the requested pose and lets `SceneUnderstandingSystem` attach\n * it. The id that comes back is the same id the anchor will report with.\n */\n async createAnchor(pose: WorldPose): Promise<string | null> {\n const entity = this.#world.createTransformEntity(\n undefined,\n this.#parent === undefined ? undefined : { parent: this.#parent },\n );\n const object3D = entity.object3D;\n if (object3D === undefined) {\n entity.destroy();\n this.#report({\n feature: \"anchors\",\n state: \"unavailable\",\n detail: \"the anchor entity has no transform to place\",\n });\n return null;\n }\n object3D.position.set(pose.position[0], pose.position[1], pose.position[2]);\n object3D.quaternion.set(\n pose.orientation[0],\n pose.orientation[1],\n pose.orientation[2],\n pose.orientation[3],\n );\n object3D.updateMatrixWorld();\n entity.addComponent(XRAnchor);\n const id = this.#idFor(entity as unknown as object);\n this.#anchorEntities.set(id, entity);\n return id;\n }\n\n removeAnchor(id: string): void {\n const entity = this.#anchorEntities.get(id);\n if (entity === undefined) return;\n this.#anchorEntities.delete(id);\n entity.destroy();\n }\n\n update(): void {\n const detection = this.#detection;\n if (detection === null || this.#host === null) return;\n if (detection.planes) this.#readPlanes();\n if (detection.meshes) this.#readMeshes();\n if (detection.anchors) this.#readAnchors();\n this.#readHits();\n }\n\n dispose(): void {\n for (const entity of this.#hitEntities.values()) entity.destroy();\n this.#hitEntities.clear();\n this.#anchorEntities.clear();\n this.#host = null;\n }\n\n #readPlanes(): void {\n const planes: WorldPlane[] = [];\n for (const entity of this.#planeQuery.entities) {\n const pose = poseOf(entity);\n if (pose === null) continue;\n const raw = entity.getValue(XRPlane, \"_plane\") as RawPlane | undefined;\n const polygon = (raw?.polygon === undefined ? [] : Array.from(raw.polygon)).map(\n (point) => [point.x, point.z] as const,\n );\n planes.push({\n id: this.#idFor(entity as unknown as object),\n pose,\n extents: extentsOf(polygon),\n orientation: orientationOf(raw?.orientation),\n label: raw?.semanticLabel?.toLowerCase() ?? null,\n ...(polygon.length === 0 ? {} : { polygon }),\n ...(raw?.lastChangedTime === undefined ? {} : { changedAt: raw.lastChangedTime }),\n });\n }\n this.#host?.planes(planes);\n this.#report({ feature: \"planes\", state: \"active\", detail: planes.length + \" surfaces\" });\n }\n\n #readMeshes(): void {\n const meshes: WorldMesh[] = [];\n for (const entity of this.#meshQuery.entities) {\n const pose = poseOf(entity);\n if (pose === null) continue;\n // IWSDK measures the mesh for us and puts the size on the component, so\n // unlike the raw WebXR path there are no vertices to walk here.\n const dimensions = entity.getVectorView(XRMesh, \"dimensions\");\n const label = entity.getValue(XRMesh, \"semanticLabel\");\n meshes.push({\n id: this.#idFor(entity as unknown as object),\n pose,\n // A `Types.Vec3` view always has three components; the assertions are\n // there because `noUncheckedIndexedAccess` cannot know that, and a\n // fallback would be a branch no input can reach.\n extents: [dimensions[0] as number, dimensions[1] as number, dimensions[2] as number],\n label: typeof label === \"string\" && label.length > 0 ? label.toLowerCase() : null,\n });\n }\n this.#host?.meshes(meshes);\n this.#report({ feature: \"meshes\", state: \"active\", detail: meshes.length + \" meshes\" });\n }\n\n #readAnchors(): void {\n const anchors: WorldAnchor[] = [];\n for (const entity of this.#anchorQuery.entities) {\n const pose = poseOf(entity);\n if (pose === null) continue;\n anchors.push({\n id: this.#idFor(entity as unknown as object),\n pose,\n // IWSDK's own flag: false until the system has attached the entity to\n // a real anchor, which is exactly what \"not tracked yet\" means here.\n tracked: entity.getValue(XRAnchor, \"attached\") === true,\n });\n }\n this.#host?.anchors(anchors);\n this.#report({ feature: \"anchors\", state: \"active\", detail: anchors.length + \" anchors\" });\n }\n\n #readHits(): void {\n for (const [id, entity] of this.#hitEntities) {\n const result = entity.getValue(EnvironmentRaycastTarget, \"xrHitTestResult\");\n const pose = poseOf(entity);\n const hits: WorldHit[] =\n result === undefined || result === null || pose === null\n ? []\n : [{ sourceId: id, pose, distance: null }];\n this.#host?.hits(id, hits);\n if (hits.length > 0) {\n this.#report({ feature: \"hitTest\", state: \"active\", detail: \"hitting the room\" });\n }\n }\n }\n\n #idFor(object: object): string {\n const existing = this.#ids.get(object);\n if (existing !== undefined) return existing;\n const id = \"world-\" + String(this.#nextId);\n this.#nextId += 1;\n this.#ids.set(object, id);\n return id;\n }\n\n #report(report: SensingReport): void {\n const stamp = report.state + \"|\" + report.detail;\n if (this.#states.get(report.feature) === stamp) return;\n this.#states.set(report.feature, stamp);\n this.#host?.report(report);\n }\n}\n\n/** The world transform of the entity IWSDK positioned for us. */\nfunction poseOf(entity: Entity): WorldPose | null {\n const object3D = entity.object3D;\n if (object3D === undefined) return null;\n object3D.getWorldPosition(TEMP_POSITION);\n object3D.getWorldQuaternion(TEMP_QUATERNION);\n return {\n position: [TEMP_POSITION.x, TEMP_POSITION.y, TEMP_POSITION.z],\n orientation: [TEMP_QUATERNION.x, TEMP_QUATERNION.y, TEMP_QUATERNION.z, TEMP_QUATERNION.w],\n };\n}\n\nfunction orientationOf(value: string | undefined): WorldPlaneOrientation {\n const lowered = value?.toLowerCase();\n if (lowered === \"horizontal\" || lowered === \"vertical\") return lowered;\n return \"unknown\";\n}\n\nfunction extentsOf(polygon: readonly (readonly [number, number])[]): readonly [number, number] {\n if (polygon.length === 0) return [0, 0];\n let minX = Infinity;\n let maxX = -Infinity;\n let minZ = Infinity;\n let maxZ = -Infinity;\n for (const [x, z] of polygon) {\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minZ = Math.min(minZ, z);\n maxZ = Math.max(maxZ, z);\n }\n return [maxX - minX, maxZ - minZ];\n}\n\n/** Our three ray sources onto IWSDK's four. */\nfunction raycastSpace(space: HitTestRequest[\"space\"]): string {\n if (space === \"left\") return RaycastSpace.Left;\n if (space === \"right\") return RaycastSpace.Right;\n return RaycastSpace.Viewer;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/iwsdk-environment",
3
- "version": "0.1.0-preview.0",
3
+ "version": "0.1.0-preview.2",
4
4
  "description": "Meta IWSDK adapter for the Reality Collective WebXR Environment Extensions - drives IWSDK's own DomeGradient, light components, transform entities and AudioSource machinery from the engine-free @realitycollective/webxr-environment core, with a one-call registerEnvironment(world) setup and an ECS system that ticks the directors. Re-exports the core.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -35,15 +35,16 @@
35
35
  "typecheck": "tsc -p tsconfig.json --noEmit"
36
36
  },
37
37
  "dependencies": {
38
- "@realitycollective/webxr-environment": "^0.1.0-preview.0"
38
+ "@realitycollective/webxr-environment": "^0.1.0-preview.2"
39
39
  },
40
40
  "peerDependencies": {
41
- "@iwsdk/core": ">=0.5.0 <0.6.0"
41
+ "@iwsdk/core": ">=0.5.0 <0.6.0",
42
+ "three": ">=0.170.0"
42
43
  },
43
44
  "devDependencies": {
44
45
  "@iwsdk/core": "0.5.3",
45
- "three": "npm:super-three@0.181.0",
46
- "@types/three": "*"
46
+ "three": "npm:super-three@0.185.0",
47
+ "@types/three": "0.185.4"
47
48
  },
48
49
  "repository": {
49
50
  "type": "git",