@displayxr/inline3d 1.45.1 → 1.47.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 +108 -0
- package/README.md +3 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/cursor-depth.d.ts +41 -0
- package/depth.d.ts +184 -0
- package/dist/call.js +6 -6
- package/docs/making-3d-look-right.md +49 -0
- package/docs/woven-canvas-rules.md +378 -0
- package/index.d.ts +53 -3
- package/js/camera/pair-cursor.js +27 -1
- package/js/inline3d-cursor-depth-playcanvas.js +20 -2
- package/js/inline3d-cursor-depth.js +160 -1
- package/js/inline3d-depth-budget.js +342 -0
- package/js/inline3d-model-entry.js +15 -1
- package/js/inline3d-model-playcanvas.js +27 -2
- package/js/inline3d-model.js +22 -2
- package/js/inline3d-splat-deferred.js +2 -0
- package/js/inline3d-splat-pc-entry.js +2 -1
- package/js/inline3d-splat-playcanvas.js +46 -43
- package/js/inline3d-splat-shared.js +311 -0
- package/js/inline3d-splat.js +31 -3
- package/js/inline3d-three.js +75 -12
- package/js/inline3d-viewer.js +15 -1
- package/js/inline3d.js +71 -0
- package/llms.txt +21 -0
- package/model.d.ts +53 -0
- package/package.json +10 -1
- package/splat.d.ts +33 -0
- package/three.d.ts +19 -4
- package/viewer.d.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,114 @@ 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.47.0 — 2026-10-29
|
|
9
|
+
|
|
10
|
+
The look-right release: a short guide for people and coding agents (`docs/making-3d-look-right.md`,
|
|
11
|
+
`llms.txt`), depth measurements and comfort-zone helpers, and opt-in `clearColor`, `setStereo` on
|
|
12
|
+
`./model` and `depth: 'auto'`. Every addition is opt-in: without the new options a page renders
|
|
13
|
+
exactly as on 1.46.0; the only default change is a few one-time console warnings.
|
|
14
|
+
|
|
15
|
+
### Comfort depth: zone-of-comfort helpers and an opt-in `depth` option
|
|
16
|
+
|
|
17
|
+
Touches the core (`.`: new pure exports), `./three` and `./depth` (the same pure exports), and the
|
|
18
|
+
preview tier (`./model`, `./splat`: one new opt-in option, one read-only handle field). Without
|
|
19
|
+
`depth`, every page renders exactly as before: no rig is declared that was not, nothing is measured.
|
|
20
|
+
|
|
21
|
+
- **`comfortZone({ viewerM = 0.6, zone = 'shibata' })`** → `{ zone, viewerM, frontD, behindD,
|
|
22
|
+
frontMm, behindMm }`: the vergence–accommodation comfort zone at a viewing distance, in diopters
|
|
23
|
+
and on-screen mm. `'shibata'` (Shibata et al. 2011: ≈ 0.57 D in front / 0.63 D behind at 0.6 m;
|
|
24
|
+
behind unbounded once the far limit passes infinity), `'percival'` (±1/3 D), or a number.
|
|
25
|
+
- **`comfortDepthFactor({ frontH, backH, tileHeightM, viewerM, zone })`** → the largest `k ≤ 1` that
|
|
26
|
+
puts a display-rig subject (depth in tile heights) inside the zone, from the exact geometry at
|
|
27
|
+
real proportions. Only ever lowers depth; 1 on missing / invalid input.
|
|
28
|
+
- **`cameraRigComfortFactor({ zNear, zFar, convergence | convergenceDiopters, verticalFov, aspect,
|
|
29
|
+
tileHeightM, viewerM, zone, ipdFactor, metersToVirtual })`** → the multiplier on a camera rig's
|
|
30
|
+
eye separation that puts its nearest / farthest points at the zone's edge. A measurement, **no
|
|
31
|
+
cap**: a camera rig's ipdFactor 1 is only physical in metre-scale worlds, so this may be well
|
|
32
|
+
above 1. Infinity with nothing off the plane, NaN on bad input. No warning is added for camera
|
|
33
|
+
factors above 1.
|
|
34
|
+
- **`depth: 'auto' | { zone }`** on `addModel` (both engines) and `addSplat` (Spark and
|
|
35
|
+
PlayCanvas), display rig only: the framed subject's depth (zoom 1, no orbit), the canvas's
|
|
36
|
+
physical height (`displayMetrics()`) and the display's NOMINAL viewer distance (0.6 m when it
|
|
37
|
+
reports none) give `k = comfortDepthFactor(…)`, applied to the display rig's `ipdFactor` and
|
|
38
|
+
`parallaxFactor` together, multiplied with `setStereo`'s values (Spark, which has no setStereo:
|
|
39
|
+
the whole factor). Re-measured on first framing, a canvas resize, the first weave and a display
|
|
40
|
+
event; never on zoom, orbit or per frame; re-declared only when k changes. Ignored with one
|
|
41
|
+
warning on a camera rig / `controls: 'page'`; an invalid value throws at the call.
|
|
42
|
+
`handle.depthFactor` reads the factor in force (1 without the option).
|
|
43
|
+
|
|
44
|
+
### Making 3D look right: depth measurements, opt-in helpers and one-time warnings
|
|
45
|
+
|
|
46
|
+
Touches the core (`.`: new pure exports, console warnings only), `./three` (re-exports, one
|
|
47
|
+
warning), the new `./depth` subpath, and the preview tier (`./viewer`, `./model`, `./splat`), all
|
|
48
|
+
opt-in. **Nothing clamps, refuses or overrides what a page asks for**: without the new options
|
|
49
|
+
every page renders exactly as on 1.46.0; the only default change is the console warnings below.
|
|
50
|
+
|
|
51
|
+
- **Depth measurements** (`./depth`, also on the root; the pure helpers on `./three`):
|
|
52
|
+
`disparityPct({ depthH, aspect, viewerM, heightM, ipdFactor })` (display rig: `e·z/(V∓z)` over
|
|
53
|
+
the tile width, the small-depth limit without `heightM`),
|
|
54
|
+
`cameraRigDisparityPct({ z, convergence, ipdFactor, metersToVirtual, verticalFov, aspect })`
|
|
55
|
+
(`100·b·(1/D − 1/z)/(2·tan(fov/2)·aspect)`, + behind, − in front), and `depthBudgetFromBounds`
|
|
56
|
+
(a `getSubjectBounds()` box → tile heights and % of the tile width). Pure; for a raw `addScene`
|
|
57
|
+
page or an agent checking its numbers. No budget tiers are built in: depth ranges measured on a
|
|
58
|
+
panel are in `docs/authoring-inline-3d.md`, as reference points.
|
|
59
|
+
- **`clearColor`** on `SceneViewer` / `addModel` / `addSplat` (three and PlayCanvas):
|
|
60
|
+
`[r, g, b, a]` in 0..1 or a CSS colour. Unset = the transparent clear, as before. An opaque clear
|
|
61
|
+
avoids the stale / black pixels the weave can show where it reads alpha 0 (web#198); match the
|
|
62
|
+
page colour behind the canvas.
|
|
63
|
+
- **`ModelHandle.setStereo({ ipdFactor, parallaxFactor } | null)`** (both engines), mirroring
|
|
64
|
+
`SplatHandle.setStereo`: a depth slider. RELATIVE on the display rig (1 = by construction), sticky,
|
|
65
|
+
`null` restores; `ipdScale` / `parallaxScale` accepted. Re-declares the display rig explicitly
|
|
66
|
+
from the first call (nothing is declared before it). `handle.rig` reads the values in force;
|
|
67
|
+
under `controls: 'page'` the page's camera rig takes them, absolute.
|
|
68
|
+
- **One-time console warnings** (`[inline3d]`, once per handle, the rig still sent as given): a
|
|
69
|
+
camera rig with `convergenceDiopters` 0 / unset ("convergence at infinity puts the whole scene in
|
|
70
|
+
front of the glass …"; also once per page from `cameraRigFromCamera` / `cameraRigFromPose` at
|
|
71
|
+
convergence 0, not repeated by `setViewRig` for that descriptor), a display rig with
|
|
72
|
+
`ipdFactor` or `parallaxFactor` above 1, a camera rig whose `verticalFov` is above π (degrees).
|
|
73
|
+
And a layer the browser keeps `'withheld'` until `firstWoven` / `rewoven()` settles on it now
|
|
74
|
+
logs its `withheldReason` once, with a hint (`cross-pass:*` / `no-quad`: a `filter`,
|
|
75
|
+
`opacity < 1`, `mask`, `clip-path`, `mix-blend-mode` or `backdrop-filter` on the canvas or an
|
|
76
|
+
ancestor). A transient withheld (a move, a resize) says nothing. `withheldReasonHint` is exported.
|
|
77
|
+
- **Docs corrected**: the camera-rig comfort note said N ≈ 0.5 m and "the runtime clamps". The
|
|
78
|
+
nominal viewer is 0.6 m on a desktop panel, and the runtime does NOT comfort-clamp an
|
|
79
|
+
app-declared rig (XR_DXR_view_rig §3); it clamps only values outside a field's sanity range.
|
|
80
|
+
- Internal: `validateSetStereo` moved to `js/inline3d-splat-shared.js` (still exported from the
|
|
81
|
+
PlayCanvas splat module); new `js/inline3d-depth-budget.js` is in the published file set.
|
|
82
|
+
|
|
83
|
+
## 1.46.0 — 2026-10-14
|
|
84
|
+
|
|
85
|
+
### Depth cursor: when the runtime draws the web cursor, hand it the depth (runtime ADR-046 Amendment 4)
|
|
86
|
+
|
|
87
|
+
Touches `./cursor-depth`, `./three` (`DepthCursor`), the core tile handle, and the preview tier
|
|
88
|
+
(`./model`, `./splat`, `./call`, `./camera`), all only under `cursor: 'depth'`.
|
|
89
|
+
|
|
90
|
+
- In a DisplayXR Browser whose runtime draws the web cursor (`XRDisplayLayer.getCursorDepthSupport()`
|
|
91
|
+
→ `"runtime"`: Windows, `XR_DXR_cursor_depth` spec ≥ 8), the depth cursor draws **no sprite** and
|
|
92
|
+
never hides the CSS cursor. Each frame it calls `layer.setCursorDepth({ disparityPx, u, v })` with
|
|
93
|
+
the **raw** content disparity under the footprint in **device px between the outermost views**
|
|
94
|
+
(< 0 = in front; no margin, clamp or filter: the runtime applies the one policy), `{ u, v }` with no
|
|
95
|
+
`disparityPx` over the element with nothing 3D under it, and `setCursorDepth()` once to withdraw
|
|
96
|
+
(pointer off the element, 2D, teardown). The runtime draws the browser's own cursor (`grab`,
|
|
97
|
+
`pointer`, `url(…)`), lifted, softened and late-latched.
|
|
98
|
+
- `"none"`, an older browser, or any other browser: unchanged — the SDK sprite, its softening and
|
|
99
|
+
CSS-shape mirroring, exactly as before. The browser answers `"none"` until its weave session is
|
|
100
|
+
live, so a `"none"` is re-asked (below).
|
|
101
|
+
- The support probe: a `"none"` is re-asked every 2 s for the first 6 asks, then every 10 s with no
|
|
102
|
+
cap (`CURSOR_RUNTIME_SLOW_RETRY_MS`), so a weave session that reports late (tens of seconds on a
|
|
103
|
+
cold GPU) still hands the cursor to the runtime. A browser without the API is never probed.
|
|
104
|
+
- Covered: `DepthCursor` (pass `update(views, { layer })`; `./model`/`./splat` on three do it),
|
|
105
|
+
the PlayCanvas cursor (`./model`, `./splat`), and the stereo-pair cursor (`./call`, `./camera`,
|
|
106
|
+
through the tile handle; its matched output-px disparity → device px).
|
|
107
|
+
- New: `CursorRuntimeLink`, `cursorRuntimeCapable`, `cursorDisparityPx`, `CURSOR_RUNTIME_RETRY_MS`,
|
|
108
|
+
`CURSOR_RUNTIME_PROBES`, `CURSOR_RUNTIME_SLOW_RETRY_MS` (`./cursor-depth`); `CursorPlacement.contentDisparity` /
|
|
109
|
+
`contentDisparityPx` / `runtime`; tile handle `setCursorDepth()` / `getCursorDepthSupport()`;
|
|
110
|
+
`XRDisplayLayer.setCursorDepth?` / `getCursorDepthSupport?` and `XRCursorDepthInit` in the types.
|
|
111
|
+
`DepthCursor.update`'s second argument may now be `{ nowMs, layer }` (a number still works).
|
|
112
|
+
- Needs DisplayXR Browser with page API patch 0409 (browser-pvt #285) and runtime spec 8 (runtime
|
|
113
|
+
#2088); safe to ship earlier, since it only activates on `"runtime"`.
|
|
114
|
+
|
|
115
|
+
|
|
8
116
|
## 1.45.1 — 2026-10-13
|
|
9
117
|
|
|
10
118
|
### Fixed — `cursor: 'depth'` showed up under a finger (touch)
|
package/README.md
CHANGED
|
@@ -11,6 +11,9 @@ live demos.
|
|
|
11
11
|
them in 3D; in any other browser the pages render as a normal 2D fallback, so they're safe to view
|
|
12
12
|
anywhere.
|
|
13
13
|
|
|
14
|
+
> **New here, or an AI coding agent? Read [Depth and comfort on a 3D display](docs/making-3d-look-right.md)
|
|
15
|
+
> first:** nine rules from real panels; the SDK's defaults do the rest.
|
|
16
|
+
|
|
14
17
|
## Install
|
|
15
18
|
|
|
16
19
|
The SDK is published as **[`@displayxr/inline3d`](https://www.npmjs.com/package/@displayxr/inline3d)**
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -107,6 +107,6 @@ package.
|
|
|
107
107
|
| Asset | Used by | Source | Credit and licence |
|
|
108
108
|
|---|---|---|---|
|
|
109
109
|
| Damaged Helmet (`DamagedHelmet.glb`) | `samples/model/` (tiles A and B), loaded at run time, not vendored | [KhronosGroup/glTF-Sample-Assets](https://github.com/KhronosGroup/glTF-Sample-Assets/tree/c6a6bd13ab2b3c685c7903d03561b8a9392f38b8/Models/DamagedHelmet) at commit `c6a6bd13ab2b3c685c7903d03561b8a9392f38b8`, via jsDelivr, unmodified | © 2018 ctxwing (rebuild and conversion to glTF 2.0), [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/legalcode). Earlier version of the model ("Battle Damaged Sci-fi Helmet") © 2016 theblueturtle_, [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/legalcode). As stated in the asset's `README.md` and `LICENSE.md`. |
|
|
110
|
-
| Duck, Draco variant (`samples/model/assets/glTF-Draco/`) | `samples/model/` (tile C) | Khronos glTF-Sample-Assets | © Sony, [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). |
|
|
110
|
+
| Duck, Draco variant (`samples/model/assets/glTF-Draco/`); glTF-Binary variant (`samples/starter-model/assets/Duck.glb`, glTF-Sample-Assets commit `c6a6bd13ab2b3c685c7903d03561b8a9392f38b8`, unmodified) | `samples/model/` (tile C), `samples/starter-model/` | Khronos glTF-Sample-Assets | © Sony, [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). |
|
|
111
111
|
| Butterfly splat (`samples/splat/assets/butterfly.sog`) | `samples/splat/`, `samples/model/` (tile B) | [Spark](https://sparkjs.dev/) `assets/splats/butterfly.spz`, converted to SOG with PlayCanvas splat-transform (177,132 gaussians, 3 SH bands kept) | Butterfly splat © World Labs (from Spark, sparkjs.dev), used with permission; converted to SOG. |
|
|
112
112
|
| Draco decoder (`vendor/draco/`) | `samples/model/` | three.js `examples/jsm/libs/draco/` | © The Draco Authors, Apache-2.0. |
|
package/cursor-depth.d.ts
CHANGED
|
@@ -146,6 +146,47 @@ export interface CursorPlacement {
|
|
|
146
146
|
targetDisparity: number;
|
|
147
147
|
/** Hybrid: true while the pointer is still and the sprite is held world-fixed. */
|
|
148
148
|
anchored?: boolean;
|
|
149
|
+
/** The RAW content disparity under the footprint (baselines, < 0 in front), before the policy; null = nothing under it. */
|
|
150
|
+
contentDisparity?: number | null;
|
|
151
|
+
/** `contentDisparity` in device px between the outermost views (what `setCursorDepth` takes); null when unknown. */
|
|
152
|
+
contentDisparityPx?: number | null;
|
|
153
|
+
/** True when the runtime drew the cursor this frame (ADR-046 Amendment 4): `active` is then false — the SDK drew nothing. */
|
|
154
|
+
runtime?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── The runtime draws the web cursor (runtime ADR-046 Amendment 4) ───────────────────────────
|
|
158
|
+
|
|
159
|
+
/** Re-ask a target that answered "none" at most this often (ms) while the cursor runs. */
|
|
160
|
+
export const CURSOR_RUNTIME_RETRY_MS: number;
|
|
161
|
+
/** At most this many support probes per target. */
|
|
162
|
+
export const CURSOR_RUNTIME_PROBES: number;
|
|
163
|
+
/** After the fast probes, a "none" is re-asked this often, without a cap (a late weave session). */
|
|
164
|
+
export const CURSOR_RUNTIME_SLOW_RETRY_MS: number;
|
|
165
|
+
/** Does `target` (an XRDisplayLayer, or a tile handle) have `setCursorDepth` + `getCursorDepthSupport`? */
|
|
166
|
+
export function cursorRuntimeCapable(target: unknown): boolean;
|
|
167
|
+
/** Baseline-unit content disparity → device px between the outermost views (`d × pxPerBaseline`); null when either is unknown. */
|
|
168
|
+
export function cursorDisparityPx(contentDisparity: number | null | undefined, pxPerBaseline: number | null | undefined): number | null;
|
|
169
|
+
/** Anything a depth can be handed to: an XRDisplayLayer, or a tile handle that forwards to one. */
|
|
170
|
+
export interface CursorDepthTarget {
|
|
171
|
+
setCursorDepth(depth?: { disparityPx?: number | null; u: number; v: number } | null): unknown;
|
|
172
|
+
getCursorDepthSupport(): Promise<string>;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The page half of a runtime-drawn cursor, used by every backend: per frame `bind(target)` (true =
|
|
176
|
+
* the runtime draws: draw no sprite, leave the CSS cursor alone), then `report(u, v, disparityPx)`
|
|
177
|
+
* while the pointer is over the element, `withdraw()` when not.
|
|
178
|
+
*/
|
|
179
|
+
export class CursorRuntimeLink {
|
|
180
|
+
constructor();
|
|
181
|
+
readonly target: CursorDepthTarget | null;
|
|
182
|
+
/** The last depth sent (diagnostics), null after a withdrawal. */
|
|
183
|
+
readonly last: { u: number; v: number; disparityPx: number | null } | null;
|
|
184
|
+
readonly support: 'runtime' | 'none' | 'pending';
|
|
185
|
+
readonly drawing: boolean;
|
|
186
|
+
bind(target: CursorDepthTarget | null | undefined): boolean;
|
|
187
|
+
report(u: number, v: number, disparityPx: number | null): boolean;
|
|
188
|
+
withdraw(): void;
|
|
189
|
+
dispose(): void;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
192
|
/** The whole pipeline with its filter state: one per cursor. Inactive with fewer than two views. */
|
package/depth.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Type definitions for @displayxr/inline3d/depth — depth measurements (also re-exported by the
|
|
2
|
+
// root entry and, minus the warning helpers, by ./three).
|
|
3
|
+
//
|
|
4
|
+
// Measurements and pure helpers only: nothing here changes a pixel, clamps a rig or refuses a
|
|
5
|
+
// value (the opt-in `depth` option of ./model and ./splat is what applies comfortDepthFactor). The units are RELATIVE — disparity as % of the TILE's width, or depth in TILE HEIGHTS on
|
|
6
|
+
// a display rig — so they hold on any panel. Depth ranges measured on a panel are in
|
|
7
|
+
// docs/authoring-inline-3d.md, as reference points.
|
|
8
|
+
|
|
9
|
+
/** The eye separation every estimate assumes, metres (0.063). No browser surface reports a real one. */
|
|
10
|
+
export const DEPTH_EYE_SEPARATION_M: number;
|
|
11
|
+
/** The viewer distance used when the display reports no nominal viewer, metres (0.6, a desktop panel). */
|
|
12
|
+
export const DEPTH_DEFAULT_VIEWER_M: number;
|
|
13
|
+
|
|
14
|
+
/** What a depth report measured against. */
|
|
15
|
+
export interface DepthReportBasis {
|
|
16
|
+
rig: 'display';
|
|
17
|
+
/** The tile's width / height. */
|
|
18
|
+
aspect: number;
|
|
19
|
+
/** Nominal viewer distance used, metres. */
|
|
20
|
+
viewerM: number;
|
|
21
|
+
/** The tile's physical height, metres, or null (then the small-depth formula, which ignores it). */
|
|
22
|
+
heightM: number | null;
|
|
23
|
+
/** The display rig's ipdFactor in force (disparity scales with it). */
|
|
24
|
+
ipdFactor: number;
|
|
25
|
+
eyeSeparationM: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* How deep a display-rig subject is. `frontH` / `backH` are MAGNITUDES in tile heights (0 when
|
|
30
|
+
* nothing is on that side); `frontPct` / `backPct` the matching disparity magnitudes, % of the
|
|
31
|
+
* tile width.
|
|
32
|
+
*/
|
|
33
|
+
export interface DepthReport {
|
|
34
|
+
frontH: number;
|
|
35
|
+
backH: number;
|
|
36
|
+
frontPct: number;
|
|
37
|
+
backPct: number;
|
|
38
|
+
basis: DepthReportBasis;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Disparity, % of the tile width, of a point `depthH` tile heights out of the glass on a DISPLAY
|
|
43
|
+
* rig: physical depth `z = depthH · heightM`, disparity `e · z / (V − z)` (e = 0.063 m × ipdFactor,
|
|
44
|
+
* V = `viewerM`) over the tile width `aspect · heightM`. Without `heightM` the small-depth limit
|
|
45
|
+
* `100 · e · depthH / (aspect · V)`. SIGNED: `depthH > 0` (in front) gives a NEGATIVE disparity,
|
|
46
|
+
* behind a positive one — as {@link cameraRigDisparityPct}. −Infinity at or past the viewer.
|
|
47
|
+
*/
|
|
48
|
+
export function disparityPct(o: {
|
|
49
|
+
depthH: number;
|
|
50
|
+
aspect: number;
|
|
51
|
+
/** Default 0.6. */
|
|
52
|
+
viewerM?: number;
|
|
53
|
+
heightM?: number;
|
|
54
|
+
/** Default 1. */
|
|
55
|
+
ipdFactor?: number;
|
|
56
|
+
/** Default 0.063. */
|
|
57
|
+
eyeSeparationM?: number;
|
|
58
|
+
}): number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Disparity, % of the tile width, of a point `z` world units down a CAMERA rig's view axis:
|
|
62
|
+
* `100 · b · (1/D − 1/z) / (2 · tan(verticalFov/2) · aspect)`, `b = 0.063 · ipdFactor ·
|
|
63
|
+
* metersToVirtual`. `+` = behind the glass, `−` = in front. `convergence` 0 / unset (and no
|
|
64
|
+
* `convergenceDiopters`) is infinity: everything is in front. NaN on bad input.
|
|
65
|
+
*/
|
|
66
|
+
export function cameraRigDisparityPct(o: {
|
|
67
|
+
z: number;
|
|
68
|
+
/** Zero-disparity distance, world units (0 = infinity). */
|
|
69
|
+
convergence?: number;
|
|
70
|
+
/** Or the descriptor's 1 / distance; wins over `convergence`. */
|
|
71
|
+
convergenceDiopters?: number;
|
|
72
|
+
ipdFactor?: number;
|
|
73
|
+
metersToVirtual?: number;
|
|
74
|
+
/** FULL vertical angle, RADIANS. */
|
|
75
|
+
verticalFov: number;
|
|
76
|
+
aspect: number;
|
|
77
|
+
}): number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A {@link DepthReport} from a display-rig subject box: `front` / `back` as
|
|
81
|
+
* `SceneViewer.getSubjectBounds()` reports them (display space, +z toward the viewer, glass at 0),
|
|
82
|
+
* in the units where one tile height is `virtualDisplayHeight`.
|
|
83
|
+
*/
|
|
84
|
+
export function depthBudgetFromBounds(o: {
|
|
85
|
+
front: number;
|
|
86
|
+
back: number;
|
|
87
|
+
virtualDisplayHeight: number;
|
|
88
|
+
aspect: number;
|
|
89
|
+
viewerM?: number;
|
|
90
|
+
heightM?: number;
|
|
91
|
+
ipdFactor?: number;
|
|
92
|
+
}): DepthReport;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A comfort zone (vergence–accommodation): `'shibata'` (Shibata et al. 2011, "The zone of
|
|
96
|
+
* comfort", J. Vision — the default), `'percival'` (±1/3 D), or a number: a symmetric |ΔD| bound,
|
|
97
|
+
* diopters (> 0).
|
|
98
|
+
*/
|
|
99
|
+
export type ComfortZoneName = 'shibata' | 'percival' | number;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A comfort zone at one viewing distance: the largest comfortable vergence change in front of the
|
|
103
|
+
* glass (`frontD`) and behind it (`behindD`), diopters, and the matching on-screen disparities in
|
|
104
|
+
* mm (`e · V · ΔD`). `behindD` is Infinity when the zone's far limit is past optical infinity.
|
|
105
|
+
*/
|
|
106
|
+
export interface ComfortZoneReport {
|
|
107
|
+
zone: ComfortZoneName;
|
|
108
|
+
viewerM: number;
|
|
109
|
+
frontD: number;
|
|
110
|
+
behindD: number;
|
|
111
|
+
frontMm: number;
|
|
112
|
+
behindMm: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The comfort zone for a viewer at `viewerM` (default 0.6 m). Shibata: near vergence limit
|
|
117
|
+
* `Dv/1.035 + 0.626`, far `Dv/1.129 − 0.442` (Dv = 1/V), so 0.57 D in front / 0.63 D behind at
|
|
118
|
+
* 0.6 m; Percival: ±1/3 D. NaN fields on bad input (a distance ≤ 0, an unknown zone).
|
|
119
|
+
*/
|
|
120
|
+
export function comfortZone(o?: { viewerM?: number; zone?: ComfortZoneName }): ComfortZoneReport;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* How much a DISPLAY rig's depth would have to shrink for a subject to sit inside a comfort zone:
|
|
124
|
+
* the largest `k ≤ 1` such that its disparities × k (ipdFactor, parallaxFactor moved with it)
|
|
125
|
+
* stay within {@link comfortZone}. ONLY EVER LOWERS DEPTH: 1 = it already fits, never above 1.
|
|
126
|
+
*
|
|
127
|
+
* Exact geometry at real proportions: the nearest point at `V − frontH · tileHeightM` from the
|
|
128
|
+
* viewer, the farthest at `V + backH · tileHeightM`, each side's `|1/V − 1/z|` against the zone.
|
|
129
|
+
* Use the NOMINAL viewer distance (what the display is built for), not a live head distance. The
|
|
130
|
+
* same depth in tile heights asks more of a small, close screen — a tablet at 0.4 m carries more
|
|
131
|
+
* vergence per tile height than a laptop at 0.6 m — which is why the answer depends on the panel.
|
|
132
|
+
*
|
|
133
|
+
* Returns 1 (no change) for missing or invalid input, and when the front reaches the viewer.
|
|
134
|
+
*/
|
|
135
|
+
export function comfortDepthFactor(o: {
|
|
136
|
+
/** MAGNITUDE, tile heights in front of the glass. */
|
|
137
|
+
frontH: number;
|
|
138
|
+
/** MAGNITUDE, tile heights behind it. */
|
|
139
|
+
backH: number;
|
|
140
|
+
/** The tile's (canvas's) physical height, metres. */
|
|
141
|
+
tileHeightM: number;
|
|
142
|
+
/** NOMINAL viewer distance, metres. Default 0.6. */
|
|
143
|
+
viewerM?: number;
|
|
144
|
+
/** Default 'shibata'. */
|
|
145
|
+
zone?: ComfortZoneName;
|
|
146
|
+
}): number;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The CAMERA-rig counterpart: the multiplier on the eye separation (ipdFactor) that puts the
|
|
150
|
+
* scene's nearest (`zNear`) and farthest (`zFar`, may be Infinity) points at the edge of the
|
|
151
|
+
* comfort zone. Measurement only; the page decides whether to apply it. Unlike the display-rig
|
|
152
|
+
* factor it can EXCEED 1, because a camera rig's 1 (63 mm × metersToVirtual in scene units) is
|
|
153
|
+
* only physical in metre-scale worlds — in large scene units it renders nearly flat.
|
|
154
|
+
*
|
|
155
|
+
* Disparity `f = b · (1/D − 1/z) / (2 · tan(verticalFov/2) · aspect)` of the tile width
|
|
156
|
+
* (b = 0.063 · ipdFactor · metersToVirtual), on the glass `p = f · aspect · tileHeightM`, in
|
|
157
|
+
* diopters `p / (0.063 · viewerM)`; the result is the smallest zone side / |ΔD| over the sides
|
|
158
|
+
* with content. Convergence 0 / unset = infinity (everything in front). Infinity when nothing is
|
|
159
|
+
* off the plane, NaN on bad input.
|
|
160
|
+
*/
|
|
161
|
+
export function cameraRigComfortFactor(o: {
|
|
162
|
+
zNear: number;
|
|
163
|
+
zFar: number;
|
|
164
|
+
/** Zero-disparity distance, world units (0 = infinity). */
|
|
165
|
+
convergence?: number;
|
|
166
|
+
/** Or 1 / distance; wins over `convergence`. */
|
|
167
|
+
convergenceDiopters?: number;
|
|
168
|
+
/** FULL vertical angle, RADIANS. */
|
|
169
|
+
verticalFov: number;
|
|
170
|
+
aspect: number;
|
|
171
|
+
tileHeightM: number;
|
|
172
|
+
/** Default 0.6. */
|
|
173
|
+
viewerM?: number;
|
|
174
|
+
/** Default 'shibata'. */
|
|
175
|
+
zone?: ComfortZoneName;
|
|
176
|
+
/** Default 1. */
|
|
177
|
+
ipdFactor?: number;
|
|
178
|
+
/** Default 1. */
|
|
179
|
+
metersToVirtual?: number;
|
|
180
|
+
}): number;
|
|
181
|
+
|
|
182
|
+
/** The hint `[inline3d]` logs for a `withheldReason` token (docs/woven-canvas-rules.md §3). */
|
|
183
|
+
export function withheldReasonHint(reason: string): string;
|
|
184
|
+
|