@terraware/web-components 5.0.2 → 5.0.4
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/components/VirtualWalkthrough/AnnotationPanel.d.ts.map +1 -1
- package/components/VirtualWalkthrough/AnnotationPanel.js +24 -10
- package/components/VirtualWalkthrough/SplatModel.js +2 -1
- package/components/VirtualWalkthrough/SplatRevealRain.d.ts +3 -1
- package/components/VirtualWalkthrough/SplatRevealRain.d.ts.map +1 -1
- package/components/VirtualWalkthrough/SplatRevealRain.js +15 -8
- package/components/VirtualWalkthrough/useRestartOnVr.d.ts +16 -0
- package/components/VirtualWalkthrough/useRestartOnVr.d.ts.map +1 -0
- package/components/VirtualWalkthrough/useRestartOnVr.js +38 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.d.ts +65 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.d.ts.map +1 -1
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +99 -1
- package/components/VirtualWalkthrough/vr-annotation-panel.d.ts +40 -7
- package/components/VirtualWalkthrough/vr-annotation-panel.d.ts.map +1 -1
- package/components/VirtualWalkthrough/vr-annotation-panel.js +265 -104
- package/components/VirtualWalkthrough/vr-annotation-scroll.d.ts +23 -0
- package/components/VirtualWalkthrough/vr-annotation-scroll.d.ts.map +1 -0
- package/components/VirtualWalkthrough/vr-annotation-scroll.js +33 -0
- package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.d.ts +32 -0
- package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.js +88 -0
- package/components/VirtualWalkthrough/xr-annotation-candidates.d.ts +8 -12
- package/components/VirtualWalkthrough/xr-annotation-candidates.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-annotation-candidates.js +11 -32
- package/components/VirtualWalkthrough/xr-annotation-targeting.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-annotation-targeting.js +9 -4
- package/components/VirtualWalkthrough/xr-gaze-dwell.d.ts +1 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-gaze-dwell.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Vec3 } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
/** Local half-extent of the unit-plane hotspot quad. */
|
|
4
|
+
const HOTSPOT_HALF_EXTENT = 0.5;
|
|
5
|
+
|
|
6
|
+
/** Pulls the stock annotation script off a hotspot entity, or undefined if it carries none. */
|
|
7
|
+
|
|
8
|
+
const scratchScale = new Vec3();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Whether `node` still hangs beneath `sceneRoot`. An entity that has been removed from the scene
|
|
12
|
+
* keeps its own `children` array - a destroyed one keeps it as an empty array - so only a walk back
|
|
13
|
+
* up the parent chain tells a live root apart from a detached one.
|
|
14
|
+
*/
|
|
15
|
+
const isInScene = (node, sceneRoot) => {
|
|
16
|
+
for (let current = node; current; current = current.parent) {
|
|
17
|
+
if (current === sceneRoot) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Collects world-space hit candidates for the openable annotation hotspots into a buffer it owns and
|
|
26
|
+
* reuses, so a caller running every frame allocates nothing. The returned array and the candidate
|
|
27
|
+
* objects inside it are overwritten by the next `collect`, which is why each caller holds its own
|
|
28
|
+
* buffer rather than sharing one.
|
|
29
|
+
*
|
|
30
|
+
* Positions and radii are re-read on every call, so hotspots that move or resize stay targetable.
|
|
31
|
+
* That read is cheap; it is the allocation around it that a per-frame caller cannot afford.
|
|
32
|
+
*/
|
|
33
|
+
class AnnotationCandidateBuffer {
|
|
34
|
+
_candidates = [];
|
|
35
|
+
_root = null;
|
|
36
|
+
constructor(_getScript) {
|
|
37
|
+
this._getScript = _getScript;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hit candidates for every openable hotspot except `excludeName`, with hit spheres derived from
|
|
42
|
+
* each hotspot's rendered world size and padded by `radiusPad` for easier aiming.
|
|
43
|
+
*/
|
|
44
|
+
collect(app, radiusPad, excludeName) {
|
|
45
|
+
// The root is looked up by a walk of the whole scene graph, so it is held onto between frames.
|
|
46
|
+
// It is re-resolved whenever it leaves the scene: annotations mount after the scene does, and
|
|
47
|
+
// the root is torn down and built afresh every time the annotation list empties and refills.
|
|
48
|
+
if (!isInScene(this._root, app.root)) {
|
|
49
|
+
this._root = app.root.findByName('annotations-root');
|
|
50
|
+
}
|
|
51
|
+
const children = this._root?.children;
|
|
52
|
+
if (!children) {
|
|
53
|
+
this._candidates.length = 0;
|
|
54
|
+
return this._candidates;
|
|
55
|
+
}
|
|
56
|
+
let count = 0;
|
|
57
|
+
for (const entity of children) {
|
|
58
|
+
if (excludeName !== undefined && entity.name === excludeName) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const script = this._getScript(entity);
|
|
62
|
+
if (!script || script.enabled === false || typeof script.onVrOpenCallback !== 'function') {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
entity.getWorldTransform().getScale(scratchScale);
|
|
66
|
+
const radius = HOTSPOT_HALF_EXTENT * scratchScale.x * radiusPad;
|
|
67
|
+
const existing = this._candidates[count];
|
|
68
|
+
if (existing) {
|
|
69
|
+
existing.entity = entity;
|
|
70
|
+
existing.script = script;
|
|
71
|
+
existing.position = entity.getPosition();
|
|
72
|
+
existing.radius = radius;
|
|
73
|
+
} else {
|
|
74
|
+
this._candidates.push({
|
|
75
|
+
entity,
|
|
76
|
+
script,
|
|
77
|
+
position: entity.getPosition(),
|
|
78
|
+
radius
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
count++;
|
|
82
|
+
}
|
|
83
|
+
this._candidates.length = count;
|
|
84
|
+
return this._candidates;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { AnnotationCandidateBuffer, HOTSPOT_HALF_EXTENT };
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export
|
|
1
|
+
import { AnnotationCandidate, AnnotationCandidateBuffer, HOTSPOT_HALF_EXTENT } from './xr-annotation-candidate-buffer';
|
|
2
|
+
export { HOTSPOT_HALF_EXTENT };
|
|
3
|
+
export type { AnnotationCandidate };
|
|
4
4
|
/** Multiplier on the hotspot's world radius to make controller targeting forgiving. */
|
|
5
5
|
export declare const HIT_RADIUS_PAD = 2.5;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
position: Vec3;
|
|
10
|
-
radius: number;
|
|
11
|
-
}
|
|
6
|
+
/** A buffer wired to the stock annotation script. Callers that collect every frame hold one of
|
|
7
|
+
* these; its results are overwritten on the next collect, so one buffer cannot be shared. */
|
|
8
|
+
export declare const createAnnotationCandidateBuffer: () => AnnotationCandidateBuffer;
|
|
12
9
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* radius derived from the hotspot's rendered world size, padded by `radiusPad` for easier aiming.
|
|
10
|
+
* One-shot collection for callers that run on an input event rather than every frame, and so have no
|
|
11
|
+
* reason to hold a buffer of their own.
|
|
16
12
|
*/
|
|
17
13
|
export declare const collectAnnotationHitCandidates: (app: any, radiusPad: number) => AnnotationCandidate[];
|
|
18
14
|
//# sourceMappingURL=xr-annotation-candidates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-annotation-candidates.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-candidates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"xr-annotation-candidates.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-candidates.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvH,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,uFAAuF;AACvF,eAAO,MAAM,cAAc,MAAM,CAAC;AAIlC;6FAC6F;AAC7F,eAAO,MAAM,+BAA+B,QAAO,yBACA,CAAC;AAEpD;;;GAGG;AACH,eAAO,MAAM,8BAA8B,GAAI,KAAK,GAAG,EAAE,WAAW,MAAM,KAAG,mBAAmB,EACrC,CAAC"}
|
|
@@ -1,40 +1,19 @@
|
|
|
1
|
-
import { Vec3 } from 'playcanvas';
|
|
2
1
|
import { Annotation } from 'playcanvas/scripts/esm/annotations.mjs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const HOTSPOT_HALF_EXTENT = 0.5;
|
|
2
|
+
import { AnnotationCandidateBuffer } from './xr-annotation-candidate-buffer.js';
|
|
3
|
+
export { HOTSPOT_HALF_EXTENT } from './xr-annotation-candidate-buffer.js';
|
|
6
4
|
|
|
7
5
|
/** Multiplier on the hotspot's world radius to make controller targeting forgiving. */
|
|
8
6
|
const HIT_RADIUS_PAD = 2.5;
|
|
9
|
-
const
|
|
7
|
+
const annotationScriptOf = entity => entity.script?.get(Annotation.scriptName);
|
|
8
|
+
|
|
9
|
+
/** A buffer wired to the stock annotation script. Callers that collect every frame hold one of
|
|
10
|
+
* these; its results are overwritten on the next collect, so one buffer cannot be shared. */
|
|
11
|
+
const createAnnotationCandidateBuffer = () => new AnnotationCandidateBuffer(annotationScriptOf);
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* radius derived from the hotspot's rendered world size, padded by `radiusPad` for easier aiming.
|
|
14
|
+
* One-shot collection for callers that run on an input event rather than every frame, and so have no
|
|
15
|
+
* reason to hold a buffer of their own.
|
|
15
16
|
*/
|
|
16
|
-
const collectAnnotationHitCandidates = (app, radiusPad) =>
|
|
17
|
-
const root = app.root.findByName('annotations-root');
|
|
18
|
-
if (!root) {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
return root.children.map(entity => ({
|
|
22
|
-
entity,
|
|
23
|
-
script: entity.script?.get(Annotation.scriptName)
|
|
24
|
-
})).filter(({
|
|
25
|
-
script
|
|
26
|
-
}) => script && script.enabled !== false && typeof script.onVrOpenCallback === 'function').map(({
|
|
27
|
-
entity,
|
|
28
|
-
script
|
|
29
|
-
}) => {
|
|
30
|
-
entity.getWorldTransform().getScale(scratchScale);
|
|
31
|
-
return {
|
|
32
|
-
entity,
|
|
33
|
-
script,
|
|
34
|
-
position: entity.getPosition(),
|
|
35
|
-
radius: HOTSPOT_HALF_EXTENT * scratchScale.x * radiusPad
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
};
|
|
17
|
+
const collectAnnotationHitCandidates = (app, radiusPad) => createAnnotationCandidateBuffer().collect(app, radiusPad);
|
|
39
18
|
|
|
40
|
-
export { HIT_RADIUS_PAD,
|
|
19
|
+
export { HIT_RADIUS_PAD, collectAnnotationHitCandidates, createAnnotationCandidateBuffer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-annotation-targeting.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-targeting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"xr-annotation-targeting.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-targeting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AA0BD;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,IAAI,EACZ,WAAW,IAAI,EACf,YAAY,sBAAsB,EAAE,KACnC,MAAM,GAAG,IAeX,CAAC"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Vec3 } from 'playcanvas';
|
|
2
2
|
|
|
3
|
+
// Reused across calls: this runs per candidate per frame, where fresh vectors would be steady GC churn.
|
|
4
|
+
const scratchDir = new Vec3();
|
|
5
|
+
const scratchToCenter = new Vec3();
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Distance along a (normalized) ray to where it first enters the sphere, or null if it never does.
|
|
5
9
|
* A ray starting inside the sphere returns 0.
|
|
6
10
|
*/
|
|
7
11
|
const raySphereEntryDistance = (origin, dir, center, radius) => {
|
|
8
|
-
const m =
|
|
12
|
+
const m = scratchToCenter.sub2(origin, center);
|
|
9
13
|
const b = m.dot(dir);
|
|
10
14
|
const c = m.dot(m) - radius * radius;
|
|
11
15
|
if (c > 0 && b > 0) {
|
|
@@ -23,16 +27,17 @@ const raySphereEntryDistance = (origin, dir, center, radius) => {
|
|
|
23
27
|
* Index of the closest candidate sphere the ray enters, or null if it hits none.
|
|
24
28
|
*/
|
|
25
29
|
const nearestAnnotationHit = (origin, direction, candidates) => {
|
|
26
|
-
const dir =
|
|
30
|
+
const dir = scratchDir.copy(direction).normalize();
|
|
27
31
|
let bestIndex = null;
|
|
28
32
|
let bestDistance = Infinity;
|
|
29
|
-
candidates.
|
|
33
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
34
|
+
const candidate = candidates[index];
|
|
30
35
|
const distance = raySphereEntryDistance(origin, dir, candidate.position, candidate.radius);
|
|
31
36
|
if (distance !== null && distance < bestDistance) {
|
|
32
37
|
bestDistance = distance;
|
|
33
38
|
bestIndex = index;
|
|
34
39
|
}
|
|
35
|
-
}
|
|
40
|
+
}
|
|
36
41
|
return bestIndex;
|
|
37
42
|
};
|
|
38
43
|
|
|
@@ -5,6 +5,7 @@ export declare class XrGazeDwell extends Script {
|
|
|
5
5
|
* dwell opens OTHER annotations while one is open (and never re-dwells the open one). */
|
|
6
6
|
activeIndex: number;
|
|
7
7
|
private _dwell;
|
|
8
|
+
private _candidates;
|
|
8
9
|
private _pieEntity?;
|
|
9
10
|
private _pieMaterial?;
|
|
10
11
|
private _pieMesh?;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-gaze-dwell.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-gaze-dwell.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,MAAM,EAKP,MAAM,YAAY,CAAC;AAmBpB,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC;6FACyF;IACzF,WAAW,SAAM;IAEjB,OAAO,CAAC,MAAM,CAAmC;
|
|
1
|
+
{"version":3,"file":"xr-gaze-dwell.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-gaze-dwell.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,MAAM,EAKP,MAAM,YAAY,CAAC;AAmBpB,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC;6FACyF;IACzF,WAAW,SAAM;IAEjB,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,WAAW,CAAqC;IAExD,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAU;IAE3B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,aAAa,CAAc;IAEnC,OAAO,CAAC,WAAW,CAAyE;IAE5F;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAGhB;IAEF,OAAO,CAAC,QAAQ,CAId;IAEF,OAAO,CAAC,cAAc;IAMtB,UAAU;IA4BV,MAAM,CAAC,EAAE,EAAE,MAAM;IAgCjB,OAAO,CAAC,UAAU;IAgBlB,uDAAuD;IACvD,OAAO,CAAC,SAAS;IAcjB,OAAO;CAiBR"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Vec3, Script, Quat, XRTYPE_VR, MeshInstance, Entity, LAYERID_IMMEDIATE } from 'playcanvas';
|
|
2
|
-
import {
|
|
2
|
+
import { createAnnotationCandidateBuffer } from './xr-annotation-candidates.js';
|
|
3
3
|
import { nearestAnnotationHit } from './xr-annotation-targeting.js';
|
|
4
4
|
import { INITIAL_DWELL_STATE, advanceDwell } from './xr-gaze-dwell-state.js';
|
|
5
5
|
import { pieShaderProgress } from './xr-progress-pie.js';
|
|
6
6
|
import { emptyMaskTexture, createProgressPieMaterial, progressPieQuadMesh } from './xr-progress-pie-material.js';
|
|
7
|
+
import { HOTSPOT_HALF_EXTENT } from './xr-annotation-candidate-buffer.js';
|
|
7
8
|
|
|
8
9
|
/** Multiplier on the hotspot's world radius for gaze targeting. */
|
|
9
10
|
const GAZE_HIT_RADIUS_PAD = 5;
|
|
@@ -21,6 +22,7 @@ class XrGazeDwell extends Script {
|
|
|
21
22
|
* dwell opens OTHER annotations while one is open (and never re-dwells the open one). */
|
|
22
23
|
activeIndex = -1;
|
|
23
24
|
_dwell = INITIAL_DWELL_STATE;
|
|
25
|
+
_candidates = createAnnotationCandidateBuffer();
|
|
24
26
|
_headPos = new Vec3();
|
|
25
27
|
_headRot = new Quat();
|
|
26
28
|
_viewOffset = new Vec3();
|
|
@@ -85,8 +87,7 @@ class XrGazeDwell extends Script {
|
|
|
85
87
|
this._pieMaterial.setParameter('uProgress', 0);
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
|
-
const
|
|
89
|
-
const candidates = collectAnnotationHitCandidates(this.app, GAZE_HIT_RADIUS_PAD).filter(candidate => candidate.entity.name !== activeName);
|
|
90
|
+
const candidates = this._candidates.collect(this.app, GAZE_HIT_RADIUS_PAD, `annotation-${this.activeIndex}`);
|
|
90
91
|
this._headRot.transformVector(LOCAL_FORWARD, this._forward);
|
|
91
92
|
const target = nearestAnnotationHit(this._headPos, this._forward, candidates);
|
|
92
93
|
const result = advanceDwell(this._dwell, target, dt, DWELL_SECONDS);
|