@playcanvas/web-components 0.12.0 → 0.13.1
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 +1 -1
- package/dist/app.d.cts +281 -0
- package/dist/app.d.ts +61 -3
- package/dist/asset.d.cts +224 -0
- package/dist/asset.d.ts +8 -2
- package/dist/async-element.d.cts +103 -0
- package/dist/async-element.d.ts +6 -4
- package/dist/colors.d.cts +1 -0
- package/dist/components/button-component.d.cts +186 -0
- package/dist/components/button-component.d.ts +1 -1
- package/dist/components/camera-component.d.cts +253 -0
- package/dist/components/camera-component.d.ts +1 -1
- package/dist/components/collision-component.d.cts +73 -0
- package/dist/components/collision-component.d.ts +1 -1
- package/dist/components/component.d.cts +82 -0
- package/dist/components/component.d.ts +1 -1
- package/dist/components/element-component.d.cts +316 -0
- package/dist/components/element-component.d.ts +1 -1
- package/dist/components/gsplat-component.d.cts +108 -0
- package/dist/components/gsplat-component.d.ts +1 -1
- package/dist/components/layoutchild-component.d.cts +110 -0
- package/dist/components/layoutchild-component.d.ts +1 -1
- package/dist/components/layoutgroup-component.d.cts +136 -0
- package/dist/components/layoutgroup-component.d.ts +1 -1
- package/dist/components/light-component.d.cts +264 -0
- package/dist/components/light-component.d.ts +1 -1
- package/dist/components/listener-component.d.cts +20 -0
- package/dist/components/listener-component.d.ts +1 -1
- package/dist/components/particlesystem-component.d.cts +52 -0
- package/dist/components/particlesystem-component.d.ts +1 -1
- package/dist/components/render-component.d.cts +76 -0
- package/dist/components/render-component.d.ts +1 -1
- package/dist/components/rigidbody-component.d.cts +88 -0
- package/dist/components/rigidbody-component.d.ts +1 -1
- package/dist/components/screen-component.d.cts +70 -0
- package/dist/components/screen-component.d.ts +1 -1
- package/dist/components/script-component.d.cts +163 -0
- package/dist/components/script-component.d.ts +1 -1
- package/dist/components/script.d.cts +94 -0
- package/dist/components/script.d.ts +1 -1
- package/dist/components/scrollbar-component.d.cts +69 -0
- package/dist/components/scrollbar-component.d.ts +1 -1
- package/dist/components/scrollview-component.d.cts +178 -0
- package/dist/components/scrollview-component.d.ts +1 -1
- package/dist/components/sound-component.d.cts +108 -0
- package/dist/components/sound-component.d.ts +1 -1
- package/dist/components/sound-slot.d.cts +134 -0
- package/dist/components/sound-slot.d.ts +2 -2
- package/dist/custom-elements.json +80 -22
- package/dist/entity-base.d.cts +67 -0
- package/dist/entity-base.d.ts +2 -2
- package/dist/entity.d.cts +131 -0
- package/dist/entity.d.ts +1 -1
- package/dist/index.d.cts +79 -0
- package/dist/index.d.ts +33 -33
- package/dist/loading-bar.d.cts +35 -0
- package/dist/material.d.cts +1011 -0
- package/dist/model.d.cts +72 -0
- package/dist/model.d.ts +1 -1
- package/dist/module.d.cts +29 -0
- package/dist/module.d.ts +16 -10
- package/dist/node.d.cts +253 -0
- package/dist/node.d.ts +1 -1
- package/dist/parse.d.cts +147 -0
- package/dist/pwc.cjs +285 -92
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +286 -93
- 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 +286 -93
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.cts +117 -0
- package/dist/scene.d.ts +1 -1
- package/dist/sky.d.cts +121 -0
- package/dist/sky.d.ts +1 -1
- package/dist/vscode.html-custom-data.json +3 -3
- package/dist/web-types.json +42 -6
- package/package.json +16 -7
- package/src/app.ts +211 -49
- package/src/asset.ts +34 -2
- package/src/async-element.ts +4 -2
- package/src/components/button-component.ts +7 -7
- package/src/components/element-component.ts +7 -7
- package/src/components/gsplat-component.ts +3 -3
- package/src/components/particlesystem-component.ts +7 -8
- package/src/components/script-component.ts +2 -2
- package/src/components/sound-slot.ts +2 -2
- package/src/loading-bar.ts +2 -2
- package/src/material.ts +2 -2
- package/src/model.ts +2 -5
- package/src/module.ts +39 -20
- package/src/sky.ts +2 -3
package/dist/pwc.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('playcanvas')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'playcanvas'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pwc = {}, global.pc));
|
|
5
5
|
})(this, (function (exports, playcanvas) { 'use strict';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -112,12 +112,18 @@
|
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
114
|
* The ModuleElement interface provides properties and methods for manipulating
|
|
115
|
-
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`}
|
|
116
|
-
* The ModuleElement interface also inherits the properties and methods of the
|
|
117
|
-
* {@link
|
|
115
|
+
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`}
|
|
116
|
+
* elements. The ModuleElement interface also inherits the properties and methods of the
|
|
117
|
+
* {@link AsyncElement} interface.
|
|
118
|
+
*
|
|
119
|
+
* The attributes are read once, when the module starts loading - on the element's first
|
|
120
|
+
* connection, or earlier if a containing `<pc-app>` boots first and collects it - so changing
|
|
121
|
+
* them later has no effect. The element becomes ready once the module has loaded. WebAssembly
|
|
122
|
+
* modules configure engine-global state that never unloads, so readiness is not re-armed by
|
|
123
|
+
* removing the element, and a re-inserted element does not load again.
|
|
118
124
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
125
|
+
* A `<pc-module>` without a `name` warns and never becomes ready; a containing `<pc-app>` still
|
|
126
|
+
* boots.
|
|
121
127
|
*
|
|
122
128
|
* @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
|
|
123
129
|
* `Ammo`.
|
|
@@ -126,19 +132,22 @@
|
|
|
126
132
|
* @attribute {string} fallback - The URL of the module's asm.js fallback script, used when
|
|
127
133
|
* WebAssembly is unavailable.
|
|
128
134
|
*/
|
|
129
|
-
class ModuleElement extends
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
super();
|
|
134
|
-
this.loadPromise = this.loadModule();
|
|
135
|
+
class ModuleElement extends AsyncElement {
|
|
136
|
+
_loadPromise = null;
|
|
137
|
+
connectedCallback() {
|
|
138
|
+
this._getLoadPromise();
|
|
135
139
|
}
|
|
136
|
-
async
|
|
140
|
+
async _loadModule() {
|
|
137
141
|
const name = this.getAttribute('name');
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
if (!name) {
|
|
143
|
+
console.warn("pc-module requires a 'name' attribute - no module was configured");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const config = {
|
|
147
|
+
glueUrl: this.getAttribute('glue') ?? undefined,
|
|
148
|
+
wasmUrl: this.getAttribute('wasm') ?? undefined,
|
|
149
|
+
fallbackUrl: this.getAttribute('fallback') ?? undefined
|
|
150
|
+
};
|
|
142
151
|
if (name === 'Basis') {
|
|
143
152
|
playcanvas.basisInitialize(config);
|
|
144
153
|
}
|
|
@@ -148,16 +157,22 @@
|
|
|
148
157
|
playcanvas.WasmModule.getInstance(name, () => resolve());
|
|
149
158
|
});
|
|
150
159
|
}
|
|
160
|
+
this._onReady();
|
|
151
161
|
}
|
|
152
162
|
/**
|
|
153
|
-
* Returns the promise that settles when the module has loaded
|
|
154
|
-
* `<pc-app>`
|
|
163
|
+
* Returns the promise that settles when the module has loaded, starting the load if it has
|
|
164
|
+
* not already started - a containing `<pc-app>` boots in document order, so it may collect
|
|
165
|
+
* this element before the element's own connectedCallback has run. A missing `name` resolves
|
|
166
|
+
* the promise without configuring anything, so a misconfigured module never blocks the app.
|
|
155
167
|
*
|
|
156
168
|
* @returns The load promise.
|
|
157
169
|
* @internal
|
|
158
170
|
*/
|
|
159
171
|
_getLoadPromise() {
|
|
160
|
-
|
|
172
|
+
if (!this._loadPromise) {
|
|
173
|
+
this._loadPromise = this._loadModule();
|
|
174
|
+
}
|
|
175
|
+
return this._loadPromise;
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
customElements.define('pc-module', ModuleElement);
|
|
@@ -187,9 +202,9 @@
|
|
|
187
202
|
this._track.setAttribute('aria-label', 'Loading');
|
|
188
203
|
this._track.setAttribute('aria-valuemin', '0');
|
|
189
204
|
this._track.setAttribute('aria-valuemax', '100');
|
|
190
|
-
//
|
|
205
|
+
// Anchored to the pc-app element, which the library's base styles make a positioned box
|
|
191
206
|
this._track.style.cssText = [
|
|
192
|
-
'position:
|
|
207
|
+
'position: absolute',
|
|
193
208
|
'top: 0',
|
|
194
209
|
'left: 0',
|
|
195
210
|
'width: 100%',
|
|
@@ -701,16 +716,44 @@
|
|
|
701
716
|
|
|
702
717
|
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
703
718
|
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
|
|
719
|
+
/**
|
|
720
|
+
* Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
|
|
721
|
+
* box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
|
|
722
|
+
* canvas and loading bar anchored to it. `:where()` keeps every declaration at zero specificity,
|
|
723
|
+
* so any page rule - however plain - overrides these defaults.
|
|
724
|
+
*/
|
|
725
|
+
const ensureBaseStyles = () => {
|
|
726
|
+
const id = 'pc-app-styles';
|
|
727
|
+
if (document.getElementById(id)) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const style = document.createElement('style');
|
|
731
|
+
style.id = id;
|
|
732
|
+
style.textContent = ':where(pc-app) { display: block; position: relative; width: 300px; height: 150px; }';
|
|
733
|
+
document.head.appendChild(style);
|
|
734
|
+
};
|
|
704
735
|
/**
|
|
705
736
|
* The AppElement interface provides properties and methods for manipulating
|
|
706
737
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
707
738
|
* The AppElement interface also inherits the properties and methods of the
|
|
708
739
|
* {@link HTMLElement} interface.
|
|
709
740
|
*
|
|
741
|
+
* The element is sized like a replaced element such as `<video>`: a block-level box that the
|
|
742
|
+
* page's CSS controls, 300x150 by default. The application's canvas always fills the element,
|
|
743
|
+
* and the drawing buffer resolution follows the element's size (capped by `max-pixel-ratio`),
|
|
744
|
+
* tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
|
|
745
|
+
* its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
|
|
746
|
+
*
|
|
710
747
|
* @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
|
|
711
748
|
* `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
|
|
712
749
|
* Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
|
|
713
750
|
* not bubble.
|
|
751
|
+
*
|
|
752
|
+
* @fires {ErrorEvent} error - Fired when the application cannot boot because no graphics device
|
|
753
|
+
* could be created (for example, a browser with WebGL disabled). `message` names the requested
|
|
754
|
+
* backends and `error` holds the underlying failure. The element never becomes ready
|
|
755
|
+
* and `app` stays `null` — listen for this event to show a fallback UI. Removing the element and
|
|
756
|
+
* re-inserting it retries the boot with its current attributes. Does not bubble.
|
|
714
757
|
*/
|
|
715
758
|
class AppElement extends AsyncElement {
|
|
716
759
|
/**
|
|
@@ -770,6 +813,12 @@
|
|
|
770
813
|
};
|
|
771
814
|
_app = null;
|
|
772
815
|
_loadProgress = 0;
|
|
816
|
+
/**
|
|
817
|
+
* Tracks the element's box so the drawing buffer and picker follow it. Created per boot once
|
|
818
|
+
* the application exists, and disconnected on teardown. `null` where ResizeObserver is
|
|
819
|
+
* unavailable (jsdom), where the boot-time resolution set is the only sizing that happens.
|
|
820
|
+
*/
|
|
821
|
+
_resizeObserver = null;
|
|
773
822
|
/**
|
|
774
823
|
* The PlayCanvas application instance. `null` until the element is ready, and again once it
|
|
775
824
|
* has been removed from the document — await {@link whenReady} or the element's `ready()`
|
|
@@ -796,8 +845,6 @@
|
|
|
796
845
|
*/
|
|
797
846
|
constructor() {
|
|
798
847
|
super();
|
|
799
|
-
// Bind methods to maintain 'this' context
|
|
800
|
-
this._onWindowResize = this._onWindowResize.bind(this);
|
|
801
848
|
// Track pointer listeners being added to and removed from descendant entities.
|
|
802
849
|
// Registered once here rather than on every boot - the handlers no-op while there is no
|
|
803
850
|
// canvas, and a re-booted element must not stack a second set.
|
|
@@ -808,6 +855,9 @@
|
|
|
808
855
|
}
|
|
809
856
|
async connectedCallback() {
|
|
810
857
|
const generation = ++this._bootGeneration;
|
|
858
|
+
// Installed before the loading bar is created: the bar anchors to this element, which
|
|
859
|
+
// these styles make a positioned block box
|
|
860
|
+
ensureBaseStyles();
|
|
811
861
|
// Created before the first await, so the bar is visible while modules and the graphics
|
|
812
862
|
// device are created, and exists before any disconnect could need to clean it up
|
|
813
863
|
if (this._loadingBar && !this._bar) {
|
|
@@ -822,8 +872,11 @@
|
|
|
822
872
|
if (generation !== this._bootGeneration) {
|
|
823
873
|
return;
|
|
824
874
|
}
|
|
825
|
-
// Create and append the canvas
|
|
875
|
+
// Create and append the canvas, filling the element's content box - the page sizes the
|
|
876
|
+
// element, and everything else follows. touch-action: none keeps touch drags driving the
|
|
877
|
+
// engine's input handlers instead of scrolling the page.
|
|
826
878
|
this._canvas = document.createElement('canvas');
|
|
879
|
+
this._canvas.style.cssText = 'display: block; width: 100%; height: 100%; touch-action: none;';
|
|
827
880
|
this.appendChild(this._canvas);
|
|
828
881
|
// Configure device types based on backend selection
|
|
829
882
|
const backendToDeviceTypes = {
|
|
@@ -833,14 +886,44 @@
|
|
|
833
886
|
};
|
|
834
887
|
const deviceTypes = backendToDeviceTypes[this._backend] || [];
|
|
835
888
|
this._optionsLocked = true;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
889
|
+
// createGraphicsDevice appends its final null-device fallback to the array in place, so
|
|
890
|
+
// the requested list is captured now for the failure message.
|
|
891
|
+
const requested = deviceTypes.join(', ');
|
|
892
|
+
let device;
|
|
893
|
+
try {
|
|
894
|
+
device = await playcanvas.createGraphicsDevice(this._canvas, {
|
|
895
|
+
// @ts-ignore - alpha needs to be documented
|
|
896
|
+
alpha: this._alpha,
|
|
897
|
+
antialias: this._antialias,
|
|
898
|
+
depth: this._depthBuffer,
|
|
899
|
+
deviceTypes: deviceTypes,
|
|
900
|
+
stencil: this._stencilBuffer
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
// The element may have been removed while device creation was failing. The teardown
|
|
905
|
+
// has already cleaned up, and the failure belongs to a boot that no longer owns the
|
|
906
|
+
// element.
|
|
907
|
+
if (generation !== this._bootGeneration) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
// Return the element to its pre-boot state - no dead canvas, no loading bar stuck at
|
|
911
|
+
// zero - before announcing the failure. Readiness deliberately stays pending: nothing
|
|
912
|
+
// it would announce (the app, the entity hierarchy) exists, so a device-less element
|
|
913
|
+
// joins the documented never-ready cases and the failure surfaces through the error
|
|
914
|
+
// event instead.
|
|
915
|
+
if (this._canvas && this.contains(this._canvas)) {
|
|
916
|
+
this.removeChild(this._canvas);
|
|
917
|
+
}
|
|
918
|
+
this._canvas = null;
|
|
919
|
+
this._bar?.destroy();
|
|
920
|
+
this._bar = null;
|
|
921
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
922
|
+
const message = `pc-app failed to create a graphics device (${requested}) - ${reason}`;
|
|
923
|
+
console.error(message, error);
|
|
924
|
+
this.dispatchEvent(new ErrorEvent('error', { message, error }));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
844
927
|
// The element may have been removed while the device was created. disconnectedCallback
|
|
845
928
|
// has already cleaned up the canvas; the device was created inside the await, so it is
|
|
846
929
|
// this boot's to release.
|
|
@@ -919,9 +1002,19 @@
|
|
|
919
1002
|
const app = new playcanvas.AppBase(this._canvas);
|
|
920
1003
|
this._app = app;
|
|
921
1004
|
app.init(createOptions);
|
|
922
|
-
|
|
1005
|
+
// FILLMODE_NONE leaves the canvas's CSS sizing alone (the engine's other fill modes
|
|
1006
|
+
// stamp window-derived pixel sizes onto it); RESOLUTION_AUTO sizes the drawing buffer
|
|
1007
|
+
// from the canvas's client size
|
|
1008
|
+
app.setCanvasFillMode(playcanvas.FILLMODE_NONE);
|
|
923
1009
|
app.setCanvasResolution(playcanvas.RESOLUTION_AUTO);
|
|
924
1010
|
this._pickerCreate();
|
|
1011
|
+
// Track the element's box rather than the window: containers resize without any window
|
|
1012
|
+
// event (splitter drags, flex reflow, animations). Guarded because jsdom has no
|
|
1013
|
+
// ResizeObserver - there, the resolution set above is the only sizing that happens.
|
|
1014
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
1015
|
+
this._resizeObserver = new ResizeObserver(() => this._syncCanvasSize());
|
|
1016
|
+
this._resizeObserver.observe(this);
|
|
1017
|
+
}
|
|
925
1018
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
926
1019
|
const assetElements = this.querySelectorAll(':scope > pc-asset');
|
|
927
1020
|
for (const assetElement of Array.from(assetElements)) {
|
|
@@ -997,8 +1090,6 @@
|
|
|
997
1090
|
// Dismiss the bar only once a frame has actually rendered; ready fires before the
|
|
998
1091
|
// first rAF tick
|
|
999
1092
|
app.once('frameend', () => this._bar?.complete());
|
|
1000
|
-
// Handle window resize to keep the canvas responsive
|
|
1001
|
-
window.addEventListener('resize', this._onWindowResize);
|
|
1002
1093
|
this._onReady();
|
|
1003
1094
|
});
|
|
1004
1095
|
}
|
|
@@ -1023,18 +1114,27 @@
|
|
|
1023
1114
|
// no longer holds.
|
|
1024
1115
|
this._hierarchyReady = false;
|
|
1025
1116
|
this._resetReady();
|
|
1026
|
-
//
|
|
1027
|
-
|
|
1117
|
+
// Stop tracking the element's size
|
|
1118
|
+
this._resizeObserver?.disconnect();
|
|
1119
|
+
this._resizeObserver = null;
|
|
1028
1120
|
// Remove the canvas
|
|
1029
1121
|
if (this._canvas && this.contains(this._canvas)) {
|
|
1030
1122
|
this.removeChild(this._canvas);
|
|
1031
1123
|
this._canvas = null;
|
|
1032
1124
|
}
|
|
1033
1125
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1126
|
+
/**
|
|
1127
|
+
* Syncs the drawing buffer and the picker to the canvas's current CSS size. The picker must
|
|
1128
|
+
* track the buffer, or picks would land at stale coordinates after a resize. Skipped while
|
|
1129
|
+
* an XR session presents - the session owns the buffer size.
|
|
1130
|
+
*/
|
|
1131
|
+
_syncCanvasSize() {
|
|
1132
|
+
if (!this.app || this.app.xr?.active) {
|
|
1133
|
+
return;
|
|
1037
1134
|
}
|
|
1135
|
+
this.app.updateCanvasSize();
|
|
1136
|
+
const { width, height } = this.app.graphicsDevice;
|
|
1137
|
+
this._picker?.resize(width, height);
|
|
1038
1138
|
}
|
|
1039
1139
|
_pickerCreate() {
|
|
1040
1140
|
const { width, height } = this.app.graphicsDevice;
|
|
@@ -1152,21 +1252,63 @@
|
|
|
1152
1252
|
}
|
|
1153
1253
|
return null;
|
|
1154
1254
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1255
|
+
/**
|
|
1256
|
+
* Converts a pointer event's client coordinates into drawing-buffer coordinates - the space
|
|
1257
|
+
* the pick buffer and the camera viewports are laid out in. When the canvas has no CSS box
|
|
1258
|
+
* to map through (jsdom; a hidden canvas receives no pointer events in a browser), the
|
|
1259
|
+
* client coordinates are passed through unmapped and `mapped` is false, so callers know the
|
|
1260
|
+
* coordinates correspond to no real geometry.
|
|
1261
|
+
*
|
|
1262
|
+
* @param event - The pointer event to convert.
|
|
1263
|
+
* @param canvas - The canvas the event was dispatched on.
|
|
1264
|
+
* @returns The buffer-space coordinates, and whether they were actually mapped.
|
|
1265
|
+
*/
|
|
1266
|
+
_getPickerCoordinates(event, canvas) {
|
|
1267
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
1268
|
+
if (canvasRect.width === 0 || canvasRect.height === 0) {
|
|
1269
|
+
return { x: event.clientX, y: event.clientY, mapped: false };
|
|
1270
|
+
}
|
|
1271
|
+
const scaleX = canvas.width / canvasRect.width;
|
|
1272
|
+
const scaleY = canvas.height / canvasRect.height;
|
|
1273
|
+
return {
|
|
1274
|
+
x: (event.clientX - canvasRect.left) * scaleX,
|
|
1275
|
+
y: (event.clientY - canvasRect.top) * scaleY,
|
|
1276
|
+
mapped: true
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Whether a camera's viewport contains the point. A camera renders into its normalized
|
|
1281
|
+
* `rect`, whose origin is the bottom-left of the canvas while buffer coordinates run from
|
|
1282
|
+
* the top-left - so the vertical test flips, as the engine's ElementInput flips it for UI
|
|
1283
|
+
* input. The right and bottom edges are exclusive: a viewport rasterizes the half-open
|
|
1284
|
+
* pixel range [left, right) x [top, bottom), so a coordinate on a shared edge belongs to
|
|
1285
|
+
* the viewport whose first pixel it is - never to the one it just left, whose pick buffer
|
|
1286
|
+
* holds nothing there.
|
|
1287
|
+
*
|
|
1288
|
+
* @param camera - The camera to test.
|
|
1289
|
+
* @param x - The x coordinate, in buffer space.
|
|
1290
|
+
* @param y - The y coordinate, in buffer space.
|
|
1291
|
+
* @param canvas - The canvas the coordinates are relative to.
|
|
1292
|
+
* @returns Whether the camera's viewport contains the point.
|
|
1293
|
+
*/
|
|
1294
|
+
_cameraContains(camera, x, y, canvas) {
|
|
1295
|
+
const rect = camera.rect;
|
|
1296
|
+
const left = rect.x * canvas.width;
|
|
1297
|
+
const bottom = (1 - rect.y) * canvas.height;
|
|
1298
|
+
const top = bottom - rect.w * canvas.height;
|
|
1299
|
+
return x >= left && x < left + rect.z * canvas.width && y >= top && y < bottom;
|
|
1166
1300
|
}
|
|
1167
1301
|
/**
|
|
1168
1302
|
* Picks the scene under the pointer and returns the graph node that was hit, or `null`.
|
|
1169
1303
|
*
|
|
1304
|
+
* The camera is resolved the way the engine's ElementInput resolves it for UI input:
|
|
1305
|
+
* enabled cameras are tried topmost-first (they render in ascending `priority` order),
|
|
1306
|
+
* skipping cameras that render to a texture and cameras whose viewport `rect` does not
|
|
1307
|
+
* contain the pointer. A camera that picks nothing ends the search if it clears the color
|
|
1308
|
+
* buffer - its background visually owns the pixel - and otherwise cedes to the cameras
|
|
1309
|
+
* beneath it, so an overlay camera only intercepts picks where it actually drew something.
|
|
1310
|
+
* The pick buffer is prepared per camera, so each camera picks from its own layers.
|
|
1311
|
+
*
|
|
1170
1312
|
* The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
|
|
1171
1313
|
* supported on WebGPU, where it returns an empty selection rather than failing - which
|
|
1172
1314
|
* silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
|
|
@@ -1176,16 +1318,40 @@
|
|
|
1176
1318
|
* @returns The graph node under the pointer, or `null` if nothing was hit.
|
|
1177
1319
|
*/
|
|
1178
1320
|
async _pickNode(event) {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
this._picker.prepare(camera, this.app.scene);
|
|
1184
|
-
const selection = await this._picker.getSelectionAsync(x, y);
|
|
1185
|
-
if (selection.length === 0)
|
|
1321
|
+
const app = this.app;
|
|
1322
|
+
const picker = this._picker;
|
|
1323
|
+
const canvas = this._canvas;
|
|
1324
|
+
if (!app || !picker || !canvas)
|
|
1186
1325
|
return null;
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1326
|
+
const { x, y, mapped } = this._getPickerCoordinates(event, canvas);
|
|
1327
|
+
// Walked from the end: the array is sorted by ascending priority, so the last camera
|
|
1328
|
+
// renders last and sits on top. Read through .at() because a pick handler may remove
|
|
1329
|
+
// cameras while an earlier iteration's read back is in flight.
|
|
1330
|
+
const cameras = app.systems.camera?.cameras ?? [];
|
|
1331
|
+
for (let i = cameras.length - 1; i >= 0; i--) {
|
|
1332
|
+
const camera = cameras.at(i);
|
|
1333
|
+
// A camera rendering to a texture is not on the canvas.
|
|
1334
|
+
if (!camera || camera.renderTarget)
|
|
1335
|
+
continue;
|
|
1336
|
+
// Coordinates that could not be mapped cannot be tested for containment.
|
|
1337
|
+
if (mapped && !this._cameraContains(camera, x, y, canvas))
|
|
1338
|
+
continue;
|
|
1339
|
+
picker.prepare(camera, app.scene);
|
|
1340
|
+
const selection = await picker.getSelectionAsync(x, y);
|
|
1341
|
+
// The element may have disconnected while the read back was in flight.
|
|
1342
|
+
if (!this._picker || !this.app)
|
|
1343
|
+
return null;
|
|
1344
|
+
if (selection.length > 0) {
|
|
1345
|
+
const item = selection[0];
|
|
1346
|
+
return item instanceof playcanvas.MeshInstance ? item.node : item.entity;
|
|
1347
|
+
}
|
|
1348
|
+
// Nothing hit. A camera that clears the color buffer paints its background over
|
|
1349
|
+
// everything beneath it, so the miss is final; one that does not is an overlay
|
|
1350
|
+
// that the cameras beneath show through, so they get their turn.
|
|
1351
|
+
if (camera.clearColorBuffer)
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
return null;
|
|
1189
1355
|
}
|
|
1190
1356
|
async _onPointerMove(event) {
|
|
1191
1357
|
if (!this._picker || !this.app)
|
|
@@ -1372,7 +1538,7 @@
|
|
|
1372
1538
|
this._maxPixelRatio = value;
|
|
1373
1539
|
if (this.app) {
|
|
1374
1540
|
this.app.graphicsDevice.maxPixelRatio = value;
|
|
1375
|
-
this.
|
|
1541
|
+
this._syncCanvasSize();
|
|
1376
1542
|
}
|
|
1377
1543
|
}
|
|
1378
1544
|
/**
|
|
@@ -2129,6 +2295,10 @@
|
|
|
2129
2295
|
* immediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed
|
|
2130
2296
|
* elsewhere, or with an unsupported asset type, never become ready.
|
|
2131
2297
|
*
|
|
2298
|
+
* A `lazy` asset loads on first use: the first time any element resolves it by `id` — a model,
|
|
2299
|
+
* a material map, a sky, a script `asset:` reference — or when the `lazy` attribute is removed,
|
|
2300
|
+
* whichever comes first. Until then it stays registered and unloaded.
|
|
2301
|
+
*
|
|
2132
2302
|
* For `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,
|
|
2133
2303
|
* `min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is
|
|
2134
2304
|
* created and — like `lazy` — are observed: changing one updates a texture that has already
|
|
@@ -2328,7 +2498,9 @@
|
|
|
2328
2498
|
if (type === 'sprite') {
|
|
2329
2499
|
data = data ?? {};
|
|
2330
2500
|
// Resolve the referenced texture atlas to its (numeric) asset id. The atlas must be
|
|
2331
|
-
// declared before the sprite so its asset already exists in the registry.
|
|
2501
|
+
// declared before the sprite so its asset already exists in the registry. Resolved
|
|
2502
|
+
// with get, not useAsset: creation-time wiring is not a use, and the engine's
|
|
2503
|
+
// sprite handler loads the atlas when the sprite itself loads.
|
|
2332
2504
|
const atlas = this.getAttribute('atlas') ?? data.textureAtlasAsset;
|
|
2333
2505
|
if (typeof atlas === 'string') {
|
|
2334
2506
|
const atlasAsset = AssetElement.get(atlas);
|
|
@@ -2489,13 +2661,18 @@
|
|
|
2489
2661
|
return this._flipY;
|
|
2490
2662
|
}
|
|
2491
2663
|
/**
|
|
2492
|
-
* Sets whether the asset should be loaded lazily.
|
|
2664
|
+
* Sets whether the asset should be loaded lazily. A lazy asset is registered without being
|
|
2665
|
+
* loaded; it loads on first use - the first time any element resolves it by `id` - or when
|
|
2666
|
+
* this flag is cleared on a registered asset, whichever comes first.
|
|
2493
2667
|
* @param value - The lazy loading flag.
|
|
2494
2668
|
*/
|
|
2495
2669
|
set lazy(value) {
|
|
2496
2670
|
this._lazy = value;
|
|
2497
2671
|
if (this.asset) {
|
|
2498
2672
|
this.asset.preload = !value;
|
|
2673
|
+
if (!value) {
|
|
2674
|
+
this.asset.registry?.load(this.asset);
|
|
2675
|
+
}
|
|
2499
2676
|
}
|
|
2500
2677
|
}
|
|
2501
2678
|
/**
|
|
@@ -2679,6 +2856,26 @@
|
|
|
2679
2856
|
}
|
|
2680
2857
|
}
|
|
2681
2858
|
customElements.define('pc-asset', AssetElement);
|
|
2859
|
+
/**
|
|
2860
|
+
* Resolves an asset reference for use: {@link AssetElement.get}, plus starting the load of a
|
|
2861
|
+
* registered asset that has not begun one - a `lazy` asset. Every element that consumes assets
|
|
2862
|
+
* resolves its references here, which is what makes `lazy` mean load on first use without any
|
|
2863
|
+
* consumer having to remember the load. The load is asynchronous - callers observe the asset's
|
|
2864
|
+
* `load` event for the resource.
|
|
2865
|
+
*
|
|
2866
|
+
* @param id - The `id` of the `<pc-asset>` element.
|
|
2867
|
+
* @returns The asset, or `undefined`.
|
|
2868
|
+
* @internal
|
|
2869
|
+
*/
|
|
2870
|
+
const useAsset = (id) => {
|
|
2871
|
+
const asset = AssetElement.get(id);
|
|
2872
|
+
// load() ignores an asset that is already loaded or loading, so repeated resolution
|
|
2873
|
+
// costs nothing.
|
|
2874
|
+
if (asset) {
|
|
2875
|
+
asset.registry?.load(asset);
|
|
2876
|
+
}
|
|
2877
|
+
return asset;
|
|
2878
|
+
};
|
|
2682
2879
|
|
|
2683
2880
|
/**
|
|
2684
2881
|
* Represents a component in the PlayCanvas engine.
|
|
@@ -2962,15 +3159,15 @@
|
|
|
2962
3159
|
if (imageEntity) {
|
|
2963
3160
|
data.imageEntity = imageEntity;
|
|
2964
3161
|
}
|
|
2965
|
-
const hoverSpriteAsset =
|
|
3162
|
+
const hoverSpriteAsset = useAsset(this._hoverSpriteAsset);
|
|
2966
3163
|
if (hoverSpriteAsset) {
|
|
2967
3164
|
data.hoverSpriteAsset = hoverSpriteAsset.id;
|
|
2968
3165
|
}
|
|
2969
|
-
const pressedSpriteAsset =
|
|
3166
|
+
const pressedSpriteAsset = useAsset(this._pressedSpriteAsset);
|
|
2970
3167
|
if (pressedSpriteAsset) {
|
|
2971
3168
|
data.pressedSpriteAsset = pressedSpriteAsset.id;
|
|
2972
3169
|
}
|
|
2973
|
-
const inactiveSpriteAsset =
|
|
3170
|
+
const inactiveSpriteAsset = useAsset(this._inactiveSpriteAsset);
|
|
2974
3171
|
if (inactiveSpriteAsset) {
|
|
2975
3172
|
data.inactiveSpriteAsset = inactiveSpriteAsset.id;
|
|
2976
3173
|
}
|
|
@@ -3132,7 +3329,7 @@
|
|
|
3132
3329
|
*/
|
|
3133
3330
|
set hoverSpriteAsset(value) {
|
|
3134
3331
|
this._hoverSpriteAsset = value;
|
|
3135
|
-
const asset =
|
|
3332
|
+
const asset = useAsset(value);
|
|
3136
3333
|
if (this.component && asset) {
|
|
3137
3334
|
this.component.hoverSpriteAsset = asset.id;
|
|
3138
3335
|
}
|
|
@@ -3168,7 +3365,7 @@
|
|
|
3168
3365
|
*/
|
|
3169
3366
|
set pressedSpriteAsset(value) {
|
|
3170
3367
|
this._pressedSpriteAsset = value;
|
|
3171
|
-
const asset =
|
|
3368
|
+
const asset = useAsset(value);
|
|
3172
3369
|
if (this.component && asset) {
|
|
3173
3370
|
this.component.pressedSpriteAsset = asset.id;
|
|
3174
3371
|
}
|
|
@@ -3204,7 +3401,7 @@
|
|
|
3204
3401
|
*/
|
|
3205
3402
|
set inactiveSpriteAsset(value) {
|
|
3206
3403
|
this._inactiveSpriteAsset = value;
|
|
3207
|
-
const asset =
|
|
3404
|
+
const asset = useAsset(value);
|
|
3208
3405
|
if (this.component && asset) {
|
|
3209
3406
|
this.component.inactiveSpriteAsset = asset.id;
|
|
3210
3407
|
}
|
|
@@ -4062,15 +4259,15 @@
|
|
|
4062
4259
|
};
|
|
4063
4260
|
// Asset references are resolved from `<pc-asset>` element ids to engine asset ids. They are
|
|
4064
4261
|
// only included when they resolve, so image/group elements (with no font) don't error.
|
|
4065
|
-
const fontAsset =
|
|
4262
|
+
const fontAsset = useAsset(this._fontAsset);
|
|
4066
4263
|
if (fontAsset) {
|
|
4067
4264
|
data.fontAsset = fontAsset.id;
|
|
4068
4265
|
}
|
|
4069
|
-
const spriteAsset =
|
|
4266
|
+
const spriteAsset = useAsset(this._spriteAsset);
|
|
4070
4267
|
if (spriteAsset) {
|
|
4071
4268
|
data.spriteAsset = spriteAsset.id;
|
|
4072
4269
|
}
|
|
4073
|
-
const textureAsset =
|
|
4270
|
+
const textureAsset = useAsset(this._textureAsset);
|
|
4074
4271
|
if (textureAsset) {
|
|
4075
4272
|
data.textureAsset = textureAsset.id;
|
|
4076
4273
|
}
|
|
@@ -4184,7 +4381,7 @@
|
|
|
4184
4381
|
*/
|
|
4185
4382
|
set fontAsset(value) {
|
|
4186
4383
|
this._fontAsset = value;
|
|
4187
|
-
const asset =
|
|
4384
|
+
const asset = useAsset(value);
|
|
4188
4385
|
if (this.component && asset) {
|
|
4189
4386
|
this.component.fontAsset = asset.id;
|
|
4190
4387
|
}
|
|
@@ -4339,7 +4536,7 @@
|
|
|
4339
4536
|
*/
|
|
4340
4537
|
set spriteAsset(value) {
|
|
4341
4538
|
this._spriteAsset = value;
|
|
4342
|
-
const asset =
|
|
4539
|
+
const asset = useAsset(value);
|
|
4343
4540
|
if (this.component && asset) {
|
|
4344
4541
|
this.component.spriteAsset = asset.id;
|
|
4345
4542
|
}
|
|
@@ -4391,7 +4588,7 @@
|
|
|
4391
4588
|
*/
|
|
4392
4589
|
set textureAsset(value) {
|
|
4393
4590
|
this._textureAsset = value;
|
|
4394
|
-
const asset =
|
|
4591
|
+
const asset = useAsset(value);
|
|
4395
4592
|
if (this.component && asset) {
|
|
4396
4593
|
this.component.textureAsset = asset.id;
|
|
4397
4594
|
}
|
|
@@ -5626,13 +5823,14 @@
|
|
|
5626
5823
|
super('particlesystem');
|
|
5627
5824
|
}
|
|
5628
5825
|
getInitialComponentData() {
|
|
5629
|
-
const asset =
|
|
5630
|
-
|
|
5826
|
+
const asset = useAsset(this._asset);
|
|
5827
|
+
// A lazy config has no resource yet - _loadAsset applies it once the load completes
|
|
5828
|
+
if (!asset || !asset.resource) {
|
|
5631
5829
|
return {};
|
|
5632
5830
|
}
|
|
5633
5831
|
if (asset.resource.colorMapAsset) {
|
|
5634
5832
|
const id = asset.resource.colorMapAsset;
|
|
5635
|
-
const colorMapAsset =
|
|
5833
|
+
const colorMapAsset = useAsset(id)?.id;
|
|
5636
5834
|
if (colorMapAsset) {
|
|
5637
5835
|
asset.resource.colorMapAsset = colorMapAsset;
|
|
5638
5836
|
}
|
|
@@ -5658,9 +5856,8 @@
|
|
|
5658
5856
|
}
|
|
5659
5857
|
}
|
|
5660
5858
|
async _loadAsset() {
|
|
5661
|
-
|
|
5662
|
-
const
|
|
5663
|
-
const asset = AssetElement.get(this._asset);
|
|
5859
|
+
await this.closestApp?.ready();
|
|
5860
|
+
const asset = useAsset(this._asset);
|
|
5664
5861
|
if (!asset) {
|
|
5665
5862
|
return;
|
|
5666
5863
|
}
|
|
@@ -5671,7 +5868,6 @@
|
|
|
5671
5868
|
asset.once('load', () => {
|
|
5672
5869
|
this.applyConfig(asset.resource);
|
|
5673
5870
|
});
|
|
5674
|
-
app.assets.load(asset);
|
|
5675
5871
|
}
|
|
5676
5872
|
}
|
|
5677
5873
|
/**
|
|
@@ -6087,7 +6283,7 @@
|
|
|
6087
6283
|
this._scheduleUpdate();
|
|
6088
6284
|
return;
|
|
6089
6285
|
}
|
|
6090
|
-
const asset =
|
|
6286
|
+
const asset = useAsset(id);
|
|
6091
6287
|
if (!asset)
|
|
6092
6288
|
return;
|
|
6093
6289
|
if (asset.loaded) {
|
|
@@ -9188,7 +9384,7 @@
|
|
|
9188
9384
|
* @returns The asset, or `raw`.
|
|
9189
9385
|
*/
|
|
9190
9386
|
const assetConversion = (rest, raw) => {
|
|
9191
|
-
const asset =
|
|
9387
|
+
const asset = useAsset(rest);
|
|
9192
9388
|
if (asset) {
|
|
9193
9389
|
return asset;
|
|
9194
9390
|
}
|
|
@@ -10008,7 +10204,7 @@
|
|
|
10008
10204
|
set asset(value) {
|
|
10009
10205
|
this._asset = value;
|
|
10010
10206
|
if (this.soundSlot) {
|
|
10011
|
-
const id =
|
|
10207
|
+
const id = useAsset(value)?.id;
|
|
10012
10208
|
if (id) {
|
|
10013
10209
|
this.soundSlot.asset = id;
|
|
10014
10210
|
}
|
|
@@ -10215,7 +10411,7 @@
|
|
|
10215
10411
|
}
|
|
10216
10412
|
getInitialComponentData() {
|
|
10217
10413
|
return {
|
|
10218
|
-
asset:
|
|
10414
|
+
asset: useAsset(this._asset),
|
|
10219
10415
|
castShadows: this._castShadows,
|
|
10220
10416
|
lodBaseDistance: this._lodBaseDistance,
|
|
10221
10417
|
lodMultiplier: this._lodMultiplier,
|
|
@@ -10236,7 +10432,7 @@
|
|
|
10236
10432
|
*/
|
|
10237
10433
|
set asset(value) {
|
|
10238
10434
|
this._asset = value;
|
|
10239
|
-
const asset =
|
|
10435
|
+
const asset = useAsset(value);
|
|
10240
10436
|
if (this.component && asset) {
|
|
10241
10437
|
this.component.asset = asset;
|
|
10242
10438
|
}
|
|
@@ -10514,8 +10710,7 @@
|
|
|
10514
10710
|
if (generation !== this._loadGeneration) {
|
|
10515
10711
|
return;
|
|
10516
10712
|
}
|
|
10517
|
-
const
|
|
10518
|
-
const asset = AssetElement.get(this._asset);
|
|
10713
|
+
const asset = useAsset(this._asset);
|
|
10519
10714
|
if (!asset) {
|
|
10520
10715
|
// An empty id is a legitimate transient (the asset may be assigned later); a
|
|
10521
10716
|
// non-empty one that resolves to nothing is a dead end - say so rather than staying
|
|
@@ -10551,7 +10746,6 @@
|
|
|
10551
10746
|
}));
|
|
10552
10747
|
this._onReady();
|
|
10553
10748
|
});
|
|
10554
|
-
app.assets.load(asset);
|
|
10555
10749
|
}
|
|
10556
10750
|
}
|
|
10557
10751
|
_unloadModel() {
|
|
@@ -11516,7 +11710,7 @@
|
|
|
11516
11710
|
return;
|
|
11517
11711
|
}
|
|
11518
11712
|
this._appElement = appElement;
|
|
11519
|
-
const asset =
|
|
11713
|
+
const asset = useAsset(this._asset);
|
|
11520
11714
|
if (!asset) {
|
|
11521
11715
|
return;
|
|
11522
11716
|
}
|
|
@@ -11535,7 +11729,6 @@
|
|
|
11535
11729
|
}
|
|
11536
11730
|
this._generateSkybox(asset);
|
|
11537
11731
|
});
|
|
11538
|
-
app.assets.load(asset);
|
|
11539
11732
|
}
|
|
11540
11733
|
}
|
|
11541
11734
|
_unloadSkybox() {
|