@displayxr/inline3d 1.3.0 → 1.5.0
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 +139 -0
- package/index.d.ts +295 -0
- package/js/inline3d-mode-switch.js +246 -0
- package/js/inline3d-model.js +1 -1
- package/js/inline3d-undock.js +251 -0
- package/js/inline3d.js +1151 -25
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,145 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
|
|
|
5
5
|
`./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
|
|
6
6
|
which tier they touch, because that is what tells you whether an upgrade can move your pixels.
|
|
7
7
|
|
|
8
|
+
## 1.5.0 — 2026-09-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **The 2D↔3D switch is EASED, and every page gets it for free.** 1.4.0 made the panel's mode a
|
|
13
|
+
page-facing control and collapsed the stereo rig the moment a 1-view mode went active — correct,
|
|
14
|
+
and a snap. The transition now ramps: a **page-initiated** switch walks every window's
|
|
15
|
+
`ipdFactor`/`parallaxFactor` between 0 and what the page asked for over **180 ms**, **smoothstep**
|
|
16
|
+
(Hermite `3t^2 - 2t^3`) — the defaults the native DisplayXR apps configure, because this is a port
|
|
17
|
+
of the sequencer they already use (`dxr::ModeSwitch`, displayxr-common) rather than a second
|
|
18
|
+
design. New dependency-free module `js/inline3d-mode-switch.js` holds the state machine;
|
|
19
|
+
`test/mode-switch.test.mjs` mirrors the C++ smoke test case for case. *(core tier — additive)*
|
|
20
|
+
|
|
21
|
+
- **The ORDER is the feature, and it is asymmetric.** Going flat (a `viewCount === 1` target) ramps
|
|
22
|
+
the disparity **out first** and forwards the mode request only when it lands, so the panel flips
|
|
23
|
+
on already-flat content instead of snapping a stereo image flat. Coming back (a 2-view target)
|
|
24
|
+
forwards the request **first** and eases the disparity in **only once the panel REPORTS 3D** —
|
|
25
|
+
ramping up any earlier would put stereo on a still-flat panel, which is the blurry double image
|
|
26
|
+
the whole mode API exists to make unreachable. A hand-rolled tween gets exactly this wrong.
|
|
27
|
+
|
|
28
|
+
- **`createInline3D({ modeSwitch: { durationMs, easing, enabled } })`** — `durationMs` default
|
|
29
|
+
`180` (`0` keeps the ordering and lands in one frame), `easing` default `'smoothstep'` (also
|
|
30
|
+
`'linear'`, `'easeoutcubic'`; an unknown name warns once and falls back), `enabled: false`
|
|
31
|
+
restores 1.4.0's snap exactly. **`wall.modeSwitch`** is the read-only live state
|
|
32
|
+
`{active, factor}` for a page that wants to move its own 2D chrome alongside the panel. The SDK
|
|
33
|
+
adds **no UI** — which key or button toggles the display stays the page's call.
|
|
34
|
+
*(core tier — additive)*
|
|
35
|
+
|
|
36
|
+
- **What a page can feel, spelled out.** The restore is to the **configured** steady factors (each
|
|
37
|
+
window's own rig, never a hardcoded 1) and the ramp is a **copy** on the way to the layer, so the
|
|
38
|
+
1.4.0 guarantees hold unchanged: a per-frame `setViewRig` loop cannot walk the page out of 2D,
|
|
39
|
+
and a lazy tile that rebuilds mid-transition comes back at the current factor. The ramp is driven
|
|
40
|
+
by **wall-clock dt** from the session's frame loop (never frame counts, so it lasts the same at
|
|
41
|
+
30 fps and 144 fps), with a timer fallback so a held request still lands when frames stop.
|
|
42
|
+
Reversing mid-flight retargets from the disparity **in force** — the first press never snaps —
|
|
43
|
+
and a reversed going-flat switch **never fires**: nothing is asked of the display at all.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **`requestRenderingMode(i)` / `setStereoEnabled(false)` for a 1-view target now resolve when the
|
|
48
|
+
request has been FORWARDED**, i.e. after the ramp (~`durationMs`), not on the call. They reject
|
|
49
|
+
as before if the browser refuses — and a refusal ramps the disparity back **up**, because a
|
|
50
|
+
refused switch must leave the page in 3D rather than flat. One new failure: a request dropped by
|
|
51
|
+
a reversal before it ever fired rejects with an `Error` named **`superseded`**. Nothing changes
|
|
52
|
+
for a page that only awaits the promise it already awaited; `{ enabled: false }` restores the old
|
|
53
|
+
timing. *(core tier — behaviour change, opt-out)*
|
|
54
|
+
|
|
55
|
+
- A mode change the page did **not** request (another tab, the shell, a page that opens with the
|
|
56
|
+
panel already flat) still **snaps** — there is nothing to ramp from, and the reported state stays
|
|
57
|
+
the sole authority for the rig. `wall.stereoCollapsed` continues to mean what the display last
|
|
58
|
+
**reported**, never what is mid-ramp.
|
|
59
|
+
|
|
60
|
+
## 1.4.0 — 2026-09-06
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- **Display modes — the page can read what the panel IS, and ask it to change.** Until now a page
|
|
65
|
+
could describe its own framing (a view rig) but knew nothing about the display it was framing
|
|
66
|
+
*for*: not its size in metres, not its pixel count, not which of the runtime's rendering modes it
|
|
67
|
+
was in, and it had no way to ask for a different one. Three pass-throughs close that:
|
|
68
|
+
**`getDisplayInfo()`** (physical size, pixel size, recommended view scale; `null` where there is
|
|
69
|
+
no glasses-free display), **`getRenderingModes()`** (every mode the runtime can put the display
|
|
70
|
+
in — view count, tile grid, per-view pixels, `hardwareDisplay3D`, `isActive`, `isRequestable`)
|
|
71
|
+
and **`requestRenderingMode(i)`**. They sit on the **`createInline3D()` result** — the panel is
|
|
72
|
+
the document's, not a tile's — and under the same names on every tile handle, routed to whichever
|
|
73
|
+
window currently holds a live layer. Gate the group with
|
|
74
|
+
**`inline3dDisplayModesSupported()`**, which requires all three methods on
|
|
75
|
+
`XRDisplayLayer.prototype` — a browser shipping half the set is one mid-implementation, and
|
|
76
|
+
calling it supported would surface as a `not a function` inside a click handler.
|
|
77
|
+
*(core tier — additive)*
|
|
78
|
+
|
|
79
|
+
- **The 2D/3D hardware state is a consequence of the mode, not a control.** There is deliberately
|
|
80
|
+
**no page-facing request** for it. Asking for a mode with `viewCount === 1` puts the panel in its
|
|
81
|
+
2D state and the browser reports that mode active — the runtime carries on weaving the same fixed
|
|
82
|
+
two-view atlas — and asking for the 2-view mode puts it back. Tying the two together makes the
|
|
83
|
+
one bad state (a flat panel showing a stereo atlas, i.e. a blurry double image rather than 2D)
|
|
84
|
+
unreachable. **`requestDisplayMode('2d'|'3d')` is gone**, and so is every mention of a "lens".
|
|
85
|
+
|
|
86
|
+
- **The SDK collapses the stereo rig automatically.** When a 1-view mode goes **active** every
|
|
87
|
+
window's `ipdFactor`/`parallaxFactor` is pushed to 0 (both eyes render from one place), and a
|
|
88
|
+
2-view mode going active restores them. This is driven by the `renderingmodechange` event — plus
|
|
89
|
+
the first `getRenderingModes()` read, so a page that OPENS with the panel already flat is
|
|
90
|
+
collapsed too — and **not** by the request. So it happens however the mode changed (this page,
|
|
91
|
+
another one, the shell), the page's own render loop is untouched, and **a refused request changes
|
|
92
|
+
nothing in either direction**, which is now structural rather than a rollback.
|
|
93
|
+
|
|
94
|
+
**The restore is exact, and that is a design property, not luck.** The flattening is a *copy*
|
|
95
|
+
pushed at the layer — a page driving a rig every frame reuses one descriptor object, so zeroing
|
|
96
|
+
it in place would write the flattening into the page's own state and the restore would restore
|
|
97
|
+
0. So each window keeps what the page asked for, untouched, and the flat rig is derived on the
|
|
98
|
+
way out. That latch also means a per-frame `setViewRig` loop cannot walk the page out of 2D, a
|
|
99
|
+
lazy tile that scrolls away and rebuilds its layer comes back flat rather than in 3D, and a
|
|
100
|
+
window that never set a rig at all is flattened (and restored) via the exact rig equivalent of
|
|
101
|
+
its `virtualDisplayHeight`. *(core tier — additive)*
|
|
102
|
+
|
|
103
|
+
- **`setStereoEnabled(bool)` is now SUGAR, and only sugar** — `false` requests the first mode with
|
|
104
|
+
`viewCount === 1 && isRequestable`, `true` the first with `viewCount === 2 && isRequestable`. It
|
|
105
|
+
touches neither the hardware state (nothing can) nor the rig (the event does that). It rejects
|
|
106
|
+
rather than inventing a mode when the panel lists none. *(core tier — behaviour change on an
|
|
107
|
+
unreleased API)*
|
|
108
|
+
|
|
109
|
+
- **`on(type, cb)` / `off(type, cb)`** — the two events, which fire on the **XRSession** and not on
|
|
110
|
+
the layer, re-emitted on the wall and on every handle: `renderingmodechange`
|
|
111
|
+
`{type, modeIndex, viewCount, mode, detail}` and `hardwaredisplaystatechange`
|
|
112
|
+
`{type, state:'2d'|'3d', detail}`. `on` returns an unsubscribe function;
|
|
113
|
+
**`onDisplayModeChange(cb)`** remains as the both-events-one-callback shape.
|
|
114
|
+
`wall.hardwareDisplayState`, `wall.activeMode` and `wall.stereoCollapsed` expose the last
|
|
115
|
+
**reported** state — never the last requested one. *(core tier — additive)*
|
|
116
|
+
|
|
117
|
+
- **Undock — lift a window's asset into a floating native viewer over the desktop.**
|
|
118
|
+
**`wall.undock`** is `{model, splat}` on a browser with `XRDisplayLayer.undock` and **`null`** on
|
|
119
|
+
one without (plain Chrome, or an older DisplayXR browser) — that null is what a page branches on;
|
|
120
|
+
**`wall.refreshUndock()`** re-reads it, **`inline3dUndockSupported()`** is the sync probe. The
|
|
121
|
+
action is **`await undock(element, {src, type, env?, pose?, margin?, title?})`** (new module
|
|
122
|
+
`js/inline3d-undock.js`, re-exported from the main entry): API-first through `layer.undock()`,
|
|
123
|
+
falling back to the `displayxr-view:` OS protocol (hidden-iframe navigation, Chrome's one-time
|
|
124
|
+
"Open DisplayXR…?" prompt) where the layer method is absent. **Call it synchronously inside the
|
|
125
|
+
click** — both paths need the transient activation. Resolves to `{ended, viewer, detached}`;
|
|
126
|
+
rejects with an Error named `not-installed` | `src-not-allowed` | `no-activation` | `busy`.
|
|
127
|
+
*(core tier — additive)*
|
|
128
|
+
|
|
129
|
+
- **[`samples/display-modes/`](samples/display-modes/)** — the whole surface on one page: the
|
|
130
|
+
`getDisplayInfo()` fields, the `getRenderingModes()` table with the active row marked, a request
|
|
131
|
+
button on every requestable row (`viewCount` 1 **or** 2) and the rows needing more than two views
|
|
132
|
+
greyed with the reason, one `setStereoEnabled` convenience button, a **read-only** hardware
|
|
133
|
+
display state badge fed by the event, and a live event log. Every action prints a greppable
|
|
134
|
+
`[display-modes] …` line so a harness can drive it from the console.
|
|
135
|
+
|
|
136
|
+
### Notes
|
|
137
|
+
|
|
138
|
+
- **Advisory scales.** `viewScaleX/Y` and `recommendedViewScaleX/Y` are what the runtime would like
|
|
139
|
+
the per-view resolution to be. The browser cannot resize a page's canvas, so nothing applies them
|
|
140
|
+
for you — a page honours them by sizing its own backing store. Ignoring them costs sharpness or
|
|
141
|
+
fill rate, never correctness.
|
|
142
|
+
- **The browser is fixed at two views.** No view synthesis exists anywhere in this stack, so a mode
|
|
143
|
+
needing MORE than two is listed (the panel really can do it) and refused. Show those rows; mark
|
|
144
|
+
them. A **one**-view mode is requestable — the browser still submits two views and the runtime
|
|
145
|
+
still weaves them; it is the panel that goes flat.
|
|
146
|
+
|
|
8
147
|
## 1.3.0 — 2026-09-04
|
|
9
148
|
|
|
10
149
|
### Added
|
package/index.d.ts
CHANGED
|
@@ -52,6 +52,114 @@ export interface XRViewRigInit {
|
|
|
52
52
|
metersToVirtual?: number;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The panel's hardware display state, as reported by `hardwaredisplaystatechange`.
|
|
57
|
+
*
|
|
58
|
+
* There is no page-facing request for it: it is a CONSEQUENCE of the active rendering mode —
|
|
59
|
+
* a `viewCount === 1` mode puts the panel flat, the 2-view mode puts it back.
|
|
60
|
+
*/
|
|
61
|
+
export type XRHardwareDisplayMode = '2d' | '3d';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What the glasses-free display physically is. Resolved by {@link TileHandle.getDisplayInfo};
|
|
65
|
+
* `null` there means the machine has no such display.
|
|
66
|
+
*
|
|
67
|
+
* `recommendedViewScaleX/Y` are **advisory**. The browser cannot resize a page's canvas, so
|
|
68
|
+
* nothing applies them for you — a page honours them by sizing its own backing store. Ignoring
|
|
69
|
+
* them costs sharpness or fill rate, never correctness.
|
|
70
|
+
*/
|
|
71
|
+
export interface XRDisplayInfo {
|
|
72
|
+
displayWidthMeters: number;
|
|
73
|
+
displayHeightMeters: number;
|
|
74
|
+
displayPixelWidth: number;
|
|
75
|
+
displayPixelHeight: number;
|
|
76
|
+
recommendedViewScaleX: number;
|
|
77
|
+
recommendedViewScaleY: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* One rendering mode the DISPLAY can be put in, as reported by the runtime.
|
|
82
|
+
*
|
|
83
|
+
* The list is the display's, not the browser's. The DisplayXR Browser renders exactly **two**
|
|
84
|
+
* views — no view synthesis exists anywhere in the stack — so a mode needing MORE than two is
|
|
85
|
+
* reported with `isRequestable: false` and {@link TileHandle.requestRenderingMode} refuses it.
|
|
86
|
+
* Show such rows (they are what the panel can do) but mark them unavailable. A `viewCount === 1`
|
|
87
|
+
* mode IS requestable: the browser still submits two views and the runtime still weaves them —
|
|
88
|
+
* it is the PANEL that goes flat, which is how a page reaches the 2D hardware state.
|
|
89
|
+
*/
|
|
90
|
+
export interface XRDisplayRenderingMode {
|
|
91
|
+
modeIndex: number;
|
|
92
|
+
name: string;
|
|
93
|
+
/** @deprecated the browser reports `name`; kept for pages written against the earlier build. */
|
|
94
|
+
modeName?: string;
|
|
95
|
+
viewCount: number;
|
|
96
|
+
/** Per-view render scale the runtime recommends for this mode — advisory, like the display's. */
|
|
97
|
+
viewScaleX: number;
|
|
98
|
+
viewScaleY: number;
|
|
99
|
+
tileColumns: number;
|
|
100
|
+
tileRows: number;
|
|
101
|
+
viewWidthPixels: number;
|
|
102
|
+
viewHeightPixels: number;
|
|
103
|
+
/** True for a glasses-free 3D mode; false for a flat one. */
|
|
104
|
+
hardwareDisplay3D: boolean;
|
|
105
|
+
isActive: boolean;
|
|
106
|
+
/** False when the browser cannot drive it — in practice, `viewCount > 2`. */
|
|
107
|
+
isRequestable: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A rendering mode went active. Re-emitted on the wall and on every tile handle. */
|
|
111
|
+
export interface RenderingModeChange {
|
|
112
|
+
type: 'renderingmodechange';
|
|
113
|
+
/** The mode now active, or -1 if the browser named none and the list could not be read. */
|
|
114
|
+
modeIndex: number;
|
|
115
|
+
/** Its view count — the thing the automatic rig collapse turns on. Null if unknown. */
|
|
116
|
+
viewCount: number | null;
|
|
117
|
+
/** The full mode row, when it could be read back. */
|
|
118
|
+
mode: XRDisplayRenderingMode | null;
|
|
119
|
+
/** The browser's own event payload, unreshaped. */
|
|
120
|
+
detail: unknown;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The panel's hardware display state changed. */
|
|
124
|
+
export interface HardwareDisplayStateChange {
|
|
125
|
+
type: 'hardwaredisplaystatechange';
|
|
126
|
+
state: XRHardwareDisplayMode | null;
|
|
127
|
+
detail: unknown;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type DisplayModeChange = RenderingModeChange | HardwareDisplayStateChange;
|
|
131
|
+
|
|
132
|
+
/** What a page may lift into the floating native viewer. `null` = no `XRDisplayLayer.undock`. */
|
|
133
|
+
export interface UndockCapabilities {
|
|
134
|
+
model: boolean;
|
|
135
|
+
splat: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface UndockOptions {
|
|
139
|
+
/** Absolute https URL (or http on loopback) of the asset the NATIVE viewer loads. */
|
|
140
|
+
src: string;
|
|
141
|
+
type: 'model' | 'splat';
|
|
142
|
+
/** Lighting the page rendered with, so the viewer can match it. */
|
|
143
|
+
env?: 'room' | 'studio' | 'sky' | 'none';
|
|
144
|
+
/** The angle the page opened the asset at, so the undocked view opens at the same one. */
|
|
145
|
+
pose?: { yaw: number; pitch?: number; zoom?: number };
|
|
146
|
+
/** The page's fit margin, when it overrides the default. */
|
|
147
|
+
margin?: number;
|
|
148
|
+
title?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface UndockHandle {
|
|
152
|
+
/**
|
|
153
|
+
* Resolves when the viewer exits. On the protocol FALLBACK it resolves immediately and
|
|
154
|
+
* `detached` is true — a protocol launch is fire-and-forget and the page never hears back.
|
|
155
|
+
*/
|
|
156
|
+
ended: Promise<void>;
|
|
157
|
+
viewer: 'model' | 'splat';
|
|
158
|
+
detached: boolean;
|
|
159
|
+
/** The `displayxr-view:` URL, on the fallback path only. */
|
|
160
|
+
url?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
55
163
|
/** Extra options for {@link Inline3D.addScene}. */
|
|
56
164
|
export interface SceneOptions extends TileOptions {
|
|
57
165
|
/**
|
|
@@ -115,8 +223,68 @@ export interface TileHandle {
|
|
|
115
223
|
* identity-posed camera rig and parent your eye cameras under the app camera
|
|
116
224
|
* (`cameraRigFromCamera(THREE, cam, { attach: true })` + `EyeCamera.setLocalFromView`), so the
|
|
117
225
|
* scene graph supplies this frame's world pose with no lag at all.
|
|
226
|
+
*
|
|
227
|
+
* While a `viewCount === 1` mode is active the rig is stored **as given** and pushed **flat**
|
|
228
|
+
* (a copy with `ipdFactor`/`parallaxFactor` at 0), so a page driving a rig every frame cannot
|
|
229
|
+
* walk out of 2D, and the 2-view mode going active restores exactly this rig.
|
|
118
230
|
*/
|
|
119
231
|
setViewRig(rig: XRViewRigInit): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* The panel this window weaves on, or `null` where there is no glasses-free display.
|
|
234
|
+
*
|
|
235
|
+
* Rejects with an `Error` on a browser without the display-mode API
|
|
236
|
+
* ({@link inline3dDisplayModesSupported}) or while this window has no live layer (lazy mode,
|
|
237
|
+
* tile off screen).
|
|
238
|
+
*/
|
|
239
|
+
getDisplayInfo(): Promise<XRDisplayInfo | null>;
|
|
240
|
+
/** Every rendering mode the display can be put in. See {@link XRDisplayRenderingMode}. */
|
|
241
|
+
getRenderingModes(): Promise<ReadonlyArray<XRDisplayRenderingMode>>;
|
|
242
|
+
/**
|
|
243
|
+
* Ask the runtime to switch the display to `modeIndex`. A thin pass-through — it resolves and
|
|
244
|
+
* rejects exactly as the browser does.
|
|
245
|
+
*
|
|
246
|
+
* Rejects with a `TypeError` for a mode whose `viewCount > 2` (the browser is fixed at two
|
|
247
|
+
* views) or an unknown index — the browser raises those synchronously, and this pass-through
|
|
248
|
+
* turns them into rejections so one `.catch()` covers every failure — and with a
|
|
249
|
+
* `NotSupportedError` `DOMException` when the request was not forwardable. Success is signalled
|
|
250
|
+
* by the session's `renderingmodechange` event, not by this promise.
|
|
251
|
+
*
|
|
252
|
+
* A `viewCount === 1` mode is requestable and is how a page goes flat.
|
|
253
|
+
*
|
|
254
|
+
* With the eased transition on (the default — see {@link ModeSwitchOptions}) a going-flat
|
|
255
|
+
* request is HELD while the disparity ramps out, so the promise resolves when the request has
|
|
256
|
+
* been forwarded rather than on the call; a request dropped by a reversal in that window
|
|
257
|
+
* rejects with an `Error` named `superseded`.
|
|
258
|
+
*/
|
|
259
|
+
requestRenderingMode(modeIndex: number): Promise<void>;
|
|
260
|
+
/**
|
|
261
|
+
* SUGAR over {@link TileHandle.requestRenderingMode}: `false` requests the first mode with
|
|
262
|
+
* `viewCount === 1 && isRequestable`, `true` the first with `viewCount === 2 && isRequestable`.
|
|
263
|
+
* It never touches the hardware display state directly (there is no such call) and never
|
|
264
|
+
* touches your rig.
|
|
265
|
+
*
|
|
266
|
+
* **The rig collapse is not part of this call.** When a 1-view mode actually goes ACTIVE the
|
|
267
|
+
* SDK zeroes every window's `ipdFactor`/`parallaxFactor` on the way to the layer and restores
|
|
268
|
+
* them when a 2-view mode does — driven by `renderingmodechange`, so it happens however the
|
|
269
|
+
* mode changed, and a **refused request changes nothing in either direction**. The flattening
|
|
270
|
+
* is a copy pushed at the layer, never a write into your descriptor, so the restore is literally
|
|
271
|
+
* the rig you last set — and it survives a per-frame `setViewRig` loop, a lazy tile rebuilding
|
|
272
|
+
* its layer, and a window that never set a rig at all.
|
|
273
|
+
*
|
|
274
|
+
* Rejects with an `Error` when no such mode is listed, otherwise as `requestRenderingMode` does.
|
|
275
|
+
*/
|
|
276
|
+
setStereoEnabled(enabled: boolean): Promise<boolean>;
|
|
277
|
+
/** Listen for one display event, re-emitted on this handle. Returns an unsubscribe function. */
|
|
278
|
+
on(type: 'renderingmodechange', cb: (e: RenderingModeChange) => void): () => void;
|
|
279
|
+
on(type: 'hardwaredisplaystatechange', cb: (e: HardwareDisplayStateChange) => void): () => void;
|
|
280
|
+
/** Drop a listener registered with {@link TileHandle.on}. */
|
|
281
|
+
off(type: 'renderingmodechange', cb: (e: RenderingModeChange) => void): void;
|
|
282
|
+
off(type: 'hardwaredisplaystatechange', cb: (e: HardwareDisplayStateChange) => void): void;
|
|
283
|
+
/**
|
|
284
|
+
* Both display events through one callback — the older shape, still supported. Returns an
|
|
285
|
+
* unsubscribe function; inert (a no-op unsubscribe) on a browser without the API.
|
|
286
|
+
*/
|
|
287
|
+
onDisplayModeChange(cb: (e: DisplayModeChange) => void): () => void;
|
|
120
288
|
/**
|
|
121
289
|
* Per-window frame counters, for diagnosing the load-induced mono fallback.
|
|
122
290
|
*
|
|
@@ -141,6 +309,44 @@ export interface Inline3D {
|
|
|
141
309
|
/** Number of currently-active (weaving) windows. */
|
|
142
310
|
readonly liveCount: number;
|
|
143
311
|
|
|
312
|
+
// The panel is the DOCUMENT's, not a tile's, so the display API lives here; the same names are
|
|
313
|
+
// on every tile handle, routed to whichever window currently holds a live layer.
|
|
314
|
+
|
|
315
|
+
/** The panel, or `null` where there is no glasses-free display. See {@link TileHandle.getDisplayInfo}. */
|
|
316
|
+
getDisplayInfo(): Promise<XRDisplayInfo | null>;
|
|
317
|
+
/** Every rendering mode the display can be put in. See {@link XRDisplayRenderingMode}. */
|
|
318
|
+
getRenderingModes(): Promise<ReadonlyArray<XRDisplayRenderingMode>>;
|
|
319
|
+
/** Switch the display to `modeIndex`. See {@link TileHandle.requestRenderingMode}. */
|
|
320
|
+
requestRenderingMode(modeIndex: number): Promise<void>;
|
|
321
|
+
/** Sugar: `false` -> a 1-view mode, `true` -> the 2-view mode. See {@link TileHandle.setStereoEnabled}. */
|
|
322
|
+
setStereoEnabled(enabled: boolean): Promise<boolean>;
|
|
323
|
+
on(type: 'renderingmodechange', cb: (e: RenderingModeChange) => void): () => void;
|
|
324
|
+
on(type: 'hardwaredisplaystatechange', cb: (e: HardwareDisplayStateChange) => void): () => void;
|
|
325
|
+
off(type: 'renderingmodechange', cb: (e: RenderingModeChange) => void): void;
|
|
326
|
+
off(type: 'hardwaredisplaystatechange', cb: (e: HardwareDisplayStateChange) => void): void;
|
|
327
|
+
/** As last REPORTED by `hardwaredisplaystatechange` — never what was last requested. */
|
|
328
|
+
readonly hardwareDisplayState: XRHardwareDisplayMode | null;
|
|
329
|
+
/** The active mode as last read/reported. `viewCount: 0` means "not read yet". */
|
|
330
|
+
readonly activeMode: { modeIndex: number; viewCount: number };
|
|
331
|
+
/** True while the SDK is holding every window's rig flat because a 1-view mode is active. */
|
|
332
|
+
readonly stereoCollapsed: boolean;
|
|
333
|
+
/**
|
|
334
|
+
* The eased 2D<->3D transition, live. `factor` is what every window's
|
|
335
|
+
* `ipdFactor`/`parallaxFactor` is being multiplied by on the way to the layer (`1` in 3D, `0`
|
|
336
|
+
* flat, in between mid-ramp); `active` is true while a page-initiated switch is in any of its
|
|
337
|
+
* phases. Read-only and purely informational — the SDK adds no UI of its own for this.
|
|
338
|
+
*/
|
|
339
|
+
readonly modeSwitch: { active: boolean; factor: number };
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* What this build can lift into the floating native viewer, or `null` on a browser with no
|
|
343
|
+
* `XRDisplayLayer.undock` — that null is what a page branches on. Read off the first live
|
|
344
|
+
* layer (`layer.getUndockCapabilities()`); `{model:false, splat:false}` is the pre-read value.
|
|
345
|
+
*/
|
|
346
|
+
readonly undock: UndockCapabilities | null;
|
|
347
|
+
/** Re-read {@link Inline3D.undock} off a live layer. */
|
|
348
|
+
refreshUndock(): Promise<UndockCapabilities | null>;
|
|
349
|
+
|
|
144
350
|
/** Weave a still side-by-side 3D photo from a URL or decoded image source. */
|
|
145
351
|
addImage(
|
|
146
352
|
canvas: HTMLCanvasElement,
|
|
@@ -217,6 +423,41 @@ export interface CreateInline3DOptions {
|
|
|
217
423
|
* SDK never touches your DOM's `will-change`, because the chrome already occludes the tiles.
|
|
218
424
|
*/
|
|
219
425
|
autoChrome?: boolean;
|
|
426
|
+
/** The eased 2D<->3D transition. On by default; see {@link ModeSwitchOptions}. */
|
|
427
|
+
modeSwitch?: ModeSwitchOptions;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* The eased 2D<->3D transition — on by default, and the same sequencer (and the same defaults)
|
|
432
|
+
* the native DisplayXR apps use.
|
|
433
|
+
*
|
|
434
|
+
* Instead of snapping the stereo rig the moment the panel's mode changes, a **page-initiated**
|
|
435
|
+
* switch ramps every window's `ipdFactor`/`parallaxFactor` between 0 and what the page asked for,
|
|
436
|
+
* in the order that looks right:
|
|
437
|
+
*
|
|
438
|
+
* - **going flat** (a `viewCount === 1` target): the disparity ramps OUT first, and the mode
|
|
439
|
+
* request is forwarded only when it lands — so the panel flips on already-flat content. That is
|
|
440
|
+
* why `requestRenderingMode()` / `setStereoEnabled(false)` resolve a ramp later than they used
|
|
441
|
+
* to: they resolve when the request has actually been forwarded.
|
|
442
|
+
* - **coming back** (a 2-view target): the request goes out at once, and the disparity eases in
|
|
443
|
+
* only once the panel REPORTS 3D — disparity on a still-flat panel is the double image the
|
|
444
|
+
* whole mode API exists to prevent.
|
|
445
|
+
*
|
|
446
|
+
* Interruptible: pressing the toggle again mid-ramp retargets from the disparity in force, and
|
|
447
|
+
* reversing a going-flat switch that has not fired yet simply ramps back up without ever asking
|
|
448
|
+
* the panel for anything (the dropped request rejects with an `Error` named `superseded`).
|
|
449
|
+
*
|
|
450
|
+
* A mode change the page did **not** request (another tab, the shell, a panel that opens flat)
|
|
451
|
+
* always snaps — there is nothing to ramp from. This is aesthetic policy only; correctness is the
|
|
452
|
+
* runtime's either way.
|
|
453
|
+
*/
|
|
454
|
+
export interface ModeSwitchOptions {
|
|
455
|
+
/** Ramp duration in ms (default `180`, matching the native default of 0.18 s). `0` = instant. */
|
|
456
|
+
durationMs?: number;
|
|
457
|
+
/** Easing curve (default `'smoothstep'`, Hermite `3t^2 - 2t^3`). */
|
|
458
|
+
easing?: 'smoothstep' | 'linear' | 'easeoutcubic';
|
|
459
|
+
/** `false` restores the plain snap of 1.4.0 (default `true`). */
|
|
460
|
+
enabled?: boolean;
|
|
220
461
|
}
|
|
221
462
|
|
|
222
463
|
/** The return of {@link startInline3D}. */
|
|
@@ -272,6 +513,48 @@ export function inline3dOcclusionByDrawOrder(): boolean;
|
|
|
272
513
|
*/
|
|
273
514
|
export function inline3dViewRigSupported(): boolean;
|
|
274
515
|
|
|
516
|
+
/**
|
|
517
|
+
* True when this browser exposes the DISPLAY-MODE API — {@link TileHandle.getDisplayInfo},
|
|
518
|
+
* {@link TileHandle.getRenderingModes} and {@link TileHandle.requestRenderingMode}. Sync + cheap;
|
|
519
|
+
* implies {@link inline3DAvailable}.
|
|
520
|
+
*
|
|
521
|
+
* Reads a capability (all three methods present on `XRDisplayLayer.prototype`), never a version or
|
|
522
|
+
* UA string, and demands all three: a browser shipping half the set is one mid-implementation.
|
|
523
|
+
* Everything the API drives is optional enhancement, so branch on this only to decide whether to
|
|
524
|
+
* show display controls — the handle methods reject with a clear `Error` rather than throwing at
|
|
525
|
+
* import or create time.
|
|
526
|
+
*/
|
|
527
|
+
export function inline3dDisplayModesSupported(): boolean;
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* True when this browser can undock through `XRDisplayLayer.undock()` — i.e. without the
|
|
531
|
+
* `displayxr-view:` protocol prompt the fallback needs. A page does not have to branch on it to
|
|
532
|
+
* undock (the helper falls back on its own); it is the probe for whether {@link Inline3D.undock}
|
|
533
|
+
* carries capabilities.
|
|
534
|
+
*/
|
|
535
|
+
export function inline3dUndockSupported(): boolean;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Undock `target`'s asset into the floating native viewer over the desktop.
|
|
539
|
+
*
|
|
540
|
+
* **Call it synchronously inside the click.** Both paths need the transient user activation — the
|
|
541
|
+
* API path to be allowed at all, the fallback to get Chrome's protocol dialog — and an `await`
|
|
542
|
+
* before this call spends it.
|
|
543
|
+
*
|
|
544
|
+
* Rejects with an `Error` whose `name` is `'not-installed'`, `'src-not-allowed'`,
|
|
545
|
+
* `'no-activation'` or `'busy'`.
|
|
546
|
+
*/
|
|
547
|
+
export function undock(target: Element, opts: UndockOptions): Promise<UndockHandle>;
|
|
548
|
+
|
|
549
|
+
/** True where a native DisplayXR viewer can exist at all (the viewers are Windows-only today). */
|
|
550
|
+
export function undockAvailable(): boolean;
|
|
551
|
+
|
|
552
|
+
/** The `displayxr-view:` URL the fallback path navigates to — exported for logging and tests. */
|
|
553
|
+
export function undockUrl(el: Element, opts: UndockOptions): string;
|
|
554
|
+
|
|
555
|
+
/** An element's rect in physical screen pixels — where the native viewer places its window. */
|
|
556
|
+
export function tileScreenRect(el: Element): { x: number; y: number; w: number; h: number; dpr: number };
|
|
557
|
+
|
|
275
558
|
/** Open the page's inline-3D session and return a manager you add windows to. */
|
|
276
559
|
export function createInline3D(
|
|
277
560
|
opts?: CreateInline3DOptions,
|
|
@@ -316,5 +599,17 @@ export interface XRDisplayLayer {
|
|
|
316
599
|
* all on the browser that has it.
|
|
317
600
|
*/
|
|
318
601
|
setViewRig?(rig: XRViewRigInit): void;
|
|
602
|
+
/**
|
|
603
|
+
* The display-mode API. All three optional for the same reason as `setViewRig`: their presence
|
|
604
|
+
* on the prototype IS the capability signal ({@link inline3dDisplayModesSupported}), and the
|
|
605
|
+
* SDK demands all three before treating the browser as supporting any of them.
|
|
606
|
+
*/
|
|
607
|
+
getDisplayInfo?(): Promise<XRDisplayInfo | null>;
|
|
608
|
+
getRenderingModes?(): Promise<ReadonlyArray<XRDisplayRenderingMode>>;
|
|
609
|
+
/** Throws `TypeError` **synchronously** for `viewCount > 2` or an unknown index. */
|
|
610
|
+
requestRenderingMode?(modeIndex: number): Promise<void>;
|
|
611
|
+
/** Undock this layer's asset into the floating native viewer. Optional; same probe rule. */
|
|
612
|
+
undock?(init: UndockOptions): Promise<unknown>;
|
|
613
|
+
getUndockCapabilities?(): Promise<UndockCapabilities>;
|
|
319
614
|
close(): void;
|
|
320
615
|
}
|