@displayxr/inline3d 1.40.1 → 1.41.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 +59 -0
- package/cursor-depth.d.ts +50 -9
- package/dist/call.js +4 -4
- package/js/camera/pair-cursor.js +116 -53
- package/js/inline3d-call.js +1 -1
- package/js/inline3d-camera.js +1 -1
- package/js/inline3d-cursor-depth-playcanvas.js +21 -17
- package/js/inline3d-cursor-depth.js +183 -61
- package/js/inline3d-three.js +26 -18
- package/model.d.ts +6 -4
- package/package.json +1 -1
- package/splat.d.ts +6 -4
- package/three.d.ts +11 -8
package/js/camera/pair-cursor.js
CHANGED
|
@@ -7,37 +7,45 @@
|
|
|
7
7
|
// two halves ARE the depth: this is ADR-046 Phase 3b done in the page. Block-match a cursor-sized
|
|
8
8
|
// footprint between the eyes and take the nearest confident match.
|
|
9
9
|
//
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
10
|
+
// The policy is the one every DisplayXR depth cursor follows (runtime ADR-046 Amendment 2), taken
|
|
11
|
+
// from the shared core (../inline3d-cursor-depth.js), not re-implemented here:
|
|
12
|
+
// - never behind the glass: only content that comes OUT of the screen moves it, to the margin
|
|
13
|
+
// (0.03 eye baselines, ~2 mm) in front of that content (cursorTarget);
|
|
14
|
+
// - on the plane it IS the OS cursor: the arrow is drawn into both halves only while the filtered
|
|
15
|
+
// disparity is past the draw threshold (-0.005 baselines, and >= 0.5 px; cursorShouldDraw), the
|
|
16
|
+
// OS cursor hidden exactly then;
|
|
17
|
+
// - the same filter (fast rise, slow sink) and the same arrow (CURSOR_ARROW, tip = hotspot, the
|
|
18
|
+
// OS arrow's 19 CSS px).
|
|
19
|
+
// What is specific to a pair: it has no 3D views, so it works in tile PIXEL disparity — OUTPUT
|
|
20
|
+
// pixels of one eye of the woven buffer, + = crossed (in front of the glass), the sign the rest of
|
|
21
|
+
// camera/ uses — and converts to the core's eye-baseline units through the panel's pixel pitch:
|
|
22
|
+
// buffer px per metre = outW / tileCssWidth / metersPerCssPx (the tile handle's displayMetrics()),
|
|
23
|
+
// times the eye separation (displayMetrics' default, 63 mm). Loaded only when a page asks for
|
|
19
24
|
// `cursor: 'depth'` — zero cost otherwise.
|
|
20
25
|
|
|
21
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
CursorPointer,
|
|
28
|
+
cursorArrowFootprint,
|
|
29
|
+
cursorArrowInset,
|
|
30
|
+
cursorDefaultHeight,
|
|
31
|
+
cursorFilterStep,
|
|
32
|
+
cursorShouldDraw,
|
|
33
|
+
cursorTarget,
|
|
34
|
+
CURSOR_ARROW,
|
|
35
|
+
CURSOR_ARROW_CSS_PX,
|
|
36
|
+
CURSOR_DEFAULT_TUNING,
|
|
37
|
+
} from '../inline3d-cursor-depth.js';
|
|
38
|
+
import { DISPLAY_METRICS_DEFAULTS } from '../inline3d-display-metrics.js';
|
|
22
39
|
import { matchBlock, MIN_NCC } from './disparity.js';
|
|
23
40
|
|
|
24
|
-
/** The arrow's height in CSS px — the OS arrow's apparent size
|
|
25
|
-
export const PAIR_CURSOR_CSS_PX =
|
|
41
|
+
/** The arrow's height in CSS px — the OS arrow's apparent size (the core's CURSOR_ARROW_CSS_PX). */
|
|
42
|
+
export const PAIR_CURSOR_CSS_PX = CURSOR_ARROW_CSS_PX;
|
|
26
43
|
/** Arrow height as a fraction of the tile height when the tile's on-page size is unknown. */
|
|
27
44
|
export const PAIR_CURSOR_HEIGHT = 0.06;
|
|
28
|
-
/**
|
|
29
|
-
export const
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
* OS arrow's outline (a 19-px-tall Windows/macOS arrow, normalised).
|
|
33
|
-
*/
|
|
34
|
-
export const PAIR_CURSOR_ARROW = Object.freeze([
|
|
35
|
-
[0, 0], [0, 16 / 19], [4.2 / 19, 12.4 / 19], [6.9 / 19, 18.6 / 19], [9.5 / 19, 17.5 / 19], [6.8 / 19, 11.5 / 19], [11.7 / 19, 11.5 / 19],
|
|
36
|
-
]);
|
|
37
|
-
/** How far in front of out-of-screen content the arrow floats, as a fraction of the eye width (crossed). */
|
|
38
|
-
export const PAIR_CURSOR_MARGIN = 0.002;
|
|
39
|
-
/** Comfort cap on how far in front the arrow comes, as a fraction of the eye width. */
|
|
40
|
-
export const PAIR_CURSOR_CLAMP = 0.08;
|
|
45
|
+
/** The arrow every DisplayXR depth cursor draws (the core's CURSOR_ARROW): tip at (0, 0), unit = its height, y down. */
|
|
46
|
+
export const PAIR_CURSOR_ARROW = CURSOR_ARROW;
|
|
47
|
+
/** One CSS px in metres when the panel cannot say (the CSS reference pixel, 1/96 in). */
|
|
48
|
+
export const PAIR_CURSOR_CSS_PX_M = 0.0254 / 96;
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* The displayed disparity of the nearest content under the cursor's footprint, or null when no
|
|
@@ -65,8 +73,8 @@ export function pairFootprintDisparity(luma, geo, u, v, height, aspect) {
|
|
|
65
73
|
const { rL, rR } = geo;
|
|
66
74
|
const k = geo.outW / rL.sw; // output px per source px
|
|
67
75
|
let best = null;
|
|
68
|
-
// The ring
|
|
69
|
-
for (const [pu, pv] of
|
|
76
|
+
// The tip, then a ring centred on the arrow's body: that is what content must not overlap.
|
|
77
|
+
for (const [pu, pv] of cursorArrowFootprint(u, v, height, aspect)) {
|
|
70
78
|
if (!(pu >= 0 && pu <= 1 && pv >= 0 && pv <= 1)) continue;
|
|
71
79
|
const su = geo.mirror ? 1 - pu : pu;
|
|
72
80
|
const x = Math.round((rL.sx + su * rL.sw) * scale - b / 2);
|
|
@@ -82,38 +90,68 @@ export function pairFootprintDisparity(luma, geo, u, v, height, aspect) {
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
93
|
+
* Output px of one eye per metre on the glass: the buffer eye is stretched over the tile's CSS
|
|
94
|
+
* width, and one CSS px is `metersPerCssPx` on the panel (the CSS reference px when unknown).
|
|
95
|
+
*/
|
|
96
|
+
export function pairCursorScale(outW, tileCssWidth, metersPerCssPx) {
|
|
97
|
+
const m = metersPerCssPx > 0 && Number.isFinite(metersPerCssPx) ? metersPerCssPx : PAIR_CURSOR_CSS_PX_M;
|
|
98
|
+
return tileCssWidth > 0 ? outW / tileCssWidth / m : outW / (1000 * m);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Output px of on-screen disparity per eye-baseline unit — the conversion between this module's
|
|
103
|
+
* pixels and the core's units: pairCursorScale × the eye separation.
|
|
88
104
|
*/
|
|
89
|
-
export function
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
export function pairPxPerBaseline(outW, tileCssWidth, metersPerCssPx, eyeSeparationM = DISPLAY_METRICS_DEFAULTS.eyeSeparationM) {
|
|
106
|
+
return pairCursorScale(outW, tileCssWidth, metersPerCssPx) * eyeSeparationM;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The arrow's target disparity in output px (+ = in front): the core's policy (cursorTarget) on
|
|
111
|
+
* the content's disparity, converted through `pxPerBaseline`. In front of content that comes OUT
|
|
112
|
+
* of the screen by the margin (0.03 baselines), else the glass — never behind it.
|
|
113
|
+
*/
|
|
114
|
+
export function pairCursorTarget(contentPx, pxPerBaseline, tuning = CURSOR_DEFAULT_TUNING) {
|
|
115
|
+
if (contentPx === null || !Number.isFinite(contentPx) || !(pxPerBaseline > 0)) return 0;
|
|
116
|
+
return -cursorTarget(tuning, true, -contentPx / pxPerBaseline) * pxPerBaseline || 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The arrow's fill, inset by one output px (at least 1/19 of its height), cached per size. */
|
|
120
|
+
const _fillBySize = new Map();
|
|
121
|
+
function arrowFill(sizePx) {
|
|
122
|
+
const d = sizePx > 0 ? Math.max(1 / 19, 1 / sizePx) : 1 / 19;
|
|
123
|
+
const key = Math.round(d * 1e4);
|
|
124
|
+
let f = _fillBySize.get(key);
|
|
125
|
+
if (!f) {
|
|
126
|
+
if (_fillBySize.size > 32) _fillBySize.clear();
|
|
127
|
+
_fillBySize.set(key, (f = cursorArrowInset(d)));
|
|
128
|
+
}
|
|
129
|
+
return f;
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
/**
|
|
95
133
|
* Draw the arrow into both halves of a woven buffer: the left eye's copy `disparityPx / 2` right of
|
|
96
|
-
* the pointer, the right eye's as far left (crossed = in front).
|
|
97
|
-
*
|
|
134
|
+
* the pointer, the right eye's as far left (crossed = in front). The shared arrow: its dark outline
|
|
135
|
+
* polygon, then the white fill inset by a px; the tip on the hotspot. Each copy is clipped to its
|
|
136
|
+
* own half, so an arrow at the edge never bleeds into the other eye.
|
|
98
137
|
* @param {number} sizePx arrow height in output px
|
|
99
138
|
*/
|
|
100
139
|
export function drawPairArrow(g, outW, outH, u, v, disparityPx, sizePx) {
|
|
101
140
|
const cy = v * outH;
|
|
141
|
+
const fill = arrowFill(sizePx);
|
|
102
142
|
for (const eye of [0, 1]) {
|
|
103
143
|
const cx = eye * outW + u * outW + (eye === 0 ? disparityPx / 2 : -disparityPx / 2);
|
|
104
144
|
g.save();
|
|
105
145
|
g.beginPath();
|
|
106
146
|
g.rect(eye * outW, 0, outW, outH);
|
|
107
147
|
g.clip();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
g.strokeStyle = '#000';
|
|
116
|
-
g.stroke();
|
|
148
|
+
for (const [poly, style] of [[CURSOR_ARROW, '#000'], [fill, '#fff']]) {
|
|
149
|
+
g.beginPath();
|
|
150
|
+
poly.forEach(([x, y], k) => (k ? g.lineTo : g.moveTo).call(g, cx + x * sizePx, cy + y * sizePx));
|
|
151
|
+
g.closePath();
|
|
152
|
+
g.fillStyle = style;
|
|
153
|
+
g.fill();
|
|
154
|
+
}
|
|
117
155
|
g.restore();
|
|
118
156
|
}
|
|
119
157
|
}
|
|
@@ -133,7 +171,12 @@ export class PairCursor {
|
|
|
133
171
|
this._luma = null;
|
|
134
172
|
this._uv = null;
|
|
135
173
|
this._content = null;
|
|
136
|
-
|
|
174
|
+
this._metricsFor = null; // the tile handle whose displayMetrics() was asked for
|
|
175
|
+
/** One CSS px on the panel, metres, once the tile's handle has said (else the CSS reference px). */
|
|
176
|
+
this.metersPerCssPx = null;
|
|
177
|
+
/** The eye separation (metres) the baseline units convert with: displayMetrics' (a default today). */
|
|
178
|
+
this.eyeSeparationM = DISPLAY_METRICS_DEFAULTS.eyeSeparationM;
|
|
179
|
+
/** The last frame's placement, for diagnostics: `{ contentPx, targetPx, disparityPx, filteredPx, drawn, pxPerBaseline }` (output px, + = in front) or null. */
|
|
137
180
|
this.last = null;
|
|
138
181
|
}
|
|
139
182
|
|
|
@@ -146,7 +189,8 @@ export class PairCursor {
|
|
|
146
189
|
* exactly then); otherwise leaves the buffer alone and the OS arrow shows, on the plane.
|
|
147
190
|
* @param {CanvasRenderingContext2D} g the woven buffer (2·outW × outH), already painted
|
|
148
191
|
* @param {object|null} luma the latest grayscale source copy (pairFootprintDisparity)
|
|
149
|
-
* @param {{rL: object, rR: object, outW: number, outH: number, mirror?: boolean}} geo
|
|
192
|
+
* @param {{rL: object, rR: object, outW: number, outH: number, mirror?: boolean, handle?: object}} geo
|
|
193
|
+
* `handle` = the tile's woven handle: its `displayMetrics()` gives the panel's pixel pitch
|
|
150
194
|
* @param {number} nowSec
|
|
151
195
|
*/
|
|
152
196
|
draw(g, luma, geo, nowSec) {
|
|
@@ -160,20 +204,39 @@ export class PairCursor {
|
|
|
160
204
|
this._luma = luma;
|
|
161
205
|
this._uv = uv;
|
|
162
206
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
207
|
+
this._askMetrics(geo.handle);
|
|
208
|
+
const r = this.pointer.canvas.getBoundingClientRect?.();
|
|
209
|
+
const pxPerBaseline = pairPxPerBaseline(geo.outW, r && r.width, this.metersPerCssPx, this.eyeSeparationM);
|
|
210
|
+
// The core's policy and filter, in its units (eye baselines, < 0 = in front).
|
|
211
|
+
const content = this._content === null ? null : -this._content / pxPerBaseline;
|
|
212
|
+
const target = cursorTarget(CURSOR_DEFAULT_TUNING, content !== null, content);
|
|
213
|
+
const d = cursorFilterStep(this.filter, CURSOR_DEFAULT_TUNING, target, nowSec);
|
|
214
|
+
const drawn = cursorShouldDraw(d, pxPerBaseline);
|
|
215
|
+
const disparity = -d * pxPerBaseline; // output px, + = in front
|
|
167
216
|
if (drawn) drawPairArrow(g, geo.outW, geo.outH, u, v, disparity, hf * geo.outH);
|
|
168
217
|
this.pointer.hideCss(drawn); // the OS arrow IS the cursor on the plane
|
|
169
|
-
this.last = { contentPx: this._content, targetPx: target, disparityPx: drawn ? disparity : 0, drawn };
|
|
218
|
+
this.last = { contentPx: this._content, targetPx: -target * pxPerBaseline || 0, disparityPx: drawn ? disparity : 0, filteredPx: disparity || 0, drawn, pxPerBaseline };
|
|
170
219
|
return this.last;
|
|
171
220
|
}
|
|
172
221
|
|
|
173
|
-
/**
|
|
222
|
+
/** Ask the tile's handle once for the panel's pixel pitch; until it answers, the CSS reference px. */
|
|
223
|
+
_askMetrics(handle) {
|
|
224
|
+
if (!handle || handle === this._metricsFor || typeof handle.displayMetrics !== 'function') return;
|
|
225
|
+
this._metricsFor = handle;
|
|
226
|
+
Promise.resolve()
|
|
227
|
+
.then(() => handle.displayMetrics())
|
|
228
|
+
.then((m) => {
|
|
229
|
+
if (this._metricsFor !== handle || !m) return;
|
|
230
|
+
if (m.metersPerCssPx > 0) this.metersPerCssPx = m.metersPerCssPx;
|
|
231
|
+
if (m.eyeSeparationM > 0) this.eyeSeparationM = m.eyeSeparationM;
|
|
232
|
+
})
|
|
233
|
+
.catch(() => {});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The OS arrow's height as a fraction of the tile's on-page height (the core's default size). */
|
|
174
237
|
_osArrowFraction() {
|
|
175
238
|
const r = this.pointer.canvas.getBoundingClientRect?.();
|
|
176
|
-
return r && r.height > 0 ?
|
|
239
|
+
return r && r.height > 0 ? cursorDefaultHeight(r.height) : PAIR_CURSOR_HEIGHT;
|
|
177
240
|
}
|
|
178
241
|
|
|
179
242
|
/** Not drawing this frame (pointer away, tile not woven): the CSS cursor comes back. */
|
package/js/inline3d-call.js
CHANGED
|
@@ -1715,7 +1715,7 @@ class Tile {
|
|
|
1715
1715
|
const r = (rects[eye] = wire.eyeCropRect(eyeW, H, A, shift, eye));
|
|
1716
1716
|
g.drawImage(v, eye * eyeW + r.sx, r.sy, r.sw, r.sh, eye * outW, 0, outW, outH);
|
|
1717
1717
|
}
|
|
1718
|
-
if (this.cursor) this.cursor.draw(g, this.luma, { rL: rects[0], rR: rects[1], outW, outH }, performance.now() / 1000);
|
|
1718
|
+
if (this.cursor) this.cursor.draw(g, this.luma, { rL: rects[0], rR: rects[1], outW, outH, handle: this.handle }, performance.now() / 1000);
|
|
1719
1719
|
}
|
|
1720
1720
|
|
|
1721
1721
|
/**
|
package/js/inline3d-camera.js
CHANGED
|
@@ -762,7 +762,7 @@ class CameraView {
|
|
|
762
762
|
}
|
|
763
763
|
}
|
|
764
764
|
if (this.cursor) {
|
|
765
|
-
const geo = { rL: eyeCropRect(eyeW, H, A, shift, 0), rR: eyeCropRect(eyeW, H, A, shift, 1), outW, outH, mirror: this.mirror };
|
|
765
|
+
const geo = { rL: eyeCropRect(eyeW, H, A, shift, 0), rR: eyeCropRect(eyeW, H, A, shift, 1), outW, outH, mirror: this.mirror, handle: this.handle };
|
|
766
766
|
this.cursor.draw(g, this._luma, geo, performance.now() / 1000);
|
|
767
767
|
}
|
|
768
768
|
}
|
|
@@ -14,18 +14,20 @@
|
|
|
14
14
|
// runtime's one-frame-late app hint.
|
|
15
15
|
//
|
|
16
16
|
// Placement: the same maths as everywhere else (./inline3d-cursor-depth.js, a port of the
|
|
17
|
-
// runtime's u_cursor_depth, ADR-046), solved in WORLD space from this
|
|
18
|
-
// through the rig node. Drawn
|
|
19
|
-
//
|
|
20
|
-
// UI layer, so
|
|
17
|
+
// runtime's u_cursor_depth, ADR-046 + Amendment 2's policy), solved in WORLD space from this
|
|
18
|
+
// frame's views carried through the rig node. Drawn — only while content under it comes out of the
|
|
19
|
+
// glass — as the classic arrow (the shared cursorArrowMesh: dark outline, white fill, tip on the
|
|
20
|
+
// hotspot) in the UI layer with depth test off, so every eye camera draws it last and nothing
|
|
21
|
+
// occludes it; the picker skips the UI layer, so the cursor never picks itself. On the plane
|
|
22
|
+
// nothing is drawn and the CSS cursor is the cursor.
|
|
21
23
|
|
|
22
24
|
import {
|
|
23
25
|
CursorDepthPlacer,
|
|
24
26
|
CursorPointer,
|
|
25
|
-
CURSOR_DEFAULT_HEIGHT,
|
|
26
27
|
CURSOR_DEFAULT_TUNING,
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
cursorArrowMesh,
|
|
29
|
+
cursorArrowFootprint,
|
|
30
|
+
cursorDefaultHeight,
|
|
29
31
|
cursorViewRay,
|
|
30
32
|
} from './inline3d-cursor-depth.js';
|
|
31
33
|
|
|
@@ -42,10 +44,11 @@ export class PlayCanvasDepthCursor {
|
|
|
42
44
|
* @param {object} viewer the PlayCanvasSplatViewer (app, rigNode, eye are read lazily).
|
|
43
45
|
* @param {{canvas: HTMLElement, height?: number, margin?: number, anchor?: string, pointerScope?: string}} opts
|
|
44
46
|
*/
|
|
45
|
-
constructor(pc, viewer, { canvas, height
|
|
47
|
+
constructor(pc, viewer, { canvas, height, margin, anchor = 'hybrid', pointerScope = 'canvas' }) {
|
|
46
48
|
this.pc = pc;
|
|
47
49
|
this.viewer = viewer;
|
|
48
|
-
|
|
50
|
+
/** Sprite height (canvas heights) when set; null = the OS arrow's size, from the canvas's on-page height. */
|
|
51
|
+
this.height = height > 0 && Number.isFinite(height) ? height : null;
|
|
49
52
|
this.pointer = new CursorPointer(canvas, { scope: pointerScope });
|
|
50
53
|
const tuning = margin > 0 && Number.isFinite(margin) ? { ...CURSOR_DEFAULT_TUNING, margin } : CURSOR_DEFAULT_TUNING;
|
|
51
54
|
this.placer = new CursorDepthPlacer(tuning, { anchor });
|
|
@@ -89,16 +92,17 @@ export class PlayCanvasDepthCursor {
|
|
|
89
92
|
console.warn('[inline3d] cursor: depth: a PlayCanvas pick did not answer in time; retrying (cursor on the glass meanwhile).');
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
const h = this.height ?? cursorDefaultHeight(this.pointer.cssHeight());
|
|
96
|
+
if (this._frame++ % PICK_EVERY === 0 && !this._inFlight) this._pick(app, rig, entries[0], uv, h);
|
|
93
97
|
const now = (globalThis.performance ? globalThis.performance.now() : Date.now()) / 1000;
|
|
94
|
-
const p = this.placer.update(views, { u: uv[0], v: uv[1], nearestPoint: this.hit, cursorHeight: this.
|
|
98
|
+
const p = this.placer.update(views, { u: uv[0], v: uv[1], nearestPoint: this.hit, cursorHeight: h, canvasPx: this.pointer.panelPx() }, now);
|
|
95
99
|
this.placement = p;
|
|
96
100
|
this.pointer.hideCss(!!p.active);
|
|
97
101
|
const sprite = this._sprite || (p.active ? this._makeSprite(app) : null);
|
|
98
102
|
if (!sprite) return;
|
|
99
103
|
sprite.mi.visible = !!p.active;
|
|
100
104
|
if (!p.active) return;
|
|
101
|
-
// The unit
|
|
105
|
+
// The unit arrow in the sprite's XY plane, tip at its origin: basis (rotation), height (scale), position = the hotspot.
|
|
102
106
|
const { x, y, z } = p.basis;
|
|
103
107
|
const q = quatFromMatrix([x[0], x[1], x[2], 0, y[0], y[1], y[2], 0, z[0], z[1], z[2], 0, 0, 0, 0, 1]);
|
|
104
108
|
sprite.node.setLocalPosition(p.position[0], p.position[1], p.position[2]);
|
|
@@ -114,11 +118,11 @@ export class PlayCanvasDepthCursor {
|
|
|
114
118
|
this.pointer.hideCss(false);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
// The
|
|
118
|
-
//
|
|
121
|
+
// The arrow: one mesh (outline triangles first, fill after — the order a depth-test-off draw
|
|
122
|
+
// needs), vertex-coloured, alpha-blended, in the UI layer.
|
|
119
123
|
_makeSprite(app) {
|
|
120
124
|
const pc = this.pc;
|
|
121
|
-
const m =
|
|
125
|
+
const m = cursorArrowMesh();
|
|
122
126
|
const mesh = new pc.Mesh(app.graphicsDevice);
|
|
123
127
|
mesh.setPositions(m.positions);
|
|
124
128
|
mesh.setColors(m.colors);
|
|
@@ -179,7 +183,7 @@ export class PlayCanvasDepthCursor {
|
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
// Render the pick buffer from the first eye and read the footprint's world points.
|
|
182
|
-
_pick(app, rig, e, uv) {
|
|
186
|
+
_pick(app, rig, e, uv, height) {
|
|
183
187
|
const pc = this.pc;
|
|
184
188
|
const aspect = e.width > 0 && e.height > 0 ? e.width / e.height : 1;
|
|
185
189
|
const W = PICK_WIDTH;
|
|
@@ -240,7 +244,7 @@ export class PlayCanvasDepthCursor {
|
|
|
240
244
|
const Mw = view.transformMatrix;
|
|
241
245
|
const f = [-Mw[8], -Mw[9], -Mw[10]];
|
|
242
246
|
const zNear = cam.camera.nearClip, zFar = cam.camera.farClip;
|
|
243
|
-
const samples =
|
|
247
|
+
const samples = cursorArrowFootprint(uv[0], uv[1], height, this.pointer.aspect())
|
|
244
248
|
.filter(([u, v]) => u >= 0 && u <= 1 && v >= 0 && v <= 1)
|
|
245
249
|
.map(([u, v]) =>
|
|
246
250
|
picker.getPointDepthAsync(Math.min(W - 1, Math.floor(u * W)), Math.min(H - 1, Math.floor(v * H))).then((lin) => {
|