@displayxr/inline3d 1.44.0 → 1.45.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 +66 -0
- package/camera.d.ts +5 -1
- package/cursor-depth.d.ts +151 -0
- package/dist/call.js +18 -4
- package/js/camera/pair-cursor.js +66 -32
- package/js/inline3d-cursor-depth-playcanvas.js +369 -69
- package/js/inline3d-cursor-depth.js +91 -20
- package/js/inline3d-cursor-option.js +24 -5
- package/js/inline3d-cursor-shape.js +962 -0
- package/js/inline3d-playcanvas-engine.js +1 -0
- package/js/inline3d-splat-layer-rig.js +3 -0
- package/js/inline3d-three.js +117 -6
- package/model.d.ts +1 -1
- package/package.json +2 -1
- package/splat.d.ts +1 -1
- package/three.d.ts +11 -1
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
// CURSOR_DRAW_THRESHOLD (-0.005, ~0.3 mm) or under CURSOR_DRAW_MIN_PX on the panel
|
|
54
54
|
// (cursorShouldDraw), and the CSS cursor is left alone;
|
|
55
55
|
// - the look: the classic arrow (CURSOR_ARROW, tip = hotspot), at the OS cursor's size
|
|
56
|
-
// (CURSOR_ARROW_CSS_PX tall on the page, cursorDefaultHeight)
|
|
56
|
+
// (CURSOR_ARROW_CSS_PX tall on the page, cursorDefaultHeight) — or, mirroring the page's CSS
|
|
57
|
+
// cursor: every CSS keyword and url() images (./inline3d-cursor-shape.js);
|
|
57
58
|
// - crosstalk softening (Amendment 2 §B.7): a LIFTED arrow is blurred by k × its ghost offset and
|
|
58
59
|
// faded toward alphaMin with its lift, both exactly zero on the glass (cursorSoften and below).
|
|
59
60
|
|
|
@@ -70,6 +71,43 @@ export {
|
|
|
70
71
|
resolveCursorSoften,
|
|
71
72
|
} from './inline3d-cursor-option.js';
|
|
72
73
|
import { CURSOR_SOFTEN_DEFAULTS } from './inline3d-cursor-option.js';
|
|
74
|
+
import { CURSOR_HIDDEN_ATTR, ensureCursorHiddenRule } from './inline3d-cursor-shape.js';
|
|
75
|
+
|
|
76
|
+
// Which sprite: the page's CSS cursor mirrored (shapes, read-back) — its own module, re-exported.
|
|
77
|
+
export {
|
|
78
|
+
CURSOR_CSS_KEYWORDS,
|
|
79
|
+
CURSOR_CSS_READ_MS,
|
|
80
|
+
CURSOR_HIDDEN_ATTR,
|
|
81
|
+
CURSOR_HIDDEN_RULE,
|
|
82
|
+
CURSOR_IMAGE_MAX_PX,
|
|
83
|
+
CURSOR_SHAPES,
|
|
84
|
+
CURSOR_SHAPE_OPTIONS,
|
|
85
|
+
CURSOR_SHAPE_TEXTURE_PX,
|
|
86
|
+
CursorCssReader,
|
|
87
|
+
CursorImageCache,
|
|
88
|
+
CursorShapeSelector,
|
|
89
|
+
cssCursorToShape,
|
|
90
|
+
cssKeywordToShape,
|
|
91
|
+
curFileHotspot,
|
|
92
|
+
cursorImageCache,
|
|
93
|
+
cursorPickShape,
|
|
94
|
+
cursorShape,
|
|
95
|
+
cursorShapeBox,
|
|
96
|
+
cursorShapePolys,
|
|
97
|
+
cursorShapeRaster,
|
|
98
|
+
cursorShapeSample,
|
|
99
|
+
cursorShapeTexture,
|
|
100
|
+
cursorSoftenPixels,
|
|
101
|
+
cursorSpriteBox,
|
|
102
|
+
cursorSpriteGLSL,
|
|
103
|
+
cursorSpriteRect,
|
|
104
|
+
drawCursorShape2D,
|
|
105
|
+
ensureCursorHiddenRule,
|
|
106
|
+
parseCssCursor,
|
|
107
|
+
readPageCursor,
|
|
108
|
+
resolveCursorSprite,
|
|
109
|
+
spriteForName,
|
|
110
|
+
} from './inline3d-cursor-shape.js';
|
|
73
111
|
|
|
74
112
|
/**
|
|
75
113
|
* Fallback sprite height, as a fraction of the canvas height, when the canvas's on-page size is
|
|
@@ -413,7 +451,6 @@ export class CursorPointer {
|
|
|
413
451
|
/** Pointer samples received so far (diagnostics). */
|
|
414
452
|
this.samples = 0;
|
|
415
453
|
this._hidden = false;
|
|
416
|
-
this._prev = '';
|
|
417
454
|
this._cx = 0;
|
|
418
455
|
this._cy = 0;
|
|
419
456
|
this._pending = false; // a sample not yet resolved to uv (resolved once, on the next read)
|
|
@@ -508,21 +545,24 @@ export class CursorPointer {
|
|
|
508
545
|
|
|
509
546
|
/**
|
|
510
547
|
* Hide (true) or restore (false) the CSS cursor; idempotent. Only ever while a sprite replaces
|
|
511
|
-
* it
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
548
|
+
* it. The hide is a data attribute (CURSOR_HIDDEN_ATTR) on the canvas — on <html> for
|
|
549
|
+
* pointerScope 'window' — plus one injected `cursor: none !important` rule for it: the page's
|
|
550
|
+
* own cursor (`pointer`, `grab`, a `url(…)` image, inline or from a stylesheet) is never
|
|
551
|
+
* written, so it is what shows the rest of the time, and what the sprite mirrors (CursorCssReader
|
|
552
|
+
* lifts the attribute for its read).
|
|
515
553
|
*/
|
|
516
554
|
hideCss(hide) {
|
|
517
555
|
if (this.keepCss) hide = false;
|
|
518
556
|
if (hide === this._hidden) return;
|
|
519
557
|
if (this.scope === 'window') {
|
|
520
558
|
pageCursorHidden(hide);
|
|
521
|
-
} else if (
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
559
|
+
} else if (this.canvas.setAttribute) {
|
|
560
|
+
if (hide) {
|
|
561
|
+
ensureCursorHiddenRule();
|
|
562
|
+
this.canvas.setAttribute(CURSOR_HIDDEN_ATTR, '');
|
|
563
|
+
} else {
|
|
564
|
+
this.canvas.removeAttribute(CURSOR_HIDDEN_ATTR);
|
|
565
|
+
}
|
|
526
566
|
}
|
|
527
567
|
this._hidden = hide;
|
|
528
568
|
}
|
|
@@ -546,20 +586,17 @@ export function supportsRawUpdate(target) {
|
|
|
546
586
|
}
|
|
547
587
|
|
|
548
588
|
// pointerScope 'window': the overlay DOM has its own cursor, so hiding it on the canvas alone is
|
|
549
|
-
// not enough.
|
|
550
|
-
// shows; a count keeps two tiles from un-hiding each other.
|
|
589
|
+
// not enough. The attribute on <html> (the injected rule covers its descendants) hides it
|
|
590
|
+
// everywhere while any depth cursor shows; a count keeps two tiles from un-hiding each other.
|
|
551
591
|
let _pageHides = 0;
|
|
552
592
|
function pageCursorHidden(hide) {
|
|
553
593
|
const doc = globalThis.document;
|
|
554
594
|
if (!doc || !doc.documentElement) return;
|
|
555
|
-
if (hide
|
|
556
|
-
const st = doc.createElement('style');
|
|
557
|
-
st.id = 'inline3d-cursor-style';
|
|
558
|
-
st.textContent = 'html.inline3d-cursor-hidden, html.inline3d-cursor-hidden * { cursor: none !important; }';
|
|
559
|
-
(doc.head || doc.documentElement).appendChild(st);
|
|
560
|
-
}
|
|
595
|
+
if (hide) ensureCursorHiddenRule(doc);
|
|
561
596
|
_pageHides = Math.max(0, _pageHides + (hide ? 1 : -1));
|
|
562
|
-
doc.documentElement
|
|
597
|
+
const el = doc.documentElement;
|
|
598
|
+
if (_pageHides > 0) el.setAttribute?.(CURSOR_HIDDEN_ATTR, '');
|
|
599
|
+
else el.removeAttribute?.(CURSOR_HIDDEN_ATTR);
|
|
563
600
|
}
|
|
564
601
|
|
|
565
602
|
/**
|
|
@@ -975,3 +1012,37 @@ export function cursorSoftArrowRaster(cx, cy, sizePx, radius, alpha, fill = CURS
|
|
|
975
1012
|
}
|
|
976
1013
|
return { x0, y0, width, height, data };
|
|
977
1014
|
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* PlayCanvas `pc.ShaderMaterial` opt-in to the depth cursor's pick (`cursor: 'depth'` on ./model
|
|
1018
|
+
* and ./splat). The pick pass draws into TWO colour targets — the pick id, then the pick depth — and
|
|
1019
|
+
* leaves out any ShaderMaterial whose fragment code does not write the second under
|
|
1020
|
+
* DEPTH_PICK_PASS (WebGL would reject the draw), so such a mesh is never under the cursor. Paste
|
|
1021
|
+
* `declarations` above `main` and `output` inside `#ifdef PICK_PASS … #else <your colour> #endif`:
|
|
1022
|
+
*
|
|
1023
|
+
* fragmentGLSL: `${CURSOR_DEPTH_PICK_GLSL.declarations}
|
|
1024
|
+
* void main() {
|
|
1025
|
+
* #ifdef PICK_PASS
|
|
1026
|
+
* ${CURSOR_DEPTH_PICK_GLSL.output}
|
|
1027
|
+
* #else
|
|
1028
|
+
* gl_FragColor = vec4(0.2, 0.3, 0.4, 1.0);
|
|
1029
|
+
* #endif
|
|
1030
|
+
* }`
|
|
1031
|
+
*
|
|
1032
|
+
* The engine's own `pickPS` chunk does the work (getPickOutput / getPickDepth), so the id and the
|
|
1033
|
+
* linear depth match its lit and gsplat materials exactly. Discard where your shader discards.
|
|
1034
|
+
*/
|
|
1035
|
+
export const CURSOR_DEPTH_PICK_GLSL = Object.freeze({
|
|
1036
|
+
declarations: '#ifdef PICK_PASS\n#include "pickPS"\n#endif\n',
|
|
1037
|
+
output: 'pcFragColor0 = getPickOutput();\n#ifdef DEPTH_PICK_PASS\npcFragColor1 = getPickDepth();\n#endif\n',
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* The WGSL twin of CURSOR_DEPTH_PICK_GLSL (a ShaderMaterial with `fragmentWGSL`, WebGPU): `output`
|
|
1042
|
+
* writes `output.color` / `output.color1` of the engine's `FragmentOutput`, inside
|
|
1043
|
+
* `#ifdef PICK_PASS … #else … #endif` in `fragmentMain`.
|
|
1044
|
+
*/
|
|
1045
|
+
export const CURSOR_DEPTH_PICK_WGSL = Object.freeze({
|
|
1046
|
+
declarations: '#ifdef PICK_PASS\n#include "pickPS"\n#endif\n',
|
|
1047
|
+
output: 'output.color = getPickOutput();\n#ifdef DEPTH_PICK_PASS\noutput.color1 = getPickDepth();\n#endif\n',
|
|
1048
|
+
});
|
|
@@ -8,7 +8,20 @@
|
|
|
8
8
|
export const CURSOR_ANCHOR_MODES = Object.freeze(['hybrid', 'screen', 'world']);
|
|
9
9
|
|
|
10
10
|
/** The keys `cursor: { … }` accepts on ./model and ./splat. */
|
|
11
|
-
export const CURSOR_OPTION_KEYS = Object.freeze(['height', 'margin', 'anchor', 'pointerScope', 'debug', 'soften']);
|
|
11
|
+
export const CURSOR_OPTION_KEYS = Object.freeze(['height', 'margin', 'anchor', 'pointerScope', 'debug', 'soften', 'shape', 'shapeFor']);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The procedural sprites the depth cursor can draw (see ./inline3d-cursor-shape.js): one per
|
|
15
|
+
* standard CSS cursor keyword (aliases such as `n-resize` → `ns-resize` share one).
|
|
16
|
+
*/
|
|
17
|
+
export const CURSOR_SHAPES = Object.freeze([
|
|
18
|
+
'arrow', 'pointer', 'grab', 'grabbing', 'move', 'text', 'vertical-text', 'crosshair', 'not-allowed',
|
|
19
|
+
'help', 'wait', 'progress', 'copy', 'alias', 'context-menu', 'cell', 'ns-resize', 'ew-resize',
|
|
20
|
+
'nesw-resize', 'nwse-resize', 'col-resize', 'row-resize', 'zoom-in', 'zoom-out',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/** `cursor.shape` values: a sprite, `'none'` (draw nothing), or `'auto'` (default) — mirror the page's CSS cursor. */
|
|
24
|
+
export const CURSOR_SHAPE_OPTIONS = Object.freeze(['auto', 'none', ...CURSOR_SHAPES]);
|
|
12
25
|
|
|
13
26
|
// ── Crosstalk softening (runtime ADR-046 Amendment 2 §B.7) ─────────────────────────────────────
|
|
14
27
|
// On a lenticular panel a little of every view leaks into its neighbours. A LIFTED cursor's leaked
|
|
@@ -124,13 +137,13 @@ export function parseCursorSoften(mode, blurK, blurMaxPx, blurMaxFrac, alphaMin,
|
|
|
124
137
|
* depth). Anything falsy is OFF — the zero-cost default. Validated here, at the call.
|
|
125
138
|
* `debug`: true logs the cursor's per-frame cost (rolling stats, at most once a second); 'both' also
|
|
126
139
|
* keeps the OS cursor visible beside the sprite (the latency check). Off by default, and free.
|
|
127
|
-
* @returns {null | {height?: number, margin?: number, anchor?: string, pointerScope?: string, debug?: boolean|'both', soften?: string | object}}
|
|
140
|
+
* @returns {null | {height?: number, margin?: number, anchor?: string, pointerScope?: string, debug?: boolean|'both', soften?: string | object, shape?: string, shapeFor?: Function}}
|
|
128
141
|
*/
|
|
129
142
|
export function resolveCursorOption(opt, who = '@displayxr/inline3d') {
|
|
130
143
|
if (opt === undefined || opt === null || opt === false) return null;
|
|
131
144
|
if (opt === 'depth' || opt === true) return {};
|
|
132
145
|
if (typeof opt !== 'object') {
|
|
133
|
-
throw new Error(`${who}: cursor ${JSON.stringify(opt)} — expected 'depth' or {
|
|
146
|
+
throw new Error(`${who}: cursor ${JSON.stringify(opt)} — expected 'depth' or { ${CURSOR_OPTION_KEYS.join(', ')} }.`);
|
|
134
147
|
}
|
|
135
148
|
const unknown = Object.keys(opt).filter((k) => !CURSOR_OPTION_KEYS.includes(k));
|
|
136
149
|
if (unknown.length) throw new Error(`${who}: cursor — unknown option(s) ${unknown.join(', ')}; expected ${CURSOR_OPTION_KEYS.join(', ')}.`);
|
|
@@ -149,14 +162,20 @@ export function resolveCursorOption(opt, who = '@displayxr/inline3d') {
|
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
if (opt.soften !== undefined) resolveCursorSoften(opt.soften, who); // validate at the call
|
|
165
|
+
if (opt.shape !== undefined && !CURSOR_SHAPE_OPTIONS.includes(opt.shape)) {
|
|
166
|
+
throw new Error(`${who}: cursor.shape ${JSON.stringify(opt.shape)} — expected ${CURSOR_SHAPE_OPTIONS.map((m) => `'${m}'`).join(' | ')}.`);
|
|
167
|
+
}
|
|
168
|
+
if (opt.shapeFor !== undefined && opt.shapeFor !== null && typeof opt.shapeFor !== 'function') {
|
|
169
|
+
throw new Error(`${who}: cursor.shapeFor must be a function (hit) => shape.`);
|
|
170
|
+
}
|
|
152
171
|
return { ...opt };
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
/** The keys `cursor: { … }` accepts on a stereo PAIR (./camera's addCameraView, ./call's tiles). */
|
|
156
|
-
export const PAIR_CURSOR_OPTION_KEYS = Object.freeze(['height', 'pointerScope', 'soften']);
|
|
175
|
+
export const PAIR_CURSOR_OPTION_KEYS = Object.freeze(['height', 'pointerScope', 'soften', 'shape', 'shapeFor']);
|
|
157
176
|
|
|
158
177
|
/**
|
|
159
|
-
* `cursor` on a stereo pair — a camera view or a call tile: `'depth'`, or `{ height, pointerScope, soften }`.
|
|
178
|
+
* `cursor` on a stereo pair — a camera view or a call tile: `'depth'`, or `{ height, pointerScope, soften, shape, shapeFor }`.
|
|
160
179
|
* A pair has no scene: its depth is MEASURED off the two halves, and the sprite is drawn into them,
|
|
161
180
|
* so there is no anchor mode (it is always on the line of sight) and the margin is the SDK's. Falsy
|
|
162
181
|
* is OFF, the zero-cost default.
|