@playcanvas/web-components 0.17.0 → 0.19.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/README.md +5 -3
- package/dist/app.d.cts +28 -3
- package/dist/app.d.ts +28 -3
- package/dist/asset.d.cts +13 -3
- package/dist/asset.d.ts +13 -3
- package/dist/components/anim-clip.d.cts +3 -1
- package/dist/components/anim-clip.d.ts +3 -1
- package/dist/components/anim-component.d.cts +5 -0
- package/dist/components/anim-component.d.ts +5 -0
- package/dist/components/audio-listener-component.d.cts +4 -0
- package/dist/components/audio-listener-component.d.ts +4 -0
- package/dist/components/button-component.d.cts +5 -0
- package/dist/components/button-component.d.ts +5 -0
- package/dist/components/camera-component.d.cts +35 -1
- package/dist/components/camera-component.d.ts +35 -1
- package/dist/components/collision-component.d.cts +4 -0
- package/dist/components/collision-component.d.ts +4 -0
- package/dist/components/element-component.d.cts +4 -0
- package/dist/components/element-component.d.ts +4 -0
- package/dist/components/gsplat-component.d.cts +4 -0
- package/dist/components/gsplat-component.d.ts +4 -0
- package/dist/components/joint-component.d.cts +12 -2
- package/dist/components/joint-component.d.ts +12 -2
- package/dist/components/layout-child-component.d.cts +4 -0
- package/dist/components/layout-child-component.d.ts +4 -0
- package/dist/components/layout-group-component.d.cts +4 -0
- package/dist/components/layout-group-component.d.ts +4 -0
- package/dist/components/light-component.d.cts +50 -1
- package/dist/components/light-component.d.ts +50 -1
- package/dist/components/particle-system-component.d.cts +4 -0
- package/dist/components/particle-system-component.d.ts +4 -0
- package/dist/components/render-component.d.cts +5 -0
- package/dist/components/render-component.d.ts +5 -0
- package/dist/components/rigid-body-component.d.cts +5 -0
- package/dist/components/rigid-body-component.d.ts +5 -0
- package/dist/components/screen-component.d.cts +4 -0
- package/dist/components/screen-component.d.ts +4 -0
- package/dist/components/script-component.d.cts +3 -0
- package/dist/components/script-component.d.ts +3 -0
- package/dist/components/script-instance.d.cts +4 -0
- package/dist/components/script-instance.d.ts +4 -0
- package/dist/components/scroll-view-component.d.cts +4 -0
- package/dist/components/scroll-view-component.d.ts +4 -0
- package/dist/components/scrollbar-component.d.cts +4 -0
- package/dist/components/scrollbar-component.d.ts +4 -0
- package/dist/components/sound-component.d.cts +4 -0
- package/dist/components/sound-component.d.ts +4 -0
- package/dist/components/sound-slot.d.cts +3 -0
- package/dist/components/sound-slot.d.ts +3 -0
- package/dist/custom-elements.json +338 -7
- package/dist/entity-base.d.cts +5 -4
- package/dist/entity-base.d.ts +5 -4
- package/dist/entity-owner.d.cts +2 -2
- package/dist/entity-owner.d.ts +2 -2
- package/dist/entity.d.cts +13 -2
- package/dist/entity.d.ts +13 -2
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/material.d.cts +18 -7
- package/dist/material.d.ts +18 -7
- package/dist/model.d.cts +11 -0
- package/dist/model.d.ts +11 -0
- package/dist/node.d.cts +12 -1
- package/dist/node.d.ts +12 -1
- package/dist/pwc.cjs +601 -206
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +601 -206
- package/dist/pwc.js.map +1 -1
- package/dist/pwc.min.js +1 -1
- package/dist/pwc.min.js.map +1 -1
- package/dist/pwc.min.mjs +1 -1
- package/dist/pwc.min.mjs.map +1 -1
- package/dist/pwc.mjs +602 -207
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.cts +19 -0
- package/dist/scene.d.ts +19 -0
- package/dist/sky.d.cts +4 -0
- package/dist/sky.d.ts +4 -0
- package/dist/vscode.html-custom-data.json +257 -62
- package/dist/wasm.d.cts +4 -0
- package/dist/wasm.d.ts +4 -0
- package/dist/web-types.json +169 -69
- package/package.json +3 -3
- package/src/app.ts +150 -71
- package/src/asset.ts +13 -4
- package/src/components/anim-clip.ts +3 -1
- package/src/components/anim-component.ts +5 -0
- package/src/components/audio-listener-component.ts +4 -0
- package/src/components/button-component.ts +5 -0
- package/src/components/camera-component.ts +63 -6
- package/src/components/collision-component.ts +4 -0
- package/src/components/element-component.ts +4 -0
- package/src/components/gsplat-component.ts +4 -0
- package/src/components/joint-component.ts +12 -2
- package/src/components/layout-child-component.ts +4 -0
- package/src/components/layout-group-component.ts +4 -0
- package/src/components/light-component.ts +108 -17
- package/src/components/particle-system-component.ts +4 -0
- package/src/components/render-component.ts +5 -0
- package/src/components/rigid-body-component.ts +5 -0
- package/src/components/screen-component.ts +4 -0
- package/src/components/script-component.ts +3 -0
- package/src/components/script-instance.ts +4 -0
- package/src/components/scroll-view-component.ts +4 -0
- package/src/components/scrollbar-component.ts +4 -0
- package/src/components/sound-component.ts +4 -0
- package/src/components/sound-slot.ts +3 -0
- package/src/entity-base.ts +28 -16
- package/src/entity-owner.ts +2 -2
- package/src/entity.ts +16 -4
- package/src/index.ts +12 -1
- package/src/material.ts +19 -8
- package/src/model.ts +14 -2
- package/src/node.ts +15 -3
- package/src/scene.ts +35 -1
- package/src/sky.ts +4 -0
- package/src/wasm.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcanvas/web-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"author": "PlayCanvas <support@playcanvas.com>",
|
|
5
5
|
"homepage": "https://playcanvas.com",
|
|
6
6
|
"description": "Web Components for the PlayCanvas Engine",
|
|
@@ -99,12 +99,12 @@
|
|
|
99
99
|
"eslint": "9.39.5",
|
|
100
100
|
"globals": "17.11.0",
|
|
101
101
|
"jsdom": "30.0.1",
|
|
102
|
-
"mediabunny": "1.55.
|
|
102
|
+
"mediabunny": "1.55.3",
|
|
103
103
|
"opentype.js": "2.0.0",
|
|
104
104
|
"playcanvas": "2.21.4",
|
|
105
105
|
"prettier": "3.9.6",
|
|
106
106
|
"publint": "0.3.24",
|
|
107
|
-
"rollup": "4.
|
|
107
|
+
"rollup": "4.63.0",
|
|
108
108
|
"serve": "14.2.6",
|
|
109
109
|
"tslib": "2.8.1",
|
|
110
110
|
"typedoc": "0.28.20",
|
package/src/app.ts
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
|
|
67
67
|
import type { AssetElement } from './asset';
|
|
68
68
|
import { AsyncElement } from './async-element';
|
|
69
|
+
import { SYNTHESIZED_EVENTS } from './entity-base';
|
|
69
70
|
import type { EntityBaseElement } from './entity-base';
|
|
70
71
|
import type { EntityOwnerElement } from './entity-owner';
|
|
71
72
|
import { LoadingBar } from './loading-bar';
|
|
@@ -73,9 +74,6 @@ import type { MaterialElement } from './material';
|
|
|
73
74
|
import { parseBool, parseEnum, parseNumber } from './parse';
|
|
74
75
|
import type { WasmElement } from './wasm';
|
|
75
76
|
|
|
76
|
-
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
77
|
-
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'] as const;
|
|
78
|
-
|
|
79
77
|
/**
|
|
80
78
|
* The event types whose listeners make an element a hover target. Hover resolution walks past
|
|
81
79
|
* elements listening for none of them, so a silent element never swallows an ancestor's
|
|
@@ -83,6 +81,48 @@ const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointeren
|
|
|
83
81
|
*/
|
|
84
82
|
const hoverEventTypes = ['pointerenter', 'pointerleave', 'pointermove'] as const;
|
|
85
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The canvas listeners each synthesized event type is driven by. Enter and leave are derived
|
|
86
|
+
* from move picks. A click is concluded from the down/up pair, with pointercancel discarding a
|
|
87
|
+
* press the browser takes back (for example a touch that becomes a scroll).
|
|
88
|
+
*/
|
|
89
|
+
const canvasEventsFor: Record<(typeof SYNTHESIZED_EVENTS)[number], readonly string[]> = {
|
|
90
|
+
pointermove: ['pointermove'],
|
|
91
|
+
pointerenter: ['pointermove'],
|
|
92
|
+
pointerleave: ['pointermove'],
|
|
93
|
+
pointerdown: ['pointerdown'],
|
|
94
|
+
pointerup: ['pointerup'],
|
|
95
|
+
click: ['pointerdown', 'pointerup', 'pointercancel']
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* How long after a click a further click on the same target still raises the click count that
|
|
100
|
+
* `detail` carries, approximating the platform's double-click time.
|
|
101
|
+
*/
|
|
102
|
+
const CLICK_CHAIN_MS = 500;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Finds the nearest common inclusive ancestor of two picked nodes - the node a click belongs to
|
|
106
|
+
* when the press and the release picked different geometry, exactly as the DOM assigns a click
|
|
107
|
+
* whose down and up have different targets.
|
|
108
|
+
*
|
|
109
|
+
* @param a - The node the press picked, or `null`.
|
|
110
|
+
* @param b - The node the release picked, or `null`.
|
|
111
|
+
* @returns The nearest common inclusive ancestor, or `null` when there is none.
|
|
112
|
+
*/
|
|
113
|
+
const commonAncestor = (a: GraphNode | null, b: GraphNode | null): GraphNode | null => {
|
|
114
|
+
const ancestors = new Set<GraphNode>();
|
|
115
|
+
for (let node = a; node !== null; node = node.parent) {
|
|
116
|
+
ancestors.add(node);
|
|
117
|
+
}
|
|
118
|
+
for (let node = b; node !== null; node = node.parent) {
|
|
119
|
+
if (ancestors.has(node)) {
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
|
|
86
126
|
/**
|
|
87
127
|
* Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
|
|
88
128
|
* box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
|
|
@@ -112,6 +152,10 @@ const ensureBaseStyles = () => {
|
|
|
112
152
|
* tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
|
|
113
153
|
* its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
|
|
114
154
|
*
|
|
155
|
+
* @elementSummary The `<pc-app>` element creates a PlayCanvas application and the canvas it renders
|
|
156
|
+
* into, and is the root of every scene. It holds the `<pc-asset>`, `<pc-material>`, `<pc-wasm>` and
|
|
157
|
+
* `<pc-scene>` elements, and the page's CSS sizes it, as it would a `<video>`.
|
|
158
|
+
*
|
|
115
159
|
* @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
|
|
116
160
|
* `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
|
|
117
161
|
* Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
|
|
@@ -178,14 +222,6 @@ class AppElement extends AsyncElement {
|
|
|
178
222
|
|
|
179
223
|
private _picker: Picker | null = null;
|
|
180
224
|
|
|
181
|
-
private _hasPointerListeners: Record<string, boolean> = {
|
|
182
|
-
pointerenter: false,
|
|
183
|
-
pointerleave: false,
|
|
184
|
-
pointerdown: false,
|
|
185
|
-
pointerup: false,
|
|
186
|
-
pointermove: false
|
|
187
|
-
};
|
|
188
|
-
|
|
189
225
|
private _hoveredEntity: EntityBaseElement | null = null;
|
|
190
226
|
|
|
191
227
|
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
@@ -194,9 +230,28 @@ class AppElement extends AsyncElement {
|
|
|
194
230
|
private _pointerHandlers: Record<string, EventListener | null> = {
|
|
195
231
|
pointermove: null,
|
|
196
232
|
pointerdown: null,
|
|
197
|
-
pointerup: null
|
|
233
|
+
pointerup: null,
|
|
234
|
+
pointercancel: null
|
|
198
235
|
};
|
|
199
236
|
|
|
237
|
+
/**
|
|
238
|
+
* The pick of each pointer's primary-button press, keyed by pointerId and kept while a click
|
|
239
|
+
* may still conclude it. The promise is stored rather than its result, so a release can
|
|
240
|
+
* await a press pick that has not resolved yet. Entries are removed by the matching
|
|
241
|
+
* pointerup or pointercancel, and only ever stored while some element listens for click -
|
|
242
|
+
* which is also what keeps those two canvas listeners attached.
|
|
243
|
+
*/
|
|
244
|
+
private _downPicks = new Map<number, Promise<GraphNode | null>>();
|
|
245
|
+
|
|
246
|
+
/** Whether any element in the tree listens for click. Maintained by _syncCanvasListeners. */
|
|
247
|
+
private _clickListened = false;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The previous click's target, time and count, for chaining successive clicks into the
|
|
251
|
+
* click count that `detail` carries. `null` until a click has fired.
|
|
252
|
+
*/
|
|
253
|
+
private _lastClick: { element: EntityBaseElement; time: number; count: number } | null = null;
|
|
254
|
+
|
|
200
255
|
private _app: AppBase | null = null;
|
|
201
256
|
|
|
202
257
|
private _loadProgress = 0;
|
|
@@ -237,12 +292,12 @@ class AppElement extends AsyncElement {
|
|
|
237
292
|
constructor() {
|
|
238
293
|
super();
|
|
239
294
|
|
|
240
|
-
// Track
|
|
241
|
-
// Registered once here rather than on every boot - the
|
|
242
|
-
// canvas, and a re-booted element must not stack a second set.
|
|
243
|
-
|
|
244
|
-
this.addEventListener(`${type}:connect`, () => this.
|
|
245
|
-
this.addEventListener(`${type}:disconnect`, () => this.
|
|
295
|
+
// Track listeners for the synthesized events being added to and removed from descendant
|
|
296
|
+
// entities. Registered once here rather than on every boot - the sync no-ops while there
|
|
297
|
+
// is no canvas, and a re-booted element must not stack a second set.
|
|
298
|
+
SYNTHESIZED_EVENTS.forEach((type) => {
|
|
299
|
+
this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
|
|
300
|
+
this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
|
|
246
301
|
});
|
|
247
302
|
}
|
|
248
303
|
|
|
@@ -605,18 +660,14 @@ class AppElement extends AsyncElement {
|
|
|
605
660
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
606
661
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
607
662
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
663
|
+
this._pointerHandlers.pointercancel = (event: Event) => {
|
|
664
|
+
this._downPicks.delete((event as PointerEvent).pointerId);
|
|
665
|
+
};
|
|
608
666
|
|
|
609
|
-
// Attach canvas
|
|
610
|
-
// created from
|
|
667
|
+
// Attach canvas listeners for element listeners registered before this boot (e.g.
|
|
668
|
+
// handlers created from inline attributes when their elements were first upgraded, or
|
|
611
669
|
// listeners carried over from before a re-boot)
|
|
612
|
-
|
|
613
|
-
const anyListeners = Array.from(
|
|
614
|
-
this.querySelectorAll<EntityBaseElement>('pc-entity, pc-model, pc-node')
|
|
615
|
-
).some((entity) => entity._hasListeners(type));
|
|
616
|
-
if (anyListeners) {
|
|
617
|
-
this._onPointerListenerAdded(type);
|
|
618
|
-
}
|
|
619
|
-
});
|
|
670
|
+
this._syncCanvasListeners();
|
|
620
671
|
}
|
|
621
672
|
|
|
622
673
|
private _pickerDestroy() {
|
|
@@ -633,15 +684,12 @@ class AppElement extends AsyncElement {
|
|
|
633
684
|
this._pointerHandlers = {
|
|
634
685
|
pointermove: null,
|
|
635
686
|
pointerdown: null,
|
|
636
|
-
pointerup: null
|
|
637
|
-
|
|
638
|
-
this._hasPointerListeners = {
|
|
639
|
-
pointerenter: false,
|
|
640
|
-
pointerleave: false,
|
|
641
|
-
pointerdown: false,
|
|
642
|
-
pointerup: false,
|
|
643
|
-
pointermove: false
|
|
687
|
+
pointerup: null,
|
|
688
|
+
pointercancel: null
|
|
644
689
|
};
|
|
690
|
+
this._downPicks.clear();
|
|
691
|
+
this._clickListened = false;
|
|
692
|
+
this._lastClick = null;
|
|
645
693
|
}
|
|
646
694
|
|
|
647
695
|
/**
|
|
@@ -867,7 +915,17 @@ class AppElement extends AsyncElement {
|
|
|
867
915
|
private async _onPointerDown(event: PointerEvent) {
|
|
868
916
|
if (!this._picker || !this.app) return;
|
|
869
917
|
|
|
870
|
-
const
|
|
918
|
+
const pick = this._pickNode(event);
|
|
919
|
+
|
|
920
|
+
// A click concludes on the matching pointerup, which needs to know what the press
|
|
921
|
+
// picked. Primary button only - the only button a click can conclude from - and only
|
|
922
|
+
// while click is listened for, since it is the click mapping that keeps the pointerup
|
|
923
|
+
// and pointercancel listeners attached to clean the entry up again.
|
|
924
|
+
if (this._clickListened && event.button === 0) {
|
|
925
|
+
this._downPicks.set(event.pointerId, pick);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const node = await pick;
|
|
871
929
|
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
872
930
|
|
|
873
931
|
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
@@ -879,6 +937,11 @@ class AppElement extends AsyncElement {
|
|
|
879
937
|
private async _onPointerUp(event: PointerEvent) {
|
|
880
938
|
if (!this._picker || !this.app) return;
|
|
881
939
|
|
|
940
|
+
// The press pick this release may conclude as a click. Claimed synchronously, so the
|
|
941
|
+
// entry is gone before any other event for this pointer can be handled.
|
|
942
|
+
const downPick = this._downPicks.get(event.pointerId);
|
|
943
|
+
this._downPicks.delete(event.pointerId);
|
|
944
|
+
|
|
882
945
|
const node = await this._pickNode(event);
|
|
883
946
|
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
884
947
|
|
|
@@ -886,47 +949,63 @@ class AppElement extends AsyncElement {
|
|
|
886
949
|
if (entityElement) {
|
|
887
950
|
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
888
951
|
}
|
|
889
|
-
}
|
|
890
952
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
953
|
+
// A click fires where the DOM fires it: at the nearest common inclusive ancestor of
|
|
954
|
+
// what the press and the release picked, for the primary button only. The press pick
|
|
955
|
+
// may still be in flight - a quick tap resolves in pick order, not event order.
|
|
956
|
+
if (!downPick || event.button !== 0) return;
|
|
957
|
+
const downNode = await downPick;
|
|
958
|
+
if (!this._picker) return;
|
|
959
|
+
|
|
960
|
+
const clickElement = this._elementWithListener(commonAncestor(downNode, node), 'click');
|
|
961
|
+
if (clickElement) {
|
|
962
|
+
const click = new PointerEvent('click', event);
|
|
963
|
+
|
|
964
|
+
// The init above copied pointerup's `detail`, which the Pointer Events spec fixes
|
|
965
|
+
// at 0 - but click is exempt: its detail is the click count, chained here as the
|
|
966
|
+
// platform chains it (same target, within the double-click window). Overridden
|
|
967
|
+
// with defineProperty because an event instance used as an init dict cannot have
|
|
968
|
+
// single fields replaced.
|
|
969
|
+
const time = performance.now();
|
|
970
|
+
const last = this._lastClick;
|
|
971
|
+
const count =
|
|
972
|
+
last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
|
|
973
|
+
this._lastClick = { element: clickElement, time, count };
|
|
974
|
+
Object.defineProperty(click, 'detail', { value: count });
|
|
975
|
+
|
|
976
|
+
clickElement.dispatchEvent(click);
|
|
907
977
|
}
|
|
908
978
|
}
|
|
909
979
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
: this._pointerHandlers[type];
|
|
980
|
+
/**
|
|
981
|
+
* Attaches exactly the canvas listeners the tree's current element listeners need, and
|
|
982
|
+
* detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
|
|
983
|
+
* this element: several synthesized types can need the same canvas listener (enter, leave
|
|
984
|
+
* and move all ride the move pick; click rides the down/up pair), so one type's removal
|
|
985
|
+
* must not detach a listener another type still uses. Re-attaching an attached listener is
|
|
986
|
+
* a no-op by EventTarget semantics, so no attach state is kept.
|
|
987
|
+
*/
|
|
988
|
+
private _syncCanvasListeners() {
|
|
989
|
+
const canvas = this._canvas;
|
|
990
|
+
if (!canvas) return; // not booted yet: _pickerCreate syncs once the handlers exist
|
|
922
991
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
);
|
|
992
|
+
const elements = Array.from(this.querySelectorAll<EntityBaseElement>('pc-entity, pc-model, pc-node'));
|
|
993
|
+
const needed = new Set<string>();
|
|
994
|
+
for (const type of SYNTHESIZED_EVENTS) {
|
|
995
|
+
if (elements.some((element) => element._hasListeners(type))) {
|
|
996
|
+
canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
|
|
928
997
|
}
|
|
929
998
|
}
|
|
999
|
+
this._clickListened = elements.some((element) => element._hasListeners('click'));
|
|
1000
|
+
|
|
1001
|
+
Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
|
|
1002
|
+
if (!handler) return;
|
|
1003
|
+
if (needed.has(canvasType)) {
|
|
1004
|
+
canvas.addEventListener(canvasType, handler);
|
|
1005
|
+
} else {
|
|
1006
|
+
canvas.removeEventListener(canvasType, handler);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
930
1009
|
}
|
|
931
1010
|
|
|
932
1011
|
/**
|
package/src/asset.ts
CHANGED
|
@@ -26,7 +26,8 @@ const renderModes = new Map<'simple' | 'sliced' | 'tiled', number>([
|
|
|
26
26
|
['tiled', SPRITE_RENDERMODE_TILED]
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/** The addressing modes for a texture asset. */
|
|
30
|
+
export type AddressMode = 'repeat' | 'clamp' | 'mirror';
|
|
30
31
|
|
|
31
32
|
const addressModes = new Map<AddressMode, number>([
|
|
32
33
|
['repeat', ADDRESS_REPEAT],
|
|
@@ -34,7 +35,8 @@ const addressModes = new Map<AddressMode, number>([
|
|
|
34
35
|
['mirror', ADDRESS_MIRRORED_REPEAT]
|
|
35
36
|
]);
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
/** The minification filter modes for a texture asset. */
|
|
39
|
+
export type MinFilterMode =
|
|
38
40
|
'nearest' | 'linear' | 'nearest-mip-nearest' | 'linear-mip-nearest' | 'nearest-mip-linear' | 'linear-mip-linear';
|
|
39
41
|
|
|
40
42
|
const minFilterModes = new Map<MinFilterMode, number>([
|
|
@@ -46,8 +48,11 @@ const minFilterModes = new Map<MinFilterMode, number>([
|
|
|
46
48
|
['linear-mip-linear', FILTER_LINEAR_MIPMAP_LINEAR]
|
|
47
49
|
]);
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The magnification filter modes for a texture asset. Magnification has no mip variants - the
|
|
53
|
+
* engine (and the GPU) only accepts these two.
|
|
54
|
+
*/
|
|
55
|
+
export type MagFilterMode = 'nearest' | 'linear';
|
|
51
56
|
|
|
52
57
|
const magFilterModes = new Map<MagFilterMode, number>([
|
|
53
58
|
['nearest', FILTER_NEAREST],
|
|
@@ -168,6 +173,10 @@ const processBufferView = (
|
|
|
168
173
|
* Apart from `lazy` and the texture options, these attributes are read once when the asset is
|
|
169
174
|
* created, so changing them later has no effect.
|
|
170
175
|
*
|
|
176
|
+
* @elementSummary The `<pc-asset>` element declares an asset for the application to load — a model,
|
|
177
|
+
* texture, font, sound, script or JSON file — under an `id` that other elements reference. Must be
|
|
178
|
+
* a direct child of `<pc-app>`.
|
|
179
|
+
*
|
|
171
180
|
* @attribute {string} id - The identifier used to reference the asset from other elements.
|
|
172
181
|
* @attribute {string} src - The URL of the asset to load.
|
|
173
182
|
* @attribute {string} type - The asset type. Inferred from the `src` file extension when omitted.
|
|
@@ -22,7 +22,9 @@ import { AnimComponentElement } from './anim-component';
|
|
|
22
22
|
* named; in a multi-track source the track named `name` is chosen, falling back to the first
|
|
23
23
|
* with a warning. The element becomes ready once its resolved track is assigned.
|
|
24
24
|
*
|
|
25
|
-
* @
|
|
25
|
+
* @elementSummary The `<pc-anim-clip>` element declares one named animation clip on its parent
|
|
26
|
+
* `<pc-anim>`, taken from the `asset` it names or, without one, from the enclosing `<pc-model>`'s
|
|
27
|
+
* own animations. Must be a direct child of `<pc-anim>`.
|
|
26
28
|
*/
|
|
27
29
|
class AnimClipElement extends AsyncElement {
|
|
28
30
|
/**
|
|
@@ -48,6 +48,11 @@ type PlaybackState = {
|
|
|
48
48
|
*
|
|
49
49
|
* Engine component: {@link AnimComponent} (`anim`).
|
|
50
50
|
*
|
|
51
|
+
* @elementSummary The `<pc-anim>` element plays animation clips over its entity's hierarchy, taken
|
|
52
|
+
* from `<pc-anim-clip>` children or from the enclosing `<pc-model>`'s own animations. The first
|
|
53
|
+
* clip plays automatically, and the `clip` attribute switches between them. Must be a child of a
|
|
54
|
+
* `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
55
|
+
*
|
|
51
56
|
* @category Components
|
|
52
57
|
*/
|
|
53
58
|
class AnimComponentElement extends ComponentElement {
|
|
@@ -10,6 +10,10 @@ import { ComponentElement } from './component';
|
|
|
10
10
|
*
|
|
11
11
|
* Engine component: {@link AudioListenerComponent} (`audiolistener`).
|
|
12
12
|
*
|
|
13
|
+
* @elementSummary The `<pc-audio-listener>` element makes its entity the point from which
|
|
14
|
+
* positional sounds are heard, typically the entity holding the `<pc-camera>`. Must be a child of a
|
|
15
|
+
* `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
16
|
+
*
|
|
13
17
|
* @category Components
|
|
14
18
|
*/
|
|
15
19
|
class AudioListenerComponentElement extends ComponentElement {
|
|
@@ -19,6 +19,11 @@ const transitionModes = new Map<'tint' | 'sprite', number>([
|
|
|
19
19
|
*
|
|
20
20
|
* Engine component: {@link ButtonComponent} (`button`).
|
|
21
21
|
*
|
|
22
|
+
* @elementSummary The `<pc-button>` element makes its entity respond to pointer input, tinting or
|
|
23
|
+
* swapping its image as the pointer hovers, presses and releases it. The entity also needs a
|
|
24
|
+
* `<pc-element>` with `use-input` set. Must be a child of a `<pc-entity>`, `<pc-model>` or
|
|
25
|
+
* `<pc-node>`.
|
|
26
|
+
*
|
|
22
27
|
* @category Components
|
|
23
28
|
*/
|
|
24
29
|
class ButtonComponentElement extends ComponentElement {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
TONEMAP_ACES,
|
|
14
14
|
TONEMAP_HEJL,
|
|
15
15
|
TONEMAP_NONE,
|
|
16
|
+
XRTYPE_AR,
|
|
16
17
|
XRTYPE_VR
|
|
17
18
|
} from 'playcanvas';
|
|
18
19
|
|
|
@@ -43,6 +44,10 @@ const tonemaps = new Map<'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2
|
|
|
43
44
|
*
|
|
44
45
|
* Engine component: {@link CameraComponent} (`camera`).
|
|
45
46
|
*
|
|
47
|
+
* @elementSummary The `<pc-camera>` element renders the scene from its entity's transform, with
|
|
48
|
+
* attributes for the projection, field of view, clip planes, clear color and tonemapping. Must be a
|
|
49
|
+
* child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
50
|
+
*
|
|
46
51
|
* @category Components
|
|
47
52
|
*/
|
|
48
53
|
class CameraComponentElement extends ComponentElement {
|
|
@@ -50,9 +55,11 @@ class CameraComponentElement extends ComponentElement {
|
|
|
50
55
|
|
|
51
56
|
private _clearColorBuffer = true;
|
|
52
57
|
|
|
58
|
+
private _clearDepth = 1;
|
|
59
|
+
|
|
53
60
|
private _clearDepthBuffer = true;
|
|
54
61
|
|
|
55
|
-
private _clearStencilBuffer =
|
|
62
|
+
private _clearStencilBuffer = true;
|
|
56
63
|
|
|
57
64
|
private _cullFaces = true;
|
|
58
65
|
|
|
@@ -91,6 +98,7 @@ class CameraComponentElement extends ComponentElement {
|
|
|
91
98
|
return {
|
|
92
99
|
clearColor: this._clearColor,
|
|
93
100
|
clearColorBuffer: this._clearColorBuffer,
|
|
101
|
+
clearDepth: this._clearDepth,
|
|
94
102
|
clearDepthBuffer: this._clearDepthBuffer,
|
|
95
103
|
clearStencilBuffer: this._clearStencilBuffer,
|
|
96
104
|
cullFaces: this._cullFaces,
|
|
@@ -110,9 +118,33 @@ class CameraComponentElement extends ComponentElement {
|
|
|
110
118
|
};
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Whether immersive AR is available. Independent of {@link vrAvailable}: a device can offer
|
|
123
|
+
* either mode without the other.
|
|
124
|
+
* @returns Whether immersive AR is available.
|
|
125
|
+
*/
|
|
126
|
+
get arAvailable(): boolean {
|
|
127
|
+
return this._available(XRTYPE_AR);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether immersive VR is available. Independent of {@link arAvailable}: a device can offer
|
|
132
|
+
* either mode without the other.
|
|
133
|
+
* @returns Whether immersive VR is available.
|
|
134
|
+
*/
|
|
135
|
+
get vrAvailable(): boolean {
|
|
136
|
+
return this._available(XRTYPE_VR);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Whether one XR session type is available on this device.
|
|
141
|
+
*
|
|
142
|
+
* @param type - The XR session type to test.
|
|
143
|
+
* @returns Whether that type is available.
|
|
144
|
+
*/
|
|
145
|
+
private _available(type: string): boolean {
|
|
114
146
|
const xrManager = this.component?.system.app.xr;
|
|
115
|
-
return xrManager
|
|
147
|
+
return Boolean(xrManager?.supported && xrManager.isAvailable(type));
|
|
116
148
|
}
|
|
117
149
|
|
|
118
150
|
/**
|
|
@@ -124,10 +156,12 @@ class CameraComponentElement extends ComponentElement {
|
|
|
124
156
|
type: 'immersive-ar' | 'immersive-vr',
|
|
125
157
|
space: 'bounded-floor' | 'local' | 'local-floor' | 'unbounded' | 'viewer'
|
|
126
158
|
) {
|
|
127
|
-
|
|
159
|
+
// Gated on the mode being started, not on XR in general: a device that offers only
|
|
160
|
+
// one of the two would otherwise accept a session it cannot serve
|
|
161
|
+
if (this.component && this._available(type)) {
|
|
128
162
|
this.component.startXr(type, space, {
|
|
129
163
|
callback: (err: any) => {
|
|
130
|
-
if (err) console.error(`WebXR
|
|
164
|
+
if (err) console.error(`WebXR ${type} failed to start: ${err.message}`);
|
|
131
165
|
}
|
|
132
166
|
});
|
|
133
167
|
}
|
|
@@ -188,6 +222,25 @@ class CameraComponentElement extends ComponentElement {
|
|
|
188
222
|
return this._clearColorBuffer;
|
|
189
223
|
}
|
|
190
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Sets the depth value the depth buffer is cleared to. Defaults to 1.
|
|
227
|
+
* @param value - The clear depth value.
|
|
228
|
+
*/
|
|
229
|
+
set clearDepth(value: number) {
|
|
230
|
+
this._clearDepth = value;
|
|
231
|
+
if (this.component) {
|
|
232
|
+
this.component.clearDepth = value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Gets the depth value the depth buffer is cleared to.
|
|
238
|
+
* @returns The clear depth value.
|
|
239
|
+
*/
|
|
240
|
+
get clearDepth(): number {
|
|
241
|
+
return this._clearDepth;
|
|
242
|
+
}
|
|
243
|
+
|
|
191
244
|
/**
|
|
192
245
|
* Sets the clear depth buffer of the camera.
|
|
193
246
|
* @param value - The clear depth buffer.
|
|
@@ -498,6 +551,7 @@ class CameraComponentElement extends ComponentElement {
|
|
|
498
551
|
...super.observedAttributes,
|
|
499
552
|
'clear-color',
|
|
500
553
|
'clear-color-buffer',
|
|
554
|
+
'clear-depth',
|
|
501
555
|
'clear-depth-buffer',
|
|
502
556
|
'clear-stencil-buffer',
|
|
503
557
|
'cull-faces',
|
|
@@ -527,11 +581,14 @@ class CameraComponentElement extends ComponentElement {
|
|
|
527
581
|
case 'clear-color-buffer':
|
|
528
582
|
this.clearColorBuffer = parseBool(newValue, true);
|
|
529
583
|
break;
|
|
584
|
+
case 'clear-depth':
|
|
585
|
+
this.clearDepth = parseNumber(newValue, 1, name);
|
|
586
|
+
break;
|
|
530
587
|
case 'clear-depth-buffer':
|
|
531
588
|
this.clearDepthBuffer = parseBool(newValue, true);
|
|
532
589
|
break;
|
|
533
590
|
case 'clear-stencil-buffer':
|
|
534
|
-
this.clearStencilBuffer = parseBool(newValue,
|
|
591
|
+
this.clearStencilBuffer = parseBool(newValue, true);
|
|
535
592
|
break;
|
|
536
593
|
case 'cull-faces':
|
|
537
594
|
this.cullFaces = parseBool(newValue, true);
|
|
@@ -19,6 +19,10 @@ import { ComponentElement } from './component';
|
|
|
19
19
|
*
|
|
20
20
|
* Engine component: {@link CollisionComponent} (`collision`).
|
|
21
21
|
*
|
|
22
|
+
* @elementSummary The `<pc-collision>` element gives its entity a collision shape — a box, sphere,
|
|
23
|
+
* capsule, cone, cylinder or mesh — for the physics simulation to collide against. Pair it with a
|
|
24
|
+
* `<pc-rigid-body>`. Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
25
|
+
*
|
|
22
26
|
* @category Components
|
|
23
27
|
*/
|
|
24
28
|
class CollisionComponentElement extends ComponentElement {
|
|
@@ -19,6 +19,10 @@ import { ComponentElement } from './component';
|
|
|
19
19
|
*
|
|
20
20
|
* Engine component: {@link ElementComponent} (`element`).
|
|
21
21
|
*
|
|
22
|
+
* @elementSummary The `<pc-element>` element gives its entity a 2D UI rectangle inside a
|
|
23
|
+
* `<pc-screen>` hierarchy, drawing an image, a line of text or nothing (`type="image"`, `"text"` or
|
|
24
|
+
* `"group"`). Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
25
|
+
*
|
|
22
26
|
* @category Components
|
|
23
27
|
*/
|
|
24
28
|
class ElementComponentElement extends ComponentElement {
|
|
@@ -13,6 +13,10 @@ import { ComponentElement } from './component';
|
|
|
13
13
|
*
|
|
14
14
|
* Engine component: {@link GSplatComponent} (`gsplat`).
|
|
15
15
|
*
|
|
16
|
+
* @elementSummary The `<pc-gsplat>` element renders the 3D Gaussian splats of a `gsplat` asset at
|
|
17
|
+
* its entity, with attributes for shadow casting and level of detail. Must be a child of a
|
|
18
|
+
* `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
19
|
+
*
|
|
16
20
|
* @category Components
|
|
17
21
|
*/
|
|
18
22
|
class GSplatComponentElement extends ComponentElement {
|
|
@@ -5,9 +5,14 @@ import { getEntity, parseBool, parseEnum, parseNumber, parseVec2, parseVec3 } fr
|
|
|
5
5
|
|
|
6
6
|
import { ComponentElement } from './component';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/** The constraint types supported by the `<pc-joint>` element. */
|
|
9
|
+
export type JointType = 'fixed' | 'ball' | 'hinge' | 'slider' | '6dof';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The motion modes for a single joint axis: fully constrained (`locked`), constrained within
|
|
13
|
+
* limits (`limited`) or unconstrained (`free`).
|
|
14
|
+
*/
|
|
15
|
+
export type MotionMode = 'locked' | 'limited' | 'free';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* The JointComponentElement interface provides properties and methods for manipulating
|
|
@@ -22,6 +27,11 @@ type MotionMode = 'locked' | 'limited' | 'free';
|
|
|
22
27
|
* a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
|
|
23
28
|
* space. The underlying engine component is in alpha, so its API may change.
|
|
24
29
|
*
|
|
30
|
+
* @elementSummary The `<pc-joint>` element constrains two rigid bodies to each other — a hinged
|
|
31
|
+
* door, a swinging chain, a sliding drawer. Its entity's transform is the joint frame, and
|
|
32
|
+
* `entity-a` and `entity-b` name the bodies. Must be a child of a `<pc-entity>`, `<pc-model>` or
|
|
33
|
+
* `<pc-node>`.
|
|
34
|
+
*
|
|
25
35
|
* @fires {CustomEvent} break - Fired when the impulse on the joint exceeds `break-impulse` and the
|
|
26
36
|
* constraint breaks. A broken joint no longer constrains its bodies; calling `refreshFrames()` on
|
|
27
37
|
* the underlying component re-attaches it. Bubbles and is composed.
|
|
@@ -12,6 +12,10 @@ import { ComponentElement } from './component';
|
|
|
12
12
|
*
|
|
13
13
|
* Engine component: {@link LayoutChildComponent} (`layoutchild`).
|
|
14
14
|
*
|
|
15
|
+
* @elementSummary The `<pc-layout-child>` element controls how its entity is sized by the
|
|
16
|
+
* `<pc-layout-group>` above it, through minimum and maximum sizes and fit proportions. Must be a
|
|
17
|
+
* child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
|
|
18
|
+
*
|
|
15
19
|
* @category Components
|
|
16
20
|
*/
|
|
17
21
|
class LayoutChildComponentElement extends ComponentElement {
|
|
@@ -34,6 +34,10 @@ const fittings = new Map<'none' | 'stretch' | 'shrink' | 'both', number>([
|
|
|
34
34
|
*
|
|
35
35
|
* Engine component: {@link LayoutGroupComponent} (`layoutgroup`).
|
|
36
36
|
*
|
|
37
|
+
* @elementSummary The `<pc-layout-group>` element arranges its entity's children in a row or
|
|
38
|
+
* column, with spacing, padding, alignment and fitting. Must be a child of a `<pc-entity>`,
|
|
39
|
+
* `<pc-model>` or `<pc-node>`.
|
|
40
|
+
*
|
|
37
41
|
* @category Components
|
|
38
42
|
*/
|
|
39
43
|
class LayoutGroupComponentElement extends ComponentElement {
|